#include <QPainter>
#include <QStyleOption>
#include <QApplication>
#include <QBoxLayout>
#include <QLayout>
#include <qmath.h>
#include "switch.h"
#include "qad_types.h"


template<typename Type> Type qClamp(const Type & v, const Type & min, const Type & max) {return (v > max ? max : (v < min ? min : v));}
QColor operator +(const QColor & c, const QColor & v) {return QColor(qClamp<int>(c.red() + v.red(), 0, 255),
																	 qClamp<int>(c.green() + v.green(), 0, 255),
																	 qClamp<int>(c.blue() + v.blue(), 0, 255),
																	 qClamp<int>(c.alpha() + v.alpha(), 0, 255));}
QColor operator *(const QColor & c, double v) {return QColor(c.red() * v, c.green() * v, c.blue() * v, c.alpha() * v);}
QColor operator /(const QColor & c, double v) {return QColor(c.red() / v, c.green() / v, c.blue() / v, c.alpha() / v);}


SHSSwitch::SHSSwitch(QWidget * parent): QAbstractButton(parent) {
	init();
}


SHSSwitch::~SHSSwitch() {
}


void SHSSwitch::init() {
	setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
	anim.setTargetObject(this);
	anim.setPropertyName("h__anim");
	anim.setDuration(200);
	anim.setEasingCurve(QEasingCurve::InOutQuad);
	anim_s.setTargetObject(this);
	anim_s.setPropertyName("h__anim_s");
	anim_s.setDuration(200);
	anim_s.setEasingCurve(QEasingCurve::InOutQuad);
	fill_color = QColor(255, 245, 60);
	back_color = QColor(216, 216, 216);
	orient_ = Qt::Horizontal;
	style_ = SHSSwitch::OneSide;
	ro_ = mo_ = mp_ = inv_ = mdir_ = false;
	ap_ = ap_s_ = 1.f;
	scale_ = 1.;
	QAbstractButton::setCheckable(true);
}


void SHSSwitch::checkStateSet() {
	QAbstractButton::checkStateSet();
	animate();
	//qDebug() << isChecked();
}


void SHSSwitch::nextCheckState() {
	if (ro_) return;
	QAbstractButton::nextCheckState();
	animate();
	//qDebug() << isChecked();
}


QSize SHSSwitch::minimumSizeHint() const {
	return QSize(10, 10) * appScale();
}


QSize SHSSwitch::sizeHint() const {
	int xs = fontHeight() * scale_ * 1.25;
#ifdef Q_OS_ANDROID
	xs /= 1.25;
#endif
	//qDebug() << "SHSSwitch::sizeHint()" << xs << fontHeight() << scale_;
	if (orient_ == Qt::Horizontal)
		return QSize(xs * 3, xs + 4);
	return QSize(xs + 4, xs * 3);
}


void SHSSwitch::mousePressEvent(QMouseEvent * e) {
	mp_ = false;
	if (!ro_) {
		anim.stop();

		float w = width(), h = height(), s, hs;
		if ((w <= 1) || (h <= 1)) return;
		QRectF rct;
		if (orient_ == Qt::Horizontal) {
			s = qMin(w, h * 3.f) / 2.f;
			hs = s / 3.f;
			rct.setRect(-s, -hs, s+s, hs+hs);
		} else {
			s = qMin(h, w * 3.f) / 2.f;
			hs = s / 3.f;
			rct.setRect(-hs, -s, hs+hs, s+s);
		}
		rct.moveCenter(QPointF(w / 2.f, h / 2.f));
		if (!rct.contains(e->pos())) {
			animate();
			QWidget::mousePressEvent(e);
			return;
		}
		mp_ = true;
		pp = e->pos();
		if (orient_ == Qt::Horizontal) {
			if (style_ == SHSSwitch::Center)
				mdir_ = (e->pos().x() < width() / 2);
		} else {
			if (style_ == SHSSwitch::Center)
				mdir_ = (e->pos().y() < height() / 2);
		}
		QAbstractButton::mousePressEvent(e);
		return;
	}
	mo_ = false;
	QWidget::mousePressEvent(e);
}


void SHSSwitch::mouseReleaseEvent(QMouseEvent * e) {
	if (ro_ || !mp_) {
		QWidget::mouseReleaseEvent(e);
		return;
	}
	mp_ = false;
	if (mo_) {
		if (ap_ > 0.5f) setChecked(false);
		else setChecked(true);
	} else {
		animate();
		QAbstractButton::mouseReleaseEvent(e);
	}
	mo_ = false;
}


void SHSSwitch::mouseMoveEvent(QMouseEvent * e) {
	if (!ro_) {
		if ((width() <= 1) || (height() <= 1)) return;
		if (!mp_) {
			QWidget::mouseMoveEvent(e);
			return;
		}
		float s = 1.;
		if (orient_ == Qt::Horizontal) {
			s = qMin<float>(width(), height() * 3.f);
		} else {
			s = qMin<float>(height(), width() * 3.f);
		}
		QPoint cp = e->pos(), dp = inv_ ? cp - pp : pp - cp;
		if (!mo_) {
			if (dp.manhattanLength() > QApplication::startDragDistance()) {
				mo_ = true;
				pp = cp;
			}
		} else {
			if (orient_ == Qt::Horizontal) {
				if (style_ == SHSSwitch::OneSide)
					ap_ += dp.x() * 2.f / s;
				else {
					if (mdir_)
						ap_ += dp.x() * 4.f / s;
					else
						ap_ -= dp.x() * 4.f / s;
				}
			} else {
				if (style_ == SHSSwitch::OneSide)
					ap_ -= dp.y() * 2.f / s;
				else
					if (mdir_)
						ap_ += dp.y() * 4.f / s;
					else
						ap_ -= dp.y() * 4.f / s;
			}
			ap_ = qClamp<float>(ap_, 0.f, 1.f);
			pp = cp;
			update();
		}
		return;
	}
	QWidget::mouseMoveEvent(e);
}


