#include "stdafx.h"
#include "draw.h"
#include <thread>
#include "help.h"
#include "calcul.h"

using namespace std;

///Plots pair of the time series mapped on to the no/accumulated distance matrix.
///@param[in] input time series to be plotted
///@param[in] result dtw method results
///@param[in] info informations about the input time series
///@param[in] params parameters
void draw::plotPair(inputMethod const &input, resultMethod const &result, inputInfo const &info, parameter const &params)
{
	CImg<short> imgA;
	CImg<short> imgB;

	auto min = std::min(calcul::vtrMin(input.A), calcul::vtrMin(input.B));
	auto max = std::max(calcul::vtrMax(input.A), calcul::vtrMax(input.B));
	auto minMean = std::min(calcul::vtrMin(calcul::vtrMean(input.A)), calcul::vtrMin(calcul::vtrMean(input.B)));
	auto maxMean = std::max(calcul::vtrMax(calcul::vtrMean(input.A)), calcul::vtrMax(calcul::vtrMean(input.B)));

	if (input.A[0].size() > 1) 
	{
		imgA = (draw::drawSeriesBars(input.A, min, max).append(draw::drawSeriesMean(input.A, minMean, maxMean), 'y')).rotate(90);
		imgB = draw::drawSeriesBars(input.B, min, max).append(draw::drawSeriesMean(input.B, minMean, maxMean), 'y');
	}
	else 
	{
		imgA = draw::drawSeries(input.A, min, max).rotate(90);
		imgB = draw::drawSeries(input.B, min, max);
	}

	CImg<short> m2 = result.matrix_noacc;

	m2 = m2.append(imgB, 'y');

	CImg<short> all;
	all = result.matrix_acc;
	all = all.append(imgA);
	all.append(m2);

	all.save(generateFileName(info, params.outDraw).c_str());

	if(params.drawSleep > 0)
		std::this_thread::sleep_for(chrono::milliseconds(params.drawSleep));
}

///Plots segmented (voting experts) pair of time series mapped on to the no/accumulated distance matrix.
///@param[in] input time series to be plotted
///@param[in] result dtw method results
///@param[in] info informations about the input time series
///@param[in] params parameters
void draw::plotSegmets(inputMethod const &input, vtr2<resultMethod> const &result, inputInfo const &info, parameter const &params)
{
	CImg<short> matrix;

	for (size_t i = 0; i < result.size(); i++)
	{
		CImg<short> row;
		for (size_t j = 0; j < result[i].size(); j++)
		{
			row = row.append(result[i][j].matrix_noacc);
		}
		matrix = matrix.append(row, 'y');
	}

	const float red[] = { 255, 255,0 };

	CImg<short> serieA;
	CImg<short> serieB;
	auto min = std::min(calcul::vtrMin(input.A), calcul::vtrMin(input.B));
	auto max = std::max(calcul::vtrMax(input.A), calcul::vtrMax(input.B));

	if (input.A[0].size() > 1) 
	{
		serieA = (draw::drawSeriesBars(input.A, min, max).append(draw::drawSeriesMean(input.A, min, max))).rotate(90);
		serieB = draw::drawSeriesBars(input.B, min, max).append(draw::drawSeriesMean(input.B, min, max), 'y');
	}
	else 
	{
		serieA = draw::drawSeries(input.A, min, max);
		serieB = draw::drawSeries(input.B, min, max);

		drawVerticalBreaks(serieA, result, red);
		drawHorizontalBreaks(serieB, result, red);

		serieA.rotate(90);
	}

	auto all = matrix.append(serieB, 'y');
	all = serieA.append(all);

	all.save(generateFileName(info, params.outDraw).c_str());

	if (params.drawSleep > 0)
		std::this_thread::sleep_for(chrono::milliseconds(params.drawSleep));
}

