//	GLESDrawer.h - OpenGL ES 2.0 mode drawer.
//  ----------------------------------------------------------------------------
//	This file is part of 'NiallsAVLib', base code for any kind of audiovisual
//	apps.
//	Copyright (C) 2012  Niall Moody
//	
//	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 GLESDrawer_H_
#define GLESDrawer_H_

#include "FontMetrics.h"
#include "GLHeaders.h"
#include "GLMatrix.h"
#include "Drawer.h"

#include <map>

///	OpenGL ES 2.0 drawer.
class GLESDrawer : public Drawer,
						  public TextLayout::MissingGlyphCallback
{
  public:
	///	Constructor.
	GLESDrawer();
	///	Destructor.
	~GLESDrawer();

	///	Handles all the OpenGL initialisation stuff.
	void initialise(int screenWidth,
					int screenHeight,
					const AppSettings& appSettings);

	///	Used to replace the font renderer for this drawer.
	/*!
		By default this is set to the FreetypeFontAtlas.
	 */
	void setFontRenderer(FontAtlas::Creator& renderer);

	///	Loads a single image from its raw(decoded) image data.
	void loadSingleImage(uint8_t *data,
						 int width,
						 int height,
						 const std::wstring& name);
	///	Loads a texture atlas from its raw(decoded) image data.
	void loadTextureAtlas(uint8_t *data,
						  int width,
						  int height,
						  const TextureAtlas& atlas);
	///	Releases the image associated with the passed-in name.
	void releaseImage(const std::wstring& name);

	///	Loads a shader.
	/*!
		Note: This drawer uses a default shader named "-default-shader-". If you
		want to override the default behaviour you *could* just load your own
		shader called "-default-shader-". You're probably better off giving it
		your own name and making use of useShader() though.
	 */
	bool loadShader(const std::wstring& vertex,
					const std::wstring& fragment,
					const std::wstring& name);
	///	Associates a shader parameter (uniform) with the current shader.
	void registerShaderParam(const std::wstring& name);
	///	Releases the named shader.
	void releaseShader(const std::wstring& name);

	///	Loads a truetype font.
	void loadFont(const std::wstring& name,
				  float size,
				  const std::wstring& identifier);
	///	Releases the named font.
	void releaseFont(const std::wstring& identifier);
	///	Used to load a glyph missing from a FontMetrics.
	FontMetrics::iterator loadMissingGlyph(wchar_t missingCharacter,
										   FontMetrics& metrics,
										   const std::wstring& fontId);

	///	Called at the start of a drawing frame.
	void beginDrawing();
	///	Called at the end of a drawing frame.
	void endDrawing();

	///	Returns the contents of the screen as a buffer of pixels.
	void getScreenPixels(std::vector<uint8_t>& pixelBuffer);

	///	Activates a shader to be used for the following operations.
	/*!
		Note: This drawer uses a default shader named "-default-shader-".
		Calling useShader(L""); will load the default shader.
	 */
	void useShader(const std::wstring& shader);
	///	Sets a shader parameter (uniform) for the current shader.
	void setShaderParam(const std::wstring& param, float val);

	///	Sets the blend mode to use.
	void setBlendMode(int val);
	///	Registers an additional blend mode.
	void registerCustomBlendMode(int identifier, CustomBlendMode *mode);

	///	Draws an image at the desired position.
	void drawImage(const std::wstring& name,
				   const TwoFloats& position,
				   const TwoFloats& size = TwoFloats(1.0f, 1.0f),
				   const ThreeFloats& colour = ThreeFloats(1.0f, 1.0f, 1.0f),
				   float alpha = 1.0f);
	///	Draws a rotated image at the desired position.
	void drawRotImage(const std::wstring& name,
					  const TwoFloats& position,
					  float angle,
					  const TwoFloats& pivot,
					  const TwoFloats& size = TwoFloats(1.0f, 1.0f),
					  const ThreeFloats& colour = ThreeFloats(1.0f, 1.0f, 1.0f),
					  float alpha = 1.0f);
	///	Draws part of an image at the desired position.
	void drawSubImage(const std::wstring& name,
					  const TwoFloats& position,
					  const TwoFloats& subPos,
					  const TwoFloats& subSize,
					  const TwoFloats& size = TwoFloats(1.0f, 1.0f),
					  const ThreeFloats& colour = ThreeFloats(1.0f, 1.0f, 1.0f),
					  float alpha = 1.0f);
	///	Draws a single image tiled multiple times within the desired rect.
	void drawTiledImage(const std::wstring& name,
						const TwoFloats& position,
						const TwoFloats& size,
						const TwoFloats& tilePos,
						const TwoFloats& tileNum,
						const ThreeFloats& colour,
						float alpha);
	///	Draws a string of text with the selected font.
	void drawText(const std::wstring& text,
				  const std::wstring& font,
				  const TwoFloats& position,
				  float boxWidth,
				  TextLayout::Justification justification = TextLayout::Left,
				  float size = 1.0f,
				  const ThreeFloats& colour = ThreeFloats(0.0f, 0.0f, 0.0f),
				  float alpha = 1.0f);
	///	Draws a string of text with the selected font, caching the TextLayout.
	void drawText(const std::wstring& text,
				  const std::wstring& font,
				  TextLayout& layout,
				  const TwoFloats& position,
				  float boxWidth,
				  TextLayout::Justification justification = TextLayout::Left,
				  float size = 1.0f,
				  const ThreeFloats& colour = ThreeFloats(0.0f, 0.0f, 0.0f),
				  float alpha = 1.0f);
	///	Draws a single line.
	void drawLine(const std::wstring& image,
				  const TwoFloats& start,
				  const TwoFloats& end,
				  const ThreeFloats& colour = ThreeFloats(0.0f, 0.0f, 0.0f),
				  float alpha = 1.0f);
	///	Draws a connected line segment, with an attempt at mitering.
	void drawConnectedLine(const std::wstring& image,
						   const std::vector<TwoFloats>& points,
						   const std::vector<float>& pointsScale,
						   const std::vector<ThreeFloats>& pointsColour,
						   const std::vector<float>& pointsAlpha);
	///	Fills a rectangle with the desired colour.
	void fillRect(const TwoFloats& position,
				  const TwoFloats& size,
				  const ThreeFloats& colour,
				  float alpha = 1.0f);
	///	Fills a rectangle with a horizontal gradient.
	void fillGradient(const TwoFloats& position,
					  const TwoFloats& size,
					  const ThreeFloats& colourTop,
					  const ThreeFloats& colourBottom,
					  float alphaTop = 1.0f,
					  float alphaBottom = 1.0f);

	///	Translates all subsequent drawing commands by the specified offset.
	void translate(const TwoFloats& offset);
	///	Scales all subsequent drawing commands by the specified amounts.
	void scale(const TwoFloats& size);
	///	Rotates all subsequent drawing commands by the specified angle.
	void rotate(float angle, const TwoFloats& pivot);

	///	Specifies a viewport to restrict drawing to.
	void setViewport(const TwoFloats& position, const TwoFloats& size);
	///	Resets the viewport.
	void resetViewport();

	///	Used to draw to a frame buffer instead of the screen.
	void startFrameBufferDrawing();
	///	Used to draw to a frame buffer instead of the screen.
	void endFrameBufferDrawing();
	///	Used to e.g. apply post-processing etc. to the contents of a frame buffer image.
	const std::wstring getFrameBufferImage() const {return L"-frame-buffer-image-";};

	///	Used to find out about the capabilities of the user's graphics card.
	bool canSupport(GraphicsCapabilities feature) const;

	///	Returns the size of the drawing area.
	const TwoFloats& getDrawingAreaSize() const {return drawingSize;};
	///	Returns the size of the window.
	const TwoFloats& getWindowSize() const {return windowSize;};

	///	Can be used to check whether GLESDrawer is supported at runtime.
	static bool isSupported();

	///	Represents a single image.
	/*!
		This image may be an entire texture, or just part of a larger
		texture atlas.
	 */
	struct ESImage : public Drawer::Image
	{
		///	The OpenGL texture this image has been loaded to.
		GLuint texture;
		///	The OpenGL vertex array object for this image.
		GLuint vertexArray;
		///	The OpenGL vertex buffer object for this image.
		GLuint vertexBuffer;
		///	The offset with the vertex array at which this image's vertex data starts.
		GLint arrayOffset;
	};
	///	Returns the internal image data for the named image.
	const Image * const getInternalImageData(const std::wstring& imageId) const;
  private:
	///	Helper method. Prints out the infolog for a shader when DebugStatements are turned on.
	void printInfoLog(GLuint shader);

	///	Helper method. Creates the VAO and TextLayoutData for a specified TextLayout.
	void createTextLayoutData(TextLayout *layout,
							  const std::vector<TextLayout::GlyphPos>& glyphs);

	///	Represents a single shader.
	struct ShaderInternal
	{
		///	The shader's OpenGL identifier.
		GLuint shader;

		///	Location of the projectionMatrix uniform.
		GLint projectionLoc;
		///	Location of the matrix uniform.
		GLint matrixLoc;

		///	Location of the colour uniform in our shader.
		GLint colourLoc;
		///	Location of the base texture sampler in our shader.
		GLint baseTextureLoc;
		///	Location of the "textureExists" uniform in our shader.
		/*!
			This is used to switch between textured rendering and solid colour
			rendering.
		 */
		GLint textureExistsLoc;
		///	Location of the "useVertexColour" uniform in our shader.
		/*!
			This is used to switch between per-vertex colouring and per-object
			colouring.
		 */
		GLint useVertexColourLoc;

		///	Any user-specified uniform locations in the shader.
		std::map<std::wstring, GLint> uniformLocs;
	};
	///	Represents a single font.
	struct FontInternal
	{
		///	The font metrics for this font.
		FontMetrics metrics;
		///	The font atlas for this font.
		FontAtlasPtr atlas;
		///	An OpenGL identifier pointing to the current atlas texture for this font.
		/*!
			This will get updated whenever we run out of space in the current
			atlas to fit all our glyphs in it. i.e. a single font may be made up
			of multiple textures, if we're trying to display a lot of foreign
			characters.

			This may need looked at, because it could get particularly
			inefficient when displaying certain languages (sigh).
		 */
		GLuint currentTexture;
		///	This contains all the textures we have loaded for this font.
		/*!
			So we can release them in releaseFont().
		 */
		std::vector<GLuint> textures;
	};
	///	Data used to draw cached TextLayouts in a single* glDrawArrays call.
	/*!
		* - Well a single call if only one texture atlas is used. It'll
		obviously have to be more than one call if the text requires more than
		one texture atlas.
	 */
	struct TextLayoutData
	{
		///	The VAO containing all the vertex data for this layout.
		GLuint textVAO;
		///	Used to garbage collect the VAO data on the GPU when it hasn't been used for a while.
		int gcCount;

		///	The data needed to draw a single texture's-worth of text.
		struct TextureData
		{
			///	The texture.
			GLuint texture;
			///	The offset into the VAO where the vertex data starts.
			GLint vaoOffset;
			///	The number of GLfloats needed to draw this texture's-worth of text.
			GLsizei vaoCount;
		};
		///	The texture and VAO data necessary to draw all the text.
		std::vector<TextureData> textureData;
	};

	///	Helper method. Draws a cached TextLayout.
	void drawCachedTextLayout(TextLayoutData& layoutData,
							  const TwoFloats& position,
							  float size,
							  const ThreeFloats& colour,
							  float alpha);

	///	Some constants.
	enum
	{
		///	The default size of the connected line buffers.
		ConnectedDefaultSize = 64
	};

	///	The size of the drawing area.
	TwoFloats drawingSize;
	///	The size of the window.
	TwoFloats windowSize;
	///	The position of our main drawing viewport.
	TwoFloats viewportPos;
	///	The size of our main drawing viewport.
	TwoFloats viewportSize;

	///	All the Images currently loaded onto the GPU.
	std::map<std::wstring, ESImage> images;
	///	All the shaders currently loaded onto the GPU.
	std::map<std::wstring, ShaderInternal> shaders;
	///	All the fonts currently loaded onto the GPU.
	std::map<std::wstring, FontInternal> fonts;

	///	Our projection matrix.
	GLMatrix projectionMatrix;
	///	Our modelview matrix.
	GLMatrix matrix;

	///	Location of the vertex position attribute in our shader.
	GLuint vertexPositionLoc;
	///	Location of the vertex colour attribute in our shader.
	GLuint vertexColourLoc;
	///	Location of the base texture coordinates attribute in our shader.
	GLuint baseTexCoordsLoc;

	///	The VAO used by drawSubImage() and drawTiledImage().
	GLuint texCoordsVAO;
	///	The texture coords VBO used by drawSubImage() and drawTiledImage().
	GLuint texCoordsVBO;

	///	The VAO used to draw connected lines.
	GLuint connectedVAO;
	///	The VBO used to draw connected lines.
	/*!
		This holds the vertex and colour data for the line, interleaved.
		Texture co-ordinates do not change, so they're held in a separate
		static VBO.
	 */
	GLuint connectedVBO;
	///	The texture VBO used to draw connected lines.
	GLuint connectedTexVBO;
	///	Holds the vertex and colour data for drawing connected lines.
	/*!
		This will change with every drawConnectedLine() call. We hold onto the
		data to avoid re-allocating the memory every call.
	 */
	std::vector<GLfloat> connectedData;

	///	The VAO used in fillRect() and fillGradient().
	GLuint rectVAO;
	///	The vertex colours VBO used in fillGradient().
	GLuint rectVBO;

	///	The TextLayout object used to layout (non-cached) text.
	TextLayout textLayout;
	///	VAO and texture data stored for all cached TextLayouts.
	std::map<TextLayout *, TextLayoutData> cachedTextLayoutData;

	///	The frame buffer object we're using.
	GLuint frameBufferObject;
	///	The last texture we bound.
	/*!
		This variable is used to ensure we skip consecutive glBindTexture()
		calls when it's the same texture we want to draw.
	 */
	GLuint lastTexture;
	///	The last VAO we bound.
	/*!
		This variable is used to ensure we skip consecutive glBindVertexArray()
		calls when it's the same object we want to draw.
	 */
	GLuint lastVAO;
	///	The current shader (will be empty if there isn't one).
	std::wstring currentShader;

	///	The font renderer creator.
	FontAtlas::Creator& fontAtlasCreator;

	///	Any registered custom blend modes.
	std::map<int, CustomBlendMode *> customBlendModes;
	///	Whether or not we want our textures to be interpolated.
	bool texInterp;

	///	Used to ensure GLEW is initialised for isSupported().
	static bool glewAlready;

	///	Image data used to draw lines (the default image).
	static const char* line_png;
	///	Image data used to draw lines (the default image).
    const int line_pngSize;// = 144;

	///	The default vertex shader as a string.
	static const wchar_t defaultVertexShader[];
	///	The default fragment shader as a string.
	static const wchar_t defaultFragmentShader[];
};

#endif
