/*
    QGL MouseController
    Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "mouse_controller.h"

#include "glmesh.h"
#include "qglview.h"

#include <QApplication>
#include <QKeyEvent>
#include <qad_types.h>

using namespace QGLEngineShaders;


MouseController::MouseController(QGLView * view_): view(view_) {
	app_scale   = 1;
	lastPos     = QPoint(-1, -1);
	cur_action  = RendererService::haNoAction;
	sel_button  = Qt::LeftButton;
	sel_mod     = Qt::ControlModifier;
	mouse_first = mouseSelect_ = mouseRotate_ = cameraOrbit_ = canSelect_ = true;
	grabMouse_ = mouse_sec = selecting_ = customMouseMove_ = false;
}


MouseController::~MouseController() {}


void MouseController::resize() {
	mouse_first = true;
	app_scale   = appScale();
}


void MouseController::mouseReleaseEvent(QMouseEvent * e) {
	if (cur_action != RendererService::haNoAction) {
		mouseMoveEvent(e);
		return;
	}
	bool add_ts = e->modifiers().testFlag(sel_mod);
	if (selecting_) {
		selecting_                 = false;
		canSelect_                 = true;
		view->renderer_.mouse_rect = QRect();
		view->scene()->selectObjects(hov_objects.toList(), add_ts);
		return;
	}
	if (canSelect_ && mouseSelect_) {
		if ((lastPos - downPos).manhattanLength() < QApplication::startDragDistance()) {
			if (e->button() == Qt::LeftButton) {
				// qDebug() << hov_objects << hov_aims;
				if (hov_objects.isEmpty() && hov_aims.isEmpty()) {
					view->scene()->clearSelection();
				} else {
					if (!hov_objects.isEmpty()) view->scene()->selectObject(hov_objects[0], add_ts);
					if (!hov_aims.isEmpty()) {
						view->scene()->selectObject(hov_aims[0], add_ts);
						hov_aims[0]->setAimSelected(true);
					}
				}
			}
			if (e->button() == Qt::RightButton) {
				if (view->renderer_.edit_mode && !view->scene()->selectedObjects().isEmpty())
					view->popupMenu(
#if QT_VERSION_MAJOR <= 5
						((QMouseEvent *)e)->globalPos()
#else
						((QMouseEvent *)e)->globalPosition().toPoint()
#endif
					);
			}
		}
	}
	canSelect_ = e->buttons() == 0;
	emit view->glMouseReleaseEvent(e);
}


void MouseController::mousePressEvent(QMouseEvent * e) {
	QPoint cpos = e->pos() * view->devicePixelRatio();
	downPos     = cpos;
	if (cur_action != RendererService::haNoAction && e->buttons() == Qt::LeftButton) {
		return;
	}
	if (selecting_) {
		selecting_                 = false;
		view->renderer_.mouse_rect = QRect();
		return;
	}
	if (!QRect(QPoint(), view->pixelSize()).contains(cpos)) return;
	lastPos = cpos;
	downPos = cpos;
	emit view->glMousePressEvent(e);
}


void MouseController::mouseMoveEvent(QMouseEvent * e) {
	QPoint cpos = e->pos() * view->devicePixelRatio();
	if (cur_action != RendererService::haNoAction && (e->buttons() == Qt::LeftButton)) {
		RendererService & rs(view->renderer_.rend_service);
		ObjectBaseList objects = view->scene()->selectedObjects(true);
		QVector<int> axis;
		switch (cur_action) {
		case RendererService::haMove:
			if (cur_handle.testFlag(RendererService::hmMoveX)) axis << 0;
			if (cur_handle.testFlag(RendererService::hmMoveY)) axis << 1;
			if (cur_handle.testFlag(RendererService::hmMoveZ)) axis << 2;
			break;
		case RendererService::haRotate:
			if (cur_handle.testFlag(RendererService::hmRotateX)) axis << 0;
			if (cur_handle.testFlag(RendererService::hmRotateY)) axis << 1;
			if (cur_handle.testFlag(RendererService::hmRotateZ)) axis << 2;
			break;
		case RendererService::haScale:
			if (cur_handle.testFlag(RendererService::hmScaleX)) axis << 0;
			if (cur_handle.testFlag(RendererService::hmScaleY)) axis << 1;
			if (cur_handle.testFlag(RendererService::hmScaleZ)) axis << 2;
			break;
		default: break;
		}
		QVector<QVector3D> scales;
		foreach(int a, axis) {
			QVector3D axe_vector;
			axe_vector[a]           = 1.;
			QMatrix4x4 axis_mat     = view->camera()->fullViewMatrix() * rs.axis_mat;
			QVector3D center_screen = axis_mat.map(rs.selection_center);
			QVector3D axe_screen    = (axis_mat.map(rs.selection_center + axe_vector) - center_screen).normalized();
			QVector3D mouse_vector(cpos - lastPos);
			mouse_vector[1] *= -1.;
			if (cur_action == RendererService::haMove) {
				double len_scl = 1. / QVector3D(axe_screen.x(), axe_screen.y(), 1.E-6).length();
				mouse_vector /= QVector3D(view->pixelWidth(), view->pixelHeight(), 1);
				mouse_vector *= -center_screen.z() * len_scl;
				axe_vector *= QVector3D::dotProduct(axe_screen, mouse_vector);
				QMatrix4x4 pmat;
				foreach(ObjectBase * o, objects) {
					pmat.setToIdentity();
					if (o->parent()) pmat = o->parent()->worldTransform().inverted();
					QVector3D dv = pmat.mapVector(axe_vector);
					if (o->isAimSelected()) {
						AimedObject * ao = (AimedObject *)o;
						ao->setAim(ao->aim() + dv);
					} else
						o->move(dv);
				}
			}
			if (cur_action == RendererService::haRotate) {
				axe_screen.setZ(0.);
				axe_screen.normalize();
				QVector3D norm = QVector3D(axe_screen.y(), -axe_screen.x(), 0.);
				axe_vector *= QVector3D::dotProduct(mouse_vector, norm) / 2. / app_scale;
				foreach(ObjectBase * o, objects)
					o->setRotation(o->rotation() + axe_vector);
			}
			if (cur_action == RendererService::haScale) {
				mouse_vector /= QVector3D(view->pixelWidth(), view->pixelHeight(), 1);
				mouse_vector *= 3. / app_scale;
				axe_vector *= QVector3D::dotProduct(axe_screen, mouse_vector);
				scales << axe_vector;
			}
		}
		if (cur_action == RendererService::haScale) {
			double sc = 0., max = 0.;
			foreach(const QVector3D & s, scales) {
				double v = QVector3D::dotProduct(s, QVector3D(1, 1, 1));
				sc += v;
				max = qMax(max, qAbs(v));
			}
			sc = max * (sc > 0. ? 1. : -1);
			QVector3D axe_vector;
			foreach(int a, axis)
				axe_vector[a] = 1.;
			foreach(ObjectBase * o, objects)
				o->scale(QVector3D(1, 1, 1) + (axe_vector * sc));
			QCursor::setPos(view->mapToGlobal(downPos / view->devicePixelRatio()));
		} else {
			lastPos = cpos;
		}
		emit view->objectsPositionChanged();
		return;
	}
	if (selecting_) {
		view->renderer_.mouse_rect = QRect(downPos, cpos).normalized();
		return;
	}
	if (e->buttons().testFlag(Qt::LeftButton)) {
		if ((cpos - downPos).manhattanLength() >= QApplication::startDragDistance()) {
			selecting_ = true;
			canSelect_ = false;
		}
		return;
	}
	QRect g_rect(QPoint(), view->pixelSize());
	if (mouseRotate_) {
		float dx = cpos.x() - lastPos.x();
		float dy = cpos.y() - lastPos.y();
		if (e->buttons().testFlag(QT_MID_BUTTON)) {
			if (cameraOrbit_) {
				view->camera()->orbitZ(dx / 4.f);
				view->camera()->orbitXY(dy / 4.f);
			} else {
				view->camera()->rotateZ(-dx / 4.f);
				view->camera()->rotateX(-dy / 4.f);
			}
			emit view->cameraPosChanged(view->camera()->pos());
		} else if (e->buttons().testFlag(Qt::RightButton)) {
			float ad = view->camera()->distance();
			view->camera()->moveLeft(dx / 1000.f * ad);
			view->camera()->moveUp(dy / 1000.f * ad);
			emit view->cameraPosChanged(view->camera()->pos());
		}
	}
	if (customMouseMove_) emit view->customMouseMoveEvent(cpos, lastPos, e->buttons());
	lastPos = cpos;
	if (e->buttons() == 0) {
		cur_handle         = QFlags<RendererService::HandleMesh>();
		cur_action         = RendererService::haNoAction;
		Qt::CursorShape cs = Qt::CrossCursor;
		if (view->renderer_.edit_mode) {
			uint hid   = view->renderer_.rend_selection.id_hover;
			cur_handle = (RendererService::HandleMesh)hid;
			if (hid >= RendererService::hmMoveX && hid <= RendererService::hmMaxMove) {
				cur_action = RendererService::haMove;
				cs         = Qt::SizeAllCursor;
			}
			if (hid >= RendererService::hmRotateX && hid <= RendererService::hmMaxRotate) {
				cur_action = RendererService::haRotate;
				cs         = Qt::PointingHandCursor;
			}
			if (hid >= RendererService::hmScaleX && hid <= RendererService::hmMaxScale) {
				cur_action = RendererService::haScale;
				cs         = Qt::SplitHCursor;
			}
		}
		if (cur_action == RendererService::haNoAction) cur_handle = QFlags<RendererService::HandleMesh>();
		view->setCursor(cs);
		view->renderer_.rend_service.current_handle = cur_handle;
	}
	if (grabMouse_) {
		QCursor::setPos(view->mapToGlobal(QRect(QPoint(), view->size()).center()));
		if (mouse_sec) {
			mouse_sec = false;
			return;
		}
		if (mouse_first) {
			mouse_first = false;
			mouse_sec   = true;
			return;
		}
		lastPos = g_rect.center();
		int dx  = cpos.x() - lastPos.x();
		int dy  = cpos.y() - lastPos.y();
		emit view->glMouseMoveEvent(new QMouseEvent(QEvent::MouseMove, QPoint(dx, dy), e->button(), e->buttons(), e->modifiers()));
		return;
	}
	emit view->glMouseMoveEvent(e);
}


void MouseController::wheelEvent(QWheelEvent * e) {
	if (mouseRotate_) {
		double ang =
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
			e->delta();
#else
			e->angleDelta().y();
#endif
		if (ang > 0) view->camera()->flyCloser(0.1f);
		if (ang < 0) view->camera()->flyFarer(0.1f);
		emit view->cameraPosChanged(view->camera()->pos());
	}
	emit view->glWheelEvent(e);
}


void MouseController::leaveEvent(QEvent *) {
	lastPos = QPoint(-1, -1);
	// qDebug() << lastPos;
}


void MouseController::mouseDoubleClickEvent(QMouseEvent * e) {
	if (e->buttons().testFlag(QT_MID_BUTTON)) emit view->doubleClick();
}
