#include "evalspinbox.h"
#include <QLineEdit>
#include <QLabel>
#include <QDebug>
#include <QRegExp>
#include <QPainter>
#include <QTimer>
#include <QStyle>
#include <QStyleOptionSpinBox>
#include "qad_types.h"


EvalSpinBox::EvalSpinBox(QWidget * parent): QAbstractSpinBox(parent) {
	status = new QWidget(lineEdit());
	cw = new QWidget(lineEdit());
	label = new QLabel(lineEdit());
	precision_ = -1;
//	label->hide();
	clear_im.load(":/icons/edit-clear-locationbar-rtl.png");
	icon_ok.load(":/icons/dialog-ok-apply.png");
	icon_fail.load(":/icons/dialog-warning.png");
	icon_calc.load(":/icons/tools-wizard.png");
	icon = icon_ok;
	status->setCursor(Qt::ArrowCursor);
	status->setToolTip("OK -> 0");
	status->hide();
	cw->setCursor(Qt::ArrowCursor);
	cw->setToolTip(tr("Clear"));
	cw->hide();
	cw_visible = false;
	//lineEdit()->setStyleSheet("color: darkgreen;");
	//lineEdit()->setText(eval.expression() + " -> " + QString::number(value(), 'G', 10));
	cw->installEventFilter(this);
	status->installEventFilter(this);
	connect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(textChanged_(QString)));
	connect(this, SIGNAL(editingFinished()), this, SLOT(setExpression_()));
	label->setText("0");
	//connect(cw, SIGNAL(mouseReleaseEvent(QMouseEvent * )), this, SLOT(clearMouseRelease(QMouseEvent * )));
}


EvalSpinBox::~EvalSpinBox() {
	delete cw;
	delete status;
	delete label;
}


bool EvalSpinBox::eventFilter(QObject * o, QEvent * e) {
	switch (e->type()) {
	case QEvent::MouseButtonRelease:
		if (o == cw) clearMouseRelease((QMouseEvent * )e);
		break;
	case QEvent::Paint:
		if (o == status) statusPaintEvent();
		if (o == cw) cwPaintEvent();
		break;
	default : break;
	}
	return QAbstractSpinBox::eventFilter(o, e);
}


void EvalSpinBox::resizeIcons() {
	int is = fontHeight();
	int tm = (lineEdit()->height() - is + 1) / 2;
	QStyleOptionFrame so;
	so.initFrom(lineEdit());
	QRect r = style()->subElementRect(QStyle::SE_LineEditContents, &so, lineEdit());
	int m0, m1, m2, m3;
	lineEdit()->getTextMargins(&m0, &m1, &m2, &m3);
	//label->setGeometry(m0 + r.x() + 2, m1 + r.y() + (r.height() - fontMetrics().height() + 1) / 2, lineEdit()->width() - 2*tm - (is * 1.2) * ((status->isVisible() ? 1 : 0) + (cw->isVisible() ? 1 : 0)), lineEdit()->height() - 2*tm);
	int lwh = label->sizeHint().width();
	label->setGeometry(lineEdit()->width() - m0 - lwh + r.x() - 2,
					   m1 + r.y() + (r.height() - fontMetrics().height() + 1) / 2,
					   lwh,// - 2*tm - (is * 1.2) * ((status->isVisible() ? 1 : 0) + (cw->isVisible() ? 1 : 0)),
					   lineEdit()->height() - 2*tm);
	status->setGeometry(lineEdit()->width() - (is + tm) * (cw->isVisible() ? 2 : 1), tm, is, is);
	cw->setGeometry(lineEdit()->width() - (is + tm) * 1, tm, is, is);
	lineEdit()->setTextMargins(m0, m1, (is * 1.2) * ((status->isVisible() ? 1 : 0) + (cw->isVisible() ? 1 : 0)), m3);
}


void EvalSpinBox::resizeEvent(QResizeEvent * e) {
	QAbstractSpinBox::resizeEvent(e);
	resizeIcons();
}


void EvalSpinBox::changeEvent(QEvent * e) {
	QAbstractSpinBox::changeEvent(e);
	if (e->type() == QEvent::LanguageChange) {
		cw->setToolTip(tr("Clear"));
		return;
	}
}


