/*
	ZRenderer.hpp
	Authors: Patrick Baggett	<ptbaggett@762studios.com>,
		     Chris Ertel		<crertel@762studios.com>,
			 James Russell		<jcrussell@762studios.com>
	Created: 3/20/2011

	Purpose:	
	
	Defines an interface for rendering to screen in graphics-library agnostic fashion.

	License:
	
	TODO
*/

#pragma once

#ifndef _ZRENDERER_HPP
#define _ZRENDERER_HPP

#include <ZRenderer/ZRendererBuild.hpp>

#include <SST/SST_WM.h>

#include <ZUtil/ZUtil.hpp>

#include <ZRenderer/ZDataBuffer.hpp>
#include <ZRenderer/ZRenderTarget.hpp>
#include <ZRenderer/ZFramebufferRenderTarget.hpp>
#include <ZRenderer/ZWindowRenderTarget.hpp>
#include <ZRenderer/ZTexture.hpp>
#include <ZRenderer/ZSampler.hpp>
#include <ZRenderer/ZShader.hpp>
#include <ZRenderer/ZShaderProgram.hpp>
#include <ZRenderer/ZShaderParams.hpp>
#include <ZRenderer/ZVertexParams.hpp>
#include <ZRenderer/ZIndexParams.hpp>

//Forward Declarations
class ZRenderer;

//Typedef for a frame context (opaque handle)
typedef void* ZFrameContext;

//Rendering phases the ZRenderer can exist in
enum ZRenderPhase
{
	ZRP_UNINITIALIZED,		//Renderer has yet to be initialized
	ZRP_IDLE,				//Renderer is idle (no frame contexts currently created)
	ZRP_FRAME_ACCEPTING,	//Renderer is accepting SubmitDrawData calls (frame contexts have been created)
	ZRP_FRAME_RENDERING,	//Renderer is rendering Geometry (a frame context is rendering)
	ZRP_SIZE
};

//Render State Structure, used to set up a render state for SubmitDrawData calls
struct ZRenderState
{
public:
	//The default constructor for render state initializes to a sane configuration
	ZRenderState()
		: EnableStencilTest(false),
		  EnableBlend(true),
		    SourceBlendFunc(SBFV_SRC_ALPHA),
			DestBlendFunc(DBFV_ONE_MINUS_SRC_ALPHA),
			blendR(0), blendG(0), blendB(0), blendA(0),
		  EnableCullTest(false),
		  EnableDepthTest(true),
		    DepthFunc(DFV_LESS_THAN),
		  EnableDepthWrite(true) { }

	/////////////////////////////////
	/* Draw Sort Relevant Settings */
	/////////////////////////////////
	
	// BLENDING
	uint32_t EnableBlend:1;				//Enables Blending
	uint32_t SourceBlendFunc:4;			//Specifies Source Blending Function
	uint32_t DestBlendFunc:3;			//Specifies Destination Blending Function

	enum SourceBlendFuncValue		//Values for the Source Blend Function
	{
		SBFV_ZERO,
		SBFV_ONE,
		SBFV_SRC_COLOR,
		SBFV_ONE_MINUS_SRC_COLOR,
		SBFV_DST_COLOR,
		SBFV_ONE_MINUS_DST_COLOR,
		SBFV_SRC_ALPHA,
		SBFV_ONE_MINUS_SRC_ALPHA,
		SBFV_DST_ALPHA,
		SBFV_ONE_MINUS_DST_ALPHA,
		SBFV_CONSTANT_COLOR,
		SBFV_ONE_MINUS_CONSTANT_COLOR,
		SBFV_CONSTANT_ALPHA,
		SBFV_ONE_MINUS_CONSTANT_ALPHA,
		SBFV_SRC_ALPHA_SATURATE,
		SBFV_SIZE
	};

