#include "pch.h"
#include <QLayout>
#include <QDoubleValidator>
#include "ColorGradient.h"
#include "util.h"
#define LeftRightGap 10
#define ColorExampleStart 30
#define ColorExmapleHeight 80
#define SliderBeginHeight 83
#define SliderBody 14
#define SliderPeak 6
#define SelectedPreview 20
#define SelectedHeight 115
ColorGradient::ColorGradient(QWidget *parent)
	: QWidget(parent)
{
	interpolationCombobox = new QComboBox(this);
	interpolationCombobox->addItem("RGB");
	interpolationCombobox->addItem("HLS");
	connect(interpolationCombobox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
		this, [this](int index) {this->setInterpolationMode((ColorGradient::ColorInterpolationMode)index); });
	modeCombobox = new QComboBox(this);
	modeCombobox->addItem("Interpolate");
	modeCombobox->addItem("Discrete");
	connect(modeCombobox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
		this, [this](int index) {this->setMode((ColorGradient::ColorMode)index);});
	colorButton = new ColorButton(this, QColor(0,0,0));
	
	connect(colorButton, &ColorButton::colorChanged, this, [this](QColor color) {
		if (selected != nullptr) setColorPointColor(*selected, color);
		});
	QVBoxLayout* vBoxLayout = new QVBoxLayout(this);
	vBoxLayout->setContentsMargins(0,0,0,0);
	QGridLayout* layoutTop = new QGridLayout();
	vBoxLayout->addLayout(layoutTop);
	layoutTop->setContentsMargins(0, 0, 0, 0);
	layoutTop->setAlignment(Qt::AlignTop);
	spacerH = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
	layoutTop->addWidget(new QLabel("ObjectiveFunction-Gradient:", this), 0, 0);
	layoutTop->addItem(spacerH, 0, 1);

	layoutTop->addWidget(modeCombobox, 0, 6);
	layoutTop->addWidget(interpolationCombobox, 0, 7);
	QGridLayout* layoutBottom = new QGridLayout();
	vBoxLayout->addLayout(layoutBottom);
	layoutBottom->setContentsMargins(0, 0, 0, 0);
	layoutBottom->setAlignment(Qt::AlignBottom);
	layoutBottom->addItem(new QSpacerItem(10, 20, QSizePolicy::Expanding, QSizePolicy::Minimum), 0, 0);
	colorLabel = new QLabel("Color:");
	layoutBottom->addWidget(colorLabel, 0, 1);
	layoutBottom->addWidget(colorButton, 0, 2);
	alphaLabel = new QLabel("Alpha:");
	layoutBottom->addWidget(alphaLabel, 0, 3);
	
	

	layoutTop->addWidget(new QLabel("ValueRange:"), 0, 2);
	minEdit = new  QLineEdit();
	minEdit->setMaximumWidth(80);
	layoutTop->addWidget(minEdit, 0, 3);
	layoutTop->addWidget(new QLabel(":"), 0, 4);
	maxEdit = new  QLineEdit();
	maxEdit->setMaximumWidth(80);
	layoutTop->addWidget(maxEdit, 0, 5);
	valueLabel = new QLabel("Value:");
	layoutBottom->addWidget(valueLabel, 0,5);


	QRegExp rxMinMax("[+-]?[0-9]*(\\.[0-9]{0,6})?");
	QRegExpValidator* validatorMinMax = new QRegExpValidator(rxMinMax, 0);
	minEdit->setValidator(validatorMinMax);
	maxEdit->setValidator(validatorMinMax);
	minEdit->setText(QString::number(minValue));
	maxEdit->setText(QString::number(maxValue));
	connect(minEdit, &QLineEdit::editingFinished, this, &ColorGradient::minEditChangeEvent);	
	connect(maxEdit, &QLineEdit::editingFinished, this, &ColorGradient::maxEditChangeEvent);

	alphaLineEdit = new QLineEdit(this);
	alphaLineEdit->setMaximumWidth(80);
	layoutBottom->addWidget(alphaLineEdit, 0, 4);

	valueEdit = new  QLineEdit();
	valueEdit->setMaximumWidth(80);
	valueEdit->setValidator(validatorMinMax);
	connect(valueEdit, &QLineEdit::editingFinished, this, &ColorGradient::setValueOfSelected);
	layoutBottom->addWidget(valueEdit, 0, 6);

	QRegExp rx("0(\\.[0-9]{0,6})?|1(\\.0*)?");
	QRegExpValidator* v = new QRegExpValidator(rx, 0);
	alphaLineEdit->setValidator(v);
	connect(alphaLineEdit, &QLineEdit::editingFinished, this, &ColorGradient::setAlphaOfSelected);


	layoutBottom->addItem(new QSpacerItem(10, 20, QSizePolicy::Expanding, QSizePolicy::Minimum), 0, 7);

	//this->setMinimumHeight(150);
	this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
	
	this->setMinimumHeight(150);
	//Dummy Data
	//TODO(ColorGradient #1) get gradient from last time or save project
	pointList.push_back(ColorPoint(0.0, QColor(0, 100, 200)));
	pointList.push_back(ColorPoint(0.5, QColor(0, 100, 50)));
	pointList.push_back(ColorPoint(1.0, QColor(200, 10, 10)));
	//select first
	selectColorPoint(&pointList.front());
}

