/*
    QGLView
    Copyright (C) 2017  Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef QGLVIEW_H
#define QGLVIEW_H

#include "glframebuffer.h"
#include <QGraphicsProxyWidget>
#include <QGraphicsView>
#include <QGLWidget>
#include <QGLShaderProgram>
#include <QGLFramebufferObject>
#include <QVector3D>
#include <QTimer>
#include <QTime>
#include <QMouseEvent>
#include <QDebug>
#include <QMutex>
#include <QThread>
#include <QApplication>
#include "glprimitives.h"
#include "glparticles_system.h"
#include "glrendererbase.h"


class QGLView: public QGraphicsView, public QGLViewBase
{
	friend class GLRendererBase;
	Q_OBJECT
	Q_PROPERTY (QColor backColor READ backColor WRITE setBackColor)
	Q_PROPERTY (double lineWidth READ lineWidth WRITE setLineWidth)
	Q_PROPERTY (double FOV READ FOV WRITE setFOV)
	Q_PROPERTY (double depthStart READ depthStart WRITE setDepthStart)
	Q_PROPERTY (double depthEnd READ depthEnd WRITE setDepthEnd)
	Q_PROPERTY (QColor ambientColor READ ambientColor WRITE setAmbientColor)
	Q_PROPERTY (QColor fogColor READ fogColor WRITE setFogColor)
	Q_PROPERTY (double fogDensity READ fogDensity WRITE setFogDensity)
	Q_PROPERTY (double fogStart READ fogStart WRITE setFogStart)
	Q_PROPERTY (double fogEnd READ fogEnd WRITE setFogEnd)
	Q_PROPERTY (FogMode fogMode READ fogMode WRITE setFogMode)
	Q_PROPERTY (bool fogEnabled READ isFogEnabled WRITE setFogEnabled)
	Q_PROPERTY (int renderMode READ renderMode WRITE setRenderMode)
	Q_PROPERTY (bool grabMouse READ isGrabMouseEnabled WRITE setGrabMouseEnabled)
	Q_PROPERTY (bool mouseRotate READ isMouseRotateEnabled WRITE setMouseRotateEnabled)
	Q_PROPERTY (bool mouseSelection READ isMouseSelectionEnabled WRITE setMouseSelectionEnabled)
	Q_PROPERTY (bool cameraOrbit READ isCameraOrbit WRITE setCameraOrbit)
	Q_PROPERTY (bool hoverHalo READ isHoverHaloEnabled WRITE setHoverHaloEnabled)
	Q_PROPERTY (QColor hoverHaloColor READ hoverHaloColor WRITE setHoverHaloColor)
	Q_PROPERTY (double hoverHaloFillAlpha READ hoverHaloFillAlpha WRITE setHoverHaloFillAlpha)
	Q_PROPERTY (bool selectionHalo READ isSelectionHaloEnabled WRITE setSelectionHaloEnabled)
	Q_PROPERTY (QColor selectionHaloColor READ selectionHaloColor WRITE setSelectionHaloColor)
	Q_PROPERTY (double selectionHaloFillAlpha READ selectionHaloFillAlpha WRITE setSelectionHaloFillAlpha)
	//Q_PROPERTY (LightingMode lightingMode READ lightingMode WRITE setLightingMode)
	Q_PROPERTY (Qt::MouseButton selectionButton READ selectionButton WRITE setSelectionButton)
	Q_PROPERTY (Qt::KeyboardModifier selectionModifier READ selectionModifier WRITE setSelectionModifier)
	Q_PROPERTY (SelectionMode selectionMode READ selectionMode WRITE setSelectionMode)
	Q_PROPERTY (QPen selectionRectPen READ selectionRectPen WRITE setSelectionRectPen)
	Q_PROPERTY (QBrush selectionRectBrush READ selectionRectBrush WRITE setSelectionRectBrush)

public:
	QGLView(QWidget * parent = 0);
	virtual ~QGLView();

	enum FogMode {Linear = GL_LINEAR, Exp = GL_EXP, Exp2 = GL_EXP2};
	enum SelectionMode {NoSelection, SingleSelection, MultiSelection};
	enum Feature {
		qglMSAA,
		qglFXAA,
		qglLinearFiltering,
		qglAnisotropicLevel,
		qglHDR,
		qglEyeAccomodationEnabled,
		qglEyeAccomodationTime,
		qglEyeAccomodationMaxSpeed,
		qglBloomEnabled,
		qglBloomThreshold,
		qglBloomFactor,
		qglBloomRadius,
		qglMotionBlurEnabled,
		qglMotionBlurFactor,
		qglMotionBlurSteps,
		qglShadowsEnabled,
		qglShadowsMapSize,
		qglShadowsSoftEnabled,
		qglReflectionsEnabled,
		qglReflectionsBlur,
		qglSSAOEnabled,
		qglSSAORadius,
		qglDepthOfFieldEnabled,
		qglDepthOfFieldAutoFocusEnabled,
		qglDepthOfFieldAutoFocusSpeed,
		qglDepthOfFieldFocus,
		qglDepthOfFieldDiaphragm
	};
	
	Q_ENUMS (FogMode)
	Q_ENUMS (SelectionMode)
	//enum LightingMode {Simple, PerPixel, DeferredShading};
	//Q_ENUMS (LightingMode)

	void stop() {killTimer(timer);}
	void start(double freq = 40.) {timer = startTimer(freq <= 0. ? 0 : 1000. / freq);}

	GLRendererBase * renderer() {return renderer_;}
	void setRenderer(GLRendererBase * r, GLRendererBase ** prev = 0) {if (prev != 0) *prev = renderer_; renderer_ = r;}

	const QGLContext * context() const {return ((const QGLWidget * )viewport())->context();}
	void makeCurrent() {((QGLWidget * )viewport())->makeCurrent();}

	QColor backColor() const {return backColor_;}
	double lineWidth() const {return lineWidth_;}
	double FOV() const {return camera().fov_;}
	double depthStart() const {return camera().depth_start;}
	double depthEnd() const {return camera().depth_end;}
	double currentFPS() const {return fps_;}
	int maxAnisotropicLevel() const {return max_anisotropic;}

	QColor ambientColor() const {return ambientColor_;}
	QColor fogColor() const {return fogColor_;}
	double fogDensity() const {return fogDensity_;}
	double fogStart() const {return fogStart_;}
	double fogEnd() const {return fogEnd_;}
	FogMode fogMode() const {return fogMode_;}
	//LightingMode lightingMode() const {return lmode;}
	bool isFogEnabled() const {return fogEnabled_;}
	bool isLightEnabled() const {return lightEnabled_;}
	bool isGrabMouseEnabled() const {return grabMouse_;}
	bool isMouseRotateEnabled() const {return mouseRotate_;}
	bool isMouseSelectionEnabled() const {return mouseSelect_;}
	bool isCameraOrbit() const {return cameraOrbit_;}
	bool isHoverHaloEnabled() const {return hoverHalo_;}
	QColor hoverHaloColor() const {return hoverHaloColor_;}
	double hoverHaloFillAlpha() const {return hoverHaloFill_;}
	bool isSelectionHaloEnabled() const {return selectionHalo_;}
	QColor selectionHaloColor() const {return selectionHaloColor_;}
	double selectionHaloFillAlpha() const {return selectionHaloFill_;}

	QVariant feature(Feature f) const {return features_.value(int(f));}
	QVariant setFeature(Feature f, const QVariant & value) {QVariant ret = features_.value(int(f)); features_[int(f)] = value; return ret;}
	bool isFeatureEnabled(Feature f) const {return features_[int(f)].toBool();}

	int renderMode() const {return (int)rmode;}
	void setRenderMode(int mode) {rmode = (GLObjectBase::RenderMode)mode;}

	void addObject(QGraphicsItem * o) {scene()->addItem(o);}
	void addObject(QWidget * o, Qt::WindowFlags f = Qt::Widget);
	void addObject(GLObjectBase * o);
	void addObject(GLObjectBase & o) {addObject(&o);}
	int objectsCount(bool all = false) {if (!all) return objects_.childCount(); int cnt = 0; objectsCountInternal(&cnt, &objects_); return cnt;}
	void removeObject(GLObjectBase * o, bool inChildren = true) {if (inChildren) removeObjectInternal(o, &objects_); else objects_.removeChild(o);}
	void removeObject(GLObjectBase & o, bool inChildren = true) {removeObject(&o, inChildren);}
	void clearObjects(bool deleteAll = false) {removeObject(camera_); objects_.clearChildren(deleteAll); addObject(camera()); sel_obj = hov_obj = 0;}
	QList<GLObjectBase * > objects(bool all = false) {return objects_.children(all);}

	int lightsCount() const {return lights_.size();}
	void removeLight(int index) {removeObject(lights_.at(index)); lights_.removeAt(index);}
	void removeLight(Light * l) {foreach (Light * i, lights_) removeObject(i); lights_.removeAll(l);}
	void clearLights(bool deleteAll = false) {if (deleteAll) foreach (Light * i, lights_) delete i; lights_.clear();}
	QList<Light * > lights() {return lights_;}

	GLTextureManager * textureManager() {return &textures_manager;}
	void addTexture(const QString & path) {textures_manager.addTexture(path);}
	void addAnimation(const QString & dir, const QString & name) {textures_manager.addAnimation(dir, name);}

	const GLObjectBase & rootObject() {return objects_;}
	GLObjectBase * object(int index) {return objects_.child(index);}
	GLObjectBase * object(const QString & name) {return objects_.child(name);}
	Light * light(int index) {return lights_[index];}
	Light * light(const QString & name) {foreach (Light * i, lights_) if (i->name_ == name) return i; return 0;}

	QPen selectionRectPen() const {return sel_pen;}
	QBrush selectionRectBrush() const {return sel_brush;}
	SelectionMode selectionMode() const {return sel_mode;}
	Qt::MouseButton selectionButton() const {return sel_button;}
	Qt::KeyboardModifier selectionModifier() const {return sel_mod;}

	void setSelectionMode(SelectionMode v) {sel_mode = v;}
	void setSelectionButton(Qt::MouseButton v) {sel_button = v;}
	void setSelectionModifier(Qt::KeyboardModifier v) {sel_mod = v;}
	void setSelectionRectPen(const QPen & v) {sel_pen = v;}
	void setSelectionRectBrush(const QBrush & v) {sel_brush = v;}
	void selectObject(GLObjectBase * o);

	GLdouble aspect, iaspect;
	//QGLShaderProgram * shader_rope;

protected:
	virtual void drawBackground(QPainter * painter, const QRectF & rect);
	virtual void resizeEvent(QResizeEvent * e) {resizeGL(viewport()->width(), viewport()->height()); QGraphicsView::resizeEvent(e);}

	void timerEvent(QTimerEvent * ) {/*globMutex.lock();*/ processKeys(); viewport()->repaint(); /*globMutex.unlock();*/}
	void initializeGL();
	void paintGL();
	void resizeGL(int width, int height);
	void mousePressEvent(QMouseEvent * e);
	void mouseMoveEvent(QMouseEvent * e);
	void mouseReleaseEvent(QMouseEvent * e);
	void wheelEvent(QWheelEvent * e);
	void leaveEvent(QEvent * );
	void keyPressEvent(QKeyEvent * e) {emit glKeyPressEvent(e); if (e->key() > 0) keys_.insert(e->key());}
	void keyReleaseEvent(QKeyEvent * e) {emit glKeyReleaseEvent(e); keys_.remove(e->key());}
	void focusOutEvent(QFocusEvent *) {keys_.clear();}

	void applyFog();
	void renderSelection();

	void checkCaps();
	void collectLights();

