 
#include <vector>

#include <ilsolver/ilosolverint.h>

#include "grammar_api.h"
#include "cfgc_api.h"

ILOSTLBEGIN

/*//////////////////////////////////
Program Parameters
//////////////////////////////// */
string filename;
bool incremental;

/*//////////////////////
Program Data
////////////////////// */
int numCfgc;
int lengthCfgc;
int minValue;
int maxValue;
int propagation = 0;
vector<cfgc_api*> v_cfgc;


/*//////////////////////////////////////////////////////////////////////////////////
ILOG CFGC Constraint 
- uses "propagation" from the global namespace
//////////////////////////////////////////////////////////////////////////////// */
class IlcGCI : public IlcConstraintI 
{
	protected:
		IloIntVarArray V;
		cfgc_api* cfgc;
		IloSolver s;
		
	public:
		IlcGCI(IloSolver _s, IloIntVarArray _V, cfgc_api* _ptr)
	: IlcConstraintI(_s), V(_V), cfgc(_ptr), s(_s) {}
		~IlcGCI() {}
	
		virtual void post();
		virtual void propagate();
		void variableAffected(int i);
		IloSolver getIloSolver() { return s; }
};

void IlcGCI::variableAffected(int i)
{
	assert(i>=0);
	assert(i<(cfgc->getVarArray()).getSize());
	assert(s.getIntVar(cfgc->getVarArray()[i]).isInProcess());

	cfgc->variableAffected(i);
	push();
}

void IlcGCI::propagate()
{
	propagation++;
	if( !cfgc->propagate() )
		fail();
}

ILCCTDEMON1(MyVarAffected, IlcGCI, variableAffected, int, i);

void IlcGCI::post ()
{
	for(int i=0; i<(cfgc->getVarArray()).getSize(); i++)
		s.getIntVar(cfgc->getVarArray()[i]).whenDomain(MyVarAffected(s, this, i));
}

IlcConstraint IlcGC(IloSolver s, IloIntVarArray V, cfgc_api* cfgc)
{
	return new (s.getHeap()) IlcGCI(s, V, cfgc);
}

ILOCPCONSTRAINTWRAPPER2(IloGC, solver, IloIntVarArray, V, cfgc_api*, cfgc)
{
	use(solver, V);
	return IlcGC(solver, V, cfgc);
}

/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ILOG Branching -- picks the min domain var, assigns it the min value. 
-- uses "incremental" from the global namespace
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// */
ILCGOAL3 (setValue, IlcInt, var, int, val, IloIntVarArray, V)
{
	if(incremental)
		for(int i=0; i< numCfgc;i++)
			v_cfgc[i]->setMarker();
	
	return getSolver().getIntVar(V[var]) == val;
}

ILCGOAL3 (removeValue, IlcInt, var, int, val, IloIntVarArray, V)
{
	if(incremental)
		for(int i=0; i< numCfgc;i++)
			v_cfgc[i]->setMarker();
	
	return getSolver().getIntVar(V[var]) != val;
}

ILCGOAL0 (backtrack)
{
	if(incremental)
		for(int i=0; i< numCfgc;i++)
			v_cfgc[i]->backtrack();
	fail();
	return 0;
}

static IlcChooseIndex1(IlcChooseMinDomain, var.getSize(), IlcIntVar)

ILCGOAL1(IlcMyBranching, IloIntVarArray , V)
{
	IloSolver solver = getSolver();

	IlcInt chosen = IlcChooseMinDomain(getSolver().getIntVarArray(V));
	
	if(chosen == -1)
		return 0;
	
	if (getSolver().getIntVar(V[chosen]).isBound()) 
		return 0;
	
	IlcInt value = getSolver().getIntVar(V[chosen]).getMin();
	
	IlcGoal goal = 	IlcOr( 	IlcOr(	IlcAnd( setValue(getSolver(), chosen, value, V),
						IlcMyBranching(getSolver(), V)), 
			backtrack(getSolver())),
				    IlcOr(	IlcAnd( removeValue(getSolver(), chosen, value, V),
						IlcMyBranching(getSolver(), V)),
						    backtrack( getSolver()) ));
	return goal;
}

ILOCPGOALWRAPPER1(IloMyBranching, solver, IloIntVarArray, V)
{
	return IlcMyBranching(solver, V);
}


/*////////////////////////////////////////////////////
PROGRAM ENTRY POINT
///////////////////////////////////////////////////// */
int main(int argc, char* argv[])
{
	if(argc < 3)
	{
		cout << "Usage: " << argv[0] << " <grammar> <algorithm> "<<endl;
		return 0;
	}
	
	filename = argv[1];
	incremental = atoi(argv[2]) > 0;

	grammar_api* grammar = new grammar_api(filename);
	grammar->printGrammar();
	
	/*//////////////////////////////////////////
	Important
	- terminal values in the grammar files are denoted with negative numbers starting from -1
	- minValue/maxValue should be in accordance with the number of terminal symbols
	- example: for left right balanced parenthesis, which only have two terminals; ( and ), maxValue is -1 and minValue is -2
	///////////////////////////////////////// */
	minValue = -2;
	maxValue = -1;
	numCfgc = 2;
	lengthCfgc = 36;

	IloEnv env;
	try 
	{
		cout<<"Filename: "<<filename<<endl;
		cout<<"Algorithm: ";(incremental)? (cout<<" Incremental"<<endl) : (cout<<" Non-Incremental"<<endl);
		cout<<"Number of cfgc: "<<numCfgc<<endl;
		cout<<"Length of cfgc: "<<lengthCfgc<<endl;
		cout<<"Domain size: "<<-1*(minValue+maxValue+1)<<endl<<endl;
		
		IloModel model(env);
		IloSolver solver(model);
		
		v_cfgc.resize(numCfgc);
		IloArray<IloIntVarArray> vars(env, numCfgc);
	
		//CFGC for each row
		for(IloInt i=0; i< numCfgc;i++)
		{
			vars[i] = IloIntVarArray(env, lengthCfgc, minValue, maxValue);
	
			cfgc_api* cfgc = new cfgc_api(grammar, solver, vars[i], lengthCfgc, incremental, i);
			v_cfgc[i]=cfgc;
			model.add( IloGC(env, vars[i], v_cfgc[i]) );
		}	
		
		// Duplicate vars for branching
		IloIntVarArray flatVars(env, lengthCfgc*numCfgc);
		for(IloInt i=0; i< numCfgc;i++)
		{
			for(IloInt j=0; j< lengthCfgc;j++)
			{
				flatVars[i*lengthCfgc + j] = IloIntVar(env, minValue, maxValue);
				model.add( vars[i][j] == flatVars[i*lengthCfgc + j]);
			}
		}
			
		IloGoal goal = IloMyBranching(env, flatVars);
		
		solver.extract(model);
		cout<<"Trying to solve..."<<flush;
		if (solver.solve(goal)) 
		{
			solver.out()<< "Solved!\n\n"; 
			for (IloInt i = 0; i < numCfgc; i++)
			{
				for(IloInt j = 0; j < lengthCfgc; j++)
					solver.out() << " " <<solver.getValue(vars[i][j]) << " "; 
				solver.out() << "\n";
			}
		}else { solver.out()<< "No solution\n";}
		
		cout<<"\nNumber of propagations        : "<<propagation<<"\n";
		solver.printInformation();
	}
	catch (IloException& ex){ cout << "Error: " << ex << endl; }
	env.end();
	
	return 0;
}