void SHSSwitch::paintEvent(QPaintEvent * e) {
	if ((width() <= 1) || (height() <= 1)) return;
	QPainter p(this);
	p.setRenderHint(QPainter::Antialiasing);
	float w = width(), h = height(), s, hs, thick, thin, gap = inv_ ? 1.f - ap_ : ap_;
	p.translate(w / 2.f, h / 2.f);
	QRectF rct;
	const float tdelim = 40.f;
	if (orient_ == Qt::Horizontal) {
		s = qMin(w, h * 3.f) / 2.f;
		s -= s / tdelim;
		hs = s / 3.f;
		rct.setRect(-s, -hs, s+s, hs+hs);
	} else {
		s = qMin(h, w * 3.f) / 2.f;
		s -= s / tdelim;
		hs = s / 3.f;
		rct.setRect(-hs, -s, hs+hs, s+s);
	}
	thick = qMax(1.f, s / tdelim);
	thin = qMax(1.f, thick / 3.f);
	p.setPen(QPen(Qt::darkGray, thick));
	float alpha = 0.6f;
	float cv = alpha + (1.f - alpha) * ap_s_;
	p.setBrush(back_color * cv + fill_color * (1.f - cv));
	p.drawRect(rct);
	QColor col = fill_color;
	float ph = hs / 1.1f;
	cv = qSqrt(ap_);
	col = back_color.darker(120) * cv + fill_color * (1.f - cv);
	p.setBrush(col);
	p.setPen(QPen(col.darker(200), thin));
	if (style_ == SHSSwitch::OneSide) {
		QPolygonF pol[3];
		if (orient_ == Qt::Horizontal) {
			pol[0] << QPointF(-ph, -ph) << QPointF(-ph, ph) << QPointF(ph * 2.5, ph) << QPointF(ph, -ph);
			pol[1] << QPointF(-ph * 2.5, -ph) << QPointF(-ph, ph) << QPointF(ph, ph) << QPointF(ph, -ph);
			pol[2].resize(pol[0].size());
			for (int i = 0; i < pol[0].size(); ++i)
				pol[2][i] = pol[0][i] * gap + pol[1][i] * (1. - gap);
			p.translate((-s + hs) * (gap - 0.5) * 2., 0.);
		} else {
			pol[0] << QPointF(-ph, -ph * 2.5) << QPointF(-ph, ph) << QPointF(ph, ph) << QPointF(ph, -ph);
			pol[1] << QPointF(-ph, -ph) << QPointF(-ph, ph) << QPointF(ph, ph * 2.5) << QPointF(ph, -ph);
			pol[2].resize(pol[0].size());
			for (int i = 0; i < pol[0].size(); ++i)
				pol[2][i] = pol[0][i] * gap + pol[1][i] * (1. - gap);
			p.translate(0., (s - hs) * (gap - 0.5) * 2.);
		}
		p.drawPolygon(pol[2]);
	} else {
		QPolygonF pol[3];
		QPointF dp;
		if (orient_ == Qt::Horizontal) {
			pol[0] << QPointF(-ph, -ph) << QPointF(-ph, ph) << QPointF(ph * 0.8, ph) << QPointF(ph / 8., -ph);
			pol[1] << QPointF(-ph, -ph) << QPointF(-ph, ph) << QPointF(0., ph) << QPointF(0., -ph);
			pol[2].resize(pol[0].size());
			for (int i = 0; i < pol[0].size(); ++i)
				pol[2][i] = pol[0][i] * gap + pol[1][i] * (1. - gap);
			dp = QPointF((-s + hs) * gap, 0.);
		} else {
			pol[0] << QPointF(-ph, -ph * 0.8) << QPointF(-ph, ph) << QPointF(ph, ph) << QPointF(ph, -ph / 8.);
			pol[1] << QPointF(-ph, 0) << QPointF(-ph, ph) << QPointF(ph, ph) << QPointF(ph, 0.);
			pol[2].resize(pol[0].size());
			for (int i = 0; i < pol[0].size(); ++i)
				pol[2][i] = pol[0][i] * gap + pol[1][i] * (1. - gap);
			dp = QPointF(0., (s - hs) * gap);
		}
		p.save();
		p.translate(dp);
		p.drawPolygon(pol[2]);
		p.restore();
		p.rotate(180.);
		p.translate(dp);
		p.drawPolygon(pol[2]);
	}
}


void SHSSwitch::animate() {
	anim.stop();
	anim.setStartValue(g_anim());
	anim.setEndValue(isChecked() ? 0. : 1.);
	anim.start();
	anim_s.stop();
	anim_s.setStartValue(g_anim_s());
	anim_s.setEndValue(isChecked() ? 0. : 1.);
	anim_s.start();
}


void SHSSwitch::s_anim(float v) {
	ap_ = v;
	update();
}


void SHSSwitch::s_anim_s(float v) {
	ap_s_ = v;
	update();
}


void SHSSwitch::setReadOnly(bool y) {
	ro_ = y;
}


void SHSSwitch::setInvertedAppearance(bool y) {
	inv_ = y;
	update();
}


void SHSSwitch::setOrientation(Qt::Orientation o) {
	orient_ = o;
	update();
}


void SHSSwitch::setSwitchStyle(SwitchStyle s) {
	style_ = s;
	update();
}


void SHSSwitch::setBackColor(const QColor & c) {
	back_color = c;
	update();
}


void SHSSwitch::setFillColor(const QColor & c) {
	fill_color = c;
	update();
}


void SHSSwitch::setScale(double s) {
	scale_ = s;
	updateGeometry();
}
