/*
 * 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.List;
import java.util.Map;
import java.util.Set;

import meta.model.Architecture;
import meta.model.Device;
import meta.model.Flow;
import meta.model.Link;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.SimpleDirectedGraph;
import org.jgrapht.traverse.DepthFirstIterator;
import org.jgrapht.alg.ConnectivityInspector;
/**
 * 
 * auto-generate residual vector for AMPL 
 * 
 * Mass flow balance: always refer to the third element.  For example Gas_1(3)
 * RPM balance: always use first index.  For example Mech_1(1)
 * Energy balance: always use second index. For example Mech_1(2)
 */
public class ResidualVector {
 	
  private Architecture arch;	
  private List<String> equations;
  public ResidualVector(Architecture arch) {
     this.arch = arch; 
     equations = new ArrayList<String>(); 
     massContinuity();
     energyBalance();
     speedConsistency();
  }
  private void speedConsistency() {
	  List<Link> linkTable = arch.getLinkTable();
      for ( Link link : linkTable)  {
    	  Flow flow = link.getFlow();
    	   if ( flow.getIndex() == Architecture.MECHANICAL) {
    	   StringBuilder sb = new StringBuilder();
    	   func(sb);
    	   Device src = link.getSource();
 		   Device sink = link.getSink();
 		   int orig = src.getIndex();
 		   int dest = sink.getIndex();
 		   String f = String.format("omega%d - omega%d =0 ;", orig,dest);
 		   sb.append(f);
 		   equations.add(sb.toString());
    	  }
      }	  
  }
  private void energyBalance() {
	  SimpleDirectedGraph<String, DefaultEdge> graph; 
	  graph = arch.getGraph(Architecture.MECHANICAL);
	     ConnectivityInspector inspector = new ConnectivityInspector(graph);
	     // Each set in the list is a disconnected graph that becomes
	     // an energy balance equation.
	     List<Set<String>>  list = inspector.connectedSets();
	     for ( Set<String> set : list) {
	    	  int n = set.size();
	    	  if ( n == 1 )  continue;
	    	  StringBuilder sb = new StringBuilder();
	    	  func(sb);
	    	  boolean first = true;
	          for(String item : set ){
	            if (!first) sb.append("+");
	            sb.append("W");
	            sb.append(item);
	            first = false;
	          }
	          sb.append("=0;");
	          equations.add(sb.toString());
	     }
  }
  private void massContinuity() {
	  SimpleDirectedGraph<String, DefaultEdge> graph;
	  graph = arch.getGraph(Architecture.GAS);
	  DepthFirstIterator  iter = new  DepthFirstIterator(graph);
	  while ( iter.hasNext() ) {
	        String v = (String) iter.next();
	        Set<DefaultEdge> in = graph.incomingEdgesOf(v);
	        Set<DefaultEdge> out = graph.outgoingEdgesOf(v);
	        // if no outputs or no inputs ignore the equation
	        if ( in.size() == 0) continue;
	        if ( out.size() == 0) continue;
	        String inFlow = inFlows(in);
	        String outFlow = outFlows(out);
	        int id = equations.size() + 1;
	        String equation = String.format("subject to conf%d: %s %s ",id, inFlow,outFlow);
	        equations.add(equation);
	  }      
  }
  private String outFlows(Set<DefaultEdge> out) {
	  StringBuilder sb = new StringBuilder();
	  for(DefaultEdge item : out ){
		  sb.append('-');
          String token = item.toString();
          String var = makeVarName(token);
          sb.append(var);
          // add bleed term if outgoing node has a bleed
          String bvar = bleedTerm(token);
          if ( bvar != null) {
        	  sb.append(bvar);
          }
     }
	 sb.append("=0;");
	 return sb.toString();
  }
  private String inFlows(Set<DefaultEdge> in) {
	  boolean more = false; // guard for + 
	  StringBuilder sb = new StringBuilder();
	  for(DefaultEdge item : in ){
		  if ( more) sb.append('+');
          String token = item.toString();
          String var = makeVarName(token);
          sb.append(var);
          more = true;
     }
	 return sb.toString();
  }
  private String makeVarName(String token) {
	  String token2 = token.substring(1,token.length()-1);
      String [] tokens = token2.split("\\s:");
      String t1 = tokens[1].trim();
      String s = String.format("m%s", t1);
	  return s;
  }
  /**
   * Compute a bleed term if the outgoing node of the edge has a bleed flow
   * @param edge
   * @return
   */
  private String bleedTerm(String edge) {
	  String term = null;
	  String token = edge.substring(1,edge.length()-1);
      String [] tokens = token.split("\\s:");
      String node = tokens[1].trim(); 
      int inode = Integer.parseInt(node);
      List<Link> linkTable = arch.getLinkTable();
      for ( Link link : linkTable)  {
    	  Flow flow = link.getFlow();
    	  if ( flow.getIndex() == Architecture.BLEED ) {
    		  Device src = link.getSource();
    		  Device sink = link.getSink();
    		  int id_src = src.getIndex();
    		  if ( id_src == inode ) {
    			  term = String.format("-BleedAir_%d", id_src) ;
    			  break;
    		  }
    		  if ( sink.getIndex() == inode ) {
    			  term = String.format("+BleedAir_%d", id_src) ;
    			  break;
    		  }
    	  }
      }
      return term;
  }
  /**
   * subject to conf1: m1-m2 = 0;
   *  start the equation with subject to conf n :  where n is a counter
   */
  private void func(StringBuilder sb) {
	  int i = equations.size() + 1;
	  String f = String.format("subject to conf%d:", i);
	  sb.append(f);
  }
  public List<String> getEquations() {
		 
		return equations;
	}  
}
