#include "stackedwidget.h"
#include <QApplication>
#include <QDebug>
#include <QMouseEvent>
#include <QPainter>

template <typename T>
inline T qClamp(T v, T min, T max) {return qMin<T>(max, qMax<T>(min, v));}


SHSStackedWidget::SHSStackedWidget(QWidget * parent): QStackedWidget(parent) {
	setMouseTracking(true);
	aw = new QWidget(this);
	aw->installEventFilter(this);
	aw->hide();
	animation.setEasingCurve(QEasingCurve::OutQuad);
	connect(&animation, &QVariantAnimation::valueChanged, this, [this](QVariant v){cur_transition = v.toDouble(); aw->update();});
	connect(&animation, &QVariantAnimation::finished, this, [this](){endMove();});
}


SHSStackedWidget::~SHSStackedWidget() {
	delete aw;
}


void SHSStackedWidget::setSwipeDirection(QBoxLayout::Direction dir) {
	direction = dir;
	is_horiz = (dir == QBoxLayout::LeftToRight || dir == QBoxLayout::RightToLeft);
}


bool SHSStackedWidget::eventFilter(QObject * o, QEvent * e) {
	if (e->type() == QEvent::Paint) {
		QPainter p(aw);
		p.fillRect(aw->rect(), palette().brush(QPalette::Window));
		if (is_horiz) {
			int dx = cur_transition * width();
			if (vis_next_index > currentIndex()) dx = -dx;
			if (direction == QBoxLayout::RightToLeft) dx = -dx;
			p.drawPixmap(dx, 0, im_from);
			p.drawPixmap(dx + (dx > 0 ? -width() : width()), 0, im_to);
		} else {
			int dy = cur_transition * height();
			if (vis_next_index > currentIndex()) dy = -dy;
			if (direction == QBoxLayout::BottomToTop) dy = -dy;
			p.drawPixmap(0, dy, im_from);
			p.drawPixmap(0, dy + (dy > 0 ? -height() : height()), im_to);
		}
		//qDebug() << "paint" << cur_transition << dx;
	}
	return QStackedWidget::eventFilter(o, e);
}


void SHSStackedWidget::resizeEvent(QResizeEvent * e) {
	aw->resize(size());
}


void SHSStackedWidget::mousePressEvent(QMouseEvent * e) {
	if (count() <= 1) return;
	press_pos = e->pos();
}


void SHSStackedWidget::mouseReleaseEvent(QMouseEvent * e) {
	if (count() <= 1) return;
	if (cur_transition < 0.25) {
		next_index = currentIndex();
		//beginMove(next_index);
		animate(0.);
	} else
		animate(1.);
}


void SHSStackedWidget::mouseMoveEvent(QMouseEvent * e) {
	if (count() <= 1) return;
	if (animation.state() != QAbstractAnimation::Stopped) return;
	cur_dp = e->pos() - press_pos;
	int cur_offset = is_horiz ? cur_dp.x() : cur_dp.y();
	if (is_moving) {
		cur_transition = (double)qAbs(cur_offset) / (is_horiz ? width() : height());
		int nind = nextIndex(cur_dp);
		if (nind != next_index) {
			vis_next_index = next_index = nind;
			if (currentIndex() == next_index) {
				endMove();
				return;
			}
			beginMove(next_index);
		}
		aw->update();
		return;
	}
	if (qAbs(cur_offset) > (QApplication::startDragDistance() * 4)) {
		vis_next_index = next_index = nextIndex(cur_dp);
		if (currentIndex() == next_index) return;
		beginMove(next_index);
	}
}


void SHSStackedWidget::beginMove(int index) {
	is_moving = true;
#if QT_VERSION_MAJOR >= 5
	const qreal dpr = devicePixelRatio();
	im_from = im_to = QPixmap(size() * dpr);
	im_from.setDevicePixelRatio(dpr);
	im_to  .setDevicePixelRatio(dpr);
#else
	im_from = im_to = QPixmap(size());
#endif
	widget(currentIndex())->render(&im_from);
	widget(index)->ensurePolished();
	widget(index)->resize(size());
	widget(index)->render(&im_to);
	aw->show();
	aw->raise();
	aw->update();
}


void SHSStackedWidget::endMove() {
	setCurrentIndex(next_index);
	is_moving = false;
	aw->hide();
}


void SHSStackedWidget::animate(double dest) {
	double ct = cur_transition;
	animation.stop();
	animation.setDuration(qClamp(qAbs(dest - ct), 0.1, 1.) * anim_duration);
	animation.setStartValue(ct);
	animation.setEndValue(dest);
	animation.start();
}


int SHSStackedWidget::nextIndex(QPoint dp) {
	int ret = currentIndex();
	switch (direction) {
	case QBoxLayout::LeftToRight: ret -= dp.x() > 0 ? 1 : -1; break;
	case QBoxLayout::RightToLeft: ret += dp.x() > 0 ? 1 : -1; break;
	case QBoxLayout::TopToBottom: ret -= dp.y() > 0 ? 1 : -1; break;
	case QBoxLayout::BottomToTop: ret += dp.y() > 0 ? 1 : -1; break;
	}
	if (is_move_to_first) {
		if (ret < currentIndex()) ret = 0;
		else ret = currentIndex();
	} else {
		if (ret < 0) ret = 0;
		if (ret >= count()) ret = count() - 1;
	}
	return ret;
}


void SHSStackedWidget::gotoPage(int index) {
	if (count() <= 1) return;
	vis_next_index = next_index = qClamp<int>(index, 0, count() - 1);
	cur_transition = 0.;
	beginMove(next_index);
	animate(1.);
}