ColorGradient::~ColorGradient()
{

}

QColor ColorGradient::getColorFromAlpha(double alpha)
{
	alpha = std::clamp(alpha, 0.0, 1.0);
	switch(mode) {
	case ColorMode::Discrete: {
		QColor result(255,255,255);
		for (ColorPoint& point : pointList) {
			if (point.alpha >= alpha) {
				result = point.color;
				break;
			}
		}
		return result;
	}
	case ColorMode::Interpolate: {
		ColorPoint before(0.0, QColor(255, 255, 255));
		ColorPoint last(1.0, QColor(255, 255, 255));
		ColorPoint* first = &before;
		ColorPoint* second = &last;
		for (ColorPoint& point : pointList) {
			if (point.alpha >= alpha) {
				second = &point;
				break;
			}
			first = &point;
		}
		double alphaBetween = util::inverseLinearInterpolation(first->alpha, second->alpha, alpha);
		switch (interpolationMode) {
		case ColorInterpolationMode::HSL:
			return util::interpolateHSL(first->color, second->color, alphaBetween);
		case ColorInterpolationMode::RGB:
		default:
			return util::interpolateRGB(first->color, second->color, alphaBetween);
		}
		
	}
	default: {
		return QColor(0, 0, 0);
	}
	}
	
}

QColor ColorGradient::getColorFromValue(double value)
{
	double alpha = util::inverseLinearInterpolation(minValue, maxValue, value);
	return getColorFromAlpha(alpha);
}

void ColorGradient::addColorPoint(double alpha)
{
	auto iter = pointList.begin();
	for (; iter != pointList.end(); iter++) {
		if (iter->alpha >= alpha) {
			break;
		}
	}
	selectColorPoint(&*pointList.insert(iter, ColorPoint(alpha, getColorFromAlpha(alpha))));
	emit gradientChanged();
}

void ColorGradient::removeColorPoint(ColorPoint& pointToRemove)
{
	pointList.erase(std::remove_if(pointList.begin(), pointList.end(),
			[&pointToRemove](ColorPoint& point) {return &point == &pointToRemove; }));
	//colorButton->setVisible(false);
	selected = nullptr;
	hideSelectionEdit(true);
	emit gradientChanged();
}

