/*
    QGL TonemappingProc
    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 "tonemapping_proc.h"

#include "qglview.h"

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

using namespace QGLEngineShaders;


TonemappingProc::TonemappingProc(Renderer * rend)
	: QThread()
	, r(rend)
	, fbo_1x1(r->view, 1, false, GL_RGB32F)
	, fbomm(r->fbo_out, Renderer::obrLighting, 3)
	, buffer_vbo(GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW) {
	shader_sum  = 0;
	timer_delim = 0;
	frame_max = cur_max = 1.;
	need_render_sum = exit_ = false;
	enabled                 = false;
	timer_tone              = startTimer(10);
}


TonemappingProc::~TonemappingProc() {
	exit_ = true;
	if (!wait(1000)) terminate();
	killTimer(timer_tone);
	if (shader_sum) delete shader_sum;
}


void TonemappingProc::init() {
	buffer_vbo.reinit();
	fbo_1x1.reinit();
	fbomm.reinit();
	QOpenGLExtraFunctions * f = r->view;
	buffer_vbo.init(f);
	f->glGenVertexArrays(1, &vbo_vao);
	f->glBindVertexArray(vbo_vao);
	buffer_vbo.bind(f);
	f->glEnableVertexAttribArray(1);
	f->glVertexAttribIPointer(1, 2, GL_UNSIGNED_INT, 0, 0);
	buffer_vbo.release(f);
	f->glBindVertexArray(0);
	fbomm.lastPlane().enablePixelBuffer();
	fbo_1x1.resize(1, 1);
	resize();
	if (!isRunning()) start();
}


void TonemappingProc::resize() {
	QOpenGLExtraFunctions * f = r->view;
	fbomm.resize();
	int pcnt = fbomm.width(fbomm.lastLevel()) * fbomm.height(fbomm.lastLevel());
	QVector<Vector2i> _data;
	_data.resize(pcnt);
	pcnt = -1;
	for (int x = 0; x < fbomm.width(fbomm.lastLevel()); ++x)
		for (int y = 0; y < fbomm.height(fbomm.lastLevel()); ++y)
			_data[++pcnt] = Vector2i(x, y);
	buffer_vbo.bind(f);
	buffer_vbo.resize(f, _data.size() * sizeof(Vector2i));
	buffer_vbo.load(f, _data.constData(), _data.size() * sizeof(Vector2i));
}


void TonemappingProc::timerEvent(QTimerEvent * e) {
	if (!fbo_1x1.isInit() || !enabled) return;
	if (timer_delim == 0) need_render_sum = true;
	timer_delim = (timer_delim + 1) % 10;
	mutex.lock();
	float fmax = frame_max;
	mutex.unlock();
	float dt = 0.01f, a = dt * 5.f, b = 1.f - a;
	cur_max = fmax * a + cur_max * b;
}


void TonemappingProc::renderSum(Framebuffer & fbo_src, int index) {
	QOpenGLExtraFunctions * f = r->view;
	int pcnt                  = fbo_src.width() * fbo_src.height();
	fbo_src.bindColorTexture(index);
	fbo_1x1.bind();
	glClearFramebuffer();
	if (shader_sum) {
		if (shader_sum->isLinked()) {
			if (shader_sum->bind()) {
				shader_sum->setUniformValue("tex", 0);
				shader_sum->setUniformValue("pcnt", float(pcnt));
				f->glBindVertexArray(vbo_vao);
				f->glEnable(GL_BLEND);
				f->glBlendFunc(GL_ONE, GL_ONE);
				f->glBlendEquation(GL_MAX);
				f->glDrawArrays(GL_POINTS, 0, pcnt);
				f->glBlendEquation(GL_FUNC_ADD);
				f->glDisable(GL_BLEND);
				f->glBindVertexArray(0);
			}
		}
	}
	fbo_1x1.release();
}


void TonemappingProc::run() {
	while (!exit_) {
		if (!enabled) {
			msleep(100);
			continue;
		}
		mutex.lock();
		if (last_data.isEmpty()) {
			mutex.unlock();
			msleep(10);
			continue;
		}
		QVector<QVector4D> data = last_data;
		last_data.clear();
		mutex.unlock();
		float max = calcHistogram(data);
		last_max << max;
		if (last_max.size() > 5) last_max.removeAt(0);
		float cm = last_max[0];
		for (int i = 1; i < last_max.size(); ++i)
			cm += last_max[i];
		cm /= last_max.size();
		mutex.lock();
		frame_max = cm;
		mutex.unlock();
	}
}


float TonemappingProc::calcHistogram(const QVector<QVector4D> & data) {
	if (data.isEmpty()) return 1.f;
	float max = 0.;
	QVector3D luma(0.299, 0.587, 0.114);
	foreach(const QVector4D & p, data) {
		float l = QVector3D::dotProduct(p.toVector3D(), luma);
		max     = qMax(max, l);
	}
	return max;
}


bool TonemappingProc::process() {
	if (!need_render_sum || !enabled) return false;
	need_render_sum = false;
	Framebuffer & fbo(fbomm.lastPlane());
	if (fbo.queriedPoints() > 0) {
		QVector<QVector4D> data = fbo.getPointsFloat();
		mutex.lock();
		last_data = data;
		mutex.unlock();
	}
	fbomm.create();
	fbo.queryPoints(0, fbo.rect(), GL_FLOAT);
	return true;
}


float TonemappingProc::frameMax() {
	if (!enabled) return 1.f;
	return cur_max;
}
