/*
 *  This file is part of CoGui.
 *  Copyright 2008 LIRMM RCR Team
 *  home page: http://www.lirmm.fr/cogui 
 *  
 *  CoGui is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  CoGui is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with CoGui; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *  
 * @author Alain Gutierrez
 */
package test_solvers;

import static choco.Choco.*;

import java.io.File;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Vector;

import choco.cp.model.CPModel;
import choco.cp.solver.CPSolver;
import choco.kernel.model.Model;
import choco.kernel.model.constraints.Constraint;
import choco.kernel.model.variables.integer.IntegerVariable;
import choco.kernel.solver.Solver;
import choco.kernel.solver.constraints.integer.extension.BinRelation;
import choco.kernel.solver.constraints.integer.extension.LargeRelation;
import fr.lirmm.rcr.cogui.model.CGObject;
import fr.lirmm.rcr.cogui.model.CGObjectConnection;
import fr.lirmm.rcr.cogui.model.CoguiConstants;
import fr.lirmm.rcr.cogui.model.DefaultCGProject;
import fr.lirmm.rcr.cogui.model.DefaultProjection;
import fr.lirmm.rcr.cogui.model.ICGProject;
import fr.lirmm.rcr.cogui.model.Projection;
import fr.lirmm.rcr.cogui.model.graph.SGGraphProperty;
import fr.lirmm.rcr.cogui.model.vocabulary.SignatureProperty;
import fr.lirmm.rcr.cogui.solver.SolverCogitant;

// TODO: Auto-generated Javadoc
/**
 * The Class TestSolvers.
 */
public class TestSolvers {

