#include "pch.h"
#include "GraphView.h"

#include <QDebug>
#include <QPushButton>
#include <QBrush>
#include <random>
#include <algorithm>
#include <chrono>
#include "util.h"

#define X_AXIS_GAP_AMOUNT 5
#define Y_AXIS_GAP_AMOUNT 10
#define X_AXIS_NUMBER_LINE_LENGTH 5
#define Y_AXIS_NUMBER_LINE_LENGTH 5
#define X_AXIS_NUMBER_GAP_LENGTH 10
#define Y_AXIS_NUMBER_GAP_LENGTH 30

#define MARGIN_TOP 10
#define MARGIN_BOTTOM 20
#define MARGIN_LEFT 50
#define MARGIN_RIGHT 10



GraphView::GraphView(QWidget* parent, Bound fixedBound):QWidget(parent), fixedBound(fixedBound), linePen(Qt::blue), rectPen(QColor(255,255,255,255)), axisPen(Qt::black)
{
	
	linePen.setJoinStyle(Qt::PenJoinStyle::RoundJoin);
	rectPen.setWidth(0);

	////generate Random number for starting color
	//std::random_device rd;  //Will be used to obtain a seed for the random number engine
	//std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()
	//std::uniform_real_distribution<double> doubleDistr(0.0, 1.0);
	//hueoffset = doubleDistr(gen);
	hueoffset = 0.5;
	setFocusPolicy(Qt::ClickFocus);
}

GraphView::~GraphView()
{
}

