#ifndef VOLUMERENDERER_H
#define VOLUMERENDERER_H

#include <QObject>
#include <qopenglfunctions_3_3_core.h>
#include "volumeloader.h"

/*!
\file
This includes the public \ref VolumeRenderer class.
*/

/*! 
A state-based OpenGL renderer for simple volume rendering.
Nearly all functions assume a usable OpenGL context is available, the caller has to make sure that this is the case.
*/
class VolumeRenderer : public QObject, protected QOpenGLFunctions_3_3_Core
{
	Q_OBJECT

public:

	enum class ProjectionType
	{
		Perspective,
		Orthographic
	};

	/*!
	Sets up basic stuff. \ref initialize has to be called before the class is usable.
	The only method guaranteed to be usable before \ref initialize is \ref getDesiredFormat.
	*/
	explicit VolumeRenderer(QObject* parent = nullptr);
	~VolumeRenderer();

	/*!
	Returns the surface format the renderer would like to have.
	*/
	QSurfaceFormat getDesiredFormat() const;

	/*!
	Initializes the volume renderer, using the currently bound OpenGL context. 
	This loads all needed shaders, initializes buffers, FBOs etc.
	If this fails for some reason (unsupported GL version), false is returned and \ref error returns an error text.
	\note The \ref VolumeRenderWindow calls this in VoxelBlur.
	\returns true when all is correctly initialized.
	*/
	bool initialize();

	/*!
	Returns the current error text of the renderer.
	Currently only set when \ref initialize fails.
	*/
	inline const QString error() const { return m_errString; }

	/*!
	Draws the current state.
	The caller has to make sure all OpenGL stuff is valid (context, visible window etc.). 
	\note The \ref VolumeRenderWindow calls this in VoxelBlur.
	*/
	void drawCurrent();

	/*!
	Needs to be called with a valid GL context to finalize specific operations like volume loading.
	*/
	void update();

	/*!
	Call this to change the viewport and projection matrices.
	\note This does NOT emit the \ref requestDraw signal.
	*/
	void setSize(int width, int height);

	/*!
	Returns the current FPS. Only makes sense in animation mode. The value only uses the last frame, so it may be drastically inaccurate.
	*/
	float getFPS() const;

	/*!
	Gets the total number of slices that were processed in the last frame.
	*/
	int getLastSliceCount() const;

	/*!
	Gets the number of processed slices which were actually rendered. This number is smaller than \ref getLastSliceCount, and excludes slices with less than 3 intersections.
	*/
	int getLastRenderedSliceCount() const;

	/*!
	The \ref VolumeRenderer, with help of a \ref VolumeLoader, loads the volume metadata and then the volume specified in the file given.
	\sa VolumeLoader
	\param metadataFile a string identifying the metadata file that includes volume information
	\param loadedVolume if the volume loaded successfully, returns the metadata of the volume
	\returns true if loading ok, false if not
	*/
	bool loadVolume(const QString& metadataFile,VolumeMetaData* loadedVolume = nullptr);

	/*!
	Loads the volume specified by the given metadata information, using the \ref VolumeLoader.
	\param metadata The metadata which identify a volume file to load
	\returns true of loading ok
	*/
	bool loadVolume(VolumeMetaData& metadata);

	/*!
	Returns the currently loaded volume meta data, contains garbage if nothing loaded yet.
	*/
	VolumeMetaData getCurrentVolume() const;

signals:
	/*!
	Emitted when the camera distance is changed.
	*/
	void distanceChanged(float d);

	/*!
	Emitted when camera rotation is changed.
	*/
	void rotationChanged(float xRot, float yRot);

	/*!
	Emitted when the light parameters changed.
	*/
	void lightParamsChanged(float azimuth, float angle);

	/*!
	Emitted when the state of the renderer is changed in a way that would change the rendered image.
	Because the VolumeRenderer can not start rendering itself (it has no way to be sure the context is bound, that the window is visible etc), 
	it instead notifies interested object that a change has occurred.

	Things that cause the signal to be emitted include changes in camera parameters (like \ref setRotation or \ref setDistance).
	If another signal is associated with this changes (like \ref distanceChanged), it will always be emitted before \ref requestDraw is emitted.
	\note The \ref VolumeRenderWindow handles this in VoxelBlur.
	*/
	void requestDraw();