///Plots minimums (Kocyan), flexible pass (Kocyan), and the distance matrix (internally used by dtw and lcss methods).  
///@param[in] matrix distance matrix
///@param[in] warpings warping paths from the dtw or lcss methods
///@param[in] minims local minimums of the no-accumulated distance matrix
///@param[in] params parameters
///@return image
CImg<short> draw::drawCombine(vtr2<node> const &matrix, vtr<resultPath> const &warpings, vtr<coord> const &minims, parameter const &params)
{
	CImg<short> img((int)matrix[0].size() - 1, (int)matrix.size() - 1, 1, 3, 0);

	const float green[] = { 0, 255, 0 };
	const float purple[] = { 200, 0, 200 };
	
	drawMatrix(img, matrix, params);

	if (params.drawFlex)
		drawFlexibleWindow(img, matrix, warpings, purple, params);
	
	drawWarpings(img, warpings, green);

	if (params.drawMin)
		drawMinimums(img, minims);

	return img;
}

///Plots no/accumulated distance matrix.
///@param[in] img picture to plot into
///@param[in] matrix results from the dtw and lcss method
///@param[in] params parameters
void draw::drawMatrix(CImg<short> &img, vtr2<node> const &matrix, parameter const &params)
{
	double min = getMin(matrix, params);
	double max = getMax(matrix, params);

	for (size_t i = 1; i < matrix.size(); i++)	//row//y 
	{ 
		for (size_t j = 1; j < matrix[i].size(); j++) //coll//x
		{
			auto colorRGB = draw::getColor(min, max, matrix[i][j].value);
			const float color[] = { colorRGB.r, colorRGB.g, colorRGB.b };

			img.draw_point((int)j - 1, (int)i - 1, color);
		}
	}
}

///Draws minimums (Kocyan) into the no/accumulated distance matrix.
///@param[in] img picture to draw into
///@param[in] minims minimums to be plotted
void draw::drawMinimums(CImg<short> &img, vtr<coord> const &minims)
{
	const float red[] = { 255, 0, 0 };

	for (size_t i = 0; i < minims.size(); i++) 
	{
		img.draw_point(minims[i].col - 1, minims[i].row - 1, red);
	}	
}

///Draws minimums (Kocyan) into the no/accumulated distance matrix.
///@param[in] img picture to draw into
///@param[in] warpings warping paths from the dtw or lcss methods
///@param[in] color warping paths color
void draw::drawWarpings(CImg<short> &img, vtr<resultPath> const &warpings, const float *color)
{
	for (size_t i = 0; i < warpings.size(); i++)
	{
		for (size_t j = 0; j < warpings[i].pathCoords.size(); j++)
		{
			img.draw_point(warpings[i].pathCoords[j].col - 1, warpings[i].pathCoords[j].row - 1, color);
		}
	}
}

///Draws horizontal breaks of the segmented (Kocyan - voting experts) time series into the no/accumulated distance matrix.
///@param[in] img picture to draw into
///@param[in] result dtw sub matrix plots results (contains lengths of time series segments)
///@param[in] color horizontal breaks color
void draw::drawHorizontalBreaks(CImg<short> &img, vtr2<resultMethod> const &result, const float *color)
{
	int len = 0;
	for (int i = 0; i < (int)result[0].size(); i++) 
	{
		len += result[0][i].matrix_noacc.width();
		img.draw_line(len - 1, 0, len - 1, (int)(img.height()), color);
	}
}

///Draws vertical breaks of the segmented (Kocyan - voting experts) time series into the no/accumulated distance matrix.
///@param[in] img picture to draw into
///@param[in] result dtw sub matrix plots results (contains lengths of time series segments)
///@param[in] color vertical breaks color
void draw::drawVerticalBreaks(CImg<short> &img, vtr2<resultMethod> const &result, const float *color)
{
	int len = 0;
	for (size_t i = 0; i < result.size(); i++)
	{
		len += result[i][0].matrix_noacc.height();
		img.draw_line(len, 0, len, (int)(img.height()), color);
	}
}

