/*
    QGLView
    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/>.
*/

#include "qglview.h"

#include "glmesh.h"
#include "gltexture_manager.h"

#include <QApplication>
#include <QKeyEvent>
#include <QOpenGLTexture>
#include <chunkstream.h>
#include <qad_types.h>

using namespace QGLEngineShaders;


QGLView::QGLView(): OpenGLWindow(), renderer_(this), mouse(this) {
	setIcon(QIcon(":/icons/qglview.png"));
	is_init              = false;
	timer                = 0;
	hoverHaloColor_      = QColor(195, 140, 255);
	selectionHaloColor_  = QColor(175, 255, 140);
	lineWidth_           = 1.;
	max_anisotropic      = 1;
	max_texture_chanels  = 8;
	lightEnabled_        = true;
	shaders_supported    = false;
	FXAA_                = false;
	fps_cnt              = 0;
	soft_shadows_quality = 1.;
	soft_shadows_samples = 32;
	soft_shadows         = false;
	fps_tm = fps_  = 0.;
	fogColor_      = Qt::darkGray;
	fogDensity_    = 0.;
	fogDecay_      = 10.;
	hoverHaloFill_ = selectionHaloFill_ = 0.15f;
	render_mode                         = rmFill;
	shadow_map_size                     = QSize(512, 512);
	// setFeature(qglFXAA, false);
	// setFeature(qglAnisotropicLevel, 8);
	// setFeature(qglEyeAccomodationEnabled, false);
	// setFeature(qglEyeAccomodationTime, 16.);
	// setFeature(qglEyeAccomodationMaxSpeed, 0.2);
	// setFeature(qglBloomEnabled, false);
	// setFeature(qglBloomThreshold, 0.9);
	// setFeature(qglBloomFactor, 1.);
	// setFeature(qglBloomRadius, 8);
	// setFeature(qglMotionBlurEnabled, false);
	// setFeature(qglMotionBlurFactor, 1.);
	// setFeature(qglMotionBlurSteps, 8);
	// setFeature(qglShadowsEnabled, false);
	// setFeature(qglShadowsMapSize, 512);
	// setFeature(qglShadowsSoftEnabled, true);
	// setFeature(qglReflectionsEnabled, false);
	// setFeature(qglReflectionsBlur, true);
	// setFeature(qglSSAOEnabled, false);
	// setFeature(qglSSAORadius, 5);
	// setFeature(qglDepthOfFieldEnabled, false);
	// setFeature(qglDepthOfFieldAutoFocusEnabled, true);
	// setFeature(qglDepthOfFieldAutoFocusSpeed, 0.1);
	// setFeature(qglDepthOfFieldFocus, 1.);
	// setFeature(qglDepthOfFieldDiaphragm, 8.);
	hoverHalo_ = selectionHalo_ = true;
	fogEnabled_ = shaders_bind = false;
	scene_                     = new Scene();
	connect(scene_, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
	connect(scene_, SIGNAL(__destroyed()), this, SLOT(__destroyed()));
	connect(scene_, SIGNAL(__objectDeleted(ObjectBase *)), this, SLOT(__objectDeleted(ObjectBase *)));
	default_camera = new Camera();
	default_camera->setPos(QVector3D(2, 2, 2));
	default_camera->setAim(QVector3D());
	camera_ = default_camera;
	//	qDebug() << camera_->aim();
	default_camera->setName("Camera");
	emit cameraPosChanged(default_camera->pos());

	/*Mesh * m       = Primitive::plane(10, 10, 10);
	// m->flipNormals();
	ObjectBase * o = new ObjectBase(m);
	o->setColor(Qt::cyan);
	scene()->addObject(o);
	delete m;*/
}


QGLView::~QGLView() {
	stop();
	scene_->clear();
	delete scene_;
	delete default_camera;
}


void QGLView::stop() {
	if (timer) killTimer(timer);
}


void QGLView::start(float freq) {
	timer = startTimer(freq <= 0.f ? 0 : int(1000.f / freq));
}


QList<Light *> QGLView::selectedLights() const {
	QList<Light *> ret;
	ObjectBaseList sol = scene_->selectedObjects();
	foreach(ObjectBase * o, sol)
		if (o->type() == ObjectBase::glLight) ret << (Light *)o;
	return ret;
}


QList<Camera *> QGLView::selectedCameras() const {
	QList<Camera *> ret;
	ObjectBaseList sol = scene_->selectedObjects();
	for (ObjectBase * o: sol) {
		if (o->type() == ObjectBase::glCamera) ret << (Camera *)o;
	}
	return ret;
}


void QGLView::reloadTextures() {
	renderer_.markReloadMaterials();
	renderer_.recreateMaterialThumbnails(true);
	scene_->setLightsChanged();
}


void QGLView::resizeEvent(QResizeEvent * e) {
	renderLater();
	mouse.resize();
}


void QGLView::timerEvent(QTimerEvent *) {
	renderNow();
	Qt::KeyboardModifiers km = QApplication::keyboardModifiers();
	foreach(int i, keys_)
		emit keyEvent((Qt::Key)i, km);
}


void QGLView::render() {
	resizeGL(pixelWidth(), pixelHeight());
	emit glBeginPaint();
	renderer_.mouse_pos = mapFromGlobal(QCursor::pos()) * devicePixelRatio();
	renderer_.renderScene();
	emit glEndPaint();
	fps_tm = time.elapsed();
	fps_cnt++;
	if (fps_tm < 1000.) return;
	time.restart();
	fps_    = fps_cnt / fps_tm * 1000.;
	fps_tm  = 0.;
	fps_cnt = 0;
}


void QGLView::initialize() {
	checkCaps();
	glPixelStorei(GL_PACK_ALIGNMENT, 1);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	glDisable(GL_MULTISAMPLE);
	glDisable(GL_BLEND);
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_TEXTURE_CUBE_MAP);
	glEnable(GL_TEXTURE_MAX_ANISOTROPY_EXT);
	glEnable(GL_CULL_FACE);
	glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
	glCullFace(GL_BACK);
	renderer_.reloadShaders();
	renderer_.init(pixelWidth(), pixelHeight());
	scene_->reinitAll();
	is_init   = true;
	prev_size = QSize();
	emit glInitializeDone();
}


