/*
    QGL RendererService
    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/>.
*/

#define GL_GLEXT_PROTOTYPES
#include "renderer_service.h"

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

#include <QOpenGLExtraFunctions>
#include <qad_types.h>

using namespace QGLEngineShaders;


RendererService::RendererService(Renderer * r_): r(r_) {
	line_width     = 1;
	current_action = haNoAction;
	current_handle = QFlags<HandleMesh>();
	mat_xyz.resize(3);
	mat_ms2.resize(3);
	color_xyz.resize(3);
	color_ms2.resize(3);
	const QVector3D _rot[3]  = {QVector3D(0, 1, 0), QVector3D(-1, 0, 0), QVector3D(0, 0, 1)};
	const QVector3D _rot2[3] = {QVector3D(0, 0, 0), QVector3D(1, 0, 0), QVector3D(0, -1, 0)};
	for (int i = 0; i < 3; ++i) {
		QMatrix4x4 m;
		m.rotate(90., _rot[i]);
		mat_xyz[i] = m;
		m.setToIdentity();
		if (!_rot2[i].isNull()) m.rotate(90., _rot2[i]);
		mat_ms2[i]   = m;
		color_xyz[i] = color_ms2[i] = QVector4D(0, 0, 0, 0.8);
		color_xyz[i][i]             = 1.;
	}
	color_ms2[0] = (color_xyz[0] + color_xyz[1]) / 2.;
	color_ms2[1] = (color_xyz[0] + color_xyz[2]) / 2.;
	color_ms2[2] = (color_xyz[1] + color_xyz[2]) / 2.;
	axis_camera  = new Camera();
	axis_camera->setAim(QVector3D());
	axis_camera->setFOV(45.);
	axis_mesh     = Primitive::arrow(12);
	size_vp_scale = size_full_scale = 1.;

	box_mesh                        = Primitive::cube(0.8, 0.8, 0.8);
	box_mesh_f                      = Primitive::cubeFrame(0.8, 0.8, 0.8);
	omni_mesh                       = Primitive::ellipsoid(2, 1, 0.5);
	omni_mesh_f                     = Primitive::ellipsoidFrame(2, 1, 0.5);
	omni_mesh->scalePoints(1.5);
	omni_mesh_f->scalePoints(1.5);
	cone_mesh   = Primitive::cone(8, 0.5, 1.);
	cone_mesh_f = Primitive::coneFrame(8, 0.5, 1.);
	QMatrix4x4 mat;
	mat.translate(0, 0, -1);
	cone_mesh->transformPoints(mat);
	cone_mesh_f->transformPoints(mat);
	cone_mesh_f->scalePoints(1.5);

	box_mesh->scalePoints(1.3);
	omni_mesh->scalePoints(1.3);
	cone_mesh->translatePoints(0, 0, 0.5);
	cone_mesh->scalePoints(1.4);
	cone_mesh->translatePoints(0, 0, -0.5);
	cone_mesh->scalePoints(1.5);

	camera_mesh = Primitive::cube(1.2, 1.2, 1.2);
	mat.translate(0, 0, -0.5);
	Mesh * m = Primitive::cone(6, 0.6, 1.);
	m->scalePoints(1.4);
	m->transformPoints(mat);
	camera_mesh->append(m);
	camera_mesh_f = Primitive::cubeFrame(1., 1., 1.);
	camera_mesh_f->append(Primitive::cubeFrame(0.5, 0.5, 0.5));
	m = Primitive::coneFrame(6, 0.6, 1.);
	m->transformPoints(mat);
	camera_mesh_f->append(m);

	line_spot_f      = Primitive::lineFrame(QVector3D(), QVector3D(0, 0, -1));
	line_camera_f    = Primitive::lineFrame(QVector3D(), QVector3D(0, 0, -1));

	handle_move_mesh = Primitive::arrow(12, 0.06);
	handle_ms_2_mesh = Primitive::torus(8, 12, 0.5, 0.02, 90);
	m                = Primitive::disc(8, 0.5, 90);
	handle_ms_2_mesh->append(m);
	m->flipNormals();
	handle_ms_2_mesh->append(m);
	delete m;

	handle_rotate_mesh = Primitive::arrow(12, 0.03);
	m                  = Primitive::torus(30, 12, 0.5, 0.06);
	m->translatePoints(QVector3D(0., 0., 0.75));
	handle_rotate_mesh->append(m);
	delete m;

	handle_scale_mesh = Primitive::cylinder(12, 0.03, 0.85);
	m                 = Primitive::ellipsoid(12, 12, 0.15);
	m->translatePoints(QVector3D(0., 0., 0.85));
	handle_scale_mesh->append(m);
	delete m;
	handle_scale_3_mesh = Primitive::ellipsoid(12, 12, 0.2);

	handle_move_mesh->scalePoints(7.5);
	handle_ms_2_mesh->scalePoints(7.5);
	handle_rotate_mesh->scalePoints(7.5);
	handle_scale_mesh->scalePoints(7.5);
	handle_scale_3_mesh->scalePoints(7.5);
}


