#include "Picef.h"

Picef::Picef(const Instance& instance, const string& fileName, const vector<Exchange>& all_cycles, double time_limit) : instance(instance), all_cycles(all_cycles) {
	
	this->fileName = fileName;
	this->time_limit = time_limit;

	this->variable_number = -1;
	this->constraint_number = -1;

	this->env = new GRBEnv();
	this->env->start();
	this->model = new GRBModel(*env);

	string gurobi_log = ::solutionPath + this->fileName + "_gurobi_log.txt";
	this->model->set(GRB_StringParam_LogFile, gurobi_log);
	this->model->set(GRB_IntParam_LogToConsole, 1);
	this->model->set(GRB_IntParam_Threads, 1);
	this->model->set(GRB_DoubleParam_IntFeasTol, 1e-9);
	this->model->set(GRB_DoubleParam_MIPGap, 0);
	this->model->set(GRB_DoubleParam_MemLimit, 16.0);

	this->arcs.clear();
	this->ad_arcs_size = 0;
	for (int i = 0; i < this->instance.getAltruisticDonors().size(); i++) {
		int ad = this->instance.getAltruisticDonors().at(i);
		for (const auto& arc : this->instance.getVertices().at(ad).getOutGoingArcs()) {
			this->arcs.push_back(make_pair(ad, arc.first));
			this->ad_arcs_size++;
		}
	}
	for (int i = 0; i < this->instance.getIncompatiblePairs().size(); i++) {
		int pair = this->instance.getIncompatiblePairs().at(i);
		for (const auto& arc : this->instance.getVertices().at(pair).getOutGoingArcs()) {
			this->arcs.push_back(make_pair(pair, arc.first));
		}
	}
	this->shortest_paths_from_ad = vector<int>(this->instance.getIncompatiblePairs().size(), ::OMEGA);
	for (int i = 0; i < this->instance.getIncompatiblePairs().size(); i++) {
		int pair = this->instance.getIncompatiblePairs().at(i);
		for (int j = 0; j < this->instance.getAltruisticDonors().size(); j++) {
			int ad = this->instance.getAltruisticDonors().at(j);
			int length = this->instance.get_shortest_path_FW(ad, pair);
			if (this->shortest_paths_from_ad.at(i) > length) {
				this->shortest_paths_from_ad.at(i) = length;
			}
		}
	}

}

Picef::Picef(const Picef& p) : instance(p.instance), all_cycles(all_cycles) {
	this->env = p.env;
	this->model = p.model;
	this->arcs = p.arcs;
	this->ad_arcs_size = p.ad_arcs_size;
	this->time_limit = p.time_limit;
}

Picef::~Picef() {
}

void Picef::solve(const double& cycle_generation_time) {

	clock_t	start;

	try {

		this->model->set(GRB_DoubleParam_TimeLimit, this->time_limit);
		add_variables();
		add_constraints_kep();
		add_objective();

		//export_model(::instanceName + "_W_check.lp");

		mycallback_picef cb = mycallback_picef(this->incumbent_value, this->bound, this->bab_nodes);
		this->model->setCallback(&cb);

		start = clock();
		this->model->optimize();
		clock_t	end = clock();
		this->time = (double)(end - start) / CLOCKS_PER_SEC;
		this->time += cycle_generation_time;

		int solCount = this->model->get(GRB_IntAttr_SolCount);
		this->status = this->model->get(GRB_IntAttr_Status);
		this->variable_number = this->model->get(GRB_IntAttr_NumVars);
		this->constraint_number = this->model->get(GRB_IntAttr_NumConstrs);
		this->bab_nodes = 0;

		if (solCount > 0) {
			extract_solution_stats();
		}
		write_stats();

		delete this->model;
		delete this->env;

	}
	catch (GRBException e) {

		cout << "[Picef::solve()] - Error code = " << e.getErrorCode() << endl;
		cout << e.getMessage() << endl;
		if (e.getErrorCode() == 10001) {
			
			clock_t	end = clock();
			this->time = (double)(end - start) / CLOCKS_PER_SEC;
			this->time += cycle_generation_time;

			int solCount = this->model->get(GRB_IntAttr_SolCount);
			this->status = this->model->get(GRB_IntAttr_Status);
			this->variable_number = this->model->get(GRB_IntAttr_NumVars);
			this->constraint_number = this->model->get(GRB_IntAttr_NumConstrs);
			this->bab_nodes = 0;

			/*if (solCount > 0) {
				extract_solution_stats();
			}*/
			write_stats();

			delete this->model;
			delete this->env;

		}
		else {
			exit(EXIT_FAILURE);
		}
	}
	catch (...) {
		cout << "[Picef::solve()] - Error during optimization" << endl;
	}

}