void EvalSpinBox::cwPaintEvent() {
	QPainter p(cw);
	p.setRenderHint(QPainter::SmoothPixmapTransform);
	p.drawImage(cw->rect(), clear_im);
}


void EvalSpinBox::statusPaintEvent() {
	QPainter p(status);
	p.setRenderHint(QPainter::SmoothPixmapTransform);
	p.drawImage(status->rect(), icon);
}


void EvalSpinBox::clearMouseRelease(QMouseEvent * e) {
	if (cw->rect().contains(e->pos())) clear();
}


void EvalSpinBox::textChanged_(const QString & text) {
	double pv = value();
	QString t = text;
	cw->setVisible(text != dt && cw_visible);
	bool td = false;
	if (t.endsWith('=')) {
		td = true;
		t.chop(1);
	}
	bool ok = eval.check(t);
	if (ok) {
		eval.evaluate();
		if (td) {
			icon = icon_calc;
			status->setToolTip("Enter to calc -> "+QString::number(value(), 'G', 10));
		} else {
			icon = icon_ok;
			status->setToolTip("OK -> "+QString::number(value(), 'G', 10));
		}
//		qDebug() << "value =" << value();
		if (pv != value()) emit valueChanged(value());
	} else {
		icon = icon_fail;
		status->setToolTip(eval.error());
	}
	resizeIcons();
}


void EvalSpinBox::setExpression_() {
	bool td = false;
	double pv = value();
	QString t = text();
	if (t.endsWith('=')) {
		td = true;
		t.chop(1);
	}
	if (eval.check(t)) {
		/*if (eval.expression() == "0") lineEdit()->clear();
		else*/ lineEdit()->setText(eval.expression());
		eval.evaluate();
		if (td) lineEdit()->setText(QString::number(value(), 'G', precision_ > 0 ? precision_ : 16));
		status->setToolTip("OK -> " + QString::number(value(), 'G', 10));
		icon = icon_ok;
	} else {
		icon = icon_fail;
		status->setToolTip(eval.error());
//		qDebug() << eval.expression();
	}
	if (!label->isHidden()) {
//		if (eval.expression() != QString::number(value(), 'G', 10) && eval.expression() != QString::number(value(), 'G', 11) && eval.isCorrect())
//			label->setText("<html><head/><body><p>" + eval.expression() + " <span style=\"color:#005500;\">-&gt; " + QString::number(value(), 'G', 10) + "</span></p></body></html>");
//		else
//			label->setText(eval.expression());
		if (eval.expression() != QString::number(value(), 'G', 10) && eval.expression() != QString::number(value(), 'G', 11) && eval.isCorrect())
			label->setText("<html><head/><body><p><span style=\"color:#005500;\">-&gt; " + QString::number(value(), 'G', 10) + "</span></p></body></html>");
		else
			label->setText("");
		lineEdit()->blockSignals(true);
		if (!eval.isCorrect()) {
			lineEdit()->setStyleSheet("color: darkred;");
			status->show();
		} else {
			lineEdit()->setStyleSheet("");
			status->hide();
		}
	//	lineEdit()->setText(eval.expression() + " -> " + QString::number(value(), 'G', 10));
		//lineEdit()->setText("");
		lineEdit()->blockSignals(false);
	}

//	qDebug() << "value =" << value();
	if (pv != value()) emit valueChanged(value());
}


void EvalSpinBox::setExpression(const QString & expr) {
	lineEdit()->setText(expr);
	//if (eval.expression() == "0") lineEdit()->clear();
	cw->setVisible(text() != dt && cw_visible);
	setExpression_();
}


void EvalSpinBox::setValue(double val) {
	lineEdit()->setText(QString::number(val, 'G', precision_ > 0 ? precision_ : 16));
	//if (val == 0) lineEdit()->clear();
	cw->setVisible(text() != dt && cw_visible);
	setExpression_();
}


void EvalSpinBox::stepBy(int steps) {
	stepByDouble(steps);
}


void EvalSpinBox::clear() {
	lineEdit()->setText(dt);
	setExpression_();
	cw->hide();
	resizeIcons();
	emit cleared();
}


