#include "MapView3D.h"

#include <QMouseEvent>
#include <QGLShaderProgram>

#include "elements/Walls.h"
#include "elements/Ground.h"
#include "elements/Handrails.h"
#include "elements/Stairs.h"
#include "elements/Doors.h"
#include "elements/Path.h"
#include "elements/ColorPoints.h"
#include "elements/Object.h"

#include "../Settings.h"

#include <Indoor/data/Timestamp.h>
#include <QDebug>

/**
 * before adding elements to the MapView via setMap(),
 * the MapViews openGL context must be initialized
 * that means: the MapView must have been added to a window,
 * which is already visible!
 */

MapView3D::MapView3D(QWidget* parent) : QOpenGLWidget(parent) {



};

void MapView3D::clear() {

	for (Renderable* r : elements) {delete r;}
	elements.clear();

}

void MapView3D::setMap(Floorplan::IndoorMap* map) {

	clear();

	if (!isGLInitialized) {throw Exception("openGL is not yet initialized. add mapView to a visible window!");}

	// first to be rendered -> the colored debug points
	this->colorPoints = new ColorPoints();
	elements.push_back(this->colorPoints);

	// 2nd is the path to the destination (if any)
	this->pathToDest = new Path();
	elements.push_back(this->pathToDest);

	this->pathWalked = new Path();
	this->pathWalked->setWidth(0.3);
	this->pathWalked->setColor(Qt::yellow);
	elements.push_back(this->pathWalked);



	// 3rd is the dude, walking along the path
	//leDude = new Object("/mnt/firma/tmp/3D/minion/minion.obj", "/mnt/firma/tmp/3D/minion/minion.png", "", 0.35);
	//leDude = new Object("/mnt/firma/tmp/3D/gnome/gnome.obj", "/mnt/firma/tmp/3D/gnome/gnome_diffuse.jpg", "/mnt/firma/tmp/3D/gnome/gnome_normal.jpg", 0.033);
	//leDude = new Object("/mnt/firma/tmp/3D/squirrel/squirrel.obj", "/mnt/firma/tmp/3D/squirrel/squirrel.jpg", "/mnt/firma/tmp/3D/squirrel/squirrel_normal.jpg", 0.033);
	//elements.push_back(leDude);

	// hereafter follows the floorplan (floors, doors, walls, stairs, ..)
	for (Floorplan::Floor* floor : map->floors) {
		elements.push_back(new Ground(floor));
		elements.push_back(new Walls(floor));
		elements.push_back(new Handrails(floor));
		elements.push_back(new Stairs(floor));
		elements.push_back(new Doors(floor));
	}





	// initialize the OpenGL context of all contained elements
	for (Renderable* r : elements) {
		r->initGL();
	}

	// i want the focus! needed for key-events
	setFocusPolicy(Qt::StrongFocus);

}

void MapView3D::setPathToDestination(const std::vector<Point3>& path) {
	this->pathToDest->set(path);
}

void MapView3D::setPathWalked(const std::vector<Point3>& path) {
	this->pathWalked->set(path);
}


void MapView3D::timerEvent(QTimerEvent *) {
	update();
}

void MapView3D::initializeGL() {

	initializeOpenGLFunctions();

	// basic config
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);

	// OpenGL is now initialized
	isGLInitialized = true;

}

void MapView3D::setVisible(bool visible) {

	// inform parent
	QOpenGLWidget::setVisible(visible);

	if (!visible) {
		// stop background update timer
		timer.stop();
	} else {
		// start background update timer
		timer.start(Settings::MapView3D::msPerFrame.ms(), this);
	}

}

void MapView3D::paintGL() {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	draw();
}

void MapView3D::resizeGL(int w, int h) {

	// Calculate aspect ratio
	qreal aspect = qreal(w) / qreal(h ? h : 1);

	// viewing frustrum [0:50] meter
	const qreal zNear = 0.02, zFar = 50, fov = 50.0;

	// Reset projection
	matProject.setToIdentity();
	matProject.scale(-1, 1, 1);
	glCullFace(GL_FRONT);
	//matProject.scale(0.05, 0.05, 0.05);
	matProject.perspective(fov, aspect, zNear, zFar);
	//matProject.scale(-0.01, 0.01, 0.01);

}

void MapView3D::rebuildLookat() {
//	QVector3D qDir(lookAt.dir.x, lookAt.dir.z, lookAt.dir.y);
//	QVector3D at = QVector3D(lookAt.pos.x, lookAt.pos.z, lookAt.pos.y);
//	QVector3D eye = at + qDir * 0.1;
//	QVector3D up = QVector3D(0,1,0);
//	matView.setToIdentity();
//	//matView.scale(0.01, 0.01, 0.01);
//	matView.lookAt(eye, at, up);
//	//matView.scale(0.99, 1, 1);
//	//matView.translate(0.7, 0, 0);
//	lightPos = eye + QVector3D(0.0, 4.0, 0.0);
//	eyePos = eye;

	const QVector3D qDir = lookAt.getDir();

	//QVector3D qDir(dir.x, dir.z, dir.y);
	const QVector3D eye = lookAt.eye_m;//(lookAt.eye_m.x, lookAt.eye_m.z, lookAt.eye_m.y);
	const QVector3D at = eye + qDir * 0.5;
	const QVector3D up = QVector3D(0,1,0);
	matView.setToIdentity();
	matView.lookAt(eye, at, up);
	lightPos = eye + QVector3D(0.0, 0.5, 0.0) + qDir * 1.2;
	eyePos = eye;



}

