#ifndef EXTERNAL_H
#define EXTERNAL_H

//#include "templates.h"
#include "structs.h"
#include "entry.h"
#include "operation.h" 
#include "help.h"
#include "parameter.h"
#include "print.h"

#if defined(_MSC_VER)
//  Microsoft 
#define EXPORT extern "C" __declspec(dllexport)
//#define IMPORT extern "C" __declspec(dllimport)
#elif defined(__GNUC__)
//  GCC
#define EXPORT extern "C" __attribute__((visibility("default")))
#define IMPORT
#else
//  do nothing and hope for the best?
#define EXPORT
#define IMPORT
#pragma warning Unknown dynamic link import/export semantics.
#endif

///Contains RGB color  
struct libColor {
	uint8_t r;		///< red
	uint8_t g;		///< green
	uint8_t b;		///< blue

	///Default constructor
	libColor() : r(0), g(0), b(0) {}
};

///Contains double array. Used in external calls.
struct libArrayDouble {
	double* arr;		///< array
	int len;			///< length of array

	///Initialization constructor
	///@param size length of the allocated array
	libArrayDouble(int size) : arr(new double[size]), len(size) { }
	///Destructor
	~libArrayDouble() 
	{ 
		delete[] arr; 
	}
};

///Contains short array. Used in external calls.
struct libArrayShort {
	short* arr;			///< array
	int len;			///< length of the array

	///Initialization constructor
	///@param size length of the allocated array
	libArrayShort(int size) : arr(new short[size]), len(size) { }
	///Destructor
	~libArrayShort() 
	{ 
		delete[] arr; 
	}
};

///Contains array of lib_color. Used in external calls.
struct libArrayColor {
	libColor* arr;			///< array
	int len;				///< length of the array

	///Initialization constructor
	///@param size length of the allocated array
	libArrayColor(int size) : arr(new libColor[size]), len(size) { }
	///Destructor
	~libArrayColor() 
	{ 
		delete[] arr;
	}
};

///Contains image data. Used in external calls.
struct libImg {
	libArrayColor img;		///< image data
	int width;					///< image width
	int height;					///< image height

	///Initialization constructor
	///@param sizeImg_ image size (number of pixel in the picture)
	///@param width_ image witdh
	///@param height_ image height
	libImg(int sizeImg_, int width_, int height_) : img(sizeImg_), width(width_), height(height_) {}
};

///Contains method results. Used in external calls.
struct libResultMethod {
	libArrayDouble score;		///< final method scores
	libImg img;				///< result image

	///Initialization constructor
	///@param sizeScore number of result scores
	///@param sizeImg image size (number of pixel in the picture)
	///@param imgWidth image witdh
	///@param imgHeight image height
	libResultMethod(int sizeScore, int sizeImg, int imgWidth, int imgHeight) : score(sizeScore), img(sizeImg, imgWidth, imgHeight) {}
};

///Copies vector to array
///@param v source vector
///@param a destination array
void copyVectorToArray(vtr<double> const &v, double* a)
{
	for (size_t i = 0; i < v.size(); i++)
	{
		a[i] = v[i];
	}
}

///Converts double* (2d external array) to the vtr2 type
///@param v source vector
///@param a destination array
vtr2<double> convertDouble2DPtrToVector(double* arr, int len, int dim)
{
	vtr2<double> out(len);

	int c = 0;
	for (int i = 0; i < len; i++)
	{
		vtr<double> element(dim);
		for (int j = 0; j < dim; j++) 
		{
			element[j] = arr[c++];
		}
	
		out[i] = element;
	}

	return out;
}

///Passes application arguments from external call (entry method for external call).
///@param argv application arguments
EXPORT void libCmd(char* argv)
{
	std::string strargv(argv);
	vtr<std::string> args = help::split(strargv, " ");
	entry::entryBase(args);
}

///Passes application arguments from external call (entry method for external call).
///@param argv application arguments
///@return final method scores
EXPORT libArrayDouble* libCmdScore(char* argv)
{
	std::string strargv(argv);
	vtr<std::string> args = help::split(strargv, " ");
	
	parameter params;
	params.setParameters(args);

	auto result = entry::entryLogic(params, false);

	libArrayDouble *out = new libArrayDouble((int)result.dtw.score.size());
	//out->arr = &result.dtw.score[0];
	copyVectorToArray(result.dtw.score, out->arr);

	return out;
}

