#include<math.h>
#include<iostream>
#include<cstring>
#include"models.h"

/*
 * Model
 */

Model::Model(){
	Data T1("Model"),T2("Experience");
	m_data = T1;
	exp_data = T2;
	CostFunction* f;
	m_cost = f;
}

Model::Model(const Model& other): m_data(other.m_data),exp_data(other.exp_data),m_cost(other.m_cost){}

Model::Model(Data T){
	Data T1("Model");
	m_data = T1;
	exp_data = T;
	CostFunction* f;
	m_cost = f;
}

//Model::Model(Data T1, Data T2, CostFunction* f): m_data(T1), exp_data(T2), m_cost(f){}

void Model::setModelData(Data T1){
	m_data = T1;
}

void Model::setExpData(Data T2){
	exp_data = T2;
}

void Model::setCost(CostFunction* f){
	m_cost = f;
}



/*
 * Linear Approximation model
 */

//Constructor & Destructor

LinearApprox::LinearApprox(): param({0.,0.}){
	Data T1("LinearApprox"),T2("ExperimentalData");
	m_data = T1;
	exp_data = T2;
	m_cost = new Khi2(m_data.getData(),exp_data.getData());
}

LinearApprox::LinearApprox(const LinearApprox& other): param(other.param){
	m_data = other.m_data;
	exp_data = other.exp_data;
	m_cost = other.m_cost;
}

LinearApprox::LinearApprox(Data T): param({0.,0.}){
	exp_data = T;
	std::vector<std::vector<double>> V1(exp_data.getCard(), std::vector<double> (2,0.));
	for(int i=0;i<exp_data.getCard();i++)
		V1[i][0] = exp_data.getData()[i][0];
	Data T1(V1,"LinearApprox");
	m_data = T1;
	m_cost = new Khi2(m_data.getData(),exp_data.getData());
}

LinearApprox::LinearApprox(Data T,std::vector<double> par): param(par){
	exp_data = T;
	std::vector<std::vector<double>> V1(exp_data.getCard(), std::vector<double> (2,0.));
	for(int i=0;i<exp_data.getCard();i++){
		V1[i][0] = exp_data.getData()[i][0];
		V1[i][1] = param[0] + param[1] * exp_data.getData()[i][0];
	}
	Data T1(V1,"LinearApprox");
	m_data = T1;
	m_cost = new Khi2(m_data.getData(),exp_data.getData());
}

//Set model data, experimental data and cost function

void LinearApprox::setModelData(Data T1){
	m_data = T1;
}

void LinearApprox::setExpData(Data T){
	exp_data = T;
	std::vector<std::vector<double>> V1(exp_data.getData());
	for(int i=0;i<exp_data.getCard();i++)
		V1[i][1] = param[0] + param[1]*V1[i][0];
	m_data.setData(V1);
	//delete m_cost;
	//m_cost = new Khi2(exp_data.getData(),m_data.getData());
}

void LinearApprox::setCost(CostFunction* f){
	delete m_cost;
	m_cost = new Khi2(exp_data.getData(),m_data.getData());
}

//Solution, Neighbor and cost getter

Data LinearApprox::getSol() const {
	return m_data;
}

void LinearApprox::setParam(std::vector<double> a){
	param = a;
	std::vector<std::vector<double>> n_val = m_data.getData();
	for(int i=0;i<n_val.size();i++)
		n_val[i][1] = param[1] * n_val[i][0] + param[0];
	m_data.setData(n_val);
	//delete m_cost;
	//m_cost = new Khi2(exp_data.getData(),m_data.getData());
}

std::vector<double> LinearApprox::getParam() const {
	return param;
}

std::vector<double> LinearApprox::getNeighbor(double ampl) const {
	double d_o=0,d_s=0;
	d_o = ampl*(2.*rand()/(RAND_MAX+1.)-1.);
	d_s = ampl*(2.*rand()/(RAND_MAX+1.)-1.);
	std::vector<double> n_param = {param[0]+d_o,param[1]+d_s};
	return n_param;
}

