/*
 * 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.fuelconsumption.service;
import java.io.FileInputStream;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.jdom.Element;

import meta.ampl.runner.AMPLException;
import meta.ampl.runner.AMPLprocess;
import meta.ampl.runner.AMPLprocessFactory;
import meta.ampl.runner.SolutionStatus;
import meta.ampl.runner.Solver;
import meta.ampl.runner.Variable;

import meta.ampl.runner.AMPLprocess;
import meta.ampl.runner.AMPLprocessFactory;
import meta.model.Architecture;
import meta.model.ArchitectureImpl;
import meta.model.ampl.AMPLdata0;
import meta.model.ampl.AMPLdata1;
import meta.model.ampl.AMPLmodel0;
import meta.model.ampl.AMPLmodel1;
import meta.model.matlab.IterVarVecBetaSpeed;
import meta.model.matlab.MATLABmodel;
import meta.model.matlab.ResidualVector;
import meta.rmi.Argument;
import meta.rmi.Service;
import meta.rmi.ServiceImpl;
import meta.rmi.ServiceRequest;
import meta.rmi.ServiceResponse;
import meta.rmi.Vector;
import meta.rmi.VectorBuilder;
import meta.rmi.Log;

import meta.util.FileUtils;

import meta.matlab.runner.MATLABException;
import meta.matlab.runner.MATLABprocessFactory;
import meta.matlab.runner.MATLABprocess;


/**
 * All SOA RMI Servers extend the ServiceImpl class and must
 * implement the abstract method execute.
 * 
 * Just finishing up the logic document now. We will only be passing design vectors.  So we will have something like this

D_1_1 D_1_2 D_1_3
D_2_1 D_2_2 D_2_3

Where the first index is the component number and the second index is the index within the design vector. Would that be OK?
The variable name for total fuel consumption is: "total_fuel_consumption".
The boolean for feasibility is: "feasible".

 * 
 */
public class FuelConsumptionService extends ServiceImpl  implements Service {
  