void ColorGradient::setColorPointAlpha(ColorPoint& point, double alpha)
{
	alpha = std::clamp(alpha, 0.0, 1.0);
	if (alpha == point.alpha) return;
	point.alpha = alpha;
	pointList.sort([](ColorPoint& a, ColorPoint& b) {
		return a.alpha < b.alpha;
		});
	if (&point == selected) {
		alphaLineEdit->setText(QString::number(alpha));
		valueEdit->setText(QString::number(util::linearInterpolate(minValue, maxValue, alpha)));
	}
	emit gradientChanged();
	update();
}

void ColorGradient::setColorPointColor(ColorPoint& point, QColor newColor)
{
	if (newColor == point.color) return;
	point.color = newColor;
	emit gradientChanged();
	update();
}

void ColorGradient::setMode(ColorMode mode)
{
	this->mode = mode;
	update();
	emit gradientChanged();
	interpolationCombobox->setEnabled(mode != ColorGradient::ColorMode::Discrete);
}

void ColorGradient::setInterpolationMode(ColorInterpolationMode mode)
{
	this->interpolationMode = mode;
	update();
	emit gradientChanged();
}

void ColorGradient::setMinValue(double value)
{
	if (value > maxValue) {
		minValue = maxValue;
		maxValue = value;
		minEdit->setText(QString::number(minValue));
		maxEdit->setText(QString::number(maxValue));
	}
	else {
		minValue = value;
		minEdit->setText(QString::number(minValue));
	}
	if (selected != nullptr) {
		valueEdit->setText(QString::number(util::linearInterpolate(minValue, maxValue, selected->alpha)));
	}
	emit gradientChanged();
}

void ColorGradient::setMaxValue(double value)
{
	if (value < minValue) {
		maxValue = minValue;
		minValue = value;
		minEdit->setText(QString::number(minValue));
		maxEdit->setText(QString::number(maxValue));
	}
	else {
		maxValue = value;
		maxEdit->setText(QString::number(maxValue));
	}
	if (selected != nullptr) {
		valueEdit->setText(QString::number(util::linearInterpolate(minValue, maxValue, selected->alpha)));
	}
	emit gradientChanged();
}

void ColorGradient::hideSelectionEdit(bool value)
{
	colorButton->setHidden(value);
	alphaLineEdit->setHidden(value);
	alphaLabel->setHidden(value);
	colorLabel->setHidden(value);
	valueEdit->setHidden(value);
	valueLabel->setHidden(value);
}

void ColorGradient::setAlphaOfSelected()
{
	if (selected == nullptr) return;
	bool success = false;
	double alpha = alphaLineEdit->text().toDouble(&success);
	if (success) {
		setColorPointAlpha(*selected, alpha);
	}

}

void ColorGradient::setValueOfSelected()
{
	if (selected == nullptr) return;
	bool success = false;
	double value = valueEdit->text().toDouble(&success);
	
	if (success) {
		if (value < minValue) {
			value = minValue;
		}
		else if (value > maxValue) {
			value = maxValue;
		}
		setColorPointAlpha(*selected, util::inverseLinearInterpolation(minValue, maxValue, value));
	}
}

void ColorGradient::selectColorPoint(ColorPoint* point)
{
	selected = point;
	colorButton->setColor(selected->color);
	alphaLineEdit->setText(QString::number(selected->alpha));
	valueEdit->setText(QString::number(util::linearInterpolate(minValue, maxValue, selected->alpha)));
	hideSelectionEdit(false);
}