double LinearApprox::getCost(){
	delete m_cost;
	m_cost = new Khi2(exp_data.getData(),m_data.getData());
	return m_cost->get();
}

//Export model parameters

void LinearApprox::exportModel() const {
	FILE * fich = fopen((exp_data.getName()+".linear_approx.data").c_str(), "w");
	fprintf(fich, "y = %lf *x + %lf", param[1], param[0]);
}

void LinearApprox::displayModel() const {
	char buffer[32];
	memset(buffer, 0, sizeof(buffer));
	snprintf(buffer, sizeof(buffer), "%g", m_cost->get());
	std::string cost(buffer);
	std::vector<std::string> commandsForGnuplot = {"set title \""+m_data.getName()+"\"","set key bmargin","plot '"+exp_data.getName()+".temp' using 1:2 w p pt 1 lc -1 title 'Experimental Data'"," replot '"+exp_data.getName()+".temp' using 3:4 w l dt 2 lc 6 title 'Linear approximation with khi2="+cost+"'"};
	FILE * temp = fopen((exp_data.getName()+".temp").c_str(), "w");
	FILE * gnuplotPipe = popen ("gnuplot -persistent", "w");
	for (int i=0; i < m_data.getCard(); i++){
		fprintf(temp, "%lf %lf %lf %lf \n", exp_data.getData()[i][0], exp_data.getData()[i][1], m_data.getData()[i][0], m_data.getData()[i][1]);
	}
	for (std::string command : commandsForGnuplot){
		fprintf(gnuplotPipe,"%s \n", command.c_str());
	}
}

//Set and get parameters

Data LinearApprox::getExpData() const {
	return exp_data;
}

void LinearApprox::setSlope(double a){
	param[1] = a;
	std::vector<std::vector<double>> n_val = m_data.getData();
	for(int i=0;i<n_val.size();i++)
		n_val[i][1] = param[1] * n_val[i][0] + param[0];
	m_data.setData(n_val);
	//delete m_cost;
	//m_cost = new Khi2(exp_data.getData(),m_data.getData());
}

double LinearApprox::getSlope() const {
	return param[1];
}

void LinearApprox::setOffset(double b){
	param[0] = b;
	std::vector<std::vector<double>> n_val = m_data.getData();
	for(int i=0;i<n_val.size();i++)
		n_val[i][1] = param[1] * n_val[i][0] + param[0];
	m_data.setData(n_val);
	//delete m_cost;
	//m_cost = new Khi2(exp_data.getData(),m_data.getData());
}

double LinearApprox::getOffset() const {
	return param[0];
}




/*
 * Polynomial Approximation model
 */

//Function to compute the value of a polynom at a point

double polynom(std::vector<double> a, double x){
	double y=0,tmp=0;
	for(int i=0;i<a.size();i++){
		tmp = y;
		y = tmp + a[i]*pow(x,i);
	}
	return y;
}

//Constructor & Destructor

PolynomialApprox::PolynomialApprox(){
	Data T1("PolynomialApprox"),T2("ExperimentalData");
	m_data = T1;
	exp_data = T2;
	m_cost = new Khi2(m_data.getData(),exp_data.getData());
	std::vector<double> a(3,0.);
	param = a;
}

PolynomialApprox::PolynomialApprox(const PolynomialApprox& other):param(other.param){
	m_data = other.m_data;
	exp_data = other.exp_data;
	m_cost = other.m_cost;
}

PolynomialApprox::PolynomialApprox(int n){
	std::vector<double> a(n+1,0.);
	param = a;
	Data T1("PolynomialApprox"),T2("ExperimentalData");
	m_data = T1;
	exp_data = T2;
	m_cost = new Khi2(m_data.getData(),exp_data.getData());
}

PolynomialApprox::PolynomialApprox(Data T, int n){
	std::vector<double> a(n+1,0.);
	param = a;
	exp_data = T;
	std::vector<std::vector<double>> V1(exp_data.getCard(), std::vector<double> (2,0.));
	for(int i=0;i<exp_data.getCard();i++)
		V1[i][0] = exp_data.getData()[i][0];
	Data T1(V1,"PolynomialApprox");
	m_data = T1;
	m_cost = new Khi2(m_data.getData(),exp_data.getData());
}