void Picef::add_variables() {

	//cycle vars
	for (int i = 0; i < this->all_cycles.size(); i++) {
		string name = "cycle_" + to_string(i);
		this->var_cycles.push_back(this->model->addVar(0.0, 1.0, 0.0, GRB_INTEGER, name));
	}
	//arcs ad vars
	this->var_chains_arcs = vector<vector<GRBVar>>(this->arcs.size(), vector<GRBVar>());
	for (int i = 0; i < this->ad_arcs_size; i++) {
		string name = "arcs_ad_" + to_string(this->arcs.at(i).first) + "_" + to_string(this->arcs.at(i).second);
		this->var_chains_arcs.at(i).push_back(this->model->addVar(0.0, 1.0, 0.0, GRB_INTEGER, name));
	}
	for (int i = this->ad_arcs_size; i < this->arcs.size(); i++) {
		for (int l = 1; l < this->instance.getChainsLength() + 1; l++) {
			string name = "arcs_pairs_" + to_string(this->arcs.at(i).first) + "_" + to_string(this->arcs.at(i).second) + "_" + to_string(l);
			this->var_chains_arcs.at(i).push_back(this->model->addVar(0.0, 1.0, 0.0, GRB_INTEGER, name));
		}
	}

}

void Picef::add_objective() {
	GRBLinExpr obj = 0;
	//KEP obj
	for (int i = 0; i < this->all_cycles.size(); i++) {
		obj += this->all_cycles.at(i).getWeight() * this->var_cycles.at(i);
	}
	for (int i = 0; i < this->arcs.size(); i++) {
		const Vertex& v = this->instance.getVertices().at(this->arcs.at(i).first);
		int weight = v.getOutGoingArcs().at(this->arcs.at(i).second);
		for (int l = 0; l < this->var_chains_arcs.at(i).size(); l++) {
			obj += weight * this->var_chains_arcs.at(i).at(l);
		}
	}
	this->model->setObjective(obj, GRB_MAXIMIZE);
}