void GraphView::paintEvent(QPaintEvent* event)
{
	std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
	QPainter painter(this);
	painter.setRenderHint(QPainter::RenderHint::HighQualityAntialiasing);
	if (graphSeriesVec.empty() && yDoubleDoubleValuesAmount == 0) {
		painter.setPen(axisPen);
		painter.setFont(QFont("Arial", 12, QFont::Bold));
		painter.drawText(rect(), Qt::AlignCenter, "No Data connected");
		return;
	}
	//Calculate LineRect
	QRect graphDisplayRect(rect());
	graphDisplayRect.setBottom(graphDisplayRect.bottom() - MARGIN_BOTTOM);
	graphDisplayRect.setLeft(graphDisplayRect.left() + MARGIN_LEFT);
	graphDisplayRect.setTop(graphDisplayRect.top() + MARGIN_TOP);
	graphDisplayRect.setRight(graphDisplayRect.right() - MARGIN_RIGHT);
	graphDisplayRect.setWidth(graphDisplayRect.width() - (graphDisplayRect.width() % X_AXIS_GAP_AMOUNT) + 1);
	graphDisplayRect.setHeight(graphDisplayRect.height() - (graphDisplayRect.height() % Y_AXIS_GAP_AMOUNT) + 1);

	painter.setBrush(rectPen.color());
	painter.drawRect(graphDisplayRect);
	painter.setBrush(Qt::BrushStyle::NoBrush);

	double stregth_factorX = graphDisplayRect.width() / rangeGraphX;
	double stregth_factorY = graphDisplayRect.height() / rangeGraphY;
	QPointF translation(graphDisplayRect.left(), graphDisplayRect.top());
	for (const GraphSeries& graphSeries : graphSeriesVec) {
		if (!graphSeries.data) {
			qDebug() << "Pointer to nothing pls help";
		}
		if (graphSeries.data->empty() || graphSeries.hide) continue;
		linePen.setColor(graphSeries.color);

		if (graphSeries.type == GraphSeries::SeriesType::Line || graphSeries.type == GraphSeries::SeriesType::LineDot) {
			linePen.setWidth(graphSeries.lineWidth + ((&graphSeries == highlightSeries) ? 5 : 0));
			painter.setPen(linePen);
			QPainterPath painterPath;

			QPointF oldpoint = graphSeries.data->at(0).toQPointF();
			painterPath.moveTo(transformGraphToView(oldpoint, stregth_factorX, stregth_factorY, translation));
			for (int i = 1; i < graphSeries.data->size(); i++) {
				QPointF newpoint = graphSeries.data->at(i).toQPointF();
				if (!inBoundX(oldpoint) && !inBoundX(newpoint)) {
					
				}
				else if (!inBoundX(oldpoint) && inBoundX(newpoint)) {
					painterPath.moveTo(transformGraphToView(oldpoint, stregth_factorX, stregth_factorY, translation));
					painterPath.lineTo(transformGraphToView(newpoint, stregth_factorX, stregth_factorY, translation));
				}
				else if (inBoundX(oldpoint) && inBoundX(newpoint)) {
					painterPath.lineTo(transformGraphToView(newpoint, stregth_factorX, stregth_factorY, translation));
				}
				else if (inBoundX(oldpoint) && !inBoundX(newpoint)) {
					painterPath.lineTo(transformGraphToView(newpoint, stregth_factorX, stregth_factorY, translation));
					break;
				}
				oldpoint = newpoint;
			}

			//painterPath.translate(translation);
			painter.drawPath(painterPath);
		}
		if (graphSeries.type == GraphSeries::SeriesType::Dot || graphSeries.type == GraphSeries::SeriesType::LineDot) {
			linePen.setWidth(2);
			painter.setPen(linePen);
			painter.setBrush(graphSeries.color);
			for (auto data: *graphSeries.data) {
				if (useInterval) {
					if (data.orginalPoint->iteration < minIter) {
						continue;
					}
					if (data.orginalPoint->iteration > maxIter) {
						break;
					}
				}
				if (graphSeries.useDataPointColor) {
					linePen.setColor(data.color);
					painter.setPen(linePen);
					painter.setBrush(data.color);
				}
				if (inBound(data.toQPointF())) {
					painter.drawEllipse(transformGraphToView(data.toQPointF(), stregth_factorX, stregth_factorY, translation), graphSeries.circleRadius, graphSeries.circleRadius);
				}
			}
			painter.setBrush(Qt::BrushStyle::NoBrush);
		}
	}

	if (yDoubleDoubleValuesAmount > 0) {
		linePen.setWidth(2);
		linePen.setColor(QColor(0,100,200, 0));
		painter.setPen(linePen);
		for (int i = 0; i < yDoubleDoubleValuesAmount; i++) {
			painter.setBrush(pointColors[i]);
			painter.drawEllipse(transformGraphToView(QPointF(yArray[2 * i], yArray[2 * i + 1]), stregth_factorX, stregth_factorY, translation), 3, 3);
		}
		painter.setBrush(Qt::BrushStyle::NoBrush);
	}




	if (drawTotalBound) {
		painter.setPen(axisPen);
		QRectF bounds(transformGraphToView(QPointF(totalBound.minX, totalBound.maxY), stregth_factorX, stregth_factorY, translation), transformGraphToView(QPointF(totalBound.maxX, totalBound.minY), stregth_factorX, stregth_factorY, translation));
		painter.drawRect(bounds);
	}




	//Draw White Rect for axis
	painter.setPen(rectPen);

	painter.setBrush(rectPen.color());
	QRect leftRect(rect().topLeft(), QPoint(graphDisplayRect.left() - 1, rect().bottom()));
	QRect topRect(rect().topLeft(), QPoint(rect().right(), graphDisplayRect.top() - 1));
	QRect bottomRect(QPoint(rect().left(), graphDisplayRect.bottom()), rect().bottomRight());
	QRect rightRect(QPoint(graphDisplayRect.right(), rect().top()), rect().bottomRight());
	painter.drawRect(leftRect);
	painter.drawRect(topRect);
	painter.drawRect(bottomRect);
	painter.drawRect(rightRect);
	painter.setBrush(Qt::BrushStyle::NoBrush);
	
	//painter.drawRect(yAxisRect);
	
	
	
	
	
	//Font for Axis;
	painter.setFont(QFont("Arial", 8));
	//draw X-Axis
	painter.setPen(axisPen);
	QRect xAxisRect(QPoint(graphDisplayRect.left(), graphDisplayRect.bottom()), QPoint(graphDisplayRect.right(), rect().bottom()));
	QPainterPath xAxisPath;
	xAxisPath.moveTo(xAxisRect.left(), xAxisRect.top());
	xAxisPath.lineTo(xAxisRect.right(), xAxisRect.top());

	int xGap = xAxisRect.width() / X_AXIS_GAP_AMOUNT;
	QRect textRect(0, 0, 40, 9);

	for (int i = 0; i < 11; i++) {
		xAxisPath.moveTo(xAxisRect.left() + i * xGap, xAxisRect.top() + X_AXIS_NUMBER_LINE_LENGTH);
		xAxisPath.lineTo(xAxisRect.left() + i * xGap, xAxisRect.top());
		textRect.moveCenter(QPoint(xAxisRect.left() + i * xGap, xAxisRect.top() + X_AXIS_NUMBER_GAP_LENGTH));
		painter.drawText(textRect, Qt::AlignCenter, xAxisNumbers[i]);
	}
	painter.drawPath(xAxisPath);

	//draw Y-Axis
	QRect yAxisRect(QPoint(rect().left(), graphDisplayRect.top()), QPoint(graphDisplayRect.left(), graphDisplayRect.bottom()));
	QPainterPath yAxisPath;
	yAxisPath.moveTo(yAxisRect.right(), yAxisRect.bottom());
	yAxisPath.lineTo(yAxisRect.right(), yAxisRect.top());
	int yGap = yAxisRect.height() / Y_AXIS_GAP_AMOUNT;
	for (int i = 0; i < 11; i++) {
		yAxisPath.moveTo(yAxisRect.right() - Y_AXIS_NUMBER_LINE_LENGTH, yAxisRect.bottom() - i * yGap);
		yAxisPath.lineTo(yAxisRect.right(), yAxisRect.bottom() - i * yGap);
		textRect.moveCenter(QPoint(yAxisRect.right() - Y_AXIS_NUMBER_GAP_LENGTH, yAxisRect.bottom() - i * yGap));
		painter.drawText(textRect, Qt::AlignRight | Qt::AlignVCenter, yAxisNumbers[i]);
	}
	painter.drawPath(yAxisPath);
	std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
	std::chrono::milliseconds time = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);


	//Mouse:
	if (mousePressed) {
		QColor blue(0, 122, 204);
		QColor gray(120, 120, 120);
		//QPointF value = transformViewToGraph(oldPositionForMouseEvent);
		QPointF viewPoint = transformGraphToView(mousePressedValue, stregth_factorX, stregth_factorY, translation);
		if (graphDisplayRect.top() <= viewPoint.y() && viewPoint.y() <= graphDisplayRect.bottom()) {
			painter.setPen(gray);
			painter.drawLine(QPointF(graphDisplayRect.left() - Y_AXIS_NUMBER_LINE_LENGTH, viewPoint.y()), QPointF(graphDisplayRect.right(), viewPoint.y()));
			textRect.moveCenter(QPoint(graphDisplayRect.left() - Y_AXIS_NUMBER_GAP_LENGTH, viewPoint.y()));
			painter.fillRect(textRect, rectPen.color());
			painter.setPen(blue);
			painter.drawText(textRect, Qt::AlignRight | Qt::AlignVCenter, QString::number(mousePressedValue.y(), 'f', 1));
		}
		if (graphDisplayRect.left() <= viewPoint.x() && viewPoint.x() <= graphDisplayRect.right()) {
			painter.setPen(gray);
			painter.drawLine(QPointF(viewPoint.x(), graphDisplayRect.top()), QPointF(viewPoint.x(), graphDisplayRect.bottom() + X_AXIS_NUMBER_LINE_LENGTH));
			textRect.moveCenter(QPoint(viewPoint.x(), graphDisplayRect.bottom() + X_AXIS_NUMBER_GAP_LENGTH));
			painter.fillRect(textRect, rectPen.color());
			painter.setPen(blue);
			painter.drawText(textRect, Qt::AlignCenter, QString::number(mousePressedValue.x(), 'f', 1));
		}
	}


	qDebug() << "PaintTime: " << time.count() << "ms";
	/*for (GraphSeries& series : graphSeriesVec) {
		qDebug() << "BEGIN  ---------";
		for (GraphDataPoint point : *series.data) {
			qDebug() << "QPoint(" << point.x << "," << point.y << ")";
		}
		qDebug() << "END  ---------";
	}*/
}