void ColorGradient::paintEvent(QPaintEvent* event)
{
	QPainter painter(this);
	painter.setRenderHint(QPainter::RenderHint::HighQualityAntialiasing);
	QRect colorExampleRect(QPoint(rect().left() + LeftRightGap, ColorExampleStart), QPoint(rect().right() - LeftRightGap, ColorExmapleHeight));
	painter.drawRect(colorExampleRect);
	//drawSliders
	QPen sliderPen;
	sliderPen.setWidth(2);
	painter.setPen(Qt::NoPen);
	
	
	for (ColorPoint& point : pointList) {
		QPainterPath slider;
		if (&point == selected) {
			sliderPen.setColor(QColor(0, 0, 0));
			painter.setPen(sliderPen);
		}
		painter.setBrush(point.color);
		int middlePosition = util::linearInterpolate(colorExampleRect.left(), colorExampleRect.right(), point.alpha);
		slider.moveTo(middlePosition, SliderBeginHeight);
		slider.lineTo(middlePosition - SliderPeak, SliderBeginHeight + SliderPeak);
		slider.lineTo(middlePosition - SliderPeak, SliderBeginHeight + SliderPeak + SliderBody);
		slider.lineTo(middlePosition + SliderPeak, SliderBeginHeight + SliderPeak + SliderBody);
		slider.lineTo(middlePosition + SliderPeak, SliderBeginHeight + SliderPeak);
		slider.closeSubpath();
		painter.drawPath(slider);
		if (&point == selected) {
			painter.setPen(Qt::NoPen);
		}
	}
	for (int pixelX = colorExampleRect.left() + 1; pixelX < colorExampleRect.right()+1; pixelX++) {
		double alpha = util::inverseLinearInterpolation(colorExampleRect.left(), colorExampleRect.right()+1, pixelX);
		QColor color = getColorFromAlpha(alpha);
		painter.setPen(color);
		painter.drawLine(pixelX, colorExampleRect.top() + 1, pixelX, colorExampleRect.bottom());
	}
	




}

void ColorGradient::mousePressEvent(QMouseEvent* event)
{
	if (childAt(event->pos()) != nullptr) {
		return;
	}
	QPoint mousePos = event->pos();
	QRect area(QPoint(rect().left() + LeftRightGap, ColorExmapleHeight), QPoint(rect().right() - LeftRightGap, ColorExmapleHeight+SliderBody+SliderPeak));
	QRect responisveArea(QPoint(area.left() - SliderPeak * 2, area.top()), QPoint(area.right() + SliderPeak * 2, area.bottom()));
	if (responisveArea.contains(mousePos)) {

		// Check for existing color point
		// Select with left mouse Button
		// Remove Point with right mouse Button
		for (ColorPoint& point : pointList) {
			double posX = util::linearInterpolate(area.left(), area.right() + 1, point.alpha);
			if (posX - SliderPeak * 2 < mousePos.x() && mousePos.x() < posX + SliderPeak * 2) {
				if (event->buttons() & Qt::LeftButton) {
					selectColorPoint(&point);
				}
				else if(event->buttons() & Qt::RightButton){
					removeColorPoint(point);
					
				}
				update();
				return;
			}
		}
		

		// Insert new Color Point
		if (event->buttons() & Qt::LeftButton) {
			double alpha = util::inverseLinearInterpolation(area.left(), area.right() + 1, mousePos.x());
			addColorPoint(alpha);

			update();
		}
	}
}

void ColorGradient::mouseMoveEvent(QMouseEvent* event)
{
	if (selected == nullptr) return;
	if (childAt(event->pos()) != nullptr) {
		return;
	}
	//TODO(Bug #1) when mouse press button mouse move gets emitted and move the selected
	//currently a workarround to not move the selected colorpoint when selecting a button
	//if (event->pos().y() < ColorExmapleHeight) return;

	QPoint mousePos = event->pos();
	QRect area(QPoint(rect().left() + LeftRightGap, ColorExmapleHeight), QPoint(rect().right() - LeftRightGap, ColorExmapleHeight + SliderBody + SliderPeak));
	double alpha = util::inverseLinearInterpolation(area.left(), area.right() + 1, mousePos.x());
	this->setColorPointAlpha(*selected, alpha);


}

void ColorGradient::minEditChangeEvent()
{
	bool success = false;
	double value = minEdit->text().toDouble(&success);
	if (success) {
		this->setMinValue(value);
	}
}

void ColorGradient::maxEditChangeEvent()
{
	bool success = false;
	double value = maxEdit->text().toDouble(&success);
	if (success) {
		this->setMaxValue(value);
	}
}