///Passes application arguments from external call (entry method for external call).
///@param argv application arguments
///@return method results (method score and image of distance matrix)
EXPORT libResultMethod* libCmdMethod(char* argv)
{
	std::string strargv(argv);
	vtr<std::string> args = help::split(strargv, " ");
	parameter params;
	params.setParameters(args);

	auto result = entry::entryLogic(params, false);

	int imgW = result.dtw.matrix_noacc.width();
	int imgH = result.dtw.matrix_noacc.height();

	libResultMethod *out = new libResultMethod((int)result.dtw.score.size(), imgW * imgH, imgW, imgH);

	//std::cout << out->score.len << std::endl;
	//std::cout << out->img.img.len << std::endl;
	//std::cout << out->img.width << std::endl;
	//std::cout << out->img.height << std::endl;

	for (size_t i = 0; i < result.dtw.score.size(); i++) 
	{
		out->score.arr[i] = result.dtw.score[i];
	}

	for (size_t i = 0; i < (size_t)imgH; i++)
	{
		for (size_t j = 0; j < (size_t)imgW; j++)
		{
			size_t idx = i * imgW + j;
			out->img.img.arr[idx].r = (uint8_t)result.dtw.matrix_noacc((unsigned int)i, (unsigned int)j, 0, 0);
			out->img.img.arr[idx].g = (uint8_t)result.dtw.matrix_noacc((unsigned int)i, (unsigned int)j, 0, 1);
			out->img.img.arr[idx].b = (uint8_t)result.dtw.matrix_noacc((unsigned int)i, (unsigned int)j, 0, 2);
			//std::cout << "lib: " << (int)out->img.img.arr[idx].r << ", " << (int)out->img.img.arr[idx].g << ", " << (int)out->img.img.arr[idx].b << std::endl;
		}
	}
	
	return out;
}

///Passes application input and arguments from external call (entry method for external call).
///@param A time series A
///@param B time series B
///@param lenA time series A length
///@param lenB time series B length
///@param argv application arguments
///@return method results (method scores and image of the distance matrix)
EXPORT libResultMethod* libPair1D(double* A, double* B, int lenA, int lenB, char* argv)
{
	std::string strargv(argv);
	vtr<std::string> args = help::split(strargv, " ");
	
	vtr3<double> input(2);
	input[0] = help::convertArrd(A, lenA);
	input[1] = help::convertArrd(B, lenB);
	
	auto result = entry::externLogic(input, args);

	int imgW = result.dtw.matrix_noacc.width();
	int imgH = result.dtw.matrix_noacc.height();

	libResultMethod *out = new libResultMethod((int)result.dtw.score.size(), imgW * imgH, imgW, imgH);

	for (size_t i = 0; i < result.dtw.score.size(); i++)
	{
		out->score.arr[i] = result.dtw.score[i];
	}

	for (size_t i = 0; i < (size_t)imgH; i++) 
	{
		for (size_t j = 0; j < (size_t)imgW; j++) 
		{
			size_t idx = i * imgW + j;
			out->img.img.arr[idx].r = (uint8_t)result.dtw.matrix_noacc((unsigned int)i, (unsigned int)j, 0, 0);
			out->img.img.arr[idx].g = (uint8_t)result.dtw.matrix_noacc((unsigned int)i, (unsigned int)j, 0, 1);
			out->img.img.arr[idx].b = (uint8_t)result.dtw.matrix_noacc((unsigned int)i, (unsigned int)j, 0, 2);
			//std::cout << "lib: " << (int)out->img.img.arr[idx].r << ", " << (int)out->img.img.arr[idx].g << ", " << (int)out->img.img.arr[idx].b << std::endl;
		}
	}

	return out;
}