	enum DestBlendFuncValue			//Values for the Destination Blend Function
	{
		DBFV_ZERO,
		DBFV_ONE,
		DBFV_SRC_COLOR,
		DBFV_ONE_MINUS_SRC_COLOR,
		DBFV_DST_COLOR,
		DBFV_ONE_MINUS_DST_COLOR,
		DBFV_SRC_ALPHA,
		DBFV_ONE_MINUS_SRC_ALPHA,
		DBFV_DST_ALPHA,
		DBFV_ONE_MINUS_DST_ALPHA,
		DBFV_CONSTANT_COLOR,
		DBFV_ONE_MINUS_CONSTANT_COLOR,
		DBFV_CONSTANT_ALPHA,
		DBFV_ONE_MINUS_CONSTANT_ALPHA,
		DBFV_SIZE
	};

	float blendR;						//Red value used when blending needs a constant color
	float blendG;						//Green value used when blending needs a constant color
	float blendB;						//Blue value used when blending needs a constant color
	float blendA;						//Alpha value used when blending needs a constant color

	// DEPTH
	uint32_t EnableDepthTest:1;			//Bit Enables Depth Testing
	uint32_t EnableDepthWrite:1;		//Sets Depth Buffer to Read Only (Depth Values are not written)
	uint32_t DepthFunc:3;				//Specifies Depth Function

		enum DepthFuncValue				//Values for the Depth Function
		{
			DFV_NEVER,
			DFV_LESS_THAN,
			DFV_EQUAL,
			DFV_LESS_THAN_EQUAL,
			DFV_GREATER_THAN,
			DFV_NOT_EQUAL,
			DFV_GREATER_THAN_EQUAL,
			DFV_ALWAYS,
			DFV_SIZE
		};

	// CULLING
	uint32_t EnableCullTest:1;			//Enable or disable face culling.
	uint32_t CullMode:2;				//Sets up front/back/both culling

		enum CullModeValue
		{
			CMV_FRONT,
			CMV_BACK,
			CMV_FRONT_AND_BACK,
			CMV_SIZE
		};

	uint32_t CullWinding:1;				//Sets winding for culling

		enum CullWindingValue
		{
			CWV_CW,
			CWV_CCW,
			CWV_SIZE
		};

	// STENCIL
	uint32_t EnableStencilTest:1;				//Bit Enables Stencil Testing
	uint32_t StencilTestFuncFront:4;			//Specifies the stencil comparisons
	uint32_t StencilTestMaskFront:8;			//Mask for front facing stencil post-op
	uint32_t StencilTestReferenceFront:8;		//Reference value for front facing stencil post-op
	uint32_t StencilTestFuncBack:4;				//Specifies the stencil comparisons
	uint32_t StencilTestMaskBack:8;				//Mask for back facing stencil post-op
	uint32_t StencilTestReferenceBack:8;		//Reference value for back facing stencil post-op

	enum StencilComparisonsValue	//Values for the stencil comparisons
	{
		SCV_NEVER,
		SCV_ALWAYS,
		SCV_LESS,
		SCV_LEQUAL,
		SCV_EQUAL,
		SCV_GEQUAL,
		SCV_GREATER,
		SCV_NOTEQUAL,
		SCV_SIZE
	};

	uint32_t StencilOpStencilFailFront:3;				//Specifies the stencil operation when stencil test fails for front-facing polys
	uint32_t StencilOpStencilPassDepthFailFront:3;		//Specifies the stencil operation when stencil test succeeds and depth test fails fails for front-facing polys
	uint32_t StencilOpStencilPassDepthPassFront:3;		//Specifies the stencil operation when stencil and depth tests pass fails for front-facing polys
	uint32_t StencilOpStencilFailBack:3;				//Specifies the stencil operation when stencil test fails for back-facing polys
	uint32_t StencilOpStencilPassDepthFailBack:3;		//Specifies the stencil operation when stencil test succeeds and depth test fails for back-facing polys
	uint32_t StencilOpStencilPassDepthPassBack:3;		//Specifies the stencil operation when stencil and depth tests pass for back-facing polys

	enum StencilOperationValue		//Values for the stencil operations
	{
		SOV_KEEP,
		SOV_ZERO,
		SOV_REPLACE,
		SOV_INCR,
		SOV_INCR_WRAP,
		SOV_DECR,
		SOV_DECR_WRAP,
		SOV_INVERT,
		SOV_SIZE
	};
};