RendererService::~RendererService() {
	delete box_mesh;
	delete box_mesh_f;
	delete omni_mesh;
	delete omni_mesh_f;
	delete cone_mesh;
	delete cone_mesh_f;
	delete camera_mesh;
	delete camera_mesh_f;
	delete line_spot_f;
	delete line_camera_f;
	delete axis_camera;
	delete axis_mesh;
	delete handle_move_mesh;
	delete handle_ms_2_mesh;
	delete handle_rotate_mesh;
	delete handle_scale_mesh;
	delete handle_scale_3_mesh;
}


void RendererService::init(int width, int height) {
	box_mesh->reinit();
	box_mesh_f->reinit();
	omni_mesh->reinit();
	omni_mesh_f->reinit();
	cone_mesh->reinit();
	cone_mesh_f->reinit();
	camera_mesh->reinit();
	camera_mesh_f->reinit();
	line_spot_f->reinit();
	line_camera_f->reinit();
	axis_mesh->reinit();
	handle_move_mesh->reinit();
	handle_ms_2_mesh->reinit();
	handle_rotate_mesh->reinit();
	handle_scale_mesh->reinit();
	handle_scale_3_mesh->reinit();
	axis_mesh->reinit();
	fillXYZObjects();
	axis_mesh->loadObjects(r->view, cur_objects);
	resize(width, height);
}


void RendererService::resize(int width, int height) {
	axis_viewport = preferredIconSize(10.);
	line_width    = lineThickness();
	size_vp_scale = 25. * appScale() / qMax(qMin(width, height), 1);
	// qDebug() << axis_viewport;
}


QMatrix4x4 RendererService::invariantSizeMatrix(QVector3D p, double * ret_scale) {
	QVector4D pos = QVector4D(p, 1.);
	double dist   = -(v_mat * pos).z();
	QMatrix4x4 m;
	m.translate(pos.toVector3D());
	m.scale(dist * size_full_scale);
	if (ret_scale) *ret_scale = dist * size_full_scale;
	return m;
}


QMatrix4x4 RendererService::parentRotationMatrix(ObjectBase * o, bool self_rotation) {
	QMatrix4x4 ret;
	if (!o) return ret;
	QMatrix4x4 pmat;
	if (o->parent()) {
		pmat = o->parent()->transform().matrixRotate();
	}
	if (self_rotation) {
		ret *= o->transform().matrixRotate();
	}
	ret = pmat * ret;
	return ret;
}


void RendererService::fillXYZObjects() {
	cur_objects.resize(3);
	for (int i = 0; i < 3; ++i) {
		cur_objects[i].color = color_xyz[i];
		mat_xyz[i].transposed().copyDataTo(cur_objects[i].modelmatrix);
	}
}


void RendererService::fillOmniObjects() {
	QList<Light *> ll = r->view->scene()->lights_used.value(Light::Omni);
	Object o;
	cur_objects.clear();
	foreach(Light * l, ll) {
		QMatrix4x4 m = invariantSizeMatrix(l->worldPos());
		m.transposed().copyDataTo(o.modelmatrix);
		o.object_id = l->id();
		cur_objects << o;
	}
}