void QGLView::checkCaps() {
	glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropic);
	shaders_supported = QOpenGLShaderProgram::hasOpenGLShaderPrograms();
}


void QGLView::__destroyed() {
	renderer_.rend_mat.mat_thumbnails.clear();
	mouse.hov_objects.clear();
}


void QGLView::__objectDeleted(ObjectBase * o) {
	if (o == camera_) setDefaultCamera();
}


void QGLView::resizeGL(int width, int height) {
	if (!is_init) return;
	if (width <= 0 || height <= 0) return;
	if (prev_size == QSize(width, height)) return;
	prev_size = QSize(width, height);
	aspect    = float(width) / float(height);
	renderer_.resize(width, height);
	// qDebug() << "resize" << width << height;
	iaspect = (aspect == 0.f) ? 0. : 1 / aspect;
	glViewport(0, 0, width, height);
	emit glResize(width, height);
}


void QGLView::keyPressEvent(QKeyEvent * e) {
	emit glKeyPressEvent(e);
	if (e->key() > 0) keys_.insert(e->key());
	if (e->key() == Qt::Key_F11) {
		emit doubleClick();
	}
}


void QGLView::keyReleaseEvent(QKeyEvent * e) {
	emit glKeyReleaseEvent(e);
	keys_.remove(e->key());
}


void QGLView::focusOutEvent(QFocusEvent *) {
	keys_.clear();
}


void QGLView::focusOn(const Box3D & bb) {
	if (bb.isEmpty() || !camera()) return;
	double size = qMax(qMax(bb.width, bb.length), bb.height);
	camera()->setAim(bb.center());
	camera()->flyToDistance(size * 1.25);
}


QByteArray QGLView::saveCamera() {
	ChunkStream cs;
	const Camera * c = default_camera;
	cs.add(1, c->pos()).add(2, c->aim()).add(3, c->rotation()).add(4, c->FOV());
	return cs.data();
}


void QGLView::restoreCamera(const QByteArray & ba) {
	if (ba.isEmpty()) return;
	setDefaultCamera();
	Camera * c = default_camera;
	QVector3D pos(c->pos()), aim(c->aim()), ang(c->rotation());
	float fov(c->FOV());
	ChunkStream cs(ba);
	cs.readAll();
	cs.get(1, pos).get(2, aim).get(3, ang).get(4, fov);
	camera()->setPos(pos);
	camera()->setAim(aim);
	camera()->setAngles(ang);
	camera()->setFOV(fov);
}


void QGLView::setShadowMapSize(QSize sz) {
	shadow_map_size = sz;
}


void QGLView::setTextureMapSize(QSize sz) {
	renderer_.maps_size = sz;
	renderer_.markReloadMaterials();
}


QSize QGLView::shadowMapSize() const {
	return shadow_map_size;
}


QSize QGLView::textureMapSize() const {
	return renderer_.maps_size;
}
