/*
 * Delivered to the U.S. Government in Accordance with Contract FA8650-10-C-7080.
 *
 * EXPORT CONTROLLED
 * ECCN#   EAR99
 * Warning:  This document contains technical data which is controlled under the
 * Bureau of Industry & Security's Export Administration Regulations and may not be
 * exported or re-exported without authorization by the Bureau of Industry & Security.
 * Diversion contrary to US law is prohibited.
 *
 * (c) 2011 United Technologies Corp.
 * Developed with the sponsorship of the Defense Advanced Research Projects Agency (DARPA).
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this data,
 * including any software or models in source or binary form, as well as any drawings, 
 * specifications, and documentation (collectively "the Data"), to deal in the Data without 
 * restriction, including without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Data, and to permit persons to whom the Data
 * is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Data.
 *
 * THE DATA IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS, SPONSORS, DEVELOPERS, CONTRIBUTORS, OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE DATA OR THE
 * USE OR OTHER DEALINGS IN THE DATA.
 */
package meta.model;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;

import org.jdom.Attribute;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.xpath.XPath;

import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.SimpleDirectedGraph;
import meta.util.Export;
/**
 *  This class implements the Architecture interface,
 *  an API to query an design architecture. 
 *  One can obtain devices, flows, and links.
 */
public class ArchitectureImpl  implements Architecture {
	private static final String XPATH_DEVICE = "architecture/device";
	private static final String XPATH_FLOW = "architecture/flow";
	private static final String XPATH_LINK = "architecture/link";
	private List<Link> linkTable;
	private List<Flow> flowTable;
	private List<String> nodeTable;
	private HashMap<String, Device> devices;
	private HashMap<String, Flow> flows;
	private String name;
	private String engineType ="?";
	private boolean embeddedEngine = false;;
	private boolean rearProp = false;;
	/**
	 * Construct an architecture from a service request composite_argument
	 * @param element
	 */
	public ArchitectureImpl(Element element) {
		linkTable = new ArrayList<Link>();
		flowTable = new ArrayList<Flow>();
		devices = new HashMap<String,Device>();
		flows = new HashMap<String,Flow>();
		Element a = element.getChild("architecture");
		String n = a.getAttributeValue("id");
		name = "Arch_".concat(n);
		addDevices(element);
		addFlows(element);
		addLinks(element);
	}
	/**
	 * Update the engine configuration depending on 
	 * the types of components present. 
	 * 
     * engine is embedded if we find 'Embedded' in the Device table
	 */
	private void updateConfig(String name) {
		if ( name.equals("Embedded")) {
		   embeddedEngine = true; 
		}   
		if ( name.equals("Single_Rear_Prop")) {
			rearProp = true;
		}  
		if ( name.equals("Multiple_Rear_Props")) {
			rearProp = true;
		} 
		if ( name.contains("Prop")) {
			 engineType = "turboprop";
		} 
		if ( name.contains("Fan")) {
			 engineType = "turbofan";
		} 
	}
	private void addDevices(Element element) {
		List<?> nodes;
		try {
			nodes = XPath.selectNodes(element, XPATH_DEVICE);
			 for (Object o : nodes ) {
				  Element e = (Element) o;
				  Attribute n = e.getAttribute("name");
				  Attribute a = e.getAttribute("id");
				  int id = a.getIntValue();
				  String name = n.getValue(); 
				  Device device = new Device(name,id);
				  add(device);
				  updateConfig(name);
			 }
		} catch (JDOMException e) {
			// then return null
		}
	}
	private void addFlows(Element element) {
		List<?> nodes;
		try {
			nodes = XPath.selectNodes(element, XPATH_FLOW);
			 for (Object o : nodes ) {
				  Element e = (Element) o;
				  Attribute n = e.getAttribute("name");
				  Attribute a = e.getAttribute("id");
				  int id = a.getIntValue();
				  String name = n.getValue(); 
				  Flow flow = new Flow(name,id);
				  add(flow);
			 }
		} catch (JDOMException e) {
			// then return null
		}
	}
	private void addLinks(Element element) {
		List<?> nodes;
		try {
			nodes = XPath.selectNodes(element, XPATH_LINK);
			 for (Object o : nodes ) {
				  Element e = (Element) o;
				  Attribute idA = e.getAttribute("id");
				  Attribute sourceA = e.getAttribute("source");
				  Attribute sinkA = e.getAttribute("sink");
				  Attribute flowA = e.getAttribute("flow");
				  String linkID = idA.getValue();
				  int sourceI = sourceA.getIntValue();
				  int sinkI = sinkA.getIntValue();
				  int flowI = flowA.getIntValue();
				  Device source = getDevice(sourceI);
				  Device sink = getDevice(sinkI);
				  Flow flow = getFlow(flowI);
				  Link link = new Link(linkID,source,sink,flow);
				  add(link);
			 }
		} catch (JDOMException e) {
			// then return null
		}
	}
	public String getEngineType() {
		return engineType;
	}
	/**
	 * The node table used for output 
	 * @see AMPLresults
	 */
	public void setNodeTable(List<String> nt) {
		nodeTable = nt;
	}
	/**
	 * Report the node table used for output
	 * @see Export
	 */
	public List<String> getNodeTable() {
		return nodeTable;
	}
	/**
	 * Reports if the engine is embedded.
	 */
	public boolean isEngineEmbedded() {
		return embeddedEngine;
	}
	/**
	 * Reports if the engine has a prop in the back
	 */
	public boolean hasRearProp() {
		return rearProp;
	}
	/**
	 * Report the link table
	 */
	public List<Link> getLinkTable() {
		return linkTable;
	}
	/**
	 * Report the flow table
	 * @return
	 */
	public List<Flow> getFlowTable() {
		return flowTable;
	}
	/**
	 * Report the architecture name
	 */
	public String getName() 
	{
		return name;
	}
	/**
	 * Add a device
	 */
	public void add(Device device) {
		int id = device.getIndex();
		String key = Integer.toString(id);
		devices.put(key, device);
	}
	/**
	 * Add a flow
	 */
	public void add(Flow flow) {
		int id = flow.getIndex();
		String key = Integer.toString(id);
		flows.put(key, flow);
		flowTable.add(flow);
	}
	/**
	 * Add a link
	 */
	public void add(Link link) {
		 linkTable.add(link);
	}
	/* (non-Javadoc)
	 * @see meta.model.Architechture#getDevice()
	 */
	public Device getDevice(int id) {
		String key = Integer.toString(id);
		return devices.get(key);
	}
	/* (non-Javadoc)
	 * @see meta.model.Architechture#getFlow()
	 */
	public Flow getFlow(int id) {
		String key = Integer.toString(id);
		return flows.get(key);
	}
	/* (non-Javadoc)
	 * @see meta.model.Architechture#getDeviceIndexes()
	 */
	public int[] getDeviceIndexes() {
		return getVector(devices);
	}
	/* (non-Javadoc)
	 * @see meta.model.Architechture#getDeviceNames()
	 */
	public String[] getDeviceNames() {
		return getNames(devices);
	}
	/* (non-Javadoc)
	 * @see meta.model.Architechture#getFlowNames()
	 */
	public String[] getFlowNames() {
		return getNames(flows);
	}
	/* (non-Javadoc)
	 * @see meta.model.Architechture#getFlowIndexes()
	 */
	public int[] getFlowIndexes() {
		return getVector(flows);
	}
	/* (non-Javadoc)
	 * @see meta.model.Architechture#getFlowTypeIndexes()
	 */
	public int[] getFlowTypeIndexes() {
		int size = linkTable.size();
		int[] ints = new int[size];
		int i = 0;
		for ( Link link : linkTable ) {
		  Flow flow = link.getFlow();
		  int type = flow.getIndex();
		  ints[i] = type;
		  i++;
		}
		return ints;
	}
	/* (non-Javadoc)
	 * @see meta.model.Architechture#getSourceIndexes()
	 */
	public int [] getSourceIndexes() {
		int size = linkTable.size();
		int[] ints = new int[size];
		int i = 0;
		for ( Link link : linkTable ) {
		  Device device = link.getSource();
		  int type = device.getIndex();
		  ints[i] = type;
		  i++;
		}
		return ints;	
	}
	/* (non-Javadoc)
	 * @see meta.model.Architechture#getSinkIndexes()
	 */
	public int [] getSinkIndexes() {
		int size = linkTable.size();
		int[] ints = new int[size];
		int i = 0;
		for ( Link link : linkTable ) {
		  Device device = link.getSink();
		  int type = device.getIndex();
		  ints[i] = type;
		  i++;
		}
		return ints;	
	}
	/* (non-Javadoc)
	 * @see meta.model.Architechture#getGraph(int flowType)
	 */
	public SimpleDirectedGraph<String, DefaultEdge> getGraph(int reqFlowType) {
		SimpleDirectedGraph<String, DefaultEdge> graph;
	     graph =  new SimpleDirectedGraph<String, DefaultEdge>(DefaultEdge.class);
		 int [] devices = getDeviceIndexes();
		 // Add vertexes to the graph
		 for ( int i=0; i< devices.length; i++) {
			 String device = String.valueOf(devices[i]);
		     graph.addVertex(device);
		 }
		 // Add edges to the graph
		 int [] sources = getSourceIndexes();
		 int [] sinks   = getSinkIndexes();
		 int [] flowTypes = getFlowTypeIndexes();
		 for ( int i=0; i < sources.length; i++) {
			 int flowType = flowTypes[i];
			 if ( flowType != reqFlowType) continue;
			 String source = String.valueOf(sources[i]);
			 String sink = String.valueOf(sinks[i]);
		     graph.addEdge(source,sink);
		 }
		 return graph;
	 }	
/**
 * Transform a Type index into an int array to send to MATLAB
 * @param map
 * @return
 */
	private int[] getVector(HashMap<String, ? extends Type> map) {
		Set<String> keySet = map.keySet();
		int size = keySet.size();
		int[] ints = new int[size];
		int i = 0;
		for (String key : keySet) {
			int index = Integer.parseInt(key);
			ints[i] = index;
			i++;
		}
		return ints;
	}
	private String[] getNames(HashMap<String, ? extends Type> map) {
		Set<String> keySet = map.keySet();
		int size = keySet.size();
		String[] names = new String[size];
		int i = 0;
		for (String key : keySet) {
			Type type = map.get(key);
			names[i] = type.getName();
			i++;
		}
		return names;
	}
	/**
	 * Report the number of devices
	 */
	public int getNumberOfDevices() {
		return devices.size();
	}
	/**
	 * Report a List of Devices that are present in the architecture 
	 */
	public List<Device> getDevices() {
		  
		  List<Device> dList = new ArrayList<Device>();
		  int size = devices.size();
		  for ( int i=1; i<= size; i++) {
			  Device device = getDevice(i);
			  dList.add(device);
		  }
		  return dList;
	}
	/**
	 * Export the architecture to XMI
	 */
	public void toXMI() {
		new Export().toXMI(this);
	}
}