///Passes application input and arguments from external call (entry method for external call).
///@param A time series A
///@param B time series B
///@param lenA time series A length
///@param lenB time series B length
///@param dims number of dimension of input time series
///@param argv application arguments
///@return method results (method scores and image of the distance matrix)
EXPORT libResultMethod* libPairMD(double* series1, double* series2, int len1, int len2, int dims, char* argv)
{
	std::string strargv(argv);
	vtr<std::string> args = help::split(strargv, " ");

	vtr3<double> input(2);
	input[0] = help::convertArr2d(series1, len1, dims);
	input[1] = help::convertArr2d(series2, len2, dims);
		
	auto result = entry::externLogic(input, args);

	int imgW = result.dtw.matrix_noacc.width();
	int imgH = result.dtw.matrix_noacc.height();

	libResultMethod *out = new libResultMethod((int)result.dtw.score.size(), imgW * imgH, imgW, imgH);

	for (size_t i = 0; i < result.dtw.score.size(); i++)
	{
		out->score.arr[i] = result.dtw.score[i];
	}

	for (size_t i = 0; i < (size_t)imgH; i++) 
	{
		for (size_t j = 0; j < (size_t)imgW; j++) 
		{
			size_t idx = i * imgW + j;
			out->img.img.arr[idx].r = (uint8_t)result.dtw.matrix_noacc((unsigned int)i, (unsigned int)j, 0, 0);
			out->img.img.arr[idx].g = (uint8_t)result.dtw.matrix_noacc((unsigned int)i, (unsigned int)j, 0, 1);
			out->img.img.arr[idx].b = (uint8_t)result.dtw.matrix_noacc((unsigned int)i, (unsigned int)j, 0, 2);
			//std::cout << "lib: " << (int)out->img.img.arr[idx].r << ", " << (int)out->img.img.arr[idx].g << ", " << (int)out->img.img.arr[idx].b << std::endl;
		}
	}

	return out;
}

EXPORT libResultMethod* libPairJagged(double* series1, double* series2, int len1, int len2, int dim, char* argv)
{
	std::string strargv(argv);
	vtr<std::string> args = help::split(strargv, " ");

	vtr3<double> input(2);
	input[0] = convertDouble2DPtrToVector(series1, len1, dim);
	input[1] = convertDouble2DPtrToVector(series2, len2, dim);

	auto result = entry::externLogic(input, args);

	int imgW = result.dtw.matrix_noacc.width();
	int imgH = result.dtw.matrix_noacc.height();

	libResultMethod *out = new libResultMethod((int)result.dtw.score.size(), imgW * imgH, imgW, imgH);

	for (size_t i = 0; i < result.dtw.score.size(); i++)
	{
		out->score.arr[i] = result.dtw.score[i];
	}

	for (size_t i = 0; i < (size_t)imgH; i++)
	{
		for (size_t j = 0; j < (size_t)imgW; j++) 
		{
			size_t idx = i * imgW + j;
			out->img.img.arr[idx].r = (uint8_t)result.dtw.matrix_noacc((unsigned int)i, (unsigned int)j, 0, 0);
			out->img.img.arr[idx].g = (uint8_t)result.dtw.matrix_noacc((unsigned int)i, (unsigned int)j, 0, 1);
			out->img.img.arr[idx].b = (uint8_t)result.dtw.matrix_noacc((unsigned int)i, (unsigned int)j, 0, 2);
			//std::cout << "lib: " << (int)out->img.img.arr[idx].r << ", " << (int)out->img.img.arr[idx].g << ", " << (int)out->img.img.arr[idx].b << std::endl;
		}
	}

	return out;
}

///Deletes point to double*.
///@param ptr pointer
EXPORT void libDeleteArrayPointer(double* ptr)
{
	delete[] ptr;
}

///Deletes point to lib_array_double*.
///@param ptr pointer
EXPORT void libDeleteArrayDouble(libArrayDouble* ptr)
{
	delete ptr;
}

///Deletes point to lib_array_short*.
///@param ptr pointer
EXPORT void libDeleteArrayShort(libArrayShort* ptr)
{
	delete ptr;
}

///Deletes point to lib_result_method*.
///@param ptr pointer
EXPORT void libDeleteStructMethod(libResultMethod* ptr)
{
	delete ptr;
}

#endif //EXTERNAL_H