//-----------------------------------------------------------------------------------------------
// Renderer.hpp
//
#pragma once
#ifndef INCLUDED_RENDERER
#define INCLUDED_RENDERER
#include "Engine/Core/PlatformDefines.hpp"
#if defined( GYPSY_ENABLE_RENDERER )

//-----------------------------------------------------------------------------------------------
#include "Engine/Core/EngineSubsystem.hpp"
#include "Engine/Renderer/Texture.hpp"
#include "Engine/Renderer/BitmapFont.hpp"


/////////////////////////////////////////////////////////////////////////////////////////////////
// Enumeration of (what I consider to be) hallmark OpenGL versions with significant
//	differences in feature sets.  DirectX and other platforms (e.g. consoles) should use
//	the highest capability enumeration whose primary feature sets they equal or exceed.
//
enum RendererCapabilityLevel
{
	RENDERER_CAPABILITY_OPENGL11,	// Fixed-function & power-of-two only, VAs but no VBOs
	RENDERER_CAPABILITY_OPENGL15,	// Fixed-function & power-of-two only, but with VBOs/IBOs
	RENDERER_CAPABILITY_OPENGL20,	// Shaders (GLSL 110) and arbitrary texture sizes
	RENDERER_CAPABILITY_OPENGL30,	// FBOs, Modern OpenGL & GLSL 130, OpenGL ES 2.0
	RENDERER_CAPABILITY_OPENGL31,	// Uniform Buffers, GLSL 140
	RENDERER_CAPABILITY_OPENGL33,	// D3D10, GLSL 330, Geometry shaders, attrib layout/locations
	RENDERER_CAPABILITY_OPENGL43,	// D3D11, GLSL 430, Tessellation & Compute shaders
	NUM_RENDERER_CAPABILITY_LEVELS
};


//-----------------------------------------------------------------------------------------------
typedef unsigned int ShaderID; // GLuint in OpenGL
typedef unsigned int ShaderProgramID; // GLuint in OpenGL
typedef int UniformLocation; // GLint in OpenGL
enum ShaderType // GLenum in OpenGL
{
	SHADER_TYPE_UNKNOWN = -1,
	SHADER_TYPE_VERTEX,
	SHADER_TYPE_FRAGMENT,
	NUM_SHADER_TYPES
};


//-----------------------------------------------------------------------------------------------
inline const char* GetNameForShaderType( ShaderType shaderType )
{
	switch( shaderType )
	{
		case SHADER_TYPE_VERTEX:	return "vertex";
		case SHADER_TYPE_FRAGMENT:	return "fragment";
		default:					return "UNKNOWN";
	}
}


//-----------------------------------------------------------------------------------------------
enum VertexAttrib
{
	VERTEX_ATTRIB_POSITION			= 0,	// "a_position"; vertex position in object space, triggers new vertex with glBegin/glVertexAttrib
	VERTEX_ATTRIB_COLOR				= 1,	// "a_color", used by most shaders; polygon/surface RGBA color at vertex
	VERTEX_ATTRIB_TEXCOORDS			= 2,	// "a_texCoords", used by most shaders; typically used for any of diffuse/specular/emissive/normal maps
	VERTEX_ATTRIB_NORMAL			= 3,	// "a_normal", used by Phong shaders; object-space normal vector direction at vertex
	VERTEX_ATTRIB_TANGENT			= 4,	// "a_tangent", used by Phong shaders; object-space direction +U texture coordinates are flowing
	VERTEX_ATTRIB_BITANGENT			= 5,	// "a_bitangent", used by Phong shaders; object-space direction +V texture coordinates are flowing
	VERTEX_ATTRIB_STRINGFRACTION	= 6,	// "a_stringFraction", used by Text shaders; 0.0 for vertexes at string start edge, 1.0 at end (linear)
	VERTEX_ATTRIB_CHARINDEX			= 7,	// "a_charIndex", used by Text shaders; [0,1) horizontally for 0th char, [1,2) for 1th char, etc. (nonlinear)
	VERTEX_ATTRIB_CELLHEIGHT		= 8,	// "a_cellHeight", used by Text shaders; 0.0 for vertexes at glyph bottoms, 1.0 at top
};


/////////////////////////////////////////////////////////////////////////////////////////////////
struct Vertex_PCT
{
	Vector3		m_position;
	Rgba		m_color;
	Vector2		m_texCoords;
};


