/*
    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 "glcamera.h"
#include "glframebuffer.h"
#include "glprimitives.h"
#include "glscene.h"
#include "mouse_controller.h"
#include "openglwindow.h"
#include "qglengine_core_export.h"
#include "qglview_settings.h"
#include "renderer.h"

#include <QElapsedTimer>
#include <QMenu>

class QGLENGINE_CORE_EXPORT QGLView
	: public OpenGLWindow
	, public ParameteredObject {
	friend class Renderer;
	friend class RendererSelection;
	Q_OBJECT
	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 gamma READ gamma WRITE setGamma)
	Q_PROPERTY(bool autoExposure READ autoExposure WRITE setAutoExposure)
	Q_PROPERTY(QColor fogColor READ fogColor WRITE setFogColor)
	Q_PROPERTY(float fogDensity READ fogDensity WRITE setFogDensity)
	Q_PROPERTY(float fogDecay READ fogDecay WRITE setFogDecay)
	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(Scene::SelectionMode selectionMode READ selectionMode WRITE setSelectionMode)
	Q_PROPERTY(QSize textureMapSize READ textureMapSize WRITE setTextureMapSize)
	Q_PROPERTY(QSize shadowMapSize READ shadowMapSize WRITE setShadowMapSize)
	Q_PROPERTY(int softShadowsSamples READ softShadowsSamples WRITE setSoftShadowsSamples)
	Q_PROPERTY(float softShadowsQuality READ softShadowsQuality WRITE setSoftShadowsQuality)
	Q_PROPERTY(bool softShadows READ softShadows WRITE setSoftShadows)

public:
	QGLView();
	virtual ~QGLView();

	enum CameraLightMode {
		clmOff,
		clmAuto,
		clmOn,
	};
	enum RenderMode {
		rmPoint = GL_POINT,
		rmLine  = GL_LINE,
		rmFill  = GL_FILL
	};

	Q_ENUM(CameraLightMode)

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

	float lineWidth() const { return settings.lineWidth; }
	float FOV() const { return settings.FOV; }
	float depthStart() const { return settings.depthStart; }
	float currentFPS() const { return fps_; }
	float gamma() const { return settings.gamma; }
	bool autoExposure() const { return settings.autoExposure; }
	int maxAnisotropicLevel() const { return max_anisotropic; }
	QString environmentMapFile() const { return renderer_.tex_env.fileHDR(); }
	bool FXAA() const { return settings.FXAA; }
	void setFXAA(bool on) { settings.FXAA = on; }

	QColor fogColor() const { return settings.fogColor; }
	float fogDensity() const { return settings.fogDensity; }
	float fogDecay() const { return settings.fogDecay; }
	bool isGrabMouseEnabled() const { return mouse.isGrabMouseEnabled(); }
	bool isMouseRotateEnabled() const { return mouse.isMouseRotateEnabled(); }
	bool isMouseSelectionEnabled() const { return mouse.isMouseSelectionEnabled(); }
	bool isCameraOrbit() const { return mouse.isCameraOrbit(); }
	bool isHoverHaloEnabled() const { return settings.hoverHalo; }
	QColor hoverHaloColor() const { return settings.hoverHaloColor; }
	float hoverHaloFillAlpha() const { return settings.hoverHaloFill; }
	bool isSelectionHaloEnabled() const { return settings.selectionHalo; }
	QColor selectionHaloColor() const { return settings.selectionHaloColor; }
	float selectionHaloFillAlpha() const { return settings.selectionHaloFill; }

	int renderMode() const { return settings.render_mode; }
	void setRenderMode(int mode) { settings.render_mode = mode; }

	bool isServiceMode() const { return renderer_.edit_mode; }
	void setServiceMode(bool yes) { renderer_.edit_mode = yes; }


	Scene::SelectionMode selectionMode() const { return scene_->selectionMode(); }
	Qt::MouseButton selectionButton() const { return mouse.selectionButton(); }
	Qt::KeyboardModifier selectionModifier() const { return mouse.selectionModifier(); }

	void setSelectionMode(Scene::SelectionMode m) { scene_->setSelectionMode(m); }
	void setSelectionButton(Qt::MouseButton v) { mouse.setSelectionButton(v); }
	void setSelectionModifier(Qt::KeyboardModifier v) { mouse.setSelectionModifier(v); }

	void selectObject(ObjectBase * o, bool add_to_selection = false) { scene_->selectObject(o, add_to_selection); }
	void clearSelection() { scene_->clearSelection(); }
	ObjectBaseList selectedObjects(bool top_only = false) const { return scene_->selectedObjects(top_only); }
	QList<Light *> selectedLights() const;
	QList<Camera *> selectedCameras() const;
	ObjectBase * selectedObject() const { return scene_->selectedObject(); }

	TextureManager * textureManager() { return renderer_.textures_manager; }
	void reloadTextures();

	Scene * scene() { return scene_; }
	void focusOn(const Box3D & bb);
	void setCameraLightMode(CameraLightMode m) { renderer_.setCameraLightMode(m); }
	CameraLightMode cameraLightMode() const { return (CameraLightMode)renderer_.cameraLightMode(); }

	Camera * camera() { return camera_; }
	const Camera * camera() const { return camera_; }
	void setCamera(Camera * camera) { camera_ = camera; }
	void setDefaultCamera() { camera_ = default_camera; }
	bool isDefaultCamera() const { return camera_ == default_camera; }
	QByteArray saveCamera();
	void restoreCamera(const QByteArray & ba);

	QImage materialThumbnail(Material * m) { return renderer_.materialThumbnail(m); }
	void setCurrentAction(RendererService::HandleAction ha) { renderer_.rend_service.setCurrentAction(ha); }
	void setContextActions(QList<QAction *> al) {
		context_menu.clear();
		context_menu.addActions(al);
	}
	void popupMenu(const QPoint & pos, QAction * at = nullptr) { context_menu.popup(pos, at); }
	void setGrabImage(bool on) { grab_formats.setFlag(Renderer::gfImage, on); }
	void setGrabImageF(bool on) { grab_formats.setFlag(Renderer::gfFloat, on); }
	QFlags<Renderer::GrabFormat> grabFormats() const { return grab_formats; }
	void setGrabFormats(QFlags<Renderer::GrabFormat> f) { grab_formats = f; }
	QImage getImage() const { return renderer_.getImage(); }
	QVector<QVector4D> getImageF() const { return renderer_.getImageF(); }
	void setGrabImageMipmap(int level) { renderer_.grab_mipmap = level; }
	void setGrabImageFMipmap(int level) { renderer_.grab_mipmap_f = level; }

	void setShadowMapSize(QSize sz) { shadow_map_size = sz; }
	void setTextureMapSize(QSize sz);
	QSize shadowMapSize() const { return shadow_map_size; }
	QSize textureMapSize() const { return renderer_.maps_size; }

	void setFramebufferSize(QSize sz) { framebuffer_size = sz; }
	void resetFramebufferSize() { framebuffer_size = QSize(); }
	QSize framebufferSize() const { return framebuffer_size; }
	QSize actualFramebufferSize() const;

	int softShadowsSamples() const { return settings.soft_shadows_samples; }
	void setSoftShadowsSamples(int s) { settings.soft_shadows_samples = s; }
	float softShadowsQuality() const { return settings.soft_shadows_quality; }
	void setSoftShadowsQuality(float s) { settings.soft_shadows_quality = s; }
	bool softShadows() const { return settings.soft_shadows; }
	void setSoftShadows(bool on) { settings.soft_shadows = on; }
	bool shadows() const { return settings.shadows; }
	void setShadows(bool on) { settings.shadows = on; }

	QByteArray saveSetting() const;
	void loadSetting(QByteArray data);

	GLfloat aspect, iaspect;
	Renderer renderer_;

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

	void timerEvent(QTimerEvent *);
	void initialize();
	void resizeGL(int width, int height);
	void mousePressEvent(QMouseEvent * e) { mouse.mousePressEvent(e); }
	void mouseMoveEvent(QMouseEvent * e) { mouse.mouseMoveEvent(e); }
	void mouseReleaseEvent(QMouseEvent * e) { mouse.mouseReleaseEvent(e); }
	void wheelEvent(QWheelEvent * e) { mouse.wheelEvent(e); }
	void mouseDoubleClickEvent(QMouseEvent * e) { mouse.mouseDoubleClickEvent(e); }
	void leaveEvent(QEvent *);

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

	void checkCaps();

private:
	void processKeys();
	bool setupViewport();

	Scene * scene_;
	Camera *camera_, *default_camera;
	MouseController mouse;
	QMenu context_menu;
	QSet<int> keys_;
	QElapsedTimer time;
	GLint max_anisotropic = 1, max_texture_chanels = 8;
	QFlags<Renderer::GrabFormat> grab_formats;
	QSize prev_size, shadow_map_size, framebuffer_size;
	QGLViewSettings settings;
	float fps_ = 0.f, fps_tm = 0.f;
	float m_motionBlurFactor = 1.f;
	int timer = 0, fps_cnt = 0, sh_id_loc = 0;
	bool shaders_bind = false;
	bool is_init      = false;

private slots:
	void __destroyed();
	void __objectDeleted(ObjectBase * o);

public slots:
	void setLineWidth(const float & arg) { settings.lineWidth = arg; }
	void setFOV(const float & arg) { settings.FOV = arg; }
	void setDepthStart(const float & arg) { settings.depthStart = arg; }
	void setGamma(const float & arg) { settings.gamma = arg; }
	void setAutoExposure(bool arg) { settings.autoExposure = arg; }
	void setEnvironmentMapFile(QString file) {
		renderer_.tex_env.setFileHDR(file);
		renderer_.recreateMaterialThumbnails(true);
	}
	void setFogColor(const QColor & arg) { settings.fogColor = arg; }
	void setFogDensity(const float & arg) { settings.fogDensity = arg; }
	void setFogDecay(const float & arg) { settings.fogDecay = arg; }
	void setGrabMouseEnabled(const bool & arg) { mouse.setGrabMouseEnabled(arg); }
	void setMouseRotateEnabled(const bool & arg) { mouse.setMouseRotateEnabled(arg); }
	void setMouseSelectionEnabled(const bool & arg) { mouse.setMouseSelectionEnabled(arg); }
	void setCustomMouseMove(const bool & arg) { mouse.setCustomMouseMove(arg); }
	void setCameraOrbit(const bool & arg) { mouse.setCameraOrbit(arg); }
	void setHoverHaloEnabled(const bool & arg) { settings.hoverHalo = arg; }
	void setHoverHaloColor(const QColor & arg) { settings.hoverHaloColor = arg; }
	void setHoverHaloFillAlpha(const float & arg) { settings.hoverHaloFill = arg; }
	void setSelectionHaloEnabled(const bool & arg) { settings.selectionHalo = arg; }
	void setSelectionHaloColor(const QColor & arg) { settings.selectionHaloColor = arg; }
	void setSelectionHaloFillAlpha(const float & arg) { settings.selectionHaloFill = arg; }

	void reloadShaders() { renderer_.reloadShaders(); }

signals:
	void glBeginPaint();
	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(ObjectBase * cur, ObjectBase * prev);
	void selectionChanged();
	void objectsPositionChanged();
	void materialsChanged();
	void materialThumbnailCreated(Material *);
	void doubleClick();
	void settingsLoaded();
};

#endif // QGLVIEW_H