PolynomialApprox::PolynomialApprox(Data T, std::vector<double> a):param(a){
	exp_data = T;
	std::vector<std::vector<double>> PolDat, ExpDat = exp_data.getData();
	for(int i=0;i<exp_data.getCard();i++){
		double x=0.,y=0.;
		x = ExpDat[i][0];
		y = polynom(param,x);
		PolDat.push_back({x,y});
	}
	Data T1(PolDat,"PolynomialApprox");
	m_data = T1;
	m_cost = new Khi2(m_data.getData(),exp_data.getData());
}

//Set model data, experimental data and cost function

void PolynomialApprox::setModelData(Data T1){
	m_data = T1;
}

void PolynomialApprox::setExpData(Data T){
	exp_data = T;
	std::vector<std::vector<double>> V1(exp_data.getData());
	for(int i=0;i<exp_data.getCard();i++){
		V1[i][1] = polynom(param,V1[i][0]);
	}
	m_data.setData(V1);
	//delete m_cost;
	//m_cost = new Khi2(exp_data.getData(),m_data.getData());
}

void PolynomialApprox::setCost(CostFunction* f){
	delete m_cost;
	m_cost = new Khi2(exp_data.getData(),m_data.getData());
}


//Solution, Neighbor and cost getter

Data PolynomialApprox::getSol() const {
	return m_data;
}

void PolynomialApprox::setParam(std::vector<double> a){
	param = a;
	std::vector<std::vector<double>> n_val = m_data.getData();
	for(int i=0;i<n_val.size();i++)
		n_val[i][1] = polynom(param,n_val[i][0]);
	m_data.setData(n_val);
	//delete m_cost;
	//m_cost = new Khi2(exp_data.getData(),m_data.getData());
}

std::vector<double> PolynomialApprox::getParam() const {
	return param;
}

std::vector<double> PolynomialApprox::getNeighbor(double ampl) const {
	double d_a=0;
	std::vector<double> n_param;
	for(int i=0;i<param.size();i++){
		d_a = ampl*(2.*rand()/(RAND_MAX+1.)-1.);
		n_param.push_back(param[i]+d_a);
	}
	return n_param;
}

double PolynomialApprox::getCost(){
	delete m_cost;
	m_cost = new Khi2(exp_data.getData(),m_data.getData());
	return m_cost->get();
}

//Export model parameters

void PolynomialApprox::exportModel() const {
	FILE * fich = fopen((exp_data.getName()+".polynomial_approx.data").c_str(), "w");
	fprintf(fich, "Polynom of degree %li with parameters : \n",param.size());
	for(int i=0;i<param.size();i++)
		fprintf(fich, "a_{%i} = %lf \n", i, param[i]);
}

void PolynomialApprox::displayModel() const {
	char buffer[32];
	memset(buffer, 0, sizeof(buffer));
	snprintf(buffer, sizeof(buffer), "%g", m_cost->get());
	std::string cost(buffer);
	std::vector<std::string> commandsForGnuplot = {"set title \""+m_data.getName()+"\"","set key bmargin","plot '"+exp_data.getName()+".temp' using 1:2 w p pt 1 lc -1 title 'Experimental Data'"," replot '"+exp_data.getName()+".temp' using 3:4 w l dt 2 lc 6 title 'Polynomial approximation with khi2="+cost+"'"};
	FILE * temp = fopen((exp_data.getName()+".temp").c_str(), "w");
	FILE * gnuplotPipe = popen ("gnuplot -persistent", "w");
	for (int i=0; i < m_data.getCard(); i++){
		fprintf(temp, "%lf %lf %lf %lf \n", exp_data.getData()[i][0], exp_data.getData()[i][1], m_data.getData()[i][0], m_data.getData()[i][1]);
	}
	for (std::string command : commandsForGnuplot){
		fprintf(gnuplotPipe,"%s \n", command.c_str());
	}
}