/////////////////////////////////////////////////////////////////////////////////////////////////
class Shader
{
public:
	Shader( ShaderID shaderID, ShaderType shaderType, const std::string& shaderName, const std::string& shaderRelativePath, const std::string& shaderVersionString, bool hasGenericVertexAttribs );
	void LoadFromFile();
	std::string GetFileName() const;

public:														// If shader file is "Data/Shaders/Basic_Vertex_110.glsl"...
	std::string					m_shaderName;				// e.g. naked shader name, without type/version/extension, e.g. "Basic"
	std::string					m_shaderVersionString;		// e.g. Shader language version number as text, e.g. "110"
	std::string					m_shaderRelativePath;		// e.g. relative to Run_Platform/ directory, e.g. "Data/Shaders/"
	ShaderType					m_shaderType;
	ShaderID					m_shaderID;
	bool						m_hasGenericVertexAttribs;	// if true, shader is "modern" with custom vertex attribs "in" (use glVertexAttribPointer); if false, shader is "old" with fixed vertex attribs (use glColorPointer, etc.)
};


/////////////////////////////////////////////////////////////////////////////////////////////////
class NamedUniformLocation
{
	const char*						m_uniformName;
	CaseSensitiveStringHashValue	m_uniformNameHash;
	UniformLocation					m_uniformLocation;
};


/////////////////////////////////////////////////////////////////////////////////////////////////
class ShaderProgram
{
public:
	ShaderProgram( ShaderProgramID programID, const Shader& vertexShader, const Shader& fragmentShader );

public:
	const Shader*						m_vertexShader;
	const Shader*						m_fragmentShader;
	ShaderProgramID						m_programID;
	std::vector< NamedUniformLocation >	m_uniformLocations;
	UniformLocation						m_uniformLocation_objectToWorldMatrix;
	UniformLocation						m_uniformLocation_worldToClipMatrix;
	UniformLocation						m_uniformLocation_diffuseTexture;
	UniformLocation						m_uniformLocation_noiseTexture;
	UniformLocation						m_uniformLocation_time;
	UniformLocation						m_uniformLocation_effectStrength;
	UniformLocation						m_uniformLocation_effectScale;
	UniformLocation						m_uniformLocation_effectSpeed;
	UniformLocation						m_uniformLocation_edgeThreshold;
	UniformLocation						m_uniformLocation_softness;
};


/////////////////////////////////////////////////////////////////////////////////////////////////
class FramebufferObject
{
public:
	FramebufferObject( const IntVector2& renderTargetTexelSize, TextureFilterMode minFilter = TEXTURE_FILTER_LINEAR, TextureFilterMode magFilter = TEXTURE_FILTER_LINEAR );

public:
	IntVector2		m_renderTargetTexelSize;
	Texture*		m_colorTexture;
	Texture*		m_depthTexture;
	unsigned int	m_fboID; // TODO FIXME: Make this a GLuint and move it into FramebufferObject_OpenGL3, when we get around to making such a thing
};


/////////////////////////////////////////////////////////////////////////////////////////////////
class Renderer : public EngineSubsystem
{
public:
	static void Create();

public:
	virtual Texture*				CreateOrGetTextureFromFile( const char* imageFilePath, TextureFilterMode minFilter=TEXTURE_FILTER_LINEAR, TextureFilterMode magFilter=TEXTURE_FILTER_NEAREST, TextureEdgeMode edgeMode=TEXTURE_EDGE_REPEAT );
	virtual LogicalTexture			CreateOrGetLogicalTextureFromFile( const char* imageFilePath, const AABB2& texCoords=AABB2::ZERO_TO_ONE, TextureFilterMode minFilter=TEXTURE_FILTER_LINEAR, TextureFilterMode magFilter=TEXTURE_FILTER_NEAREST, TextureEdgeMode edgeMode=TEXTURE_EDGE_CLAMP );
	virtual LogicalTexture			CreateOrGetLogicalTextureFromFile( const char* imageFilePath, const IntVector2& sheetCoords, const IntVector2& sheetLayout, TextureFilterMode minFilter=TEXTURE_FILTER_LINEAR, TextureFilterMode magFilter=TEXTURE_FILTER_NEAREST, TextureEdgeMode edgeMode=TEXTURE_EDGE_CLAMP );
	virtual Texture*				CreateTextureFromData( const char* internalTextureName, unsigned char* imageData, const IntVector2& dimensions, int numChannels, TextureFilterMode minFilter=TEXTURE_FILTER_LINEAR, TextureFilterMode magFilter=TEXTURE_FILTER_NEAREST, TextureEdgeMode edgeMode=TEXTURE_EDGE_REPEAT ) = 0;
	virtual BitmapFont*				CreateOrGetFont( const char* bitmapFontName );
	virtual ShaderProgram*			CreateOrGetShaderProgram( const char* vertexShaderName, const char* fragmentShaderName );
	virtual FramebufferObject*		CreateFramebufferObject( const IntVector2& renderTargetTexelSize, TextureFilterMode minFilter = TEXTURE_FILTER_LINEAR, TextureFilterMode magFilter = TEXTURE_FILTER_LINEAR ) = 0;
	virtual void					SetDefaultFont( const char* defaultFontName );
	virtual BitmapFont*				GetDefaultFont() const { return m_defaultFont; }
	virtual Texture*				GetDefaultTexture() const { return m_plainWhiteTexture; }
	virtual Texture*				GetDefaultDiffuseMap() const { return m_plainWhiteTexture; }
	virtual Texture*				GetDefaultNormalMap() const { return m_defaultNormalMap; }
	virtual Texture*				GetDefaultSpecGlossEmitMap() const { return m_defaultSpecGlossEmitMap; }
	virtual Texture*				GetMissingTextureTexture() const { return m_missingTextureTexture; }
	virtual ShaderProgram*			GetBuiltInUnlitShaderProgram() const { return m_builtInShaderUnlit; }
	virtual const char*				GetAPIDescription() const = 0;
	virtual RendererCapabilityLevel	GetCapabilityLevel() const { return m_capabilityLevel; }
	virtual void					SetMaxCapabilityLevel( RendererCapabilityLevel maxCapabilityLevelToAllow ) { m_maxCapabilityLevel = maxCapabilityLevelToAllow; }
	virtual const char*				GetShaderFileExtension() const = 0;
	virtual void					FlushAndFinish() = 0;