///Draws flexible warping pass (Kocyan) into the no/accumulated distance matrix.
///@param[in] img picture to draw into
///@param[in] matrix no/accumulated distance matrix 
///@param[in] warpings warping path from the dtw method
///@param[in] color flexible pass color
///@param[in] params parameters
void draw::drawFlexibleWindow(CImg<short> &img, vtr2<node> const &matrix, vtr<resultPath> const &warpings, const float *color, parameter const &params)
{
	for (size_t i = 0; i < warpings.size(); i++)
	{
		for (size_t k = 0; k < warpings[i].pathCoords.size(); k++) 
		{
			int kd = (int)k + params.fd;
			coord past = kd < (int)warpings[i].pathCoords.size() ? warpings[i].pathCoords[kd] : warpings[i].pathCoords.back();
			for (int j = 0; j < (int)matrix[warpings[i].pathCoords[k].row].size(); j++)
			{

				if (calcul::isFlexiblePass(warpings[i].pathCoords[k].row, j, past, params.fw))
					img.draw_point(j, warpings[i].pathCoords[k].row, color);
			}
		}
	}
}

///Draws time series graph.
///@param[in] series time series to be plotted
///@param[in] min minimal value found in the two input time series
///@param[in] max maximal value found in the two input time series
///@return image
CImg<short> draw::drawSeries(vtr2<double> const &series, double min, double max)
{
	int height = (int)ceil(series.size() * 0.1);
	CImg<short> img((int)series.size(), height, 1, 3, 0);
	double mul = (height - 1) / abs(max + abs(min));

	int negativeFix = min < 0 ? (int)ceil(mul * abs(min)) : 0;

	const float color[] = { 255, 0, 0 };
	for (size_t i = 0; i < series.size() - 1; i++)
	{
		int y1 = (int)(mul * series[i][0]);
		int y2 = (int)(mul * series[i + 1][0]);

		img.draw_line((int)i, y1 + negativeFix, (int)i + 1, y2 + negativeFix, color);
	}

	return img.mirror('y');
}

///Draws time series in bar format.
///@param[in] series time series to be plotted
///@param[in] min minimal value found in the two input time series
///@param[in] max maximal value found in the two input time series
///@return image
CImg<short> draw::drawSeriesBars(vtr2<double> const &series, double min, double max)
{
	int barSize = (int)ceil(series.size() * 0.01);
	CImg<short> img((int)series.size(), barSize * (int)series[0].size(), 1, 3, 0);

	size_t dims = series[0].size();

	for (size_t i = 0; i < dims; i++)
	{
		for (size_t j = 0; j < series.size(); j++) //col x
		{
			color mycolor = getColor(min, max, series[j][i]);
			const float color[] = { mycolor.r, mycolor.g, mycolor.b };

			img.draw_line((int)j, (int)i * barSize, (int)j, ((int)i + 1) * barSize, color);
		}
	}

	return img;
}

///Draws averaged time series (used for plotting time series with multiple dimensions).
///@param[in] series time series to be plotted
///@param[in] min minimal value found in the two input time series
///@param[in] max maximal value found in the two input time series
///@return image
CImg<short> draw::drawSeriesMean(vtr2<double> const &series, double min, double max)
{
	int height = (int)ceil(series.size() * 0.1);
	CImg<short> img((int)series.size(), height, 1, 3, 0);
	double mul = (height - 1) / abs(max + abs(min)/*- floor(max - min)*/);	// -2 ..means are double... so they overlap with bars

	auto means = calcul::vtrMean(series);

	int negativeFix = min < 0 ? (int)ceil(mul * abs(min)) : 0;

	const float color[] = { 255, 0, 0 };
	for (size_t i = 0; i < series.size() - 1; i++)
	{
		int y1 = (int)floor(mul * means[i]);
		int y2 = (int)floor(mul * means[i + 1]);

		img.draw_line((int)i, y1 + negativeFix, (int)i + 1, y2 + negativeFix, color);
	}

	return img.mirror('y');
}