//Set and get parameters

Data PolynomialApprox::getExpData() const {
	return exp_data;
}




/*
 * Traveling SalesPerson problem
 */

std::vector<int> vdtovi(std::vector<double> Vd){
	std::vector<int> Vi;
	for(int i=0;i<Vd.size();i++)
		Vi.push_back(int(Vd[i]));
	return Vi;
}
std::vector<double> vitovd(std::vector<int> Vi){
	std::vector<double> Vd;
	for(int i=0;i<Vi.size();i++)
		Vd.push_back(Vi[i]);
	return Vd;
}

//Constructor & Destructor

TSP::TSP(){
	Data T1("TSP"),T2("Cities");
	T2.randSet(50);
	T1.setData(T2.getData());
	m_data = T1;
	exp_data = T2;
	std::vector<int> o(exp_data.getCard(),0);
	for(int i=0;i<exp_data.getCard();i++)
		o[i] = i;
	order = o;
	m_cost = new Distance(exp_data.getData(),order);
}

TSP::TSP(const TSP& other):order(other.order){
	m_data = other.m_data;
	exp_data = other.exp_data;
	m_cost = other.m_cost;
}

TSP::TSP(Data T){
	exp_data = T;
	std::vector<int> o(exp_data.getCard(),0);
	std::vector<std::vector<double>> V1(exp_data.getCard(), std::vector<double> (2,0.));
	for(int i=0;i<exp_data.getCard();i++){
		o[i] = i;
		V1[i] = exp_data.getData()[o[i]];
	}
	order = o;
	Data T1(V1,"TSP"+exp_data.getName());
	m_data = T1;
	m_cost = new Distance(exp_data.getData(),order);
}

TSP::TSP(Data T, std::vector<int> o){
	exp_data = T;
	order = o;
	std::vector<std::vector<double>> V1(exp_data.getCard(), std::vector<double> (2,0.));
	for(int i=0;i<exp_data.getCard();i++){
		V1[i] = exp_data.getData()[order[i]];
	}
	Data T1(V1,"TSP"+exp_data.getName());
	m_data = T1;
	m_cost = new Distance(exp_data.getData(),order);
}

//Set model data, experimental data and cost function

void TSP::setModelData(Data T1){
	m_data = T1;
}

void TSP::setExpData(Data T){
	exp_data = T;
	if(exp_data.getCard() != order.size()){
		std::vector<int> o(exp_data.getCard(), 0);
		for(int i=0;i<exp_data.getCard();i++)
			o[i] = i;
		order = o;
		m_data.setData(exp_data.getData());
	}
	else{
		std::vector<std::vector<double>> T2(m_data.getData());
		for(int i=0;i<exp_data.getCard();i++)
			T2[i] = exp_data.getData()[order[i]];
		m_data.setData(T2);
	}
	//delete m_cost;
	//m_cost = new Distance(exp_data.getData(),order);
}

void TSP::setCost(CostFunction* f){
	delete m_cost;
	m_cost = new Distance(exp_data.getData(),order);
}

//Solution, Neighbor and cost getter

Data TSP::getSol() const {
	return m_data;
}

void TSP::setParam(std::vector<double> a){
	order = vdtovi(a);
	std::vector<std::vector<double>> n_val(m_data.getData());
	for(int i=0;i<order.size();i++)
		n_val[i] = exp_data.getData()[order[i]];
	m_data.setData(n_val);
	//delete m_cost;
	//m_cost = new Distance(exp_data.getData(),order);
}