QPointF GraphView::transformGraphToView(QPointF& point, double stregth_factorX, double stregth_factorY, QPointF& translation) const
{
	return QPointF((point.x() - actualBound.minX) * stregth_factorX, (actualBound.maxY - point.y()) * stregth_factorY) + translation;
}


QPointF GraphView::transformViewToGraph(QPointF& point) const
{
	QRect graphDisplayRect(rect());
	graphDisplayRect.setBottom(graphDisplayRect.bottom() - MARGIN_BOTTOM);
	graphDisplayRect.setLeft(graphDisplayRect.left() + MARGIN_LEFT);
	graphDisplayRect.setTop(graphDisplayRect.top() + MARGIN_TOP);
	graphDisplayRect.setRight(graphDisplayRect.right() - MARGIN_RIGHT);
	graphDisplayRect.setWidth(graphDisplayRect.width() - (graphDisplayRect.width() % X_AXIS_GAP_AMOUNT) + 1);
	graphDisplayRect.setHeight(graphDisplayRect.height() - (graphDisplayRect.height() % Y_AXIS_GAP_AMOUNT) + 1);
	QPointF translation(graphDisplayRect.left(), graphDisplayRect.top());
	double stregth_factorX = graphDisplayRect.width() / rangeGraphX;
	double stregth_factorY = graphDisplayRect.height() / rangeGraphY;
	return QPointF(((point.x()-translation.x())/stregth_factorX)+ actualBound.minX, actualBound.maxY - ((point.y()-translation.y())/stregth_factorY));
}