void RendererService::fillAimedObjects(const ObjectBaseList & objects, Mesh * line_mesh) {
	Object o;
	cur_objects.clear();
	cur_aims.clear();
	QVector<QVector3D> & lv(line_mesh->vertices());
	QVector<QVector3D> & ln(line_mesh->normals());
	QVector<QVector2D> & lt(line_mesh->texcoords());
	QVector<Vector2i> & lind(line_mesh->indicesLines());
	lv.clear();
	foreach(ObjectBase * go, objects) {
		AimedObject * ao = (AimedObject *)go;
		QMatrix4x4 m;
		m = invariantSizeMatrix(ao->worldPos()) * parentRotationMatrix(ao);
		m.transposed().copyDataTo(o.modelmatrix);
		o.object_id = ao->id();
		cur_objects << o;

		lv << ao->worldPos() << ao->worldAim();

		m = invariantSizeMatrix(ao->worldAim());
		m.transposed().copyDataTo(o.modelmatrix);
		o.object_id = ao->id() + 1;
		cur_aims << o;
	}
	ln.resize(lv.size());
	lt.resize(lv.size());
	lind.resize(lv.size() / 2);
	for (int i = 0; i < lind.size(); ++i) {
		lind[i] = Vector2i(i * 2, i * 2 + 1);
	}
}


void RendererService::fillHandleObjects(QVector3D center,
                                        HandleMesh ids[],
                                        const QVector<QMatrix4x4> & mats,
                                        const QVector<QVector4D> & colors,
                                        QMatrix4x4 add_mat,
                                        int count) {
	QMatrix4x4 m = invariantSizeMatrix(center) * add_mat;
	cur_objects.resize(count);
	for (int i = 0; i < count; ++i) {
		cur_objects[i].color     = colors[i];
		QMatrix4x4 omat          = m * mats[i];
		cur_objects[i].object_id = ids[i];
		if (current_handle.testFlag(ids[i])) {
			cur_objects[i].color = QVector4D(0, 1, 1, 1);
		}
		omat.transposed().copyDataTo(cur_objects[i].modelmatrix);
	}
}


bool RendererService::calculateCenter() {
	ObjectBaseList sol = r->view->scene()->selectedObjects(true);
	if (sol.isEmpty()) return false;
	selection_center = sol[0]->worldPos();
	if (sol.size() > 1) {
		Box3D bb;
		foreach(ObjectBase * o, sol) {
			o->calculateBoundingBox();
			bb |= o->boundingBox();
		}
		if (!bb.isEmpty()) {
			selection_center = bb.center();
		}
	}
	axis_mat = QMatrix4x4();
	if ((sol.size() == 1)) {
		if (current_action == haMove) {
			if (sol[0]->isAimSelected()) selection_center = ((AimedObject *)sol[0])->worldAim();
		} else {
			axis_mat = parentRotationMatrix(sol[0]);
		}
	}
	return true;
}