class ZRenderer : public ZThread
{
protected:
	//Default Constructor
	ZRenderer() : ZThread("ZRenderer Thread") { }

public:
	//Virtual Destructor
	virtual ~ZRenderer() { }

	//////////////////////////
	/* Lifecycle Operations */
	//////////////////////////

	/*
	virtual public ZRenderer::Init
	
	Initializes the Renderer for use as a long running subsystem.  After this call, the 
	render thread will be running.
	
	@return (bool)
	@context (all)
	*/
	virtual bool Init() = 0;

	/*
	virtual public ZRenderer::Shutdown
	
	Shuts down the system and stops the render thread.
	
	@return (void)
	@context (all)
	*/
	virtual void Shutdown() = 0;

	///////////////////////////
	/* Renderer Data Getters */
	///////////////////////////

	/*
	virtual public ZRenderer::GetCapabilities
	
	Returns a map describing the capabilities and configuration of this renderer.  All
	renderer implementations must map the following keys:

		gl				{ directx, opengl }
		gl.version		{ 9.0c, 10.0, 10.1, 11.0, 11.1, 2.1, 3.1, 3.2, 3.3 ... }
			: version number should always have major.minor format and should be the highest version required
		gl.shaderlang	{ hlsl_40, hlsl_30, hlsl, glsl_14, glsl_13, glsl_12, glsl_11, glsl, ... }
			: shading languages accepted should be listed in a comma separated list in order of preference

		data_buffer.block_alignment { integer }
			: alignment requirement for buffer blocks

		frame_buffer_render_target.max_color_attachments { integer }
			: maximum number of color buffer attachments

	Other keys may be supported depending upon the implementation.
	
	@return (const ZHashMap<ZString, ZString>&) - map of capabilities
	@context (all)
	*/
	virtual const ZHashMap<ZString, ZString>& GetCapabilities() = 0;

	/*
	virtual public ZRenderer::GetRenderPhase
	
	Gets the current rendering phase.
	
	@return (ZRenderPhase) - current rendering phase
	@context (all)
	*/
	virtual ZRenderPhase GetRenderPhase() = 0;

	//////////////////////////
	/* Logistics Operations */
	//////////////////////////

	/*
	virtual public ZRenderer::CreateDataBuffer
	
	Creates a data buffer which contains data stored in graphics memory.  The data
	buffer is created but not initialized with values (use FillBuffer or MapBuffer).

	Keep in mind when allocating a buffer that defines block data that determining the 
	required size to fit all blocks includes taking into account alignment requirements.
	The alignment requirement for block data for this renderer can be determined through
	the capabilities map with key 'buffer.block_alignment'.
	
	@param _type - the type of buffer to allocate
	@param _usage - the usage pattern the buffer will follow
	@param _size - the size (in bytes) to allocate
	@return (ZDataBuffer) - new data buffer object, NULL if not successful
	@context (all)
	*/
	virtual ZPtr<ZDataBuffer> CreateDataBuffer(ZDataBufferType _type,
											   ZDataBufferUsage _usage, 
											   size_t _size
											   ) = 0;

	/*
	virtual public ZRenderer::CreateDrawParams
	
	Creates a draw parameters binding class, which is provided to the draw calls and
	is used to bind data to shader variables.
	
	@param _type - the type of draw parameters binding
	@return (ZPtr<ZDrawParams>) - new draw parameters object, NULL if not successful
	@context (all)
	*/
	virtual ZPtr<ZDrawParams> CreateDrawParams(ZDrawParamsType _type) = 0;

	/*
	virtual public ZRenderer::CreateFrameBufferRenderTarget
	 
	Function to create a framebuffer-backed render target.
	 
	@param _width - number of pixels wide to make the frame buffer
	@param _height - number of pixels tall to make the frame buffer
	@return (ZPtr<ZFramebufferRenderTarget>) - new frame buffer render target, NULL if not successful
	@context (all)
	*/
	virtual ZPtr<ZFramebufferRenderTarget> CreateFrameBufferRenderTarget(size_t _width, size_t _height) = 0;
	