private:
	void collectObjectLights(GLObjectBase * o);
	void objectsCountInternal(int * cnt, GLObjectBase * where) {++(*cnt); foreach (GLObjectBase * i, where->children_) objectsCountInternal(cnt, i);}
	void removeObjectInternal(GLObjectBase * o, GLObjectBase * where) {foreach (GLObjectBase * i, where->children_) {if (o == i) where->removeChild(i); else removeObjectInternal(o, i);}}
	void renderSingleSelection(GLObjectBase & o);
	//void renderSingleShadow(GLObjectBase & o);
	void renderHalo(const GLObjectBase * obj, const int iid, const QColor & color, const double & fill);
	QList<QGraphicsItem * > collectGraphicItems();
	void collectGraphicItems(QList<QGraphicsItem * > & list, QGraphicsItem * o);
	void reloadThisShaders();
	void processKeys();

	QPoint lastPos, downPos;
	QMutex v_mutex;
	GLTextureManager textures_manager;
	GLObjectBase objects_;
	QList<Light * > lights_;
	uint cid;
	QMap<uint, GLObjectBase * > ids;
	QSet<int> keys_;
	FogMode fogMode_;
	QColor backColor_, fogColor_, ambientColor_, hoverHaloColor_, selectionHaloColor_;
	QPainter * painter_;
	QTime time, ktm_;
	QPen sel_pen;
	QBrush sel_brush;
	GLint max_anisotropic, max_texture_chanels;
	GLObjectBase::RenderMode rmode;
	//LightingMode lmode;
	GLObjectBase * sel_obj, * hov_obj;
	GLFramebuffer fbo_selection;
	QGLShaderProgram * shader_select, * shader_halo;
	GLRendererBase * renderer_;
	SelectionMode sel_mode;
	Qt::MouseButton sel_button;
	Qt::KeyboardModifier sel_mod;
	GLRendererBase::RenderingParameters start_rp;
	QHash<int, QVariant> features_;
	double lineWidth_;
	double fogDensity_, fogStart_, fogEnd_, fps_, fps_tm, hoverHaloFill_, selectionHaloFill_, m_motionBlurFactor;
	int timer, fps_cnt, sh_id_loc;
	bool is_init, fogEnabled_, lightEnabled_, grabMouse_, mouse_first, mouseRotate_, mouseSelect_, customMouseMove_;
	bool shaders_supported, changed_, cameraOrbit_;
	bool hoverHalo_, selectionHalo_, mouseThis_, shaders_bind, selecting_;

