#ifndef Renderer_hpp
#define Renderer_hpp

#include "Context.hpp"

#include "Sampler.hpp"
#include "Matrix.hpp"
#include "State.hpp"
#include "FIFOCache.hpp"
#include "XVertex.hpp"

namespace swShader
{
	class RenderTarget;
	class Rasterizer;
	class Texture;
	class Clipper;
	class Viewport;
	class VertexBuffer;
	class IndexBuffer;
	class VertexShader;
	class PixelShader;
	class Blitter;

	class Renderer : public Context
	{
	public:
		Renderer(RenderTarget *renderTarget);

		~Renderer();

		void drawPrimitive(const VertexBuffer *VB, const IndexBuffer *IB);

		void setPixelShader(const char *pixelShaderFile);
		void setVertexShader(const char *vertexShaderFile);
			
		void setTextureMap(int sampler, Texture *texture);
		void releaseTextures();

		// Fixed-function pipeline
		void setTexCoordIndex(int stage, int texCoordIndex);
		void setStageOperation(int stage, Sampler::StageOperation stageOperation);
		void setFirstArgument(int stage, Sampler::SourceArgument firstArgument);
		void setSecondArgument(int stage, Sampler::SourceArgument secondArgument);
		void setThirdArgument(int stage, Sampler::SourceArgument thirdArgument);
		void setFirstModifier(int stage, Sampler::ArgumentModifier firstModifier);
		void setSecondModifier(int stage, Sampler::ArgumentModifier secondModifier);
		void setThirdModifier(int stage, Sampler::ArgumentModifier thirdModifier);
		void setDestinationArgument(int stage, Sampler::DestinationArgument destinationArgument);
		void setTextureFilter(int sampler, Sampler::FilterType textureFilter);
		void setAddressingMode(int sampler, Sampler::AddressingMode addressingMode);

		void setDepthCompare(DepthCompareMode depthCompareMode);
		void setAlphaCompare(AlphaCompareMode alphaCompareMode);
		void setDepthWriteEnable(bool depthWriteEnable);
		void setAlphaTestEnable(bool alphaTestEnable);
		void setCullMode(CullMode cullMode);

		void setShadingMode(ShadingMode shadingMode);
		void setSpecularEnable(bool specularEnable);

		void setAlphaBlendEnable(bool alphaBlendEnable);
		void setSourceBlendFactor(BlendFactor sourceBlendFactor);
		void setDestBlendFactor(BlendFactor destBlendFactor);

		void setAlphaReference(int alphaReference);

		// Programmable pipelines
		void setPixelShaderConstantF(int index, const float value[4]);

		void setVertexShaderConstantF(int index, const float value[4]);
		void setVertexShaderConstantI(int index, const int value[4]);
		void setVertexShaderConstantB(int index, const bool boolean);

		// Clipper
		void setFOV(float FOV);
		void setNearClip(float nearClip);
		void setFarClip(float farClip);

		void setClipFlags(int flags);
		void setClipPlane(int index, const float plane[4]);
		const float *getClipPlane(int index);

		// Transformations
		void setModelTransform(const Matrix &M);
		void setViewTransform(const Matrix &V);
		void setBaseTransform(const Matrix &B);

	private:
		const State &status() const;
		void update(FVFFlags FVF);
		void renderPolygon(XVertex **V, int n, FVFFlags FVF);

		char *pixelShaderFile;
		FIFOCache<State, PixelShader> *pixelShaderCache;
		PixelShader *pixelShader;

		char *vertexShaderFile;
		FIFOCache<char*, VertexShader> *vertexShaderCache;
		VertexShader *vertexShader;

		int top;
		int index[16];
		XVertex vertexCache[16];

		Clipper *clipper;
		Viewport *viewport;
		Rasterizer *rasterizer;
		Blitter *blitter;

		float W;   // Viewport width
		float H;   // Viewport height

		float tanFOV;
		float nearClip;
		float farClip;

		Matrix M;	// Model/Geometry/World matrix
		Matrix V;	// View/Camera/Eye matrix
		Matrix B;	// Base matrix
		Matrix P;	// Projection matrix

		Matrix PB;		// P * B
		Matrix PBV;		// P * B * V
		Matrix PBVM;	// P * B * V * M

		// User-defined clipping planes
		float plane[6][4];

		bool updateModelMatrix;
		bool updateViewMatrix;
		bool updateBaseMatrix;
		bool updateProjectionMatrix;

		bool updatePixelShader;
		bool updateVertexShader;

		bool updateClipper;
	};
}

#endif   // Renderer_hpp