#include "mesh_viewer.h"

#include <tgmath.h>
#include <QOpenGLShader>
#include <QOpenGLDebugLogger>
#include <QtGlobal>


MeshViewer::MeshViewer(QWidget *parent) : QOpenGLWidget(parent) {
	setMouseTracking(true);
	setFocus();
}


void MeshViewer::updateViewMatrix() {
	view = trans * rot;
}


QSize MeshViewer::sizeHint() const {
	return QSize(640, 480);
}


void MeshViewer::initializeGL() {
	QOpenGLFunctions *glf = context()->functions();
#ifdef QT_DEBUG
	const QSurfaceFormat &format = context()->format();
	qDebug("MeshViewer: OpenGL %s%d.%d %s",
		   format.renderableType() == QSurfaceFormat::OpenGLES ? "ES " : "",
		   format.majorVersion(), format.minorVersion(),
		   format.profile() == QSurfaceFormat::CoreProfile ? "core profile"
		   : (format.profile() == QSurfaceFormat::CompatibilityProfile ? "compatibility profile"
			  : ""));
	QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(this);
	if (!logger->initialize()) {
		qDebug("OpenGL debug output unavailable");
	}
	connect(logger, &QOpenGLDebugLogger::messageLogged,
			[](const QOpenGLDebugMessage &message) {
				qDebug() << "OpenGL:" << message.message();
			});
    logger->startLogging();
#endif
	if (!program.addShaderFromSourceFile(QOpenGLShader::Vertex,
										 ":/shader.vert")) {
		qFatal("%s", program.log().toUtf8().constData());
	}
	if (!program.addShaderFromSourceFile(QOpenGLShader::Fragment,
										 ":/shader.frag")) {
		qFatal("%s", program.log().toUtf8().constData());
	}
	if (!program.link()) {
		qFatal("%s", program.log().toUtf8().constData());
	}
	program.bind();

	program.setUniformValue("alpha", (GLfloat) 1);
	program.setUniformValue("wf_col", WIREFRAME_COLOR);

	glf->glClearColor(1, 1, 1, 0);

	glf->glEnable(GL_DEPTH_TEST);
	glf->glEnable(GL_MULTISAMPLE);

	qDebug("MeshViewer: initialization complete");
	is_initialized = true;
	emit initialized();
}


void MeshViewer::resizeGL(int w, int h) {
	proj.setToIdentity();
	proj.perspective(FOV, (float) w/h, .01, 100);
	program.setUniformValue("proj", proj);
	update();
}


void MeshViewer::paintGL() {
	// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	QMatrix4x4 trans;
	program.bind();
	program.setUniformValue("view", view);
	for (MeshView &m : meshes) {
		m.paint(program);
	}
}


void MeshViewer::addMesh(const MyMesh &mesh) {
	Q_ASSERT(isValid());
	makeCurrent();
	meshes.emplace_back(mesh, program);
	doneCurrent();
	update();
}


void MeshViewer::removeMesh(const MyMesh &mesh) {
	makeCurrent();
	meshes.remove_if([&](const MeshView &mv) {
		return &mv.mesh == &mesh;
	});
	doneCurrent();
	update();
}


void MeshViewer::updateForReal() {
	setEnabled(true);
	setVisible(true);
	update();
	repaint();
	makeCurrent();
	paintGL();
	doneCurrent();
}


void MeshViewer::mousePressEvent(QMouseEvent *e) {
	if (e->button() == Qt::MiddleButton) {
		mouse_pos = e->pos();
	} else if (e->button() == Qt::LeftButton) {
		float x = e->x();
		float y = height() - e->y();
		float depth;
		makeCurrent();
		QOpenGLFunctions *glf = context()->functions();
		glf->glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth);
		doneCurrent();
		QVector3D position {x, y, depth};
		position = position.unproject(view, proj, rect());
		emit clicked(QVector3D(position));
	} else {
		e->ignore();
	}
}


void MeshViewer::mouseReleaseEvent(QMouseEvent *e) {
	(void) e;
	rot_start = rot;
}


void MeshViewer::mouseMoveEvent(QMouseEvent *e) {
	if (e->buttons() & Qt::MiddleButton) {
		QPoint delta = e->pos() - mouse_pos;
		rot = rot_start;
		rot.rotate(delta.x() / 5., 0, 1, 0);
		rot.rotate(delta.y() / 5., QVector3D(1, 0, 0) * rot);
		updateViewMatrix();
		update();
	}
}


void MeshViewer::wheelEvent(QWheelEvent *e) {
	trans(2, 3) -= e->angleDelta().y() / 1000. * trans(2, 3);
	trans.optimize();
	updateViewMatrix();
	update();
}