void MapView3D::setCurrentEstimation(const Point3 pos, const Point3 dir) {
	const float angle = std::atan2(dir.y, dir.x) * 180 / M_PI;
	if (leDude) {
		leDude->setPosition(pos.x, pos.y, pos.z);
		leDude->setRotation(0, 0, -angle + 90);
	}
}

void MapView3D::setLookAt(const Point3 pos_m, const Point3 dir) {
	lookAt.eye_m = QVector3D(pos_m.x, pos_m.z, pos_m.y) + QVector3D(dir.x, dir.z, dir.y) * 0.1;
	lookAt.dir = QVector3D(dir.x, dir.z, dir.y);
	rebuildLookat();
}

void MapView3D::setLookDir(const Point3 dir) {
	lookAt.dir = QVector3D(dir.x, dir.z, dir.y);
	rebuildLookat();
}

void MapView3D::setLookEye(const Point3 eye_m) {
	lookAt.eye_m = QVector3D(eye_m.x, eye_m.z, eye_m.y);
	rebuildLookat();
}



void MapView3D::mousePressEvent(QMouseEvent* evt) {
	mouseState.down = true;
	mouseState.x = evt->x();
	mouseState.y = evt->y();
}

void MapView3D::mouseMoveEvent(QMouseEvent* evt) {

	const float dx = evt->x() - mouseState.x;
	const float dy = evt->y() - mouseState.y;
	mouseState.x = evt->x();
	mouseState.y = evt->y();

	// PI*0.3 head movement left/right and up/down
	//const float yFac = (this->height() / 2) / (M_PI * 0.3);
	//const float xFac = (this->width() / 2) / (M_PI * 0.3);


	const float angleX = dx/3.0f;
	const float angleY = dy/3.0f;
	lookAt.ax += angleX;
	lookAt.ay += angleY;
	lookAt.dirRot.setToIdentity();
	lookAt.dirRot.rotate(lookAt.ax, QVector3D(0,1,0));
	lookAt.dirRot.rotate(lookAt.ay, QVector3D(0,0,1));

	//lookAt.dirRot.rotate(angleX, QVector3D(0,1,0));
	//lookAt.dirRot.rotate(angleY, QVector3D(0,0,1));

	rebuildLookat();

}

void MapView3D::mouseReleaseEvent(QMouseEvent* evt) {
	(void) evt;
	mouseState.down = false;
}


void MapView3D::keyPressEvent(QKeyEvent* evt) {

	const QVector3D dir = lookAt.getDir() * 0.5;
	const QVector3D side(-dir.z(), 0, dir.x());

	if (evt->key() == Qt::Key_W) {lookAt.eye_m += dir; rebuildLookat();}
	if (evt->key() == Qt::Key_S) {lookAt.eye_m -= dir; rebuildLookat();}

	if (evt->key() == Qt::Key_A) {lookAt.eye_m += side; rebuildLookat();}
	if (evt->key() == Qt::Key_D) {lookAt.eye_m -= side; rebuildLookat();}

}

void MapView3D::toggleRenderMode() {

	renderMode = (RenderMode) (((int)renderMode + 1) % 2);		// normally %3 but lines crash the smartphone

	for (Renderable* r : elements) {
		switch (renderMode) {
			case RenderMode::OUTLINE:
				r->setOutlineOnly(true);
				r->setTransparent(false);
				break;
			case RenderMode::TRANSPARENT:
				r->setOutlineOnly(false);
				r->setTransparent(true);
				break;
			case RenderMode::NORMAL:
				r->setOutlineOnly(false);
				r->setTransparent(false);
				break;
		}
	}

}

void MapView3D::draw() {

	//const Timestamp ts1 = Timestamp::fromUnixTime();

	// clear everything
	glClearColor(0,0,0,1);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD);
	glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);

	for (Renderable* r : elements) {

		QOpenGLShaderProgram& program = r->getProgram();
		program.bind();

		// set the matrices
		program.setUniformValue("m_matrix", r->modelMatrix.mat);
		program.setUniformValue("mv_matrix", matView * r->modelMatrix.mat);
		program.setUniformValue("mvp_matrix", matProject * matView * r->modelMatrix.mat);
		program.setUniformValue("lightWorldPos", lightPos);
		program.setUniformValue("eyeWorldPos", eyePos);

		r->render(renderParams);

	}

	//const Timestamp ts2 = Timestamp::fromUnixTime();
	//qDebug("%d ms", (ts2-ts1).ms());


}
