package sampling;

import gurobi.GRB;
import gurobi.GRBEnv;
import gurobi.GRBException;
import gurobi.GRBLinExpr;
import gurobi.GRBModel;
import gurobi.GRBVar;

import java.util.ArrayList;
import java.util.Random;

import backwardAlgorithm.DataHandler;
import backwardAlgorithm.Solution;

public class Sampler {

	Random r;
	GRBEnv env;
	GRBModel sModel;
	Solution optSol;
	
	
public Sampler(DataHandler data) throws GRBException{
	r = new Random(0);
	env = new GRBEnv(null);
	env.set(GRB.IntParam.OutputFlag, 0);
	
	optSol = new Solution(data , -1);
	getOptSol(data,optSol);

}

private void getOptSol(DataHandler data, Solution sol) throws GRBException {
	sModel = new GRBModel(env);
	sModel.reset();
	
	GRBVar[] y = new GRBVar[data.T]; // Production
	GRBVar[] v = new GRBVar[data.T]; // Setup
	GRBVar[] I = new GRBVar[data.T]; // Inventory
	GRBVar[] s = new GRBVar[data.T]; // Shortage
	
	GRBVar[][] a = new GRBVar[data.T][data.T]; // Intended production
	
	//Create variables  
	for (int i = 0; i < data.T; i++) {
		v[i] = sModel.addVar(0, 1, data.f[i], GRB.BINARY, "v["+i+"]");
		y[i] = sModel.addVar(0, Double.POSITIVE_INFINITY, data.c[i], GRB.CONTINUOUS, "y["+i+"]");
		I[i] = sModel.addVar(0, Double.POSITIVE_INFINITY, data.h[i], GRB.CONTINUOUS, "I["+i+"]");
		s[i] = sModel.addVar(0, Double.POSITIVE_INFINITY, data.q[i], GRB.CONTINUOUS, "s["+i+"]");
		for(int j=i; j< data.T; j++){
			a[i][j] = sModel.addVar(0, Double.POSITIVE_INFINITY, 0, GRB.CONTINUOUS, "a["+i+","+j+"]");}

	}

	//Integrate variables
	sModel.update();

	//Add inventory constraints for each time period > 0
	for (int t = 1; t <data.T; t++) {
		GRBLinExpr expr = new GRBLinExpr();
		expr.addTerm(1, y[t]);
		expr.addTerm(1, I[t-1]);
		expr.addTerm(1, s[t]);
		expr.addTerm(-1, I[t]);
		
		sModel.addConstr(expr, GRB.EQUAL, data.demand[t], "c"+t);
	}
	
	//Add inventory constraints for time period  0

	GRBLinExpr expr0 = new GRBLinExpr();
	expr0.addTerm(1, y[0]);
	expr0.addTerm(1, s[0]);
	expr0.addTerm(-1, I[0]);
	sModel.addConstr(expr0, GRB.EQUAL, data.demand[0], "c0");
	
	//Add setup constraints for each time period
	for (int t = 0; t <data.T; t++) {
		GRBLinExpr expr = new GRBLinExpr();
		expr.addTerm(1, y[t]);
		expr.addTerm(-data.cap[t], v[t]);
				
		sModel.addConstr(expr, GRB.LESS_EQUAL, 0, "cs"+t);
	}
	
	//Add a_ij constraints 1
	for (int t = 0; t <data.T; t++) {
		GRBLinExpr expr = new GRBLinExpr();
		for (int j = t; j < data.T; j++) {
			expr.addTerm(1, a[t][j]);
		}
		expr.addTerm(-1, y[t]);
		sModel.addConstr(expr, GRB.EQUAL, 0, "ca"+t);
	}
	//Add a_ij constraints 2
	for (int t = 0; t <data.T; t++) {
		GRBLinExpr expr = new GRBLinExpr();
		for (int j = 0; j <= t; j++) {
			expr.addTerm(1, a[j][t]);
		}
		expr.addTerm(1, s[t]);
		sModel.addConstr(expr, GRB.EQUAL, data.demand[t], "ca2"+t);
	}
	

	//Integrate constraints		
	sModel.update();
	sModel.optimize();
	
	if(sModel.get(GRB.IntAttr.Status) == GRB.INFEASIBLE){
		System.out.println("INFEASIBLE!!!!!!!!!!!!!!!!!!!!!!!!!!");
	}else{
		
	sol.obj = Math.round(sModel.get(GRB.DoubleAttr.ObjVal)*100)/100.0;
		
	for(int i=0; i<data.T; i++){
		sol.y[i]=Math.round(y[i].get(GRB.DoubleAttr.X)*1000)/1000.0;
		sol.v[i]=(int)Math.round(v[i].get(GRB.DoubleAttr.X));
		sol.I[i]=Math.round(I[i].get(GRB.DoubleAttr.X)*1000)/1000.0;
		sol.s[i]=Math.round(s[i].get(GRB.DoubleAttr.X)*1000)/1000.0;
	    }
	}
	calcM(data,a,sol);

	 sModel.reset();
	
		
}

public void sampleR(DataHandler data, ArrayList<Solution> sample, double obj) throws GRBException {
	if(sample.size() == 0){sample.add(optSol);}

	for (int i = 0; i < 200; i++) {
		Solution nSol = randomSol(data);
		if(nSol.obj<=obj ){
			nSol.sampled = true;
			sample.add(nSol);
			}
		}
	}

public void sampleLS(DataHandler data, ArrayList<Solution> sample, double obj) throws GRBException {
	
	if(sample.size() == 0){sample.add(optSol);}

	//Solution pSol = perturbation(data);
	Solution pSol = randomSol(data);
	Solution iSol = pSol;
	
	for (int i = 0; i < 100; i++) {
		
		Solution nSol = neighbor2(data,iSol);
	
		if(nSol.obj<iSol.obj){
			iSol = nSol;
		}
		if(nSol.obj<=obj && nSol.obj<=optSol.obj*1.25){
			nSol.sampled = true;
			sample.add(nSol);
		}
		
		
	}
		
}

private Solution neighbor2(DataHandler data, Solution iSol) throws GRBException {

	Solution pSol = new Solution (data, -1);
	
	for (int t = 0; t < data.T; t++) {
		if(r.nextDouble()<=0.2){
			if(optSol.v[t] == 0){pSol.v[t] = 1;}
			else{pSol.v[t] = 0;}
		}
		else{
			pSol.v[t] = optSol.v[t];
		}
	}
	
	completeSolution(data,pSol);
	
	//pSol.printobj();
	
	return pSol;

}

private Solution randomSol(DataHandler data) throws GRBException {
Solution pSol = new Solution (data, -1);

	for (int t = 0; t < data.T; t++) {
		if(r.nextBoolean() ){
			pSol.v[t] = 0;
		}
		else{
			pSol.v[t] = 1;
		}
	}
	
	completeSolution(data,pSol);
	
	//pSol.printobj();
	
	return pSol;
}

private Solution neighbor(DataHandler data, Solution iSol) throws GRBException {

	Solution nSol = new Solution (data, -1);
	
	for (int t = 0; t < data.T; t++) {
		nSol.v[t] = iSol.v[t];
	}
	
	int flip = r.nextInt(data.T);
	if(nSol.v[flip]==0){nSol.v[flip]=1;}
	else{nSol.v[flip]=0;}
	
	completeSolution(data,nSol);

	return nSol;
		
}

private Solution perturbation(DataHandler data) throws GRBException {

	Solution pSol = new Solution (data, -1);
	
	for (int t = 0; t < data.T; t++) {
		if(r.nextDouble()<=0.2){
			if(optSol.v[t] == 0){pSol.v[t] = 1;}
			else{pSol.v[t] = 0;}
		}
		else{
			pSol.v[t] = optSol.v[t];
		}
	}
	
	completeSolution(data,pSol);
	
	//pSol.printobj();
	
	return pSol;
}

private void completeSolution(DataHandler data, Solution sol) throws GRBException {

	sModel = new GRBModel(env);
	sModel.reset();
	
	GRBVar[] y = new GRBVar[data.T]; // Production
	GRBVar[] I = new GRBVar[data.T]; // Inventory
	GRBVar[] s = new GRBVar[data.T]; // Shortage
	
	//Create variables  
	for (int i = 0; i < data.T; i++) {
		y[i] = sModel.addVar(0, data.cap[i]*sol.v[i], data.c[i], GRB.CONTINUOUS, "y["+i+"]");
		I[i] = sModel.addVar(0, Double.POSITIVE_INFINITY, data.h[i], GRB.CONTINUOUS, "I["+i+"]");
		s[i] = sModel.addVar(0, Double.POSITIVE_INFINITY, data.q[i], GRB.CONTINUOUS, "s["+i+"]");
	}

	//Integrate variables
	sModel.update();

	//Add inventory constraints for each time period > 0
	for (int t = 1; t <data.T; t++) {
		GRBLinExpr expr = new GRBLinExpr();
		expr.addTerm(1, y[t]);
		expr.addTerm(1, I[t-1]);
		expr.addTerm(1, s[t]);
		expr.addTerm(-1, I[t]);
		
		sModel.addConstr(expr, GRB.EQUAL, data.demand[t], "c"+t);
	}
	
	//Add inventory constraints for time period  0

	GRBLinExpr expr0 = new GRBLinExpr();
	expr0.addTerm(1, y[0]);
	expr0.addTerm(1, s[0]);
	expr0.addTerm(-1, I[0]);
	sModel.addConstr(expr0, GRB.EQUAL, data.demand[0], "c0");
		
	//Integrate constraints		
	sModel.update();
	sModel.optimize();
	
	if(sModel.get(GRB.IntAttr.Status) == GRB.INFEASIBLE){
		System.out.println("INFEASIBLE!!!!!!!!!!!!!!!!!!!!!!!!!!");
	}else{
		
	double obj=0;
	for (int j = 0; j < data.T; j++) {
		obj+=sol.v[j]*data.f[j];
	}
	
	obj+=sModel.get(GRB.DoubleAttr.ObjVal);
	
	sol.obj = Math.round(obj*100)/100.0;
		
	for(int i=0; i<data.T; i++){
		sol.y[i]=y[i].get(GRB.DoubleAttr.X);
		sol.I[i]=I[i].get(GRB.DoubleAttr.X);
		sol.s[i]=s[i].get(GRB.DoubleAttr.X);
	    }
	}
 
	 sModel.reset();
	
}
// This is the sampling scheme we report in the paper
public void sampleWithx(DataHandler data, ArrayList<Solution> sample, double zbar) throws GRBException {
	if(sample.size() == 0){sample.add(optSol);}

	for (int i = 0; i < 50; i++) {
		Solution nSol = randomSolFromx(data);
		if(nSol.obj<=zbar ){
			nSol.sampled = true;
			sample.add(nSol);
			}
		}
//System.out.println("SAMPLE SIZE: "+sample.size());

}

private Solution randomSolFromx(DataHandler data) throws GRBException {
	Solution rSol = new Solution (data, -1);

	int[] xcut = new int[data.T];
	int count = 0;
	
	while(count<2*data.aBudget){
		int pos=r.nextInt(data.T);
		if(xcut[pos]==0){
			xcut[pos]=1;
			//System.out.println("Random attack "+pos);
			count++;
		}
	}
		
	
	solveThirdStage(data, xcut, rSol);
	
	//rSol.print();
	
	return rSol;

}	
	
public void solveThirdStage(DataHandler data, int[] xcut , Solution sol) throws GRBException {
	
	sModel = new GRBModel(env);
	sModel.reset();
	
	GRBVar[] y = new GRBVar[data.T]; // Production
	GRBVar[] v = new GRBVar[data.T]; // Setup
	GRBVar[] I = new GRBVar[data.T]; // Inventory
	GRBVar[] s = new GRBVar[data.T]; // Shortage

	GRBVar[][] a = new GRBVar[data.T][data.T]; // Intended production
	
	//Create variables  
	for (int i = 0; i < data.T; i++) {
		v[i] = sModel.addVar(0, 1-xcut[i], data.f[i], GRB.BINARY, "v["+i+"]");
		y[i] = sModel.addVar(0, Double.POSITIVE_INFINITY, data.c[i], GRB.CONTINUOUS, "y["+i+"]");
		I[i] = sModel.addVar(0, Double.POSITIVE_INFINITY, data.h[i], GRB.CONTINUOUS, "I["+i+"]");
		s[i] = sModel.addVar(0, Double.POSITIVE_INFINITY, data.q[i], GRB.CONTINUOUS, "s["+i+"]");
		for(int j=i; j< data.T; j++){
			a[i][j] = sModel.addVar(0, Double.POSITIVE_INFINITY, 0, GRB.CONTINUOUS, "a["+i+","+j+"]");}
	}

	//Integrate variables
	sModel.update();

	//Add inventory constraints for each time period > 0
	for (int t = 1; t <data.T; t++) {
		GRBLinExpr expr = new GRBLinExpr();
		expr.addTerm(1, y[t]);
		expr.addTerm(1, I[t-1]);
		expr.addTerm(1, s[t]);
		expr.addTerm(-1, I[t]);
		
		sModel.addConstr(expr, GRB.EQUAL, data.demand[t], "c"+t);
	}
	
	//Add inventory constraints for time period  0

	GRBLinExpr expr0 = new GRBLinExpr();
	expr0.addTerm(1, y[0]);
	expr0.addTerm(1, s[0]);
	expr0.addTerm(-1, I[0]);
	sModel.addConstr(expr0, GRB.EQUAL, data.demand[0], "c0");
	
	//Add setup constraints for each time period
	for (int t = 0; t <data.T; t++) {
		GRBLinExpr expr = new GRBLinExpr();
		expr.addTerm(1, y[t]);
		expr.addTerm(-data.cap[t], v[t]);
				
		sModel.addConstr(expr, GRB.LESS_EQUAL, 0, "cs"+t);
	}
	
	//Add a_ij constraints 1
	for (int t = 0; t <data.T; t++) {
		GRBLinExpr expr = new GRBLinExpr();
		for (int j = t; j < data.T; j++) {
			expr.addTerm(1, a[t][j]);
		}
		expr.addTerm(-1, y[t]);
		sModel.addConstr(expr, GRB.EQUAL, 0, "ca"+t);
	}
	//Add a_ij constraints 2
	for (int t = 0; t <data.T; t++) {
		GRBLinExpr expr = new GRBLinExpr();
		for (int j = 0; j <= t; j++) {
			expr.addTerm(1, a[j][t]);
		}
		expr.addTerm(1, s[t]);
		sModel.addConstr(expr, GRB.EQUAL, data.demand[t], "ca2"+t);
	}
	
	//Integrate constraints		
	sModel.update();
	sModel.optimize();
	
	if(sModel.get(GRB.IntAttr.Status) == GRB.INFEASIBLE){
		System.out.println("SAMPLE INFEASIBLE !!!!!!!!!!!!!!!!!!!!!!!!!!");
	}else{
	
	sol.obj = Math.round(sModel.get(GRB.DoubleAttr.ObjVal)*100)/100.0;	
	
	for(int i=0; i<data.T; i++){
		sol.y[i]=y[i].get(GRB.DoubleAttr.X);
		sol.v[i]=(int)Math.round(v[i].get(GRB.DoubleAttr.X));
		sol.I[i]=I[i].get(GRB.DoubleAttr.X);
		sol.s[i]=s[i].get(GRB.DoubleAttr.X);
		}
	calcM(data,a,sol);
	
	}
}

private void calcM(DataHandler data, GRBVar[][] a, Solution sol) throws GRBException {
	
	
	for (int t = 0; t < data.T; t++) {
		double M = 0;
		for (int j = t; j < data.T; j++) {
			M+=data.q[j]*a[t][j].get(GRB.DoubleAttr.X);
		}
		double holding = 0;
		for (int j = t+1; j < data.T; j++) {
			for(int l=t; l<j; l++){
				holding+=data.h[l]*a[t][j].get(GRB.DoubleAttr.X);
			}
		}
		M=M-holding;
		M=M-data.c[t]*sol.y[t];
		M=M-data.f[t]*sol.v[t];
		sol.M[t] = Math.ceil(M);
		if(sol.M[t]<0) System.out.println("PARITY CHECK: NEGATIVE M");
	}
	
	
	
	/*
	sol.print();
	double[][] aux = new double[data.T][data.T];
	for (int t = 0; t < data.T; t++) {
		for (int j = t; j < data.T; j++) {
			aux[t][j]=a[t][j].get(GRB.DoubleAttr.X);
			if(aux[t][j]>0){
			System.out.println("a["+t+","+j+"]: "+aux[t][j]);}
		}
	}
	*/

}











}