void RendererService::drawCurrentHandleObjects() {
	if (current_action == haNoAction) return;
	if (calculateCenter()) {
		HandleMesh ids[3];
		switch (current_action) {
		case haMove:
			ids[0] = hmMoveX;
			ids[1] = hmMoveY;
			ids[2] = hmMoveZ;
			break;
		case haRotate:
			ids[0] = hmRotateX;
			ids[1] = hmRotateY;
			ids[2] = hmRotateZ;
			break;
		case haScale:
			ids[0] = hmScaleX;
			ids[1] = hmScaleY;
			ids[2] = hmScaleZ;
			break;
		default: break;
		}
		fillHandleObjects(selection_center, ids, mat_xyz, color_xyz, axis_mat);
		Mesh * hm = currentHandleMesh();
		QVector<uchar> sel;
		sel.fill(0, 3);
		if (hm) {
			hm->loadObjects(r->view, cur_objects);
			hm->loadSelections(r->view, sel);
			hm->draw(r->view, 3);
		}
		if (current_action == haMove || current_action == haScale) {
			switch (current_action) {
			case haMove:
				ids[0] = hmMoveXY;
				ids[1] = hmMoveXZ;
				ids[2] = hmMoveYZ;
				break;
			case haScale:
				ids[0] = hmScaleXY;
				ids[1] = hmScaleXZ;
				ids[2] = hmScaleYZ;
				break;
			default: break;
			}
			hm = handle_ms_2_mesh;
			fillHandleObjects(selection_center, ids, mat_ms2, color_ms2, axis_mat);
			hm->loadObjects(r->view, cur_objects);
			hm->loadSelections(r->view, sel);
			hm->draw(r->view, 3);
			if (current_action == haScale) {
				hm = handle_scale_3_mesh;
				QVector<QMatrix4x4> mv;
				mv.resize(1);
				QVector<QVector4D> cv;
				cv.fill(QVector4D(1, 1, 0.5, 1), 1);
				ids[0] = hmMaxScale;
				fillHandleObjects(selection_center, ids, mv, cv, axis_mat, 1);
				hm->loadObjects(r->view, cur_objects);
				hm->loadSelections(r->view, sel);
				hm->draw(r->view, 1);
			}
		}
	}
}


void RendererService::drawLights() {
	QGLView * v = r->view;
	RendererSelection & rs(r->rend_selection);

	fillOmniObjects();
	omni_mesh->loadObjects(v, cur_objects);
	r->fillSelectionsBuffer(rs.cur_selections_, lights2objectList(v->scene()->lights_used.value(Light::Omni)));
	omni_mesh->loadSelections(v, rs.cur_selections_);
	omni_mesh->draw(v, cur_objects.size());
	ObjectBaseList ll = lights2objectList(r->view->scene()->lights_used.value(Light::Cone));
	fillAimedObjects(ll, line_spot_f);
	cone_mesh->loadObjects(v, cur_objects);
	r->fillSelectionsBuffer(rs.cur_selections_, ll);
	cone_mesh->loadSelections(v, rs.cur_selections_);
	cone_mesh->draw(v, cur_objects.size());
	box_mesh->loadObjects(v, cur_aims);
	box_mesh->loadSelections(v, rs.cur_selections_);
	box_mesh->draw(v, cur_aims.size());
}


void RendererService::drawLightsFrame(QColor color) {
	QGLView * v = r->view;
	RendererSelection & rs(r->rend_selection);

	fillOmniObjects();
	setObjectsColor(cur_objects, color);
	omni_mesh_f->loadObjects(v, cur_objects);
	r->fillSelectionsBuffer(rs.cur_selections_, lights2objectList(v->scene()->lights_used.value(Light::Omni)));
	omni_mesh_f->loadSelections(v, rs.cur_selections_);
	omni_mesh_f->draw(v, cur_objects.size());

	ObjectBaseList ll = lights2objectList(r->view->scene()->lights_used.value(Light::Cone));
	fillAimedObjects(ll, line_spot_f);
	setObjectsColor(cur_objects, color);
	cone_mesh_f->loadObjects(v, cur_objects);
	r->fillSelectionsBuffer(rs.cur_selections_, ll);
	cone_mesh_f->loadSelections(v, rs.cur_selections_);
	cone_mesh_f->draw(v, cur_objects.size());

	setObjectsColor(cur_aims, color);
	box_mesh_f->loadObjects(v, cur_aims);
	box_mesh_f->loadSelections(v, rs.cur_selections_);
	box_mesh_f->draw(v, cur_aims.size());
}


void RendererService::drawCameras() {
	QGLView * v = r->view;
	RendererSelection & rs(r->rend_selection);

	ObjectBaseList cl = cameras2objectList(r->view->scene()->cameras_used);
	cl.removeOne(r->view->camera());
	fillAimedObjects(cl, line_camera_f);
	camera_mesh->loadObjects(v, cur_objects);
	r->fillSelectionsBuffer(rs.cur_selections_, cl);
	camera_mesh->loadSelections(v, rs.cur_selections_);
	camera_mesh->draw(v, cur_objects.size());
	box_mesh->loadObjects(v, cur_aims);
	box_mesh->loadSelections(v, rs.cur_selections_);
	box_mesh->draw(v, cur_aims.size());
}


