#include <sicstus/sicstus.h>
//#include "sicstus.h"
/* ex_glue.h is generated by splfr from the foreign/[2,3] facts.
   Always include the glue header in your foreign resource code.
*/
#include "lars_glue.h"
#include <mlpack/methods/lars/lars.hpp>
#include <mlpack/core.hpp>

// including helper functions for converting between arma structures and arrays
#include "../../helper_files/helper.hpp"

using namespace arma;
using namespace mlpack;
using namespace std;
using namespace mlpack::regression;

// Global Variable of the Lars regressor object so it can be accessed from all functions
LARS regressor;
bool isModelTrained = false;

// input:	const bool 		useCholesky, 
//			const double 	lambda1, 
//			const double 	lambda2, 
//			const double 	tolerance
// output: 
void initModelNoDataNoGram(SP_integer useCholesky, 
							double lambda1, double lambda2, double tol)
{
	regressor = new LARS((useCholesky == 1), lambda1, lambda2, tol);
	isModelTrained = false;
}

// input:	const bool 			useCholesky, 
//			const arma::mat & 	gramMatrix, 
//			const double 		lambda1, 
//			const double 		lambda2, 
//			const double 		tolerance
// output: 
void initModelNoDataWithGram(SP_integer useCholesky, 
								float *gramArr, SP_integer gramSize, SP_integer gramRowNum, 
								double lambda1, double lambda2, double tol)
{
	mat gramMatrix = convertArrayToMat(gramArr, gramSize, gramRowNum);

	regressor = new LARS((useCholesky == 1), gramMatrix, lambda1, lambda2, tol);
	isModelTrained = false;
}

// input:	const arma::mat & 		dataMatrix, 
//			const arma::rowvec & 	responses, 
//			const bool 				transposeData, 
//			const bool 				useCholesky, 
//			const double 			lambda1, 
//			const double 			lambda2, 
//			const double 			tolerance
// output: 
void initModelWithDataNoGram(float *dataMatArr, SP_integer dataMatSize, SP_integer dataMatRowNum, 
								float *responsesArr, SP_integer responsesArrSize, 
								SP_integer transposeData, 
								SP_integer useCholesky, 
								double lambda1, double lambda2, double tol)
{
	// convert the Prolog array to arma::mat
	mat data = convertArrayToMat(dataMatArr, dataMatSize, dataMatRowNum);
	// check if labels fit the data
	if (data.n_cols != responsesArrSize)
	{
		raisePrologSystemExeption("The number of data points does not match the number of labels!");
	    return;
	}
	// convert the Prolog array to arma::rowvec
	rowvec responsesVector = convertArrayToRowvec(responsesArr, responsesArrSize);


	
	try
    {
        regressor = new LARS(data, responsesVector, (transposeData == 1), (useCholesky == 1), lambda1, lambda2, tol);
    }
    catch(const std::exception& e)
    {
        raisePrologSystemExeption(e.what());
        return;
    }
	isModelTrained = true;
}

// input:	const arma::mat & 		dataMatrix, 
//			const arma::rowvec & 	responses, 
//			const bool 				transposeData, 
//			const bool 				useCholesky, 
//			const arma::mat & 		gramMatrix, 
//			const double 			lambda1, 
//			const double 			lambda2, 
//			const double 			tolerance
// output: 
void initModelWithDataWithGram(float *dataMatArr, SP_integer dataMatSize, SP_integer dataMatRowNum, 
								float *responsesArr, SP_integer responsesArrSize, 
								SP_integer transposeData, 
								SP_integer useCholesky, 
								float *gramMatArr, SP_integer gramMatSize, SP_integer gramMatRowNum, 
								double lambda1, double lambda2, double tol)
{
	// convert the Prolog array to arma::mat
	mat data = convertArrayToMat(dataMatArr, dataMatSize, dataMatRowNum);
	// check if labels fit the data
	if (data.n_cols != responsesArrSize)
	{
		raisePrologSystemExeption("The number of data points does not match the number of labels!");
	    return;
	}

	// convert the Prolog array to arma::rowvec
	rowvec responsesVector = convertArrayToRowvec(responsesArr, responsesArrSize);
	// convert the Prolog array to arma::mat
	mat gram = convertArrayToMat(gramMatArr, gramMatSize, gramMatRowNum);


	try
    {
        regressor = new LARS(data, responsesVector, (transposeData == 1), (useCholesky == 1), gram, lambda1, lambda2, tol);
    }
    catch(const std::exception& e)
    {
        raisePrologSystemExeption(e.what());
        return;
    }
	isModelTrained = true;
}

// input:
// output: const std::vector<size_t>&
void activeSet(float **activeSetArr, SP_integer *activeSetSize)
{
	std::vector<size_t> activeVec = regressor.ActiveSet();

	// give back the sizes and the converted results as arrays
	*activeSetSize = activeVec.size();

	*activeSetArr = convertToArray(activeVec);
}

// input:
// output: arma::vec& 
void beta(float **betaArr, SP_integer *betaArrSize)
{
	if (!isModelTrained)
	{
		raisePrologSystemExeption("The Model is not trained!");
        return;
	}
	
	// create the ReturnVector
	vec betaReturnVector = regressor.Beta();

	// return the Vector
	returnVectorInformation(betaReturnVector, betaArr, betaArrSize);
}

