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

#ifndef QGLVIEW_H
#define QGLVIEW_H

#include "glframebuffer.h"
#include "glparticles_system.h"
#include "glprimitives.h"
#include "glrendererbase.h"
#include "openglwindow.h"

#include <QElapsedTimer>
#include <QTime>


class QGLView
	: public OpenGLWindow
	, public QGLViewBase {
	friend class GLRendererBase;
	friend class GLObjectBase;
	Q_OBJECT
	Q_PROPERTY(QColor backColor READ backColor WRITE setBackColor)
	Q_PROPERTY(float lineWidth READ lineWidth WRITE setLineWidth)
	Q_PROPERTY(float FOV READ FOV WRITE setFOV)
	Q_PROPERTY(float depthStart READ depthStart WRITE setDepthStart)
	Q_PROPERTY(float depthEnd READ depthEnd WRITE setDepthEnd)
	Q_PROPERTY(QColor ambientColor READ ambientColor WRITE setAmbientColor)
	Q_PROPERTY(QColor fogColor READ fogColor WRITE setFogColor)
	Q_PROPERTY(float fogDensity READ fogDensity WRITE setFogDensity)
	Q_PROPERTY(float fogStart READ fogStart WRITE setFogStart)
	Q_PROPERTY(float 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(float hoverHaloFillAlpha READ hoverHaloFillAlpha WRITE setHoverHaloFillAlpha)
	Q_PROPERTY(bool selectionHalo READ isSelectionHaloEnabled WRITE setSelectionHaloEnabled)
	Q_PROPERTY(QColor selectionHaloColor READ selectionHaloColor WRITE setSelectionHaloColor)
	Q_PROPERTY(float selectionHaloFillAlpha READ selectionHaloFillAlpha WRITE setSelectionHaloFillAlpha)
	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)

public:
	QGLView();
	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)

	void stop();
	void start(float freq = 60.);

	GLRendererBase * renderer();
	void setRenderer(GLRendererBase * r, GLRendererBase ** prev = nullptr);

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

	QColor ambientColor() const { return ambientColor_; }
	QColor fogColor() const { return fogColor_; }
	float fogDensity() const { return fogDensity_; }
	float fogStart() const { return fogStart_; }
	float fogEnd() const { return fogEnd_; }
	FogMode fogMode() const { return fogMode_; }
	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_; }
	float hoverHaloFillAlpha() const { return hoverHaloFill_; }
	bool isSelectionHaloEnabled() const { return selectionHalo_; }
	QColor selectionHaloColor() const { return selectionHaloColor_; }
	float 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(GLObjectBase * o);
	//	void addObject(GLObjectBase & o) {addObject(&o);}
	int objectsCount(bool all = false);
	void removeObject(GLObjectBase * o, bool inChildren = true);
	void removeObject(GLObjectBase & o, bool inChildren = true);
	void clearObjects(bool deleteAll = false);
	QList<GLObjectBase *> objects(bool all = false);

	int lightsCount() const;
	void removeLight(int index);
	void removeLight(Light * l);
	void clearLights(bool deleteAll = false);
	QList<Light *> lights() { return lights_; }

	void addTexture(const QString & path);
	void addAnimation(const QString & dir, const QString & 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);
	Light * light(const QString & name);

	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 selectObject(GLObjectBase * o);
	GLObjectBase * selectedObject() const { return sel_obj; }

	void glReleaseTextures(int channels = 8);
	QByteArray saveCamera();
	void restoreCamera(const QByteArray & ba);
	QByteArray saveFeatures();
	void restoreFeatures(const QByteArray & ba);


	GLfloat aspect, iaspect;
	QMatrix4x4 cur_mvpm;

protected:
	void render();
	void resizeEvent(QResizeEvent * e);

	void timerEvent(QTimerEvent *);
	void initialize();
	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 mouseDoubleClickEvent(QMouseEvent * e);

	void keyPressEvent(QKeyEvent * e);
	void keyReleaseEvent(QKeyEvent * e);
	void focusOutEvent(QFocusEvent *);

	void applyFog();
	void renderSelection();

	void checkCaps();
	void collectLights();

private:
	void objectDeleted(GLObjectBase * o);
	void collectObjectLights(GLObjectBase * o);
	void objectsCountInternal(int * cnt, GLObjectBase * where);
	void removeObjectInternal(GLObjectBase * o, GLObjectBase * where);
	void renderSingleSelection(GLObjectBase & o);
	// void renderSingleShadow(GLObjectBase & o);
	void renderHalo(const GLObjectBase * obj, const uint iid, const QColor & color, const float & fill);
	void reloadThisShaders();
	void processKeys();
	bool setupViewport();

	QPoint lastPos, downPos;
	GLObjectBase objects_;
	QList<Light *> lights_;
	//	uint cid;
	QHash<uint, GLObjectBase *> ids;
	QSet<int> keys_;
	FogMode fogMode_;
	QColor backColor_, fogColor_, ambientColor_, hoverHaloColor_, selectionHaloColor_;
	QElapsedTimer time, ktm_;
	GLint max_anisotropic, max_texture_chanels;
	GLObjectBase::RenderMode rmode;
	GLObjectBase *sel_obj, *hov_obj;
	GLFramebuffer fbo_selection;
	QOpenGLShaderProgram *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_;
	QSize prev_size;
	float lineWidth_;
	float fogDensity_, fogStart_, fogEnd_, fps_, fps_tm, hoverHaloFill_, selectionHaloFill_, m_motionBlurFactor;
	int timer, fps_cnt, sh_id_loc, deleting_;
	bool is_first_draw, is_init, fogEnabled_, lightEnabled_, grabMouse_, mouse_first, mouseRotate_, mouseSelect_, customMouseMove_;
	bool shaders_supported, changed_, cameraOrbit_, need_init_;
	bool hoverHalo_, selectionHalo_, shaders_bind, selecting_;

public slots:
	void setBackColor(const QColor & arg) { backColor_ = arg; }
	void setLineWidth(const float & arg) { lineWidth_ = arg; }
	void setFOV(const float & arg) { camera()->fov_ = arg; }
	void setDepthStart(const float & arg) { camera()->depth_start = arg; }
	void setDepthEnd(const float & arg) { camera()->depth_end = arg; }
	void setAmbientColor(const QColor & arg) { ambientColor_ = arg; }
	void setFogColor(const QColor & arg) { fogColor_ = arg; }
	void setFogDensity(const float & arg) { fogDensity_ = arg; }
	void setFogStart(const float & arg) { fogStart_ = arg; }
	void setFogEnd(const float & 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 float & arg) { hoverHaloFill_ = arg; }
	void setSelectionHaloEnabled(const bool & arg) { selectionHalo_ = arg; }
	void setSelectionHaloColor(const QColor & arg) { selectionHaloColor_ = arg; }
	void setSelectionHaloFillAlpha(const float & arg) { selectionHaloFill_ = arg; }

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

signals:
	void glBeforePaint();
	void glBeginPaint();
	void glPainting();
	void glEndPaint();
	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);
	void objectAdded(GLObjectBase *);
	void doubleClick();
};

#endif // QGLVIEW_H