GraphDataPoint GraphView::findNearestGraphDataPointFromGraphCoordinate(QPointF& graphpoint)
{
	if (graphSeriesVec.empty() || graphSeriesVec[0].data->empty()) {
		return GraphDataPoint();
	}
	GraphDataPoint min = graphSeriesVec[0].data->at(0);
	double minSqaredDistance = min.squaredDistance(graphpoint);
	for (GraphSeries& series : graphSeriesVec) {
		for (const GraphDataPoint point : *series.data) {
			double distance = point.squaredDistance(graphpoint);
			if (distance < minSqaredDistance) {
				minSqaredDistance = distance;
				min = point;
				highlightSeries = &series;
			}
		}
	}
	return min;
}

//Help Function:
double sqaredDistance(const QPointF& a, const QPointF& b)
{
	return std::pow(a.x() - b.x(), 2) + std::pow(a.y() - b.y(), 2);
}


GraphDataPoint GraphView::findNearestGraphDataPointFromViewCordinate(QPointF& viewpoint)
{
	if (graphSeriesVec.empty()) {
		return GraphDataPoint();
	}
	QRect graphDisplayRect(rect());
	graphDisplayRect.setBottom(graphDisplayRect.bottom() - MARGIN_BOTTOM);
	graphDisplayRect.setLeft(graphDisplayRect.left() + MARGIN_LEFT);
	graphDisplayRect.setTop(graphDisplayRect.top() + MARGIN_TOP);
	graphDisplayRect.setRight(graphDisplayRect.right() - MARGIN_RIGHT);
	graphDisplayRect.setWidth(graphDisplayRect.width() - (graphDisplayRect.width() % X_AXIS_GAP_AMOUNT) + 1);
	graphDisplayRect.setHeight(graphDisplayRect.height() - (graphDisplayRect.height() % Y_AXIS_GAP_AMOUNT) + 1);
	QPointF translation(graphDisplayRect.left(), graphDisplayRect.top());
	double stregth_factorX = graphDisplayRect.width() / rangeGraphX;
	double stregth_factorY = graphDisplayRect.height() / rangeGraphY;

	GraphDataPoint min = graphSeriesVec[0].data->at(0);
	double minSqaredDistance = sqaredDistance(min.toQPointF(), viewpoint);
	std::vector<GraphSeries>::iterator actualBestSeries = graphSeriesVec.begin();
	for (auto seriesIter = graphSeriesVec.begin(); seriesIter != graphSeriesVec.end(); seriesIter++) {
		for (const GraphDataPoint point : *seriesIter->data) {
			double distance = sqaredDistance(transformGraphToView(point.toQPointF(), stregth_factorX, stregth_factorY, translation), viewpoint);
			if (distance < minSqaredDistance) {
				minSqaredDistance = distance;
				min = point;
				//highlightSeries = &*seriesIter;
				actualBestSeries = seriesIter;
			}
		}
	}
	std::rotate(graphSeriesVec.begin(), actualBestSeries + 1, graphSeriesVec.end());
	highlightSeries = &graphSeriesVec.back();
	return min;
}