	/*
	virtual public ZRenderer::CreateRenderBuffer
	 
	Function to create a render buffer for use in a frame buffer render target.
	 
	@param _type - the type of render buffer this will be
	@param _width - number of pixels wide to make the render buffer
	@param _height - number of pixels tall to make the render buffer
	@return (ZPtr<ZRenderBuffer>) - new render buffer, NULL if not successful
	@context (all)
	*/
	virtual ZPtr<ZRenderBuffer> CreateRenderBuffer(ZRenderBufferType _type, size_t _width, size_t _height) = 0;

	/*
	virtual public ZRenderer::CreateSampler
	
	Creates a sampler object to use as a view onto a texture.
	
	@return (ZPtr<ZSampler>) - new sampler object, NULL if unsuccessful
	@context (all)
	*/
	virtual ZPtr<ZSampler> CreateSampler() = 0;

	/*
	virtual public ZRenderer::CreateShader
	 
	Function to create a shader object for the renderer.
	 
	@param _type - type of shader to create:
					ZST_SOFT	: Soft shaders
					ZST_VERTEX	: Vertex shaders
					ZST_HULL	: Hull shaders
					ZST_DOMAIN	: Domain shaders
					ZST_GEOMETRY: Geometry shaders
					ZST_FRAGMENT: Fragment (pixel) shaders

	@return (ZPtr<ZShader>) - the created shader, NULL if not successful
	@context (all)
	*/
	virtual ZPtr<ZShader> CreateShader(ZShaderType _type) = 0;

	/*
	virtual public ZRenderer::CreateShaderProgram
	 
	Function to create a shader program.
	 
	 @return (ZPtr<ZShaderProgram>) - new shader program, NULL if not successful
	 @context (all)
	*/
	virtual ZPtr<ZShaderProgram> CreateShaderProgram() = 0;

	/*
	virtual public ZRenderer::CreateTexture
	
	Creates a texture object.

	In the case of cube map textures, the bitmap format describes a single 'side'
	of the cube map texture.

	If the bitmap provided has NULL for data, then the texture storage is initialized
	but no data is copied.
	
	@param _type - the type of texture
	@param _format - the texture internal storage format
	@param _usage - the usage type of the texture
	@param _bitmap - bitmap structure containing image format and size, as well as data (can be NULL)
	@param _generateMipmaps - indicating whether or not we should generate mipmaps
	@return (ZPtr<ZTexture>) - new texture object, NULL if not successful
	@context (all)
	*/
	virtual ZPtr<ZTexture> CreateTexture(ZTextureType _type, 
										 ZTextureFormat _format,
										 ZTextureUsage _usage,
										 const ZBitmap& _bitmap,
										 bool _generateMipmaps) = 0;

	/*
	virtual public ZRenderer::CreateWindowRenderTarget
	
	Creates a window render target.
	
	@param _window - the window handle (created using libsst-wm)
	@param _screenIndex - the screen index bound to the window
	@return (ZPtr<ZRenderTarget>) - new window render target, NULL if not successful
	@context (all)
	*/
	virtual ZPtr<ZRenderTarget> CreateWindowRenderTarget(SST_Window _window, 
														 size_t _screenIndex
														 ) = 0;

	////////////////////////
	/* Frame Data Getters */
	////////////////////////

	/*
	virtual public ZRenderer::GetFrameRenderTarget

	Gets the render target set for the given frame context.

	@param _frameContext - frame context
	@return (ZRenderTarget*) - the render target for the given context
	@context (all)
	*/
	virtual ZRenderTarget* GetFrameRenderTarget(ZFrameContext _frameContext) = 0;

	//////////////////////////
	/* Rendering Operations */
	//////////////////////////