///Finds maximal value in the distance matrix.
///@param[in] matrix distance matrix
///@param[in] params parameters
///@return maximal value in the distance matrix
double draw::getMax(vtr2<node> const &matrix, parameter const &params)
{
	int lenA = (int)matrix.size() - 1;
	int lenB = (int)matrix[0].size() - 1;

	double max = constant::MIN_double;

	const int w = (int)(lenB * params.w);
	double coef = lenB / static_cast<double>(lenA);

	for (int i = 1; i < lenA + 1; i++) // Iterating over rows
	{
		const size_t start = calcul::dtwPassStart(i, w, coef);
		const size_t end = calcul::dtwPassEnd(i, w, coef, lenB);
		for (size_t j = start; j < end; j++)
		{
			if (matrix[i][j].value > max)
				max = matrix[i][j].value;
		}
	}

	return max;
}

///Finds minimal value in the distance matrix.
///@param[in] matrix distance matrix
///@param[in] params parameters
///@return maximal value in the distance matrix
double draw::getMin(vtr2<node> const &matrix, parameter const &params)
{
	int lenA = (int)matrix.size() - 1;
	int lenB = (int)matrix[0].size() - 1;

	double min = constant::MAX_double;

	const int w = (int)(lenB * params.w);
	double coef = lenB / static_cast<double>(lenA);

	for (int i = 1; i < lenA + 1; i++) // Iterating over rows
	{
		const size_t start = calcul::dtwPassStart(i, w, coef);
		const size_t end = calcul::dtwPassEnd(i, w, coef, lenB);
		for (size_t j = start; j < end; j++)
		{
			if (matrix[i][j].value < min)
				min = matrix[i][j].value;
		}
	}

	return min;
}

///Generates file name for output image file (Only bmp format is supported).
///@param[in] info informations about the input time series
///@param[in] path output file path (folder or specific file name)
///@return file name (path + name)
string draw::generateFileName(inputInfo const &info, string path)
{
	stringstream ss;
	if (help::isPath(path)) 
	{
		if (help::isFolder(path))
		{
			ss << path
			<< "\\"
			<< info.nameA
			<< ((info.nameA == to_string(info.idxA)) ? "" : ("(" + to_string(info.idxA + 1) + ")"))
			<< "-"
			<< info.nameB
			<< ((info.nameB == to_string(info.idxB)) ? "" : ("(" + to_string(info.idxB + 1) + ")"))
			<< ".bmp";
		}
		else
		{
			ss << path;
		}
	}
	else
	{
		size_t nameIdx = path.find_last_of("\\/") + 1;
	    string nameOnly = path.substr(nameIdx, (int)path.size() - nameIdx);

		size_t extIdx = nameOnly.rfind(".bmp");

		if (extIdx != string::npos && nameOnly.size() - (extIdx) == 4) 
		{
			ss << path;
		}
		else
		{
			ss << path << ".bmp";
		}
	}

	return ss.str();
}

///Generates distance matrix cell color.
///@param[in] min minimal value in the distance matrix
///@param[in] max maximal value in the distance matrix
///@param[in] value specific distance cell value 
///@return color
color draw::getColor(double min, double max, double value)
{
	double mul = (max != 0) ? 1275 / abs(max - min) : 0;  //0-x ... x+1colors

	float lvl = (float)(mul * value);
	int lvlBorder = (int)(lvl / 255) * 255;
	
	float exact = lvl - lvlBorder;
	exact = exact > 0 ? exact : 255;

	color c;
	if (lvl <= 255)				//black - blue
	{
		c.r = 0;
		c.g = 0;
		c.b = lvl;
	}
	else if (lvl <= 2 * 255)	//blue - purple
	{
		c.r = exact;
		c.g = 0;
		c.b = 255;
	}
	else if (lvl <= 3 * 255)	//purple - red
	{
		c.r = 255;
		c.g = 0;
		c.b = 255 - exact;
	}
	else if (lvl <= 4 * 255)	//red - orange - yellow
	{
		c.r = 255;
		c.g = exact;
		c.b = 0;
	}
	else if (lvl <= 5 * 255)	//yellow - white
	{
		c.r = 255;
		c.g = 255;
		c.b = exact;
	}
	else						//white (peaks)
	{
		c.r = 255;
		c.g = 255;
		c.b = 255;
	}

	return c;
}