	virtual void	ClearScreen( const Rgba& clearColor ) const = 0;
	virtual void	ClearDepth( float clearDepth ) const = 0;
	virtual void	ClearScreenAndDepth( const Rgba& clearColor, float clearDepth ) const = 0;
	virtual void	SetScreenVirtualOrtho( const Vector2& virtualScreenCoordSize, bool isOriginAtTop=false );
	virtual void	SetScreenVirtualOrtho( const Vector2& virtualScreenCoordsBottomLeft, const Vector2& virtualScreenCoordsTopRight ) = 0;
	virtual void	PushMatrix() = 0;
	virtual void	PopMatrix() = 0;
	virtual void	UseShaderProgram( const ShaderProgram* shaderProgram ) = 0;
	virtual void	UseFramebuffer( const FramebufferObject* framebufferObject ) = 0;
	virtual void	SetLineThickness( float lineThickness ) = 0;
	virtual void	BindTextureToSampler( const Texture* texture, int samplerIndex ) = 0;

	// Basic 2D drawing (inefficient in mass quantity, great for small/indie games, debugging, basic UI, etc.)
	virtual void	Draw2dLineSegment( const Vector2& startPos, const Vector2& endPos, const Rgba& startColor, const Rgba& endColor ) = 0;
	virtual void	Draw2dDisc( const Vector2& center, float radius, float numSides, const Rgba& color ) = 0;
	virtual void	Draw2dDiscOutline( const Vector2& center, float radius, float numSides, const Rgba& color ) = 0;
	virtual void	Draw2dAABB( const Vector2& mins, const Vector2& maxs, const Rgba& color ) = 0;
	virtual void	Draw2dAABB( const AABB2& bounds, const Rgba& color ) = 0;
	virtual void	Draw2dTexturedAABB( const AABB2& bounds, const Vector2& texCoordsAtMins, const Vector2& texCoordsAtMaxs, const Rgba& tint, const Texture& texture ) = 0;
	virtual void	Draw2dTexturedAABB( const AABB2& bounds, const Vector2& relativeTexCoordsAtMins, const Vector2& relativeTexCoordsAtMaxs, const Rgba& tint, const LogicalTexture& logicalTexture );
	virtual void	Draw2dConvexPoly( const Vector2& anchorPos, const Vector2* ccwVertexesRelativeToAnchor, int numVertexes, const Rgba& color ) = 0;
	virtual void	Draw2dTextGlyph( unsigned char glyphUCS, const Vector2& center, float cellHeight, const Rgba& tint, BitmapFont* font = nullptr );
	virtual void	Draw2dTextStringAtPosition( const char* latinChars, const Vector2& textMins, float cellHeight, const Rgba& tint, BitmapFont* font = nullptr ) = 0;
	virtual void	Draw2dTextString( const char* latinChars, const Vector2& anchorPos, float cellHeight, const Rgba& tint, Alignment2D anchorLocation = ALIGNMENT_2D_CENTER, BitmapFont* font = nullptr );
	virtual void	Draw2dTextStringInBox( const char* latinChars, const AABB2& box, float cellHeight, const Rgba& tint, Alignment2D alignmentInBox = ALIGNMENT_2D_CENTER, BitmapFont* font = nullptr );
	virtual void	Draw2dTextStringInBoxWordWrapped( const char* latinChars, const AABB2& box, float cellHeight, const Rgba& tint, float interiorPadding = 0.f, BitmapFont* font = nullptr );