public slots:
	void setBackColor(const QColor & arg) {backColor_ = arg;}
	void setLineWidth(const double & arg) {lineWidth_ = arg;}
	void setFOV(const double & arg) {camera().fov_ = arg;}
	void setDepthStart(const double & arg) {camera().depth_start = arg;}
	void setDepthEnd(const double & arg) {camera().depth_end = arg;}
	void setAmbientColor(const QColor & arg) {ambientColor_ = arg;}
	void setFogColor(const QColor & arg) {fogColor_ = arg;}
	void setFogDensity(const double & arg) {fogDensity_ = arg;}
	void setFogStart(const double & arg) {fogStart_ = arg;}
	void setFogEnd(const double & arg) {fogEnd_ = arg;}
	void setFogMode(const FogMode & arg) {fogMode_ = arg;}
	void setFogEnabled(const bool & arg) {fogEnabled_ = arg;}
	void setLightEnabled(const bool & arg) {lightEnabled_ = arg;}
	void setGrabMouseEnabled(const bool & arg) {grabMouse_ = arg; mouse_first = true;}
	void setMouseRotateEnabled(const bool & arg) {mouseRotate_ = arg;}
	void setMouseSelectionEnabled(const bool & arg) {mouseSelect_ = arg;}
	void setCustomMouseMove(const bool & arg) {customMouseMove_ = arg;}
	void setCameraOrbit(const bool & arg) {cameraOrbit_ = arg;}
	void setHoverHaloEnabled(const bool & arg) {hoverHalo_ = arg;}
	void setHoverHaloColor(const QColor & arg) {hoverHaloColor_ = arg;}
	void setHoverHaloFillAlpha(const double & arg) {hoverHaloFill_ = arg;}
	void setSelectionHaloEnabled(const bool & arg) {selectionHalo_ = arg;}
	void setSelectionHaloColor(const QColor & arg) {selectionHaloColor_ = arg;}
	void setSelectionHaloFillAlpha(const double & arg) {selectionHaloFill_ = arg;}
	//void setLightingMode(const LightingMode & arg) {lmode = arg;}

	void reloadShaders() {if (renderer_ != 0) renderer_->reloadShaders(); reloadThisShaders();}
	void deselect() {sel_obj = 0;}

signals:
	void glBeginPaint();
	void glPainting();
	void glKeyPressEvent(QKeyEvent * e);
	void glKeyReleaseEvent(QKeyEvent * e);
	void glMousePressEvent(QMouseEvent * e);
	void glMouseMoveEvent(QMouseEvent * e);
	void glMouseReleaseEvent(QMouseEvent * e);
	void glWheelEvent(QWheelEvent * e);
	void glResize(int, int);
	void glInitializeDone();
	void cameraPosChanged(QVector3D pos);
	void keyEvent(Qt::Key key, Qt::KeyboardModifiers mod);
	void customMouseMoveEvent(QPoint curpos, QPoint lastpos, Qt::MouseButtons buttons);

	void hoverChanged(GLObjectBase * cur, GLObjectBase * prev);
	void selectionChanged(GLObjectBase * cur, GLObjectBase * prev);

};

#endif // QGLVIEW_H
