/*
 * 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.ampl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import static meta.util.FileUtils.path;
import meta.ampl.runner.AMPLException;
import meta.model.Architecture;
import meta.model.Device;
import meta.model.Link;
import meta.util.FileUtils;
/**
 * Level 0 AMPL model built via templates 
 */
public class AMPLmodel1 {
	private static String ATMOSPHERE_DEVICE= "99";
	private HashMap<String, List<String>> deviceMap;

	private List<String> atmosphere;
	private List<String> combustor;
	private List<String> core_diffuser;
	private List<String> core_nozzle;
	private List<String> fan_diffuser;
	private List<String> fan_DTC;
	private List<String> fan_nDTC;
    private List<String> fan_nozzle;
	private List<String> gearbox;
	private List<String> hpc;
	private List<String> hpt;
	private List<String> ipt;
	private List<String> lpc;
	private List<String> lpt;
    private List<String> propFront;
    private List<String> propRear;
    
    private String tdir;
	public AMPLmodel1(Properties properties) throws Exception {
		tdir = properties.getProperty("level1.template.dir");
		deviceMap = new HashMap<String, List<String> >();
	    atmosphere = FileUtils.read(path(tdir,"Atmosphere.txt"));
	    combustor = FileUtils.read(path(tdir,"Combustor.txt"));
	    core_diffuser = FileUtils.read(path(tdir,"Core_Diffuser.txt"));
	    core_nozzle = FileUtils.read(path(tdir,"Core_Nozzle.txt"));
	    fan_diffuser = FileUtils.read(path(tdir,"Fan_Diffuser.txt"));
	    fan_DTC = FileUtils.read(path(tdir,"Fan_DTC.txt"));
	    fan_nDTC = FileUtils.read(path(tdir,"Fan_nDTC.txt"));
	    fan_nozzle = FileUtils.read(path(tdir,"Fan_Nozzle.txt"));
	    
	    gearbox = FileUtils.read(path(tdir,"Gearbox.txt"));
	    hpc = FileUtils.read(path(tdir,"HPC.txt"));
	    hpt = FileUtils.read(path(tdir,"HPT.txt"));
	    ipt = FileUtils.read(path(tdir,"IPT.txt"));
	    lpc = FileUtils.read(path(tdir,"LPC.txt"));
	    lpt = FileUtils.read(path(tdir,"LPT.txt"));
	    propFront = FileUtils.read(path(tdir,"Propeller_Front.txt"));
	    propRear = FileUtils.read(path(tdir,"Propeller_Rear.txt"));
		
		deviceMap.put("Atmosphere", atmosphere);
        deviceMap.put("Burner", combustor);
        deviceMap.put("Core_Diffuser", core_diffuser);
        deviceMap.put("Core_Nozzle", core_nozzle);
        deviceMap.put("Fan_Diffuser", fan_diffuser);
		deviceMap.put("Fan_(DTC)", fan_DTC);
		deviceMap.put("Fan_(nDTC)", fan_nDTC);
		deviceMap.put("Fan_Nozzle", fan_nozzle);
		deviceMap.put("Gearbox", gearbox);
		deviceMap.put("HPC", hpc);
		deviceMap.put("HPT", hpt);
		deviceMap.put("IPT", ipt);
		deviceMap.put("LPC", lpc);
		deviceMap.put("LPT", lpt);
		deviceMap.put("Propeller_Front", propFront);
		deviceMap.put("Propeller_Rear", propRear);
	}
	/**
	 * 
	 * @param source - source index
	 * @param sink   - sink index
	 * @param template - 
	 * @return
	 */
	private List<String> newSourceDevice(String source, String sink, List<String> template) {
		List<String> lines = new ArrayList<String>();
		for (String line : template) {
			String s = line.replaceAll("\\$_m", source);
			s = s.replaceAll("\\$_g", source);
			s = s.replaceAll("\\$_b", source);
			s = s.replaceAll("%_m", sink);
			s = s.replaceAll("%_g", sink);
			s = s.replaceAll("%_b", sink);
			lines.add(s);
		}
		return lines;
	}
	/**
	 * 
	 * @param source - source index
	 * @param sink   - sink index
	 * @param template - 
	 * @return
	 */
	private List<String> newSinkDevice(String source, String sink, List<String> template) {
		List<String> lines = new ArrayList<String>();
		for (String line : template) {
			String s = line.replaceAll("%_m", source);
			s = s.replaceAll("%_g", source);
			s = s.replaceAll("%_b", source);
			s = s.replaceAll("\\$_m", sink);
			s = s.replaceAll("\\$_g", sink);
			s = s.replaceAll("\\$_b", sink);
			lines.add(s);
		}
		return lines;
	}
	/**
	 * If the Core_Diffuser appears as a source but not 
	 * a sink, then connect it to the atmosphere
	 */
    private boolean isCoreDiffuserConnectedToAtm(Architecture arch) {
     List<Link> linkTable = arch.getLinkTable();
     boolean sinkDev = false;
     boolean srcDev = false;
     for ( Link link : linkTable ) {
    	Device device = link.getSink();
    	if ( device.getName().equals("Core_Diffuser")) {
    		sinkDev = true;
    		break;
    	}
     }
     for ( Link link : linkTable ) {
     	Device device = link.getSource();
     	if ( device.getName().equals("Core_Diffuser")) {
     		if (!sinkDev) {
				srcDev = true;
			}
     		break;
     	}
      }	
    return srcDev;
    }	
    /**
     * Is the device a propeller or a diffuser? 
     */
    private boolean isPropOrDiffuser(String name) {
      if ( name.contains("Diffuser")) return true;
      if ( name.contains("Propeller")) return true;
      return false;
    }
	/**
	 * Build the AMPL model
	 * @param arch
	 * @return ampl model
	 * @throws AMPLException
	 */
	 public List<String> build(Architecture arch) throws Exception {
			// used to decide if device has already been seen
			Set<String> deviceSet = new HashSet<String>();
			boolean coreDiffConnToAtm = this.isCoreDiffuserConnectedToAtm(arch);
			ArrayList<String> model = new ArrayList<String>();
			List<String> at = deviceMap.get("Atmosphere");
			List<String> amplatDevice = newSourceDevice("99", "99", at);
			model.addAll(amplatDevice);
			// loop over the source devices
			List<Link> linkTable = arch.getLinkTable();
			for ( Link link : linkTable )  {
				Device source  = link.getSource();
				Device sink = link.getSink();
				String name = source.getName();
				if ( ! isPropOrDiffuser(name)) continue;
				if (deviceSet.contains(name))
					continue;
				List<String> template = deviceMap.get(name);
				if (template == null) {
					System.out.println("**** NO TEMPLATE " + name);
					continue;
				}
				String sourceIndex = source.getIndexAsString();
				String sinkIndex = sink.getIndexAsString();
				if ( name.equals("Core_Diffuser")) {
					if (coreDiffConnToAtm) {
						sinkIndex = "99";
					}
				}
				List<String> amplDevice = newSourceDevice(sourceIndex, sinkIndex, template);
				model.addAll(amplDevice);
				deviceSet.add(name);
			}
			// loop over the sink devices
			for ( Link link : linkTable ) {
				Device source  = link.getSource();
				Device sink = link.getSink();
				String name = sink.getName();
				if (deviceSet.contains(name)) 
					continue;
				List<String> template = deviceMap.get(name);
				if (template == null) {
					System.out.println("**** NO TEMPLATE " + name);
					continue;
				}
				String sourceIndex = source.getIndexAsString();
				String sinkIndex = sink.getIndexAsString();
				List<String> amplDevice = newSinkDevice(sourceIndex, sinkIndex, template);
				model.addAll(amplDevice);
				deviceSet.add(name);
			}
	 
	 // if the engine architecture is embedded add the embedded block
	 if ( arch.isEngineEmbedded() ) {
		 List<String> embeddedTemplate = deviceMap.get("Embedded");
		 model.addAll(embeddedTemplate);
	 }
 
	 ResidualVector rv = new ResidualVector(arch);
	 List<String> equ = rv.getEquations();
	 model.addAll(equ);
	 List<String> objective = getObjective(arch.getNumberOfDevices());
	 model.addAll(objective);
	 return filter(model);
	//   return model;
	}
	/**
	 * Move the param and var declarations to the top and
	 * filter out duplicate lines.
	 */ 
	private List<String> filter(List<String> model) {
		   Set<String> filter = new LinkedHashSet<String>();
		   for ( String s : model) {
			   if ( s.startsWith("var" ) ) filter.add(s);
			   if ( s.startsWith("param" ) ) filter.add(s);
		   }
		   for ( String s : model) {
			   if ( s.startsWith("var" ) )  continue;
			   if ( s.startsWith("param" ) ) continue;
			   filter.add(s);
		   }
		   List<String> list = new ArrayList<String>(filter); 
		return list;
	}
	/**
	 * Return the objective function template
	 */
	private List<String> getObjective(int numDevices) throws Exception {
		// $_total_number_components
		String nd = String.valueOf(numDevices);
		List<String> objectiveText = FileUtils.read(path(tdir,"Objective.txt"));
		List<String> lines = new ArrayList<String>();
		for (String line : objectiveText) {
			if (line.contains("$_total_number_components")) {
				String s = line.replace("$_total_number_components", nd);
				lines.add(s);
				continue;
			}
			lines.add(line);
		}
		return lines;
	}
}