bool GraphView::inBoundX(QPointF& point)
{

	return point.x() >= actualBound.minX && point.x() <= actualBound.maxX;
}

bool GraphView::inBoundY(QPointF& point)
{
	return point.y() >= actualBound.minY && point.y() <= actualBound.maxY;;
}

bool GraphView::inBound(QPointF& point)
{
	return inBoundX(point) && inBoundY(point);
}

void GraphView::keyPressEvent(QKeyEvent* event)
{
	switch (event->key()) {
	case Qt::Key_Up:
		fixedBound.move(this, Bound::Change::MoveUp);
		break;
	case Qt::Key_Right:
		fixedBound.move(this, Bound::Change::MoveRight);
		break;
	case Qt::Key_Left:
		fixedBound.move(this, Bound::Change::MoveLeft);
		break;
	case Qt::Key_Down:
		fixedBound.move(this, Bound::Change::MoveDown);
		break;
	case Qt::Key_PageUp:
	case Qt::Key_Plus:
		if (event->modifiers() & Qt::ShiftModifier) {
			fixedBound.move(this, Bound::Change::ZoomInY);
		}
		else if (event->modifiers() & Qt::ControlModifier) {
			fixedBound.move(this, Bound::Change::ZoomInX);
		}
		else {
			fixedBound.move(this, Bound::Change::ZoomIn);
		}
		break;
	case Qt::Key_PageDown:
	case Qt::Key_Minus:
		if (event->modifiers() & Qt::ShiftModifier) {
			fixedBound.move(this, Bound::Change::ZoomOutY);
		}
		else if (event->modifiers() & Qt::ControlModifier) {
			fixedBound.move(this, Bound::Change::ZoomOutX);
		}
		else {
			fixedBound.move(this, Bound::Change::ZoomOut);
		}
		break;
	case Qt::Key_R:
		resetBound();
		update();
	default:
		QWidget::keyPressEvent(event);
	}
	//If not reacted forward:
	QWidget::keyPressEvent(event);
}

void GraphView::resetBound()
{
	calculateTotalMatrixBound();
	fixedBound = totalBound;
	calculateRangeXY();
	generateAxisNumberStrings();
}

void GraphView::wheelEvent(QWheelEvent* event)
{
	QPoint numPixels = event->pixelDelta();
	QPoint numDegrees = event->angleDelta() / 8;

	if (!numPixels.isNull()) {
		//scrollWithPixels(numPixels);
		switch (numPixels.y()) {
		case -1:
			fixedBound.move(this, Bound::Change::ZoomIn);
			break;
		case 1:
			fixedBound.move(this, Bound::Change::ZoomOut);
		default:
			break;
		}
	}
	else if (!numDegrees.isNull()) {
		QPoint numSteps = numDegrees / 15;
		switch (numSteps.y()) {
		case 1:
			if (event->modifiers() & Qt::ShiftModifier) {
				fixedBound.move(this, Bound::Change::ZoomInY);
			}
			else if (event->modifiers() & Qt::ControlModifier) {
				fixedBound.move(this, Bound::Change::ZoomInX);
			}
			else {
				fixedBound.move(this, Bound::Change::ZoomIn);
			}
			break;
		case -1:
			if (event->modifiers() & Qt::ShiftModifier) {
				fixedBound.move(this, Bound::Change::ZoomOutY);
			}
			else if (event->modifiers() & Qt::ControlModifier) {
				fixedBound.move(this, Bound::Change::ZoomOutX);
			}
			else {
				fixedBound.move(this, Bound::Change::ZoomOut);
			}
			break;
		default:
			break;
		}
	}

	event->accept();
}