	/*!
	Like \ref requestDraw, but requests that the window call \ref update instead
	*/
	void requestUpdate();
public slots:
	/*!
	Sets the light parameters. The light source is directional, identified by azimuth angle around the Y vector and elevation angle around the local Z. 
	When both are 0, this corresponds to a light vector of \f$ (1,0,0) \f$.
	*/
	void setLightParameters(float azimuth, float angle);
	/*!
	\f$ \delta \f$-form of \ref setLightParameters which applies a change to the current values.
	*/
	void lightDelta(float azimuth, float angle);
	void setRotation(float x, float y);
	void rotationDelta(float x, float y);
	void setDistance(float d);
	void distanceDelta(float d);

	void setRenderBoundingBox(bool b);
	void setFixSlicingMatrix(bool b);
	void setShowLightDirection(bool b);
	
	void setSliceDelta(float sliceDelta);
	void setProjectionType(ProjectionType pt);
	void setLowerWindowLimit(float w);
	void setUpperWindowLimit(float w);

	void setSmoothVolume(bool b);
	void setGaussianSigma(float s);

	/*!
	Toggles the depth-of-field effect on/off
	*/
	void setApplyDOF(bool v);
	/*!
	Sets the distance to the focal plane, \f$ d \in [0,1]\f$.
	0 corresponds to the Z value nearest to the camera, 1 to the one farthest away
	*/
	void setFocalPlane(float d);

	/*!
	Sets the rate at which the blurring increases with distance to the focal plane. Should be larger than zero.
	*/
	void setBlurAmount(float R);
private slots:
	/*!
	Currently unused due to a bug in Qt 5.2 with AMD graphics cards.
	*/
	void processLogMessage(const QOpenGLDebugMessage& msg);
private:
	Q_DISABLE_COPY(VolumeRenderer)

	//private d reference, see volumerenderer_private.h
	class VolumeRendererPrivate& d;
	QString m_errString;

	/*!
	Tests if the current OpenGL context meets the requirements of the renderer.
	The most "modern" feature required are compute shaders.
	*/
	bool checkGLRequirements();

	/*!
		Helper function to create a OpenGL shader program, with vertex, fragment and optional geometry shader.
	*/
	QOpenGLShaderProgram* createShader(const QString& vShaderName, const QString& fShaderName, const QString& gShaderName="");
	/*!
		Helper function to create a OpenGL compute shader program.
	*/
	QOpenGLShaderProgram* createComputeShader(const QString& cShaderName);
	/*!
	Loads, compiles & links all shaders the renderer requires. They are loaded through the Qt resource system.
	*/
	bool initShaders();
	/*!
	Initializes VAOs/VBOs etc, for bounding box, slices ...
	*/
	bool initBuffers();
	/*!
	Initializes the required textures, currently rather empty because they are recreated in \ref processVolume.
	*/
	bool initTextures();

	/*!
	Creates/updates the required framebuffer objects to fit the current viewport size.
	*/
	void updateFBOs();
	
	//this stuff is in volumerenderer.cpp
	/*!
	Calculates a 3D Gaussian convolution kernel depending on current sigma. The kernel has equal size in all dimensions.
	\param size an output parameter, containing the size of the kernel in each dimension.
	\returns a 3D Gaussian convolution kernel in linear form
	*/
	QVector<float> setupGaussian(int& size);
	/*!
	Processes the raw volume data passed according to the current metadata and settings.
	This includes Gaussian filtering, Gradient calculation, data scaling and flipping.
	\param data the raw volume data
	*/
	void processVolume(const QByteArray& data);
	/*!
	Calculates a new projection matrix depending on current viewport parameters.
	*/
	void updateProjection();
	
	void traverseSlices(const QVector<QVector3D>& transformedBox, int count,float zStart, float zDelta);
	int traverseSlicesDOF(const QVector<QVector3D>& transformedBox, int count,float zStart, float zDelta,float focusZ,QOpenGLFramebufferObject** buffers);
	bool updateSliceGeometry(const QVector<QVector3D>& box, const float zPos, GLint& firstIdx, GLsizei& count);
	void drawVolume();
	void drawBoundingBox();
	void drawLine(const QVector3D& pointA,const QVector3D& pointB, const QVector3D& color);
};

#endif // VOLUMERENDERER_H
