/**********************************************************************
 * LeechCraft - modular cross-platform feature rich internet client.
 * Copyright (C) 2006-2014  Georg Rudoy
 *
 * Boost Software License - Version 1.0 - August 17th, 2003
 *
 * Permission is hereby granted, free of charge, to any person or organization
 * obtaining a copy of the software and accompanying documentation covered by
 * this license (the "Software") to use, reproduce, display, distribute,
 * execute, and transmit the Software, and to prepare derivative works of the
 * Software, and to permit third-parties to whom the Software is furnished to
 * do so, all subject to the following:
 *
 * The copyright notices in the Software and this entire statement, including
 * the above license grant, this restriction and the following disclaimer,
 * must be included in all copies of the Software, in whole or in part, and
 * all derivative works of the Software, unless such copies or derivative
 * works are solely in the form of machine-executable object code generated by
 * a source language processor.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
 * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **********************************************************************/

#include "smoothscroll.h"

#include <QObject>
#include <QBasicTimer>
#include <QWebFrame>
#include <QMouseEvent>
#include <QApplication>

namespace LeechCraft
{
namespace Poshuku
{
	class SmoothScrollTicker : public QObject
	{
		SmoothScroll *Scroll_;
		QBasicTimer Timer_;
	public:
		SmoothScrollTicker (SmoothScroll *scr)
		: Scroll_ (scr)
		{
		}

		void Start (int interval)
		{
			if (!Timer_.isActive ())
				Timer_.start (interval, this);
		}

		void Stop ()
		{
			Timer_.stop ();
		}
	private:
		void timerEvent (QTimerEvent*)
		{
			Scroll_->Tick ();
		}
	};

	SmoothScroll::SmoothScroll ()
	: State_ (SSteady)
	, Threshold_ (10)
	, Ticker_ (new SmoothScrollTicker (this))
	, Timestamp_ (QTime::currentTime ())
	, Target_ (0)
	{
	}

	int SmoothScroll::GetThreshold () const
	{
		return Threshold_;
	}

	void SmoothScroll::SetThreshold (int thr)
	{
		if (thr >= 0)
			Threshold_ = thr;
	}

	void SmoothScroll::SetTarget (QWebFrame *t)
	{
		Target_ = t;
	}

	namespace
	{
		QPoint Deaccelerate (const QPoint& speed, int a = 1, int max = 64)
		{
			int x = qBound (-max, speed.x (), max);
			int y = qBound (-max, speed.y (), max);
			x = !x ? x : (x > 0) ? qMax (0, x - a) : qMin (0, x + a);
			y = !y ? y : (y > 0) ? qMax (0, y - a) : qMin (0, y + a);
			return QPoint (x, y);
		}
	};

	void SmoothScroll::HandleMousePress (QMouseEvent *event)
	{
		event->ignore ();

		if (event->button () != Qt::LeftButton)
			return;

		if (IgnoreList_.removeAll (event))
			return;

		switch (State_)
		{
			case SSteady:
				event->accept ();
				State_ = SPressed;
				PressPos_ = event->pos ();
				break;
			case SAutoScroll:
				event->accept ();
				State_ = SStop;
				Speed_ = QPoint (0, 0);
				PressPos_ = event->pos ();
				Offset_ = GetScrollOffset ();
				Ticker_->Stop ();
				break;
			default:
				break;
		}
	}

	void SmoothScroll::HandleMouseRelease (QMouseEvent *event)
	{
		event->ignore ();

		if (event->button () != Qt::LeftButton)
			return;

		if (IgnoreList_.removeAll (event))
			return;

		QPoint delta;
		switch (State_)
		{
			case SPressed:
				event->accept ();
				State_ = SSteady;
				if (Target_)
				{
					QMouseEvent *e1 = new QMouseEvent (QEvent::MouseButtonPress,
							PressPos_, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
					QMouseEvent *e2 = new QMouseEvent (*event);
					IgnoreList_ << e1 << e2;
					QApplication::postEvent (Target_, e1);
					QApplication::postEvent (Target_, e2);
				}
				break;
			case SManualScroll:
				event->accept ();
				delta = event->pos () - PressPos_;
				if (Timestamp_.elapsed () > 100)
				{
					Timestamp_ = QTime::currentTime ();
					Speed_ = delta - Delta_;
					Delta_ = delta;
				}
				Offset_ = GetScrollOffset ();
				PressPos_ = event->pos ();
				if (Speed_ == QPoint (0, 0))
					State_ = SSteady;
				else
				{
					Speed_ /= 4;
					State_ = SAutoScroll;
					Ticker_->Start (10);
				}
				break;
			case SStop:
				event->accept ();
				State_ = SSteady;
				Offset_ = GetScrollOffset ();
				break;
			default:
				break;
		}
	}

	void SmoothScroll::HandleMouseMove (QMouseEvent *event)
	{
		event->ignore ();
		if (!(event->buttons () & Qt::LeftButton))
			return;

		if (IgnoreList_.removeAll (event))
			return;

		QPoint delta;
		switch (State_)
		{
			case SPressed:
			case SStop:
				delta = event->pos () - PressPos_;
				if (abs (delta.x ()) > Threshold_ ||
						abs (delta.y ()) > Threshold_)
				{
					Timestamp_ = QTime::currentTime ();
					State_ = SManualScroll;
					Delta_ = QPoint (0, 0);
					PressPos_ = event->pos ();
					event->accept ();
				}
				break;
			case SManualScroll:
				event->accept ();
				delta = event->pos () - PressPos_;
				SetScrollOffset (Offset_ - delta);
				if (Timestamp_.elapsed () > 100)
				{
					Timestamp_ = QTime::currentTime ();
					Speed_ = delta - Delta_;
					Delta_ = delta;
				}
			default:
				break;
		}
	}

	QPoint SmoothScroll::GetScrollOffset () const
	{
		return Target_->scrollPosition ();
	}

	void SmoothScroll::SetScrollOffset (const QPoint& p)
	{
		Target_->setScrollPosition (p);
	}

	void SmoothScroll::Tick ()
	{
		if (State_ == SAutoScroll)
		{
			Speed_ = Deaccelerate (Speed_);
			SetScrollOffset (Offset_ - Speed_);
			Offset_ = GetScrollOffset ();
			if (Speed_ == QPoint (0, 0))
			{
				State_ == SSteady;
				Ticker_->Stop ();
			}
		}
		else
			Ticker_->Stop ();
	}
}
}