double EvalSpinBox::value() const {
	if (eval.isCorrect()) {
		return eval.lastResult().real();
	}
	return 0.;
}


const QString & EvalSpinBox::expression() const {
	return eval.expression();
}


bool EvalSpinBox::isCleared() const {
	return (dt == eval.expression() || (value() == 0 && dt.isEmpty()));
}


QSize EvalSpinBox::sizeHint() const {
	QSize s = QAbstractSpinBox::sizeHint();
	s.setWidth(120);
	return s;
}


QAbstractSpinBox::StepEnabled EvalSpinBox::stepEnabled() const {
	return StepUpEnabled | StepDownEnabled;
}


void EvalSpinBox::focusInEvent(QFocusEvent * event) {
//	qDebug() << "focus_in";
	label->hide();
	status->show();
	lineEdit()->blockSignals(true);
	lineEdit()->setStyleSheet("");
	if (eval.expression() == "0") lineEdit()->clear();
	lineEdit()->blockSignals(false);
	QAbstractSpinBox::focusInEvent(event);
	resizeIcons();
}


void EvalSpinBox::focusOutEvent(QFocusEvent * event) {
	QAbstractSpinBox::focusOutEvent(event);
//	qDebug() << eval.expression() << QString::number(value(), 'G', 10);
//	if (eval.expression() != QString::number(value(), 'G', 10) && eval.expression() != QString::number(value(), 'G', 11) && eval.isCorrect())
//		label->setText("<html><head/><body><p>" + eval.expression() + " <span style=\"color:#005500;\">-&gt; " + QString::number(value(), 'G', 10) + "</span></p></body></html>");
//	else
//		label->setText(eval.expression());
	if (eval.expression() != QString::number(value(), 'G', 10) && eval.expression() != QString::number(value(), 'G', 11) && eval.isCorrect())
		label->setText("<html><head/><body><p><span style=\"color:#005500;\">-&gt; " + QString::number(value(), 'G', 10) + "</span></p></body></html>");
	else
		label->setText("");
	label->show();
	lineEdit()->blockSignals(true);
	if (!eval.isCorrect()) lineEdit()->setStyleSheet("color: darkred;");
	else status->hide();
//	lineEdit()->setText(eval.expression() + " -> " + QString::number(value(), 'G', 10));
	//lineEdit()->clear();
	lineEdit()->blockSignals(false);
	resizeIcons();
}


void EvalSpinBox::wheelEvent(QWheelEvent * event) {
	if (event->modifiers().testFlag(Qt::ShiftModifier))
		stepByDouble(event->delta() > 0 ? 0.1 : -0.1);
	else
		QAbstractSpinBox::wheelEvent(event);
}


void EvalSpinBox::stepByDouble(double steps) {
	//qDebug() << "step" << steps;
	if (isReadOnly()) return;
	QString t = text();
	if (eval.check(t)) {
		t = eval.expression();
		//QRegExp re("(\\-?\\d+)");
		QRegExp re("[+-]?([0-9]+([.][0-9]*)?|[.][0-9]+)");
		int pos = 0;
		if ((pos = re.indexIn(t)) != -1) {
			double v = t.mid(pos, re.matchedLength()).toDouble();
			v += steps;
			t.remove(pos, re.matchedLength());
			t.insert(pos, QString::number(v));
		} else {
			double v = steps;
			t = QString::number(v) + t;
		}
		eval.check(t);
		lineEdit()->setText(eval.expression());
	}
}


void EvalSpinBox::setDefaultText(const QString & t) {
//	bool def = (!cw->isHidden());
	dt = t;
//	if (def) {
//		lineEdit()->setText(dt);
//		setExpression_();
//	}
	//if (t == eval.expression() || (value() == 0 && t.isEmpty())) clear();
	cw->setVisible((eval.expression() != dt || (dt.isEmpty() && eval.expression() == "0")) && cw_visible);
	resizeIcons();
}


void EvalSpinBox::setClearButtonVisible(bool visible) {
	cw_visible = visible;
	cw->setVisible((eval.expression() != dt || (dt.isEmpty() && eval.expression() == "0")) && cw_visible);
	resizeIcons();
}
