/*
    QGL RendererSelection
    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_selection.h"

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

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

using namespace QGLEngineShaders;


RendererSelection::RendererSelection(Renderer * r_): r(r_), fbo_selection(r->view, 6) {
	sel_frame   = Primitive::plane(2., 2.);
	id_hover    = 0;
	line_thick_ = 2.;
	scale_      = 0.5;
}


RendererSelection::~RendererSelection() {
	delete sel_frame;
}


void RendererSelection::init(int width, int height) {
	fbo_selection.reinit();
	sel_frame->reinit();
	resize(width, height);
}


void RendererSelection::resize(int width, int height) {
	line_thick_ = lineThickness() + 1.;
	scale_      = 0.5 / appScale();
	fbo_selection.enablePixelBuffer();
	fbo_selection.resize(width * scale_, height * scale_);
}


void RendererSelection::generateObjectsID(Scene & scene) {
	ids.clear();
	aim_ids.clear();
	QList<int> passes = scene.geometries_used.keys();
	foreach(int p, passes) {
		QMapIterator<Mesh *, ObjectBaseList> it(scene.geometries_used[p]);
		while (it.hasNext()) {
			it.next();
			foreach(ObjectBase * o, it.value()) {
				uint id = qHash(o);
				ids[id] = o;
				o->id_  = id;
			}
		}
		QMapIterator<int, QList<Light *>> lit(scene.lights_used);
		while (lit.hasNext()) {
			lit.next();
			foreach(ObjectBase * o, lit.value()) {
				uint id         = qHash(o);
				ids[id]         = o;
				aim_ids[id + 1] = o;
				o->id_          = id;
			}
		}
		foreach(Camera * o, scene.cameras_used) {
			uint id         = qHash(o);
			ids[id]         = o;
			aim_ids[id + 1] = o;
			o->id_          = id;
		}
	}
}


void RendererSelection::renderSelection(Scene & scene) {
	QOpenGLShaderProgram * prog = 0;
	QGLView * view              = r->view;
	MouseController & mc(view->mouse);
	if (r->bindShader(Renderer::srSelectionFill, &prog)) {
		mc.hov_objects.clear();
		mc.hov_aims.clear();
		id_hover = 0;
		if (fbo_selection.queriedPoints() > 0) {
			if (fbo_selection.queriedPoints() == 1) {
				id_hover       = fbo_selection.getPoint();
				ObjectBase * o = ids.value(id_hover);
				if (o)
					mc.hov_objects << o;
				else {
					o = aim_ids.value(id_hover);
					if (o) mc.hov_aims << o;
				}
				// qDebug() << id_hover;
			} else {
				QVector<uint> points = fbo_selection.getPointsByte();
				QSet<uint> ids_hover;
				foreach(uint i, points)
					ids_hover << i;
				foreach(uint i, ids_hover) {
					ObjectBase * o = ids.value(i);
					if (o) mc.hov_objects << o;
					o = aim_ids.value(i);
					if (o) mc.hov_aims << o;
				}
				// qDebug() << ids_hover;
			}
		}

		fbo_selection.bind();

		fbo_selection.setWriteBuffers();
		glEnableDepth();
		glClearFramebuffer(QColor(0, 0, 0, 0));
		r->setUniformCamera(prog, view->camera());
		r->renderObjects(scene, rpSolid);
		r->renderObjects(scene, rpTransparent);
		view->glClear(GL_DEPTH_BUFFER_BIT);

		RendererService & rs(r->rend_service);
		rs.drawLights();
		rs.drawCameras();
		rs.drawCurrentHandleObjects();
		if (r->mouse_rect.isNull())
			fbo_selection.queryPoint(0, r->mouse_pos * scale_);
		else
			fbo_selection.queryPoints(0, QRect(r->mouse_rect.topLeft() * scale_, r->mouse_rect.size() * scale_));

		// qDebug() << id_hover;
		fbo_selection.bindColorTexture(sbrSrcHover, sbrSrcHover);
		fbo_selection.bindColorTexture(sbrSrcSelect, sbrSrcSelect);
		fbo_selection.setWriteBuffers(QVector<int>() << sbrHovered << sbrSelected);
		if (!view->isHoverHaloEnabled() && !view->isSelectionHaloEnabled())
			glClearFramebuffer(QColor(0, 0, 0, 0), false);
		else {
			r->bindShader(Renderer::srSelectionHalo, &prog);
			r->setUniformHalo(prog, "hover", view->hoverHaloColor(), view->hoverHaloFillAlpha());
			r->setUniformHalo(prog, "selection", view->selectionHaloColor(), view->selectionHaloFillAlpha());
			prog->setUniformValue("has_hover", view->isHoverHaloEnabled() && (id_hover > 0) ? 1.f : 0.f);
			prog->setUniformValue("has_selection", view->isSelectionHaloEnabled() ? 1.f : 0.f);
			prog->setUniformValue("fb_hover", (int)sbrSrcHover);
			prog->setUniformValue("fb_selection", (int)sbrSrcSelect);
			prog->setUniformValue("hover_id",
								  QVector4D(float(id_hover & 0xFF) / 255.f,
											float((id_hover >> 8) & 0xFF) / 255.f,
											float((id_hover >> 16) & 0xFF) / 255.f,
											float((id_hover >> 24) & 0xFF) / 255.f));
			r->renderQuad(prog, r->quad, view->camera());
		}

		prog = r->shader_fxaa;
		if (r->bindShader(prog)) {
			r->setUniformCamera(prog, 0, true, fbo_selection.size());
			fbo_selection.bindColorTexture(sbrHovered);
			fbo_selection.setWriteBuffer(sbrHoveredFXAA);
			r->renderQuad(prog, r->quad, 0, false);
			fbo_selection.bindColorTexture(sbrSelected);
			fbo_selection.setWriteBuffer(sbrSelectedFXAA);
			r->renderQuad(prog, r->quad, 0, false);
		}
		fbo_selection.release();
	}
}


void RendererSelection::renderSelectionFrame() {
	QOpenGLShaderProgram * prog = 0;
	if (r->bindShader(Renderer::srSelectionFrame, &prog)) {
		QMatrix4x4 mat;
		double mrx = r->mouse_rect.x(), mrw = r->mouse_rect.width(), vw = r->view->pixelWidth();
		double mry = r->mouse_rect.y(), mrh = r->mouse_rect.height(), vh = r->view->pixelHeight();
		mat.translate(-1. + (mrw + mrx * 2) / vw, 1. - (mrh + mry * 2) / vh, 0.);
		mat.scale(mrw / vw, mrh / vh, 0.);
		r->initQuad(sel_frame, mat);
		prog->setUniformValue("size", QVector2D(mrw / vw, mrh / vh));
		prog->setUniformValue("thickness", line_thick_);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		r->renderQuad(prog, sel_frame);
		glDisable(GL_BLEND);
	}
}


void RendererSelection::drawSelection(Framebuffer & fbo_out, int index_out) {
	QOpenGLShaderProgram * prog = 0;
	if (r->bindShader(Renderer::srSelectionApply, &prog)) {
		fbo_selection.bindColorTextures();
		fbo_out.bindColorTexture(index_out);
		prog->setUniformValue("fb_out", 0);
		prog->setUniformValue("fb_hover", (int)sbrHoveredFXAA);
		prog->setUniformValue("fb_select", (int)sbrSelectedFXAA);
		r->renderQuad(prog, r->quad, r->view->camera());
		if (!r->mouse_rect.isNull()) {
			renderSelectionFrame();
		}
	}
}