void GraphView::mouseMoveEvent(QMouseEvent* event)
{
	//QToolTip::showText(event->globalPos(), "Hello", this);
	QPointF delta = event->pos() - oldPositionForMouseEvent;
	oldPositionForMouseEvent = event->pos();
	double rangeX = std::abs(actualBound.maxX - actualBound.minX);
	int width =  (rect().right() - 10) - (rect().left() + 50);
	width -= (width % X_AXIS_GAP_AMOUNT) + 1;
	double pixelWidthX = rangeX / (double)width ;
	double rangeY = std::abs(actualBound.maxY - actualBound.minY);
	int height = (rect().bottom() - 20) - (rect().top() + 20);
	height -= (height % Y_AXIS_GAP_AMOUNT) + 1;
	double pixelWidthY = rangeY / (double)height;



	fixedBound.move(this, delta, pixelWidthX, pixelWidthY);

}

void GraphView::mousePressEvent(QMouseEvent* event)
{
	mousePressed = true;
	//To calculate the delta for mouse move  event
	oldPositionForMouseEvent = event->pos();
	mousePressedValue = transformViewToGraph(oldPositionForMouseEvent);
	GraphDataPoint nearestPoint = findNearestGraphDataPointFromViewCordinate(oldPositionForMouseEvent);
	QString metaFileName;
	if (highlightSeries && highlightSeries->run)
	{
		metaFileName = " " + QString::fromStdString(highlightSeries->run->name);
	}
	QString string =metaFileName;
	QToolTip::showText(event->globalPos() - QPoint(0, 35), string);
}

void GraphView::mouseReleaseEvent(QMouseEvent* event)
{
	highlightSeries = nullptr;
	mousePressed = false;
	update();
}

void GraphView::update()
{
	calculateRangeXY();
	generateAxisNumberStrings();
	QWidget::update();
}

void GraphView::setDrawBound(bool value)
{
	drawTotalBound = value;
}
/**
 *  Delete all series.
 *
 */
void GraphView::reset()
{
	graphSeriesVec.clear();
}


void GraphView::calculateTotalMatrixBound()
{
	if (yDoubleDoubleValuesAmount > 0) {
		/*double maxX, maxY;
		double minX = maxX = yArray[0], minY = maxY = yArray[1];
		for (int i = 1; i < yDoubleDoubleValuesAmount; i++) {
			
			if (yArray[2 * i] < minX) {
				minX = yArray[2 * i];
			}else if (yArray[2 * i] > maxX) {
				maxX = yArray[2 * i];
			}
			if (yArray[2 * i + 1] < minY) {
				minY = yArray[2 * i + 1];
			}
			else if (yArray[2 * i + 1] > maxY) {
				maxY = yArray[2 * i + 1];
			}
		}
		totalBound.minX = minX;
		totalBound.minY = minY;
		totalBound.maxX = maxX;
		totalBound.maxY = maxY;*/

		// Calculate AveragePoint of Matrix
		
		
		
		
	}
}

void GraphView::calculateTotalGraphBound()
{
	totalBound.minX = std::min_element(std::begin(graphSeriesVec), std::end(graphSeriesVec), [](const GraphSeries& a, const GraphSeries& b) -> bool {return a.minX < b.minX; })->minX;
	totalBound.maxX = std::max_element(std::begin(graphSeriesVec), std::end(graphSeriesVec), [](const GraphSeries& a, const GraphSeries& b) -> bool {return a.maxX < b.maxX; })->maxX;
	totalBound.minY = std::min_element(std::begin(graphSeriesVec), std::end(graphSeriesVec), [](const GraphSeries& a, const GraphSeries& b) -> bool {return a.minY < b.minY; })->minY;
	totalBound.maxY = std::max_element(std::begin(graphSeriesVec), std::end(graphSeriesVec), [](const GraphSeries& a, const GraphSeries& b) -> bool {return a.maxY < b.maxY; })->maxY;
	
}



void GraphView::calculateRangeXY()
{
	actualBound = fixedBound;
	rangeGraphX = std::abs(actualBound.maxX - actualBound.minX);
	rangeGraphY = std::abs(actualBound.maxY - actualBound.minY);
	if (std::abs(rangeGraphX) < 0.0001) {
		actualBound.minX -= 1.0;
		actualBound.maxX += 1.0;
		rangeGraphX = 2;
	}
	if (std::abs(rangeGraphY) < 0.0001) {
		actualBound.minY -= 1.0;
		actualBound.maxY += 1.0;
		rangeGraphY = 2;
	}
}

//TODO: graphview 
void GraphView::calculateMinMaxXY(GraphSeries& lgs)
{
	auto pairX = std::minmax_element(lgs.data->begin(), lgs.data->end(), [](const GraphDataPoint& a, const GraphDataPoint& b) -> bool {return a.x < b.x; });
	lgs.minX = pairX.first->x;
	lgs.maxX = pairX.second->x;
	auto pairY = std::minmax_element(lgs.data->begin(), lgs.data->end(), [](const GraphDataPoint& a, const GraphDataPoint& b) -> bool {return a.y < b.y; });
	lgs.minY = pairY.first->y;
	lgs.maxY = pairY.second->y;
}

void GraphView::addSeries(std::vector<GraphDataPoint>* line, RunData* run, QColor color, GraphSeries::SeriesType type)
{
	if (line->empty()) {
		qDebug() << "Series is empty!";
		return;
	}
	GraphSeries lgs;
	lgs.run = run;
	lgs.data = line;
	lgs.type = type;
	calculateMinMaxXY(lgs);
	lgs.color = color;
	graphSeriesVec.push_back(lgs);
	calculateTotalGraphBound();
	if (!useFixedBound) calculateRangeXY();
	generateAxisNumberStrings();
}

QColor GraphView::generateNextColorForGraph()
{
	/* http://devmag.org.za/2012/07/29/how-to-choose-colours-procedurally-algorithms/
		use golden ratio 0.618033988749895f
	*/
	hueoffset = std::fmod(hueoffset + 0.618033988749895f, 1.0);
	return QColor::fromHsvF(hueoffset, 0.83, 1.0);
}


QColor interpolate(QColor& first, QColor& second, double alpha)
{
	double firstH, firstS, firstL;
	double secondH, secondS, secondL;

	first.getHslF(&firstH, &firstS, &firstL);
	second.getHslF(&secondH, &secondS, &secondL);

	const double h = util::linearInterpolate(firstH, secondH, alpha);
	const double s = util::linearInterpolate(firstS, secondS, alpha);
	const double l = util::linearInterpolate(firstL, secondL, alpha);

	return QColor::fromHslF(h, s, l);
}



void GraphView::addYMatrix(double* yArray, int length, std::vector<SolutionPointData>& solVec)
{
	this->yArray = yArray; yDoubleDoubleValuesAmount = length;
	this->solVec = &solVec;
	if (pointColors != nullptr) delete pointColors;
	pointColors = new QColor[length];
}
void GraphView::autoZoomOut()
{
	if (yDoubleDoubleValuesAmount == 0) return;
	QPointF sumDistance(0, 0);
	for (int i = 0; i < yDoubleDoubleValuesAmount; i++) {
		sumDistance += QPointF(std::abs(yArray[2 * i]), std::abs(yArray[2 * i + 1]));
	}
	QPointF avgDistance(sumDistance.x() / yDoubleDoubleValuesAmount,
		sumDistance.y() / yDoubleDoubleValuesAmount);
	double scalar = 3;
	double addScalar = 1;
	if (fixedBound.maxX < avgDistance.x() * scalar) {
		fixedBound.minX = -avgDistance.x() * scalar * addScalar;
		fixedBound.maxX = avgDistance.x() * scalar * addScalar;
		fixedBound.minY = -avgDistance.y() * scalar * addScalar;
		fixedBound.maxY = avgDistance.y() * scalar * addScalar;
	}
}

void GraphView::updateGraphColors(ColorGradient& gradient)
{
	if (yDoubleDoubleValuesAmount == 0) return;
	double bestValue = 0;
	double worstValue = 100;
	for (int i = 0; i < solVec->size(); i++) {
		pointColors[i] = gradient.getColor( std::clamp(util::inverseLinearInterpolation(bestValue, worstValue, solVec->at(i).objectiveFunction), 0.0, 1.0));
	}
}

void GraphView::generateAxisNumberStrings()
{
	for (int i = 0; i < 11; i++) {
		xAxisNumbers[i] = QString::number(actualBound.minX + i * (rangeGraphX / (double)X_AXIS_GAP_AMOUNT), 'f', 1);
		yAxisNumbers[i] = QString::number(actualBound.minY + i * (rangeGraphY / (double)Y_AXIS_GAP_AMOUNT), 'f', 1);
	}
}