void Picef::add_constraints_kep() {
	//KEP 
	//packing
	for (int i = 0; i < this->instance.getIncompatiblePairs().size(); i++) {
		int pair = this->instance.getIncompatiblePairs().at(i);
		GRBLinExpr sum = 0;
		for (int j = 0; j < this->arcs.size(); j++) {
			if (this->arcs.at(j).second == pair) {
				for (int l = 0; l < this->var_chains_arcs.at(j).size(); l++) {
					sum += this->var_chains_arcs.at(j).at(l);
				}
			}
		}
		for (int j = 0; j < this->var_cycles.size(); j++) {
			if (this->all_cycles.at(j).isVertexInExchange(pair)) {
				sum += this->var_cycles.at(j);
			}
		}
		this->model->addConstr(sum <= 1, "packing_" + to_string(pair));
	}
	for (int i = 0; i < this->instance.getAltruisticDonors().size(); i++) {
		int ad = this->instance.getAltruisticDonors().at(i);
		GRBLinExpr sum = 0;
		for (int j = 0; j < this->ad_arcs_size; j++) {
			if (this->arcs.at(j).first == ad) {
				sum += this->var_chains_arcs.at(j).at(0);
			}
		}
		this->model->addConstr(sum <= 1, "packing_" + to_string(ad));
	}
	//PICEF
	for (int i = 0; i < this->instance.getIncompatiblePairs().size(); i++) {
		int pair = this->instance.getIncompatiblePairs().at(i);
		for (int l = 1; l < this->instance.getChainsLength(); l++) {
			int l_conv = l - 1;
			GRBLinExpr sum = 0;
			if (l != 1) {
				//pairs start at level 2
				for (int j = this->ad_arcs_size; j < this->arcs.size(); j++) {
					if (this->arcs.at(j).second == pair) {
						sum += this->var_chains_arcs.at(j).at(l_conv);
					}
				}
			}
			for (int j = this->ad_arcs_size; j < this->arcs.size(); j++) {
				if (this->arcs.at(j).first == pair) {
					sum -= this->var_chains_arcs.at(j).at(l_conv + 1);
				}
			}
			if (l == 1) {
				for (int j = 0; j < this->ad_arcs_size; j++) {
					if (this->arcs.at(j).second == pair) {
						sum += this->var_chains_arcs.at(j).at(l_conv);
					}
				}
			}
			this->model->addConstr(sum >= 0, "picef_" + to_string(pair) + "_" + to_string(l));
		}
	}
	//Force to zero pairs in first position: not required but better
	/*GRBLinExpr sum = 0;
	for (int j = this->ad_arcs_size; j < this->arcs.size(); j++) {
		sum += this->var_chains_arcs.at(j).at(0);
	}
	this->model->addConstr(sum <= 0, "picef_to_zero");*/
	 
	//reduced set by Dickerson
	GRBLinExpr sum = 0;
	for (int i = 0; i < this->instance.getIncompatiblePairs().size(); i++) {
		int pair = this->instance.getIncompatiblePairs().at(i);
		for (int j = 0; j < this->arcs.size(); j++) {
			if (this->arcs.at(j).first == pair) {
				int min_lenght = min(this->var_chains_arcs.at(j).size(), this->shortest_paths_from_ad.at(i));
				for (int l = 0; l < min_lenght; l++) {
					sum += this->var_chains_arcs.at(j).at(l);
				}
			}
		}
	}
	this->model->addConstr(sum <= 0, "reduced_to_zero");
}

void Picef::export_model(const string& name) {
	this->model->write(name);
}

void Picef::solution_check() {
	unordered_set<int> visits;
	double obj = 0.0;
	//cout <<toString()<<"\n";
	for (const auto& ex : this->solution) {
		obj += ex.getWeight();
		for (int i = 1; i < ex.getSequence().size(); i++) {
			if (visits.find(ex.getSequence().at(i)) != visits.end()) {
				cout << "double next no good packing constraints for " << ex.getSequence().at(i) << "\n";
				export_model(::instanceName + "_W_no_good_packing.lp");
				exit(EXIT_FAILURE);
			}
		}
	}
	if (!::equal(obj, round(this->incumbent_value))) {
		cout << "wrong obj " << obj << "\t" << this->incumbent_value << "\n";
		export_model(::instanceName + "_W_no_good_obj.lp");
		exit(EXIT_FAILURE);
	}
}

int Picef::get_next_in_chain(const Vertex& v, const int& length) {
	int next = -1;
	for (int i = this->ad_arcs_size; i < this->arcs.size(); i++) {
		if (this->arcs.at(i).first == v.getId()) {
			//cout << "v " << v.getId() << " " << i << " " << length << endl;
			if (this->var_chains_arcs.at(i).at(length + 1).get(GRB_DoubleAttr_X) > 0.5) {
				if (next != -1) {
					cout << "double next no good packing constraints for " << v.getId() << "\n";
					export_model(::instanceName + "_W_no_good_packing.lp");
					exit(EXIT_FAILURE);
				}
				next = this->arcs.at(i).second;
			}
		}
		
	}
	return next;
}