	// Vertex array drawing
	virtual void	DrawVertexArrayLines( const Vertex_PCT* lineVertexes, int numVertexes ) = 0;
	virtual void	DrawVertexArrayTriangles( const Vertex_PCT* triangleVertexes, int numVertexes ) = 0;

protected:
	Renderer();
	virtual	~Renderer();
	virtual void			StartUp();
	virtual void			DetermineCapabilityLevel() = 0;
	virtual void			CreateBuiltInTextures();
	virtual void			CreateBuiltInShaders( RendererCapabilityLevel renderingCapabilityLevel ) = 0;
	virtual void			SwapBuffers() = 0;
	virtual Texture*		CreateTextureFromFile( const char* imageFilePath, TextureFilterMode minFilter, TextureFilterMode magFilter, TextureEdgeMode edgeMode ) = 0;
	virtual BitmapFont*		CreateBitmapFont( const char* bitmapFontName );
	virtual ShaderProgram*	CreateShaderProgramFromFiles( const char* vertexShaderName, const char* fragmentShaderName );
	virtual ShaderProgram*	CreateShaderProgramFromSource( const char* vertexShaderSource, const char* fragmentShaderSource, const char* shaderName, const char* shaderVersionString );
	virtual Shader*			CreateOrGetShader( const char* shaderName, ShaderType shaderType );
	virtual Shader*			CreateShader( ShaderType shaderType, const std::string& shaderName, const std::string& shaderRelativePath, const std::string& shaderVersionString );
	virtual void			GetLegalShaderVersionStringList( std::vector< std::string >& out_legalShaderVersionStrings ) = 0; // e.g. for OpenGL3.3: "330", "150", "140", "130", "120", "110"
	virtual Shader*			CompileShader( const char* shaderSource, ShaderType shaderType, const std::string& shaderName, const std::string& shaderRelativePath, const std::string& shaderVersionString ) = 0;
	virtual ShaderProgram*	LinkShaderProgram( const Shader& vertexShader, const Shader& fragmentShader ) = 0;

protected:
	std::map< HashedCaseInsensitiveString, Texture* >			m_textureRegistry;
	std::map< HashedCaseInsensitiveString, BitmapFont* >		m_fontRegistry;
	std::map< HashedCaseInsensitiveString, ShaderProgram* >		m_shaderProgramRegistry; // Fully-linked complete shader programs
	std::map< HashedCaseInsensitiveString, Shader* >			m_shaderRegistry; // Individual shader objects, (vertex, fragment, etc.)

public:
	MatrixStack44				m_worldToClipTransform;
	MatrixStack44				m_objectToWorldTransform;

protected:
	std::string					m_defaultFontName;
	BitmapFont*					m_defaultFont;
	Texture*					m_plainWhiteTexture;		// Default diffuse/albedo texture - 1x1 of Rgba(255,255,255)
	Texture*					m_defaultNormalMap;			// Default tangent-space normal map texture - 1x1 of Rgba(127,127,255) = xyz(0,0,1)
	Texture*					m_defaultSpecGlossEmitMap;	// Default specular-glossy-emissive texture - 1x1 of Rgba(127,127,0)
	Texture*					m_missingTextureTexture;	// Missing textures get this pointer instead - 4x4 magenta/black checkerboard
	ShaderProgram*				m_builtInShaderUnlit;		// Built-in shader for general unlit rendering (e.g. simple fixed-function pipeline)
	RendererCapabilityLevel		m_capabilityLevel;
	RendererCapabilityLevel		m_maxCapabilityLevel;

	// Renderer state - changing any of these may likely stall the graphics pipeline
public: // #FIXME TEMP HACK FOR #LD31
	const ShaderProgram*		m_currentShaderProgram;
	const FramebufferObject*	m_currentFramebufferObject;
	float						m_currentLineThickness;

	friend class Application;
};


//-----------------------------------------------------------------------------------------------
extern Renderer* theRenderer;


#endif // GYPSY_ENABLE_RENDERER
#endif // INCLUDED_RENDERER
