#include "pch.h"
#include "Plott.h"
#include "util.h"



#include <chrono>

#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 X_AXIS_LEGEND_TOP_BEGIN 12
#define Y_AXIS_LEGEND_LEFT_BEGIN 12

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



Plott::Plott(QWidget *parent)
	: QWidget(parent)
{
	setFocusPolicy(Qt::ClickFocus);
}

Plott::~Plott()
{
}
void Plott::setVisibleWindow(double xMin, double xMax, double yMin, double yMax)
{
	window.xMin = xMin;
	window.xMax = xMax;
	window.yMin = yMin;
	window.yMax = yMax;
}
void Plott::setDefaultVisibleWindow(double xMin, double xMax, double yMin, double yMax)
{
	defaultWindow.xMin = xMin;
	defaultWindow.xMax = xMax;
	defaultWindow.yMin = yMin;
	defaultWindow.yMax = yMax;
}
void Plott::resetToDefaultWindow()
{
	window.xMin = defaultWindow.xMin;
	window.xMax = defaultWindow.xMax;
	window.yMin = defaultWindow.yMin;
	window.yMax = defaultWindow.yMax;
}
void Plott::setAxisLegend(QString xAxisLegend, QString yAxisLegend)
{
	this->xAxisLegend = xAxisLegend;
	this->yAxisLegend = yAxisLegend;
}
QPointF Plott::transformGraphToView(QPointF graphpoint) const
{
	QRect graphDisplayRect = getDisplayRect();
	QPointF translation(graphDisplayRect.left(), graphDisplayRect.top());
	double stregth_factorX = graphDisplayRect.width() / std::abs(window.xMax - window.xMin);
	double stregth_factorY = graphDisplayRect.height() / std::abs(window.yMax - window.yMin);
	return QPointF((graphpoint.x() - window.xMin) * stregth_factorX, (window.yMax - graphpoint.y()) * stregth_factorY) + translation;
}
QPointF Plott::transformGraphToView(QPointF graphpoint, double stregth_factorX, double stregth_factorY, QPointF translation) const
{
	return QPointF((graphpoint.x() - window.xMin) * stregth_factorX, (window.yMax - graphpoint.y()) * stregth_factorY) + translation;
}
QPointF Plott::transformViewToGraph(QPointF viewpoint) const
{
	QRect graphDisplayRect = getDisplayRect();
	QPointF translation(graphDisplayRect.left(), graphDisplayRect.top());
	double stregth_factorX = graphDisplayRect.width() / std::abs(window.xMax - window.xMin);
	double stregth_factorY = graphDisplayRect.height() / std::abs(window.yMax - window.yMin);
	return QPointF(((viewpoint.x() - translation.x()) / stregth_factorX) + window.xMin, window.yMax - ((viewpoint.y() - translation.y()) / stregth_factorY));
}

QPointF Plott::transformViewToGraph(QPointF viewpoint, double stregth_factorX, double stregth_factorY, QPointF translation) const
{
	return QPointF(((viewpoint.x() - translation.x()) / stregth_factorX) + window.xMin, window.yMax - ((viewpoint.y() - translation.y()) / stregth_factorY));
}




void Plott::drawData(QPainter& painter)
{
}

QRect Plott::getDisplayRect() 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);
	return graphDisplayRect;
}