	/*
	The renderer can maintain a few different states, each of which is described by an enumeration.  The various
	rendering operations affect this state, and thread synchronization calls such as WaitFrameStart and WaitFrameEnd 
	will wait until events are triggered after state changes, as follows.

		      BeginFrame()
		 		   |    SubmitDrawData()
				   |		   |	 RenderFrame()			 
				   |   +-------+-------+   |			    					 		
				   |   |	   |	   |   |											 			
	---------------+---+-------+-------+---+-----------------------+---------------
	===============|///////////////////////|///////////////////////|===============
	 ZRP_IDLE ... =|/ ZRP_FRAME_ACCEPTING /|/ ZRP_FRAME_RENDERING /|= ZRP_IDLE ...
	===============|///////////////////////|///////////////////////|===============
										   |					   |
				   						   +-> WaitFrameStart()	   +-> WaitFrameEnd()
				   						   ^					   |
										   |    Frame to Render?   |
										   +<----------------------+ 

	Because of the multi threaded nature of ZRenderer, multiple threads can call SubmitDrawData to populate
	the render lists full of buffered data to draw.  A single call should be placed to BeginFrame for each 
	frame context that is desired, and a single call to RenderFrame should occur for each frame context that has
	been created.

	It is guaranteed that order will be maintained between calls to RenderFrame, meaning if RenderFrame is called
	for frame context 0, and then afterwards RenderFrame is called for frame context 1, frame context 0 is guaranteed
	to render in its entirety before frame context 0 is rendered.
	*/

	/*
	virtual public ZRenderer::BeginFrame
	
	Tells the renderer that threads will begin providing draw data for a frame of rendering.  The value returned
	is later used as a frame context for render calls.

	BeginFrame can be called multiple times, each time returning a new frame context that can be provided
	for draw calls.

	BeginFrame should be called once for as many frame contexts there will be before beginning render calls
	for any of them.  Calling BeginFrame after other frames have begun rendering results in undefined 
	behavior.  Frame contexts should never be re-used across multiple render cycles - always ask for a new one.

	@param _canvas - the canvas to render to
	@return (ZFrameContext) - the frame context that has been created
	@context (all)
	*/
	virtual ZFrameContext BeginFrame(ZPtr<ZRenderTarget> _canvas) = 0;

	/*
	virtual public ZRenderer::EndFrame

	TODO

	@param _frameContext - the frame context we are done submitting draw data to
	@return (void)
	@context (all)
	*/
	virtual void EndFrame(ZFrameContext _frameContext) = 0;

	/*
	virtual public ZRenderer::SubmitDrawData
	
	Draws geometry defined in a DataBuffer (Queues them up for rendering).
	
	@param _frameContext - the frame context to provide data for
	@param _shaderProgram - the shader program to draw with
	@param _shaderParams - the shader parameters binding
	@param _vertexParams - the vertex parameters binding
	@param _indexParams - the index parameters binding
	@param _renderState - the render state to render the geometry with
	@param _drawGroup - the draw group to render this call with; objects with lower draw group
		are rendered before objects with higher draw group
	@return (void)
	@context (all)
	*/
	virtual void SubmitDrawData(ZFrameContext _frameContext,
								ZPtr<ZShaderProgram> _shaderProgram,
								ZPtr<ZShaderParams> _shaderParams,
								ZPtr<ZVertexParams> _vertexParams,
								ZPtr<ZIndexParams> _indexParams,
								const ZRenderState& _renderState,
								int _drawGroup = 0
								) = 0;

	/*
	virtual public ZRenderer::RenderFrame
	
	Renders the frame context.

	@param _frameContext - the frame context to render
	@return (void)
	@context (all)
	*/
	virtual void RenderFrame(ZFrameContext _frameContext) = 0;

	/*
	virtual public ZRenderer::WaitFrameStart

	Calling thread waits until RenderFrame is called for the given frame context.
	
	@param _frameContext - the frame context to wait for
	@return (void)
	@context (all)
	*/
	virtual void WaitFrameStart(ZFrameContext _frameContext) = 0;

	/*
	virtual public ZRenderer::WaitFrameEnd
	
	Calling thread waits until after the render thread has finished rendering a frame.
	
	@param _frameContext - the frame context to wait for
	@return (void)
	@context (all)
	*/
	virtual void WaitFrameEnd(ZFrameContext _frameContext) = 0;
};

#endif
