//	GLImmediateDrawer.h - OpenGL immediate 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 GLIMMEDIATEDRAWER_H_
#define GLIMMEDIATEDRAWER_H_

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

#include <map>

///	OpenGL immediate mode drawer.
class GLImmediateDrawer : public Drawer,
						  public TextLayout::MissingGlyphCallback
{
  public:
	///	Constructor.
	GLImmediateDrawer();
	///	Destructor.
	~GLImmediateDrawer();

	///	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.
	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.
	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;};

	///	Represents a single image.
	/*!
		This image may be an entire texture, or just part of a larger
		texture atlas.
	 */
	struct ImmediateImage : public Drawer::Image
	{
		///	The OpenGL texture this image has been loaded to.
		GLuint texture;
	};
	///	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);

	///	Represents a single shader.
	struct ShaderInternal
	{
		///	The shader's OpenGL identifier.
		GLuint shader;
		///	The shader's base texture uniform location.
		GLint baseTexLoc;
		///	The shader's (non-texture) uniform locations.
		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;
	};

	///	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, ImmediateImage> 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;

	///	The TextLayout object used to layout text.
	TextLayout textLayout;

	///	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 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;

	///	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;
};

#endif