// Draws the axis
void Plott::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);
	
	QRect graphDisplayRect = getDisplayRect();
	int xGap = graphDisplayRect.width() / X_AXIS_GAP_AMOUNT;
	int yGap = graphDisplayRect.height() / Y_AXIS_GAP_AMOUNT;
	if(enableGrid){
		QPainterPath gridPath;
		for (int i = 1; i < X_AXIS_GAP_AMOUNT; i++) {
			gridPath.moveTo(graphDisplayRect.left() + i * xGap, graphDisplayRect.top());
			gridPath.lineTo(graphDisplayRect.left() + i * xGap, graphDisplayRect.bottom());
		}
		
		for (int i = 0; i < Y_AXIS_GAP_AMOUNT + 1; i++) {
			gridPath.moveTo(graphDisplayRect.left(), graphDisplayRect.bottom() - i * yGap);
			gridPath.lineTo(graphDisplayRect.right(), graphDisplayRect.bottom() - i * yGap);
		}
		QPen graypen(QColor(150, 150, 150));
		painter.setPen(graypen);
		painter.drawPath(gridPath);
	}
	
	drawData(painter);
	
	//Draw White Rect for axis
	QPen blackpen(QColor(0, 0, 0));
	blackpen.setWidth(1);
	QPen whitePen(QColor(255, 255, 255));
	whitePen.setWidth(1);
	painter.setPen(whitePen);
	painter.setBrush(whitePen.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);




	//Font for Axis;
	painter.setFont(QFont("Arial", 8));
	//draw X-Axis
	painter.setPen(blackpen);
	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());

	QRect textRect(0, 0, 40, 9);

	for (int i = 0; i < X_AXIS_GAP_AMOUNT + 1; 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, QString::number(util::linearInterpolate(window.xMin, window.xMax, (1/ (double)X_AXIS_GAP_AMOUNT )*i), 'f', 1));
	}
	
	painter.drawPath(xAxisPath);
	//Draw XAxisLegend
	QRect xAxisLegendRect(QPoint(xAxisRect.left(), rect().bottom() - X_AXIS_LEGEND_TOP_BEGIN), rect().bottomRight());
	painter.drawText(xAxisLegendRect, Qt::AlignCenter, xAxisLegend);

	//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());
	
	for (int i = 0; i < Y_AXIS_GAP_AMOUNT + 1; 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, QString::number(util::linearInterpolate(window.yMin, window.yMax, (1 / (double)Y_AXIS_GAP_AMOUNT) * i), 'f', 1));
	}
	painter.drawPath(yAxisPath);
	//Draw YAxisLegend
	//to draw vertical the painter have to translated and rotated (order is crucial)
	QRect yAxisLegendRect(0, 0, graphDisplayRect.bottom(), Y_AXIS_LEGEND_LEFT_BEGIN);
	painter.translate(0,graphDisplayRect.bottom());
	painter.rotate(-90.0);
	painter.drawText(yAxisLegendRect, Qt::AlignCenter, yAxisLegend);
	painter.rotate(+90.0);
	painter.translate(0, -graphDisplayRect.bottom());



	if (isLeftMousePressed) {
		QColor blue(0, 122, 204);
		QColor gray(120, 120, 120);
		//QPointF value = transformViewToGraph(oldPositionForMouseEvent);
		QPointF viewPoint = transformGraphToView(mousePressedValue);
		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, whitePen.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, whitePen.color());
			painter.setPen(blue);
			painter.drawText(textRect, Qt::AlignCenter, QString::number(mousePressedValue.x(), 'f', 1));
		}
	}
	if (isRightMousePressed) {
		QColor blue(0, 122, 204);
		painter.setPen(blue);
		painter.drawRect(QRectF(transformGraphToView(this->mousePressedValue), oldPositionForMouseEvent));
	}
	std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
	std::chrono::milliseconds timeElapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
	
}

void Plott::keyPressEvent(QKeyEvent* event)
{
	double percent = 0.1;
	switch (event->key()) {
	case Qt::Key_Up:
		window.moveUp(percent);
		break;
	case Qt::Key_Right:
		window.moveRight(percent);
		break;
	case Qt::Key_Left:
		window.moveLeft(percent);
		break;
	case Qt::Key_Down:
		window.moveDown(percent);
		break;
	case Qt::Key_PageUp:
	case Qt::Key_Plus:
		if (event->modifiers() & Qt::ShiftModifier) {
			//ZoomInY
			window.ZoomInY(percent);
		}
		else if (event->modifiers() & Qt::ControlModifier) {
			//ZoomInX
			window.ZoomInX(percent);
		}
		else {
			//Zoom
			window.ZoomIn(percent);
		}
		break;
	case Qt::Key_PageDown:
	case Qt::Key_Minus:
		if (event->modifiers() & Qt::ShiftModifier) {
			//ZoomOutX
			window.ZoomOutY(percent);
		}
		else if (event->modifiers() & Qt::ControlModifier) {
			//ZoomOutX
			window.ZoomOutX(percent);
		}
		else {
			//ZoomOut
			window.ZoomOut(percent);
		}
		break;
	case Qt::Key_R:
		resetToDefaultWindow();
	default:
		QWidget::keyPressEvent(event);
	}
	update();
}

void Plott::wheelEvent(QWheelEvent* event)
{
	double percent = 0.1;
	QPoint numDegrees = event->angleDelta() / 8;
	if (!numDegrees.isNull()) {
		QPoint numSteps = numDegrees / 15;
		switch (numSteps.y()) {
		case 1:
			if (event->modifiers() & Qt::ShiftModifier) {
				window.ZoomInY(percent);
			}
			else if (event->modifiers() & Qt::ControlModifier) {
				window.ZoomInX(percent);
			}
			else {
				window.ZoomIn(percent);
			}
			break;
		case -1:
			if (event->modifiers() & Qt::ShiftModifier) {
				window.ZoomOutY(percent);
			}
			else if (event->modifiers() & Qt::ControlModifier) {
				window.ZoomOutX(percent);
			}
			else {
				window.ZoomOut(percent);
			}
			break;
		default:
			break;
		}
		update();
	}

	event->accept();
}

void Plott::mouseMoveEvent(QMouseEvent* event)
{
	if (isLeftMousePressed) {
		//Move window by calculating the change in visibleWindow
		QPointF delta = event->pos() - oldPositionForMouseEvent;
		oldPositionForMouseEvent = event->pos();
		double rangeX = window.rangeX();
		int width = (rect().right() - 10) - (rect().left() + 50);
		width -= (width % X_AXIS_GAP_AMOUNT) + 1;
		double pixelWidthX = rangeX / (double)width;
		double rangeY = window.rangeY();
		int height = (rect().bottom() - 20) - (rect().top() + 20);
		height -= (height % Y_AXIS_GAP_AMOUNT) + 1;
		double pixelWidthY = rangeY / (double)height;
	
		window.xMin += -delta.x() * pixelWidthX;
		window.xMax += -delta.x() * pixelWidthX;
		window.yMin += delta.y() * pixelWidthY;
		window.yMax += delta.y() * pixelWidthY;
		update();
	}
	else if (isRightMousePressed) {

		oldPositionForMouseEvent = event->pos();
		update();
	}
	
}