void GraphView::setUseFixedBound(bool value)
{
	//if value == other update else do nothing
	if (useFixedBound != value) {
		useFixedBound = value;
		calculateRangeXY();
		actualBound = fixedBound;
		generateAxisNumberStrings();
	}
}

void GraphView::generateAndAddRandomLine()
{
	std::random_device rd;  //Will be used to obtain a seed for the random number engine
	std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()


	std::uniform_real_distribution<double> realDistr(-30, 30);
	std::uniform_int_distribution<int> intScaleDistr(1, 3);

	for (int randomSeries = 0; randomSeries < 1; randomSeries++) {
		std::vector<GraphDataPoint> randomPointVec(101);
		int scale = intScaleDistr(gen);
		for (int i = 0; i < randomPointVec.size(); i++) {
			randomPointVec[i] = GraphDataPoint(i + 500, i * scale + realDistr(gen));
		}
		addLine(&randomPointVec, nullptr);
	}
}

void GraphView::addLine(std::vector<GraphDataPoint>* line, RunData* run)
{
	addSeries(line, run,generateNextColorForGraph(), GraphSeries::SeriesType::Line);
}

void GraphView::addLine(std::vector<GraphDataPoint>* line, RunData* run, QColor color)
{
	addSeries(line, run, color, GraphSeries::SeriesType::Line);
}

void GraphView::addDots(std::vector<GraphDataPoint>* dots, RunData* run)
{
	addSeries(dots, run, generateNextColorForGraph(), GraphSeries::SeriesType::Dot);
}

void GraphView::addDots(std::vector<GraphDataPoint>* dots, RunData* run, QColor color)
{
	addSeries(dots, run, color, GraphSeries::SeriesType::Dot);
}

void GraphView::removeAllSeriesWithRundata(RunData* run)
{
	//TODO not implemented jet
	//std::for_each(graphSeriesVec);
}

void GraphView::removeAllSeries()
{
}
void GraphView::setMinIter(int value)
{
	minIter = value;
	update();
}
void GraphView::setMaxIter(int value)
{
	maxIter = value;
	update();
}

void Bound::move(GraphView* widget,const Change& type, const double changePercentage)
{
	double changeX = std::abs(minX - maxX) * changePercentage;
	double changeY = std::abs(minY - maxY) * changePercentage;
	switch (type) {
	case Change::MoveLeft:
		minX -= changeX;
		maxX -= changeX;
		break;
	case Change::MoveRight:
		minX += changeX;
		maxX += changeX;
		break;
	case Change::MoveUp:
		minY += changeY;
		maxY += changeY;
		break;
	case Change::MoveDown:
		minY -= changeY;
		maxY -= changeY;
		break;
	case Change::ZoomOut:
		minY -= changeY;
		maxY += changeY;
		minX -= changeX;
		maxX += changeX;
		break;
	case Change::ZoomIn:
		minY += changeY;
		maxY -= changeY;
		minX += changeX;
		maxX -= changeX;
		break;
	case Change::ZoomInX:
		minX += changeX;
		maxX -= changeX;
		break;
	case Change::ZoomInY:
		minY += changeY;
		maxY -= changeY;
		break;
	case Change::ZoomOutX:
		minX -= changeX;
		maxX += changeX;
		break;
	case Change::ZoomOutY:
		minY -= changeY;
		maxY += changeY;
		break;
	default:
		break;
	}
	//TODO: Move out of bound in calculate widget
	widget->update();
}

void Bound::move(GraphView* widget, QPointF& delta, double realPixelWidthX, double realPixelWidthY)
{
	minX += -delta.x() * realPixelWidthX;
	maxX += -delta.x() * realPixelWidthX;
	minY += delta.y() * realPixelWidthY;
	maxY += delta.y() * realPixelWidthY;
	widget->update();
}

void Bound::move(GraphView* widget, QPointF& targetPoint)
{
	//Calculate MiddlePoint
	QPointF actualMiddlePoint((maxX - minX) / 2.0, (maxY - minY) / 2.0);
	QPointF dif = targetPoint - actualMiddlePoint;
	minX += dif.x();
	maxX += dif.x();
	minY += dif.y();
	maxY += dif.y();
}