	private Properties properties;
	private AMPLprocess ampl;
	private MATLABprocess matlab;
	private Architecture arch;
	private Map<String,Variable> map;
	private double tfc;
    public FuelConsumptionService(String d) throws RemoteException, AMPLException, MATLABException {
    	super(d);
    	loadServiceProperties();
    	if ( nop() ) {
    		Log.info("Fuel Consumption Service is in NOP mode for testing the tool chain");
    		return;
    	}
    	ampl = AMPLprocessFactory.create();
        matlab = MATLABprocessFactory.create(properties);
    }
	/**
	 * Load the Fuel Consumption Service property file
	 * @throws RemoteException if the properties cannot be read
	 */
	private void loadServiceProperties() throws RemoteException {
		
		// If the property -Dfuelconsumption.properties is set, then this is the pathname
		// to the Fuel consumption property file. If missing, then the default path of
		// "aee.properties" is used
		String props = System.getProperty("fuelconsumption.properties", "fuelconsumption.properties");
		properties = new Properties();
		try {
			properties.load(new FileInputStream(props));
		} catch (IOException ioex) {
			throw new RemoteException(String.format(
					"Error loading property file \"%s\": %s", props, ioex.getMessage()));
		}
	}
    /**
     * For testing and debugging the tool chain. 
     * If the property mode.nop is true then
     * do nothing but return a good service response
     */
	private boolean nop() {
		// check for nop mode 
		String nop = properties.getProperty("mode.nop");
		if ( nop != null ) {
			if ( nop.equalsIgnoreCase("true")) {
				return true;
			}
		}
		return false;
	}
	protected void execute() {
		if ( nop() )  {
			fillResponse();
			return;
		}
		tfc = Double.NaN; 
		ServiceRequest sr = getServiceRequest();
		List<?> list = sr.getCompositeArg("engine", "architecture");
		if ( list != null) {
		    Element e = (Element) list.get(0);
		    arch = new ArchitectureImpl(e);
		    IterVarVecBetaSpeed s = new IterVarVecBetaSpeed(arch);
		    ResidualVector residualVec = new ResidualVector(arch);
		    List<String> equ = residualVec.getEquations(); 
		    try {
		     	matlab.reset();
		    	doAMPL();
				MATLABmodel model = new MATLABmodel(properties);
				model.defineIterVector(s.getVector());
				List<String> built = model.build(arch); 
				model.defModelResidualVector(built,equ); 
				runMATLAB(); 
				fillgoodResponse();
			} catch (Exception e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		}
		 
	}
	private void doAMPL() throws Exception {
		String path = System.getProperty("java.io.tmpdir");
	       AMPLmodel1 amplModel = new AMPLmodel1(properties);
		   List<String> model = amplModel.build(arch);
		   String fn = String.format("%s\\%s.mod",path,arch.getName());
		   FileUtils.writeFile(fn,model); 
		   AMPLdata1 amplDat = new AMPLdata1(properties);
		   List<String> data = amplDat.build(arch);
		   fn = String.format("%s\\%s.dat",path,arch.getName());
		   FileUtils.writeFile(fn,data); 
		   ampl.setModel(model);
		   ampl.setData(data);
		   ampl.setSolver(Solver.IPOPT);
	       ampl.solve();
	       SolutionStatus solutionStatus = ampl.getSolutionStatus();
	       System.out.println(solutionStatus);
	       map = ampl.getVariables();
		  // map = fakeGetVars();
		   
	}
	/**
	 * We will pass design vectors from AMPL to MATLAB  
     * They are of the following form:
     * D_1_1 D_1_2 D_1_3
     * D_2_1 D_2_2 D_2_3
     * 
     * Where the first index is the component number and the second index is
     * the index within the design vector.
     * @param map contains all the AMPL variables and values
	 * @throws MATLABException 
	 */
	private void giveDesignVariablesToMATLAB(Map<String,Variable> map) throws MATLABException {
		Set<String> keys = map.keySet();
		for (String key : keys) {
		   if ( key.startsWith("D_")) {
		      Variable variable = map.get(key);
		      double d = Double.valueOf(variable.value());
              matlab.setDouble(key,  d);
		   }
		}
	  
	}
	private Map<String,Variable> fakeGetVars() {
		List<String> lines = null;
		try {
			lines = FileUtils.read("T:\\TEMP\\vars.txt");
			matlab.setDouble("total_fuel_consumption",200.0);
			matlab.setDouble("feasible",1.0);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		HashMap map = new HashMap<String,Variable>();
        for (String line : lines) {
            String [] s = line.split("\\s+");
            if ( s[0].startsWith(":") ) continue;
            if ( s[0].startsWith(";") ) break;
            double d = Double.parseDouble(s[2]);
            Double val = new Double(d);
            Variable variable = new Variable(s[1],val);
            map.put(s[1], variable);
 	     }
        return map;
	}
	/**
	 * initialize MATLAB with ambient conditions  
	 */
	private void initMATLAB() throws Exception {
		ServiceRequest request = getServiceRequest();
		
		Map<String,Vector> ambMap = VectorBuilder.build(request, "engine", "vectors");
		Vector thrust = ambMap.get("thrust");
		if ( thrust == null ) {
			String msg = "Thrust not found in FuelConsumption request";
			errorOut(msg);
		}
		Vector t = ambMap.get("t");
		if ( t == null ) {
			String msg = "Tamb not found in Fuel Consumption request";
			errorOut(msg);
		}
		Vector p = ambMap.get("p");
		if ( p == null ) {
			String msg = "Pamb not found in Fuel Consumption request";
			errorOut(msg);
		}
		Vector mach = ambMap.get("mach");
		if ( mach == null ) {
			String msg = "Mach not found in Fuel Consumption request";
			errorOut(msg);
		}
		matlab.setDouble("Thrust_v", thrust.getValues());
		matlab.setDouble("Mach_v",   mach.getValues());
		matlab.setDouble("Pamb_v",   p.getValues());
		matlab.setDouble("Tamb_v",   t.getValues());
		giveDesignVariablesToMATLAB(map);
	}
	private void errorOut(String msg) throws Exception
	{
		ServiceResponse response = getServiceResponse();
		Log.err(msg);
		response.setStatus(ServiceResponse.BAD_REQUEST);
		response.setStatusMsg(ServiceResponse.BAD_REQUEST_MSG);
		throw new Exception(msg);
	}
	/**
	 * Run the MATLAB model
	 * @throws Exception 
	 */
	private void runMATLAB() throws Exception {
		initMATLAB();
		String workspace = properties.getProperty("matlab.workspace");
		String mfile = properties.getProperty("matlab.mfile");
		matlab.setWorkingDirectory(workspace);
	 //	matlab.execute(mfile);
		try {
	 	  tfc = matlab.getDouble("total_fuel_consumption");
		} catch(MATLABException e) {} // will return NaN on error
		 
	}
	private void fillgoodResponse() {
		ServiceResponse response = getServiceResponse();
		String tFc = Double.toString(tfc);
		Argument totalFuel = new Argument("total_fuel_consumption","real",tFc,"");
		response.addContent(totalFuel.toElement());
	}
	/**
	 * Fill the Service Response with nonsense data 
	 * Method for testing debugging the tool chain
	 */
	private void fillResponse() {
		ServiceResponse response = getServiceResponse();
		Argument tsfc = new Argument("TSFC","real","10.0","");
		Argument totalFuel = new Argument("total_fuel_consumption","real","100.0","");
		Argument feasible = new Argument("feasible","boolean","true","");
		Argument to1 = new Argument("To_1","real","400.","kelvin");
		Argument po1 = new Argument("Po_1","real","103000.","pascal");
		response.addContent(tsfc.toElement());
		response.addContent(totalFuel.toElement());
		response.addContent(feasible.toElement());
		response.addContent(to1.toElement());
		response.addContent(po1.toElement());
	}
}