void Plott::mousePressEvent(QMouseEvent* event)
{
	if (event->buttons() == Qt::LeftButton) {
		isLeftMousePressed = true;
	}
	else if (event->buttons() == Qt::RightButton) {
		isRightMousePressed = true;
	}
	//To calculate the delta for mouse move  event
	oldPositionForMouseEvent = event->pos();
	mousePressedValue = transformViewToGraph(oldPositionForMouseEvent);
	update();
}

void Plott::mouseReleaseEvent(QMouseEvent* event)
{
	if (isRightMousePressed) {
		QPointF mouseViewPostion = transformGraphToView(mousePressedValue);
		if (std::abs(mouseViewPostion.x() - oldPositionForMouseEvent.x()) > 0 || std::abs(mouseViewPostion.y() - oldPositionForMouseEvent.y()) > 0) {
					//set visible window to selection 
					VisibleWindow selectedWindow(0,0,0,0);
					QPointF endPosition = transformViewToGraph(oldPositionForMouseEvent);
					bool pressedIsLowerX = mousePressedValue.x() < endPosition.x();
					selectedWindow.xMin = pressedIsLowerX? mousePressedValue.x() : endPosition.x();
					selectedWindow.xMax = pressedIsLowerX ? endPosition.x() : mousePressedValue.x();

					bool pressedIsLowerY = mousePressedValue.y() < endPosition.y();
					selectedWindow.yMin = pressedIsLowerY ? mousePressedValue.y() : endPosition.y();
					selectedWindow.yMax = pressedIsLowerY ? endPosition.y() : mousePressedValue.y();
					handleSelectedWindow(selectedWindow, event);
		}
	}
	isLeftMousePressed = false;
	isRightMousePressed = false;
	
	update();
}

void Plott::handleSelectedWindow(VisibleWindow& window, QMouseEvent* event)
{
	setWindow(window);
}

void Plott::setWindow(VisibleWindow& window)
{
	this->window = window;
	update();
}

void VisibleWindow::moveUp(double percent)
{
	double range = rangeY();
	yMin += percent * range;
	yMax += percent * range;

}

void VisibleWindow::moveDown(double percent)
{
	double range = rangeY();
	yMin -= percent * range;
	yMax -= percent * range;
}

void VisibleWindow::moveLeft(double percent)
{
	double range = rangeX();
	xMin -= percent * range;
	xMax -= percent * range;
}

void VisibleWindow::moveRight(double percent)
{
	double range = rangeX();
	xMin += percent * range;
	xMax += percent * range;
}

void VisibleWindow::ZoomIn(double percent)
{
	//Both sides same percentage
	percent /= 2; 
	double Xrange = rangeX();
	if (Xrange == 0) Xrange = 1.0;
	xMin += percent * Xrange;
	xMax -= percent * Xrange;
	double Yrange = rangeY();
	if (Yrange == 0) Yrange = 1.0;
	yMin += percent * Yrange;
	yMax -= percent * Yrange;
}

void VisibleWindow::ZoomInX(double percent)
{
	percent /= 2;
	double Xrange = rangeX();
	if (Xrange == 0) Xrange = 1.0;
	xMin += percent * Xrange;
	xMax -= percent * Xrange;
}

void VisibleWindow::ZoomInY(double percent)
{
	percent /= 2;
	double Yrange = rangeY();
	if (Yrange == 0) Yrange = 1.0;
	yMin += percent * Yrange;
	yMax -= percent * Yrange;
}

void VisibleWindow::ZoomOut(double percent)
{
	//Both sides same percentage
	percent /= 2;
	double Xrange = rangeX();
	if (Xrange == 0) Xrange = 1.0;
	xMin -= percent * Xrange;
	xMax += percent * Xrange;
	double Yrange = rangeY();
	if (Yrange == 0) Yrange = 1.0;
	yMin -= percent * Yrange;
	yMax += percent * Yrange;
}

void VisibleWindow::ZoomOutX(double percent)
{
	percent /= 2;
	double Xrange = rangeX();
	if (Xrange == 0) Xrange = 1.0;
	xMin -= percent * Xrange;
	xMax += percent * Xrange;
}

void VisibleWindow::ZoomOutY(double percent)
{
	percent /= 2;
	double Yrange = rangeY();
	if (Yrange == 0) Yrange = 1.0;
	yMin -= percent * Yrange;
	yMax += percent * Yrange;
}

double VisibleWindow::rangeX() const
{
	return xMax - xMin;
}

double VisibleWindow::rangeY() const
{
	return yMax - yMin;
}

bool VisibleWindow::inBoundX(QPointF point) const
{
	return point.x() >= xMin && point.x() <= xMax;
}

bool VisibleWindow::inBoundY(QPointF point) const
{
	return point.y() >= yMin && point.y() <= yMax;
}

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