void RendererService::drawCamerasFrame(QColor color) {
	QGLView * v = r->view;
	RendererSelection & rs(r->rend_selection);

	ObjectBaseList cl = cameras2objectList(r->view->scene()->cameras_used);
	cl.removeOne(r->view->camera());
	fillAimedObjects(cl, line_camera_f);
	setObjectsColor(cur_objects, color);
	camera_mesh_f->loadObjects(v, cur_objects);
	r->fillSelectionsBuffer(rs.cur_selections_, cl);
	camera_mesh_f->loadSelections(v, rs.cur_selections_);
	camera_mesh_f->draw(v, cur_objects.size());

	setObjectsColor(cur_aims, color);
	box_mesh_f->loadObjects(v, cur_aims);
	box_mesh_f->loadSelections(v, rs.cur_selections_);
	box_mesh_f->draw(v, cur_aims.size());
}


void RendererService::setObjectsColor(QVector<Object> & ol, QColor col) {
	QVector4D cv = QColor2QVector(col);
	for (int i = 0; i < ol.size(); ++i)
		ol[i].color = cv;
}


void RendererService::renderService() {
	QOpenGLShaderProgram * prog = 0;
	QOpenGLExtraFunctions * f   = r->view;
	size_full_scale             = tan(r->view->camera()->FOV() / 2. * deg2rad) * size_vp_scale;
	v_mat                       = r->view->camera()->fullViewMatrix();
	f->glEnable(GL_MULTISAMPLE);
	glEnableDepth();
	f->glClear(GL_DEPTH_BUFFER_BIT);
	glDisable(GL_CULL_FACE);
	if (r->bindShader(Renderer::srServiceFrame, &prog)) {
		prog->setUniformValue("qgl_ProjMatrix", r->view->camera()->projectionMatrix(r->view->aspect));

		/// lights
		r->setUniformCamera(prog, r->view->camera());

		prog->setUniformValue("line_width", 2.f);
		prog->setUniformValue("z_offset", 0.f);
		drawLightsFrame(Qt::white);
		drawCamerasFrame(Qt::white);
		prog->setUniformValue("line_width", 1.f);
		prog->setUniformValue("z_offset", -1.E-2f);
		drawLightsFrame(Qt::black);
		drawCamerasFrame(Qt::black);
	}
	if (r->bindShader(Renderer::srServiceLine, &prog)) {
		r->setUniformCamera(prog, r->view->camera());
		line_object.color = QColor2QVector(Qt::white);
		line_spot_f->loadObject(f, line_object);
		line_camera_f->loadObject(f, line_object);
		line_spot_f->draw(f, 1);
		line_camera_f->draw(f, 1);
	}
	glEnable(GL_CULL_FACE);
	if (r->bindShader(Renderer::srServiceFill, &prog)) {
		r->setUniformCamera(prog, r->view->camera());

		/// handles
		f->glEnable(GL_BLEND);
		f->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		drawCurrentHandleObjects();
		f->glDisable(GL_BLEND);

		/// axis
		f->glViewport(0, 0, axis_viewport.width(), axis_viewport.height());
		axis_camera->setPos(-r->view->camera()->direction() * 3.);
		axis_camera->setAim(QVector3D());
		axis_camera->setRotation(r->view->camera()->rotation());
		r->setUniformCamera(prog, axis_camera, true, axis_viewport);
		axis_mesh->draw(f, 3);
		f->glViewport(0, 0, r->view->pixelWidth(), r->view->pixelHeight());
	}
	f->glDisable(GL_MULTISAMPLE);
}


Mesh * RendererService::currentHandleMesh() {
	switch (current_action) {
	case haMove: return handle_move_mesh;
	case haRotate: return handle_rotate_mesh;
	case haScale: return handle_scale_mesh;
	default: break;
	}
	return 0;
}