void Picef::extract_solution_stats() {
	this->incumbent_value = this->model->get(GRB_DoubleAttr_ObjVal);
	this->bound = this->model->get(GRB_DoubleAttr_ObjBound);
	this->bab_nodes = this->model->get(GRB_DoubleAttr_NodeCount);

	this->solution.clear();
	for (int k = 0; k < this->all_cycles.size(); k++) {
		if (var_cycles.at(k).get(GRB_DoubleAttr_X) > 0.5) {
			this->solution.push_back(this->all_cycles.at(k));
		}
	}

	for (int i = 0; i < this->ad_arcs_size; i++) {
		if (this->var_chains_arcs.at(i).at(0).get(GRB_DoubleAttr_X) > 0.5) {
			//cout << "ad " << this->arcs.at(i).first << "\t" << this->arcs.at(i).second << endl;
			vector<int> seq = { 0, this->arcs.at(i).first,this->arcs.at(i).second };
			int length = 1;
			const Vertex& v = this->instance.getVertices().at(this->arcs.at(i).first);
			int weight = v.getOutGoingArcs().at(this->arcs.at(i).second);
			int current = this->arcs.at(i).second;
			int next = -1;
			do {

				int l_conv = length - 1;
				const Vertex& u = this->instance.getVertices().at(current);
				next = get_next_in_chain(u, l_conv);
				if (next != -1) {
					length++;
					weight += u.getOutGoingArcs().at(next);
					seq.push_back(next);
					current = next;
				}

			} while (next != -1 && length < this->instance.getChainsLength());
			length++;
			Exchange chain = Exchange("chain", weight, length, seq, this->instance.getVertexNumber());
			this->solution.push_back(chain);
		}
	}

	/*for (int i = this->ad_arcs_size; i < this->arcs.size(); i++) {
		for (int l = 0; l < this->var_chains_arcs.at(i).size(); l++) {
			if (this->var_chains_arcs.at(i).at(l).get(GRB_DoubleAttr_X) > 0.5) {
				cout << "pair " << this->arcs.at(i).first << "\t" << this->arcs.at(i).second << "\t" << l+1 << "\n";
			}
		}
	}*/
	this->transplant_number = 0;
	for (const auto& ex : this->solution) {
		for (int i = 1; i < ex.getSequence().size(); i++) {
			int id = ex.getSequence().at(i);
			if (instance.getVertices().at(id).getType() == 'I') {
				this->transplant_number++;
			}
		}
	}

	solution_check();
}

void Picef::write_stats() {
	if (this->fileName != "") {

		string path_solution_name = this->fileName + "_sol_exchanges.txt";
		string res = "OBJECTIVE VALUE:\t" + to_string(this->incumbent_value) + "\n";
		res.append("COLUMNS:\n");
		for (const auto& col : this->solution) {
			res.append("\t" + col.toString() + "\n\n");
		}
		::removeAndWriteFile(path_solution_name, res);


		this->csv_line = to_string(this->variable_number) + "," + to_string(this->constraint_number) + "," + to_string(this->bab_nodes) + "," + to_string(this->status) + "," + to_string(this->incumbent_value) + "," +
			to_string(this->bound) + "," + to_string(this->time) + "\n";

		ofstream mystream;
		mystream.open(::solutionPath + "Solutions_PICEF_" + ::thread + ".csv", ios::app);
		mystream << this->fileName << "," << this->csv_line;
		mystream.close();

	}
}

const int& Picef::get_status() const {
	return this->status;
}

const int& Picef::get_transplant_number() const {
	return this->transplant_number;
}

const int& Picef::get_variable_number() const {
	return this->variable_number;
}

const int& Picef::get_constraint_number() const {
	return this->constraint_number;
}

const double& Picef::get_incumbent_value() const {
	return this->incumbent_value;
}

const double& Picef::get_bound() const {
	return this->bound;
}

const double& Picef::get_time() const {
	return this->time;
}

const bool& Picef::get_is_optimal() const {
	return this->is_optimal;
}

const string& Picef::get_csv_line() const {
	return this->csv_line;
}

const vector<Exchange>& Picef::get_solution() const {
	return this->solution;
}

string Picef::toString() const {
	string res = "OBJ: " + to_string(this->incumbent_value) + "\tBOUND: " + to_string(this->bound) + "\tTIME: " + to_string(this->time) + "\n\tSOLUTION:\n";
	for (const auto& ex : this->solution) {
		res += "\t" + ex.toString() + "\n";
	}
	return res;
}

Picef& Picef::operator=(const Picef& p) {
	this->time_limit = p.time_limit;
	return *this;
}