	/**
	 * The main method.
	 * 
	 * @param args the args
	 */
	public static void main(String[] args) {
		Hashtable<String,String> options = new Hashtable<String,String>();
		for (int i = 0; i < args.length; i++) {
			StringTokenizer st = new StringTokenizer(args[i], ",;");
			while (st.hasMoreTokens()) {
				String token = st.nextToken().toLowerCase().trim();
				int pos = token.indexOf('=');
				if (pos < 0)
					options.put(token, "true");
				else
					options.put(token.substring(0, pos).trim(), token
							.substring(pos + 1).trim());
			}
		}
		File cogxmlFile=null;
		String graphName="G",queryName="Q";
		if (options.get("-file") != null)
			cogxmlFile=new File(options.get("-file"));
		if(options.get("-target")!=null)
			graphName=options.get("-target");
		if(options.get("-query")!=null)
			queryName=options.get("-query");
		if (options.get("-help") != null || options.get("-h") != null || cogxmlFile==null) {
			System.out.println("Options:");
			System.out
					.println( "        -help            display this page\n"
							+ "        -file=<value>    cogxml file path\n"
							+ "        -target=<value>  the target graph name (default is \"G\")\n"
							+ "        -query=<value>   the query graph name (default is \"Q\")\n");
			return;
		}
		if(!cogxmlFile.exists())
		{
			System.out.println("cannot find file: "+cogxmlFile.getAbsolutePath());
			return;
		}
		try {
			ICGProject prj=new DefaultCGProject();
			prj.load(cogxmlFile);
			long startTime = System.currentTimeMillis();
			SolverCogitant solver=new SolverCogitant();
			solver.connect();
			solver.commitVocabulary(prj.getVocabulary());
			CGObject graph=prj.getGraphHierarchy().searchByLabel(graphName);
			CGObject query=prj.getGraphHierarchy().searchByLabel(queryName);
			if(graph==null|| query==null)
			{
				System.out.println("cannot find graphs "+graphName+" and(or) "+queryName);
				return;
			}
			// search projections with cogitant
			Vector<Projection> projections=solver.getProjections(query,graph );
			System.out.println(""+projections.size()+" projections found");
			System.out.println("cogitant run during "+(System.currentTimeMillis() - startTime)+" ms");
			startTime = System.currentTimeMillis();			
			SolverChoco solver2=new SolverChoco(prj,graph);
			projections=solver2.getProjections(query);
			System.out.println(""+projections.size()+" projections found");
			System.out.println("choco run during "+(System.currentTimeMillis() - startTime)+" ms");
		}catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	
	/**
	 * The Class SolverChoco.
	 */
	public static class SolverChoco {
		
		/** The relations. */
		CGObject[] concepts=null,relations=null;
//		boolean[][] matrice;
		/** The feasible edges. */
		ArrayList<int[]> feasibleEdges=null;
		
		/** The feasible relations. */
		Map<String,ArrayList<int[]>> feasibleRelations=null;			
		
		/** The project and the target graph */
		ICGProject prj;
		CGObject graph;
		/**
		 * Instantiates a new solver choco.
		 * 
		 * @param graph the target graph
		 * @param prj the loaded project
		 */
		public SolverChoco(ICGProject prj,CGObject graph)
		{
			this.prj=prj;
			this.graph=graph;
			SGGraphProperty gprop=(SGGraphProperty)graph.getProperty("GRAPH_PROPERTY");
			concepts=new CGObject[gprop.getConcepts().size()];
			relations=new CGObject[gprop.getRelations().size()];
			int count=0;
			Hashtable<String,Integer> verticesIndex=new Hashtable<String,Integer>();
			for(CGObject concept:((Map<String,CGObject>)gprop.getConcepts()).values()) 
			{
				concepts[count]=concept;
				verticesIndex.put(concept.getId(), new Integer(count));
				count++;
			}
			count=0;
			for(CGObject rel:((Map<String,CGObject>)gprop.getRelations()).values()) 
			{
				relations[count]=rel;
				verticesIndex.put(rel.getId(), new Integer(count));
				count++;
			}
			// build feasible edges list
			feasibleEdges=new ArrayList<int[]>();			
			Iterator<CGObjectConnection> edgeIt=(Iterator<CGObjectConnection>)gprop.getEdgeIterator();
//			matrice=new boolean [concepts.length][relations.length];
//			for(boolean[] bt:matrice) for(boolean b:bt) b=true;
			while(edgeIt.hasNext())
			{
				CGObjectConnection cnx=edgeIt.next();
				if(cnx.getNature()!=CoguiConstants.EDGE_CONCEPT_RELATION) continue;
				int cIndex,rIndex;
				if(cnx.getSource().getNature()==CoguiConstants.CONCEPT)
				{
					cIndex=verticesIndex.get(cnx.getSource().getId()).intValue();
					rIndex=verticesIndex.get(cnx.getTarget().getId()).intValue();
				}
				else
				{
					rIndex=verticesIndex.get(cnx.getSource().getId()).intValue();
					cIndex=verticesIndex.get(cnx.getTarget().getId()).intValue();
				}
//				matrice[cIndex][rIndex]=false;
				String snumorder = (String) cnx.getPropertyValue("NUMORDER");
				int numorder = Integer.parseInt(snumorder);
				feasibleEdges.add(new int[]{cIndex,numorder,rIndex});
			}
			feasibleRelations=new Hashtable();		
			for(CGObject rel:((Map<String,CGObject>)gprop.getRelations()).values()) 
			{
				verticesIndex.get(rel.getId());
				Vector<CGObject>adjs=gprop.getAdjacents(rel);
				int[] tuple=new int[adjs.size()];
				int i=0;
				for(CGObject concept:adjs)
				{
					tuple[i++]=verticesIndex.get(concept.getId());
				}
				ArrayList<int[]> array=feasibleRelations.get(""+tuple.length);
				if(array==null)
				{
					array=new ArrayList<int[]>();	
					feasibleRelations.put(""+tuple.length,array);
				}
				array.add(tuple);
			}
			
		}
		
		/**
		 * Arity of.
		 * 
		 * @param relation the relation
		 * 
		 * @return the int
		 */
		private int arityOf(CGObject relation)
		{
			CGObject rt = prj.getRelationTypeHierarchy().searchById(relation.getType().getTypes().get(0).getId());
			if(rt!=null)
			{
			SignatureProperty sprop=(SignatureProperty)rt.getProperty("signature");
			if(sprop!=null) return sprop.getArity();
			}
			return -1;
		}
		
		/**
		 * Gets the projections.
		 * 
		 * @param query the query
		 * 
		 * @return the projections
		 * 
		 * @throws Exception the exception
		 */
		public Vector<fr.lirmm.rcr.cogui.model.Projection> getProjections(CGObject query)
		throws Exception {
		Model m = new CPModel();
		Solver s = new CPSolver();
		SGGraphProperty gprop=(SGGraphProperty)query.getProperty("GRAPH_PROPERTY");
		Hashtable<String,Integer> idxC=new Hashtable();
		Hashtable<String,Integer> idxR=new Hashtable();
		/**
		 * populate variables for concepts
		 */
		int cpt=0;
		IntegerVariable[] conceptVars=makeIntVarArray("conceptVars",gprop.getConcepts().size(),0,concepts.length-1,"cp:bound","cp:no_decision");
		Iterator<CGObject> conceptIt=gprop.getConceptIterator();
		while(conceptIt.hasNext())
		{
			CGObject concept=conceptIt.next();
			idxC.put(concept.getId(),new Integer(cpt++));
		}
		/**
		 * populate variables for relations
		 */
		cpt=0;
		IntegerVariable[] relationVars=makeIntVarArray("relationVars",gprop.getRelations().size(),0,relations.length-1,"cp:bound","cp:no_decision");
		Iterator<CGObject> relIt=gprop.getRelationIterator();
		while(relIt.hasNext())
		{
			CGObject rel=relIt.next();
			idxR.put(rel.getId(), new Integer(cpt++));
		}
		Iterator<CGObjectConnection> edgeIt=(Iterator<CGObjectConnection>)gprop.getEdgeIterator();

/* for injective projections only */
//		m.addConstraint(allDifferent(relationVarArray));
//		m.addConstraint(allDifferent(conceptVarArray));
		
		while(edgeIt.hasNext())
		{
			CGObjectConnection cnx=edgeIt.next();
			if(cnx.getNature()!=CoguiConstants.EDGE_CONCEPT_RELATION) continue;
			String snumorder = (String) cnx.getPropertyValue("NUMORDER");
			int numorder = Integer.parseInt(snumorder);
			CGObject c=(cnx.getSource().getNature()==CoguiConstants.CONCEPT)?
					cnx.getSource():cnx.getTarget();
			CGObject r=(cnx.getSource().getNature()==CoguiConstants.CONCEPT)?
					cnx.getTarget():cnx.getSource();
			int max_num=concepts.length>relations.length?concepts.length:relations.length;
			IntegerVariable cVar=makeIntVar("cVar",0,concepts.length-1);
			IntegerVariable rVar=makeIntVar("rVar",0,relations.length-1);
			IntegerVariable nVar=makeIntVar("nVar",1,arityOf(r));
			IntegerVariable[] tuple=new IntegerVariable[]{cVar,nVar,rVar};
			/** 
			 * add constraints 
			 * */
			m.addConstraint(
					and(
					eq(cVar,conceptVars[idxC.get(c.getId())]),
					and(
							eq(rVar,relationVars[idxR.get(r.getId())]),
							eq(nVar,numorder))));
			m.addConstraint(relationTupleAC(tuple, new CheckType(c,r)));
			m.addConstraint(feasTupleAC(feasibleEdges, tuple));
			
		}
		s.read(m);
		Boolean solutionReady=s.solve();
		int cpt2=0;
		Vector<Projection> result = new Vector<Projection>();
		Hashtable sourceIndex = new Hashtable();
		Vector targets=new Vector();
		while(solutionReady!=null && solutionReady.booleanValue())
		{
				cpt2++;
//				System.out.println(s.pretty());
				solutionReady=s.nextSolution();
				for(String key:idxC.keySet())
				{
					int idx=s.getVar(conceptVars[idxC.get(key)]).getVal();
					sourceIndex.put(key, concepts[idx].getId());
					targets.add(concepts[idx]);
				}
				for(String key:idxR.keySet())
				{
					int idx=s.getVar(relationVars[idxR.get(key)]).getVal();
					sourceIndex.put(key, relations[idx].getId());
					targets.add(relations[idx]);
				}
				result.add(new DefaultProjection(query, graph, sourceIndex, targets));
		}		
		return result;
		}
		
		/**
		 * The Class CheckType.
		 */
		public class CheckType implements LargeRelation {
			
			/** The q_r. */
			CGObject q_c,q_r;
			
			/**
			 * Instantiates a new check type.
			 * 
			 * @param c the c
			 * @param r the r
			 */
			CheckType(CGObject c,CGObject r)
			{
				q_c=c;
				q_r=r;
			}
	        
        	/* (non-Javadoc)
        	 * @see choco.kernel.solver.constraints.integer.extension.LargeRelation#checkTuple(int[])
        	 */
        	public boolean checkTuple(int[] tuple) 
	        {
	        	if(tuple[0]>=concepts.length || tuple[2]>=relations.length) 
	        	{
	        	System.out.println("coucou tuple=("+tuple[0]+","+tuple[1]+","+tuple[2]+")");
	        	return false;
	        	}
	        	CGObject c=concepts[tuple[0]];
	        	boolean c_ok=prj.getConceptTypeHierarchy().isKindOf(c.getType(), q_c.getType());
	        	CGObject r=relations[tuple[2]];
	        	boolean r_ok=prj.getRelationTypeHierarchy().isKindOf(r.getType(), q_r.getType());
	            return r_ok && c_ok;
	        }
	        
        	/* (non-Javadoc)
        	 * @see choco.kernel.solver.constraints.integer.extension.LargeRelation#isConsistent(int[])
        	 */
        	public boolean isConsistent(int[] tuple){
	        	return tuple[1]>0 && checkTuple(tuple);
	        }
	 
	    }		
	}

}