std::vector<double> TSP::getParam() const {
	return vitovd(order);
}
/*
std::vector<double> TSP::getNeighbor(double ampl) const {	//This method take 2 random cities and swap them
	std::vector<int> n_order_i(order);
	for(int i=0;i<floor(10.*ampl+1.);i++){
		int indA = 0, indB = 0, tmp = 0;
		indA = rand()%order.size();
		indB = rand()%order.size();
		if(indA!=indB){
			tmp = n_order_i[indA];
			n_order_i[indA] = n_order_i[indB];
			n_order_i[indB] = tmp;
		}
	}
	std::vector<double> n_order_d(vitovd(n_order_i));
	return n_order_d;
}
*/
/*
std::vector<double> TSP::getNeighbor(double ampl) const {	//This method take one random city and put it at the end of the path
	std::vector<int> n_order_i(order);
	for(int i=0;i<floor(10.*ampl+1.);i++){
		int ind = 0, tmp = 0;
		ind = rand()%order.size();
		if(ind!=(order.size()-1)){
			std::vector<int> n_order_ii(n_order_i);
			for(int j=ind;j<(order.size()-1);j++)
				n_order_i[j] = n_order_ii[j+1];
			n_order_i[order.size()-1] = n_order_ii[ind];
		}
	}
	std::vector<double> n_order_d(vitovd(n_order_i));
	return n_order_d;
}
*/

std::vector<double> TSP::getNeighbor(double ampl) const {	//This method take a subset of the path and shuffle it
	std::vector<int> n_order_i(order);
	std::vector<int> tmp;
	int set_length = floor(10.*ampl+1.);
	int ind_set = 0, ind_new = 0;
	ind_set = rand()%(order.size()-set_length+1);
	for(int i=0;i<set_length;i++){
		tmp.push_back(order[ind_set+i]);
	}
	for(int i=0;i<set_length;i++){
		ind_new = rand()%tmp.size();
		n_order_i[ind_set+i] = tmp[ind_new];
		tmp.erase(tmp.begin()+ind_new);
	}
	std::vector<double> n_order_d(vitovd(n_order_i));
	return n_order_d;
}

double TSP::getCost(){
	delete m_cost;
	m_cost = new Distance(exp_data.getData(),order);
	return m_cost->get();
}

//Export model parameters

void TSP::exportModel() const {
	FILE * fich = fopen((exp_data.getName()+".TSP.data").c_str(), "w");
	fprintf(fich, "The Salesperson should go through thoses %li cities in this order to get distance %lf : \n",order.size(),m_cost->get());
	for(int i=0;i<m_data.getCard();i++)
		fprintf(fich, "%lf %lf  \n", m_data.getData()[i][0], m_data.getData()[i][1]);
}

void TSP::displayModel() const {
	char buffer[32];
	memset(buffer, 0, sizeof(buffer));
	snprintf(buffer, sizeof(buffer), "%g", m_cost->get());
	std::string cost(buffer);
	std::vector<std::string> commandsForGnuplot = {"set title \""+m_data.getName()+"\"","set key bmargin","plot '"+exp_data.getName()+".temp' using 1:2 w p pt 1 lc -1 title 'Experimental Data'"," replot '"+exp_data.getName()+".temp' using 3:4 w l dt 2 lc 6 title 'Short path estimation with distance="+cost+"'"};
	FILE * temp = fopen((exp_data.getName()+".temp").c_str(), "w");
	FILE * gnuplotPipe = popen ("gnuplot -persistent", "w");
	for (int i=0; i < m_data.getCard(); i++){
		fprintf(temp, "%lf %lf %lf %lf \n", exp_data.getData()[i][0], exp_data.getData()[i][1], m_data.getData()[i][0], m_data.getData()[i][1]);
	}
	for (std::string command : commandsForGnuplot){
		fprintf(gnuplotPipe,"%s \n", command.c_str());
	}
}
//Set and get parameters

Data TSP::getExpData() const {
	return exp_data;
}

void TSP::setOrder(std::vector<int> o){
	order = o;
	std::vector<std::vector<double>> n_val(m_data.getData());
	for(int i=0;i<order.size();i++)
		n_val[i] = exp_data.getData()[order[i]];
	m_data.setData(n_val);
	//delete m_cost;
	//m_cost = new Distance(exp_data.getData(),order);
}

std::vector<int> TSP::getOrder() const {
	return order;
}