// input:
// output: std::vector<arma::vec>&
void betaPath(float **betaPathArr, SP_integer *betaPathColNum, SP_integer *betaPathRowNum)
{
	if (!isModelTrained)
	{
		raisePrologSystemExeption("The Model is not trained!");
        return;
	}
	// get the betaPath matrix
	vector<vec> matrix = regressor.BetaPath();

	// return the matrix dimensions
	*betaPathColNum = matrix[0].size();
	*betaPathRowNum = matrix.size(); 

	// return the matrix as array
	*betaPathArr = convertToArray(matrix);
}

// input: 	const arma::mat &data, 
//			const arma::rowvec &responses, 
//			const bool rowMajor
//
// output: 	double minimum cost error
double computeError(float *dataMatArr, SP_integer dataMatSize, SP_integer dataMatRowNum, 
								float *responsesArr, SP_integer responsesArrSize, SP_integer rowMajor)
{
	if (!isModelTrained)
	{
		raisePrologSystemExeption("The Model is not trained!");
        return 0.0;
	}
	if(dataMatSize / dataMatRowNum != responsesArrSize)
	{
		cout << "Target dim doesnt fit to the Data dim" << endl;
		return 0.0;
	}
	// convert the Prolog array to arma::mat
	mat data = convertArrayToMat(dataMatArr, dataMatSize, dataMatRowNum);
	// convert the Prolog array to arma::rowvec
	rowvec responsesVector = convertArrayToRowvec(responsesArr, responsesArrSize);

	// run the model function
	double result;
	try
	{
		result = regressor.ComputeError(data, responsesVector, (rowMajor == 1));
	}
	catch(const std::exception& e)
	{
	    raisePrologSystemExeption(e.what());
	    return 0.0;
	}
	return result;
}

// input:
// output: std::vector<double>&
void lambdaPath(float **lambdaPathArr, SP_integer *lambdaPathSize)
{
	if (!isModelTrained)
	{
		raisePrologSystemExeption("The Model is not trained!");
        return;
	}
	std::vector<double> lambdaPathVec = regressor.LambdaPath();

	// give back the sizes and the converted results as arrays
	*lambdaPathSize = lambdaPathVec.size();

	*lambdaPathArr = convertToArray(lambdaPathVec);
}


// input:
// output: arma::mat& upper triangular cholesky factor
void matUtriCholFactor(float **factorMatArr, SP_integer *factorMatColNum, SP_integer *factorMatRowNum)
{
	if (!isModelTrained)
	{
		raisePrologSystemExeption("The Model is not trained!");
        return;
	}
	// create the ReturnMat
	mat factorReturnMat = regressor.MatUtriCholFactor();
	
	// return the Matrix
	returnMatrixInformation(factorReturnMat, factorMatArr, factorMatColNum, factorMatRowNum);
}

// input: 	const arma::mat &points, 
//			arma::rowvec &predictions <-, 
//			const bool rowMajor
// output:
void predict(float *pointsMatArr, SP_integer pointsMatSize, SP_integer pointsMatRowNum, float **predicArr, SP_integer *predicArrSize, SP_integer rowMajor)
{
	if (!isModelTrained)
	{
		raisePrologSystemExeption("The Model is not trained!");
        return;
	}
	// convert the Prolog array to arma::mat
	mat points = convertArrayToMat(pointsMatArr, pointsMatSize, pointsMatRowNum);
	// create the ReturnVector
	rowvec predicReturnVector;
	

	try
	{
		regressor.Predict(points, predicReturnVector, (rowMajor == 1));
	}
	catch(const std::exception& e)
	{
		raisePrologSystemExeption(e.what());
        return;
	}


	// return the Vector
	returnVectorInformation(predicReturnVector, predicArr, predicArrSize);
}

// input: 	const arma::mat &data, 
//			const arma::rowvec &responses, 
//			arma::vec& beta, 
//			const bool transposeData
//
// output: double minimum cost error
double train(float *dataMatArr, SP_integer dataMatSize, SP_integer dataMatRowNum, 
									float *responsesArr, SP_integer responsesArrSize,
									float **betaArr, SP_integer *betaArrSize, 
									SP_integer transposeData)
{
	if(dataMatSize / dataMatRowNum != responsesArrSize)
	{
		cout << "Target dim doesnt fit to the Data dim" << endl;
		return 0.0;
	}
	// convert the Prolog array to arma::mat
	mat data = convertArrayToMat(dataMatArr, dataMatSize, dataMatRowNum);
	// convert the Prolog array to arma::rowvec
	rowvec responsesVector = convertArrayToRowvec(responsesArr, responsesArrSize);

	// create the ReturnVector
	vec betaReturnVector;
	

	// run the model function
	double error;
	try
	{
		error = regressor.Train(data, responsesVector, betaReturnVector, (transposeData == 1));
	}
	catch(const std::exception& e)
	{
		raisePrologSystemExeption(e.what());
        return 0.0;
	}
	

	// return the Vector
	returnVectorInformation(betaReturnVector, betaArr, betaArrSize);
	isModelTrained = true;
	return error;
}