#include "Renderer.hpp"

#include "VertexBuffer.hpp"
#include "IndexBuffer.hpp"
#include "Error.hpp"
#include "Texture.hpp"
#include "Viewport.hpp"
#include "Clipper.hpp"

namespace swShader
{
	Renderer::Renderer(const RenderTarget *renderTarget)
	{
		PixelProcessor::setRenderTarget(renderTarget);

		batchSize = 16;
		triangleBatch = new XVertex[batchSize * 3];

		clipper = new Clipper();
		viewport = new Viewport(renderTarget);

		updateViewMatrix = true;
		updateBaseMatrix = true;
		updateProjectionMatrix = true;

		updateVertexShader = true;
		updateClipPlanes = true;
	}

	Renderer::~Renderer()
	{
		delete[] triangleBatch;
		triangleBatch = 0;

		delete clipper;
		clipper = 0;

		delete viewport;
		viewport = 0;

		releaseTextures();
	}

	void Renderer::drawPrimitive(const VertexBuffer *VB, const IndexBuffer *IB)
	{
		if(!clipper || !viewport) throw INTERNAL_ERROR;
		if(!VB || !IB) throw Error("No primitive specified");

		updateClipper();
		setViewport(viewport);

		setVertexBuffer(VB);
		VertexProcessor::clearCache();

		FVFFlags FVF = VertexProcessor::getOutputFormat();
		PixelProcessor::setVertexFormat(FVF);

		const float W = viewport->getWidth();
		const float H = viewport->getHeight();

		const float L = viewport->getLeft();
		const float T = viewport->getTop() + H;

		for(int i = 0; i < IB->numFaces; i += batchSize)
		{
			for(int j = 0; j < batchSize; j++)
			{
				const int k = i + j;
				if(k >= IB->numFaces) break;
			
				const IndexBuffer::Face &face = IB->face[k];

				triangleBatch[3 * j + 0] = processVertex(face[0]);
				triangleBatch[3 * j + 1] = processVertex(face[1]);
				triangleBatch[3 * j + 2] = processVertex(face[2]);
			}

			for(int j = 0; j < batchSize; j++)
			{
				if(i + j >= IB->numFaces) break;

				XVertex **V = clipper->clipTriangle(triangleBatch[3 * j + 0],
													triangleBatch[3 * j + 1],
													triangleBatch[3 * j + 2], FVF);
				int n = clipper->getNumVertices();
				if(n < 3) continue;

				for(int i = 0; i < n; i++)
				{
					XVertex &v = *V[i];

					const float RHW = 1.0f / v.w;
						
					v.x = L + W * v.x * RHW;
					v.y = T - H * v.y * RHW;
					v.z = v.z * RHW;
					v.w = RHW;

					for(int t = 0; t < FVF.textureCount(); t++)
					{
						v.T[t].u *= RHW;
						v.T[t].v *= RHW;
					}
				}

				renderPolygon(V, n, FVF);
			}
		}
	}

	void Renderer::setRenderTarget(const RenderTarget *renderTarget)
	{
		PixelProcessor::setRenderTarget(renderTarget);

		delete viewport;
		viewport = new Viewport(renderTarget);
	}

	void Renderer::updateClipper()
	{
		if(updateClipPlanes || updateProjectionMatrix || updateViewMatrix || updateBaseMatrix || updateVertexShader)
		{
			if(!clipper) throw INTERNAL_ERROR;

			int flags = clipper->getClipFlags();

			if(VertexProcessor::isFixedFunction())   // Clip plane in world space
			{
				const Matrix &X = getViewTransform();

				if(flags & Clipper::CLIP_PLANE0) clipper->setClipPlane(0, X * Plane(plane[0]));
				if(flags & Clipper::CLIP_PLANE1) clipper->setClipPlane(1, X * Plane(plane[1]));
				if(flags & Clipper::CLIP_PLANE2) clipper->setClipPlane(2, X * Plane(plane[2]));
				if(flags & Clipper::CLIP_PLANE3) clipper->setClipPlane(3, X * Plane(plane[3]));
				if(flags & Clipper::CLIP_PLANE4) clipper->setClipPlane(4, X * Plane(plane[4]));
				if(flags & Clipper::CLIP_PLANE5) clipper->setClipPlane(5, X * Plane(plane[5]));
			}
			else   // Clip plane in clip space
			{
				// Transform from [-1,1]x[-1,1]x[0,1] to [0,1]x[0,1]x[0,1]
				const Matrix X = Matrix(0.5, 0, 0, 0.5,
				                        0.5, 1, 0, 0.5,
				                        0,   0, 1, 0,
				                        0,   0, 0, 1);

				if(flags & Clipper::CLIP_PLANE0) clipper->setClipPlane(0, X * Plane(plane[0]));
				if(flags & Clipper::CLIP_PLANE1) clipper->setClipPlane(1, X * Plane(plane[1]));
				if(flags & Clipper::CLIP_PLANE2) clipper->setClipPlane(2, X * Plane(plane[2]));
				if(flags & Clipper::CLIP_PLANE3) clipper->setClipPlane(3, X * Plane(plane[3]));
				if(flags & Clipper::CLIP_PLANE4) clipper->setClipPlane(4, X * Plane(plane[4]));
				if(flags & Clipper::CLIP_PLANE5) clipper->setClipPlane(5, X * Plane(plane[5]));
			}

			updateClipPlanes = false;
		}
	}

	void Renderer::setPixelShader(const char *pixelShaderFile)
	{
		PixelProcessor::setShader(pixelShaderFile);
	}

	void Renderer::setVertexShader(const char *vertexShaderFile)
	{
		VertexProcessor::setShader(vertexShaderFile);
		updateVertexShader = true;
	}

	void Renderer::setTextureMap(int stage, Texture *texture)
	{
		PixelProcessor::setTextureMap(stage, texture);
	}

	void Renderer::setTexCoordIndex(int stage, int texCoordIndex)
	{
		PixelProcessor::setTexCoordIndex(stage, texCoordIndex);
	}

	void Renderer::releaseTextures()
	{
		PixelProcessor::releaseTextures();
	}

	void Renderer::setStageOperation(int stage, Sampler::StageOperation stageOperation)
	{
		PixelProcessor::setStageOperation(stage, stageOperation);
	}

	void Renderer::setFirstArgument(int stage, Sampler::SourceArgument firstArgument)
	{
		PixelProcessor::setFirstArgument(stage, firstArgument);
	}

	void Renderer::setSecondArgument(int stage, Sampler::SourceArgument secondArgument)
	{
		PixelProcessor::setSecondArgument(stage, secondArgument);
	}

	void Renderer::setThirdArgument(int stage, Sampler::SourceArgument thirdArgument)
	{
		PixelProcessor::setThirdArgument(stage, thirdArgument);
	}

	void Renderer::setFirstModifier(int stage, Sampler::ArgumentModifier firstModifier)
	{
		PixelProcessor::setFirstModifier(stage, firstModifier);
	}

	void Renderer::setSecondModifier(int stage, Sampler::ArgumentModifier secondModifier)
	{
		PixelProcessor::setSecondModifier(stage, secondModifier);
	}

	void Renderer::setThirdModifier(int stage, Sampler::ArgumentModifier thirdModifier)
	{
		PixelProcessor::setThirdModifier(stage, thirdModifier);
	}

	void Renderer::setDestinationArgument(int stage, Sampler::DestinationArgument destinationArgument)
	{
		PixelProcessor::setDestinationArgument(stage, destinationArgument);
	}

	void Renderer::setTextureFilter(int stage, Sampler::FilterType textureFilter)
	{
		PixelProcessor::setTextureFilter(stage, textureFilter);
	}

	void Renderer::setAddressingMode(int stage, Sampler::AddressingMode addressMode)
	{
		PixelProcessor::setAddressingMode(stage, addressMode);
	}

	void Renderer::setDepthCompare(DepthCompareMode depthCompareMode)
	{
		PixelProcessor::setDepthCompare(depthCompareMode);
	}

	void Renderer::setAlphaCompare(AlphaCompareMode alphaCompareMode)
	{
		PixelProcessor::setAlphaCompare(alphaCompareMode);
	}

	void Renderer::setDepthWriteEnable(bool depthWriteEnable)
	{
		PixelProcessor::setDepthWriteEnable(depthWriteEnable);
	}

	void Renderer::setAlphaTestEnable(bool alphaTestEnable)
	{
		PixelProcessor::setAlphaTestEnable(alphaTestEnable);
	}

	void Renderer::setCullMode(CullMode cullMode)
	{
		PixelProcessor::setCullMode(cullMode);
	}

	void Renderer::setShadingMode(ShadingMode shadingMode)
	{
		PixelProcessor::setShadingMode(shadingMode);
	}

	void Renderer::setLightEnable(int light, bool lightEnable)
	{
		VertexProcessor::setLightEnable(light, lightEnable);
	}

	void Renderer::setSpecularEnable(int light, bool specularEnable)
	{
		VertexProcessor::setSpecularEnable(light, specularEnable);
		PixelProcessor::setSpecularEnable(VertexProcessor::specularEnabled());
	}

	void Renderer::setAmbientLight(const Color<float> &ambientLight)
	{
		VertexProcessor::setAmbientLight(ambientLight);
	}

	void Renderer::setLightPosition(int light, const Point &lightPosition)
	{
		VertexProcessor::setLightPosition(light, lightPosition);
	}

	void Renderer::setLightColor(int light, const Color<float> &lightColor)
	{
		VertexProcessor::setLightColor(light, lightColor);
	}

	void Renderer::setLightAttenuation(int light, float constant, float linear, float quadratic)
	{
		VertexProcessor::setLightAttenuation(light, constant, linear, quadratic);
	}

	void Renderer::setLightRange(int light, float lightRange)
	{
		VertexProcessor::setLightRange(light, lightRange);
	}

	void Renderer::setMaterialEmission(const Color<float> &emission)
	{
		VertexProcessor::setMaterialEmission(emission);
	}

	void Renderer::setMaterialAmbient(const Color<float> &materialAmbient)
	{
		VertexProcessor::setMaterialAmbient(materialAmbient);
	}

	void Renderer::setMaterialDiffuse(const Color<float> &diffuseColor)
	{
		VertexProcessor::setMaterialDiffuse(diffuseColor);
	}

	void Renderer::setMaterialSpecular(const Color<float> &specularColor)
	{
		VertexProcessor::setMaterialSpecular(specularColor);
	}

	void Renderer::setMaterialShininess(float specularPowerR, float specularPowerG, float specularPowerB, float specularPowerA)
	{
		float specularPower[4];

		specularPower[0] = specularPowerR;
		specularPower[1] = specularPowerG;
		specularPower[2] = specularPowerB;
		specularPower[3] = specularPowerA;

		VertexProcessor::setMaterialShininess(specularPower);
	}

	void Renderer::setAlphaBlendEnable(bool alphaBlendEnable)
	{
		PixelProcessor::setAlphaBlendEnable(alphaBlendEnable);
	}

	void Renderer::setSourceBlendFactor(BlendFactor sourceBlendFactor)
	{
		PixelProcessor::setSourceBlendFactor(sourceBlendFactor);
	}

	void Renderer::setDestBlendFactor(BlendFactor destBlendFactor)
	{
		PixelProcessor::setDestBlendFactor(destBlendFactor);
	}

	void Renderer::setAlphaReference(int alphaReference)
	{
		PixelProcessor::setAlphaReference(alphaReference);
	}

	void Renderer::setPixelShaderConstantF(int index, const float value[4], int count)
	{
		for(int i = 0; i < count; i++)
		{
			PixelProcessor::setFloatConstant(index, value);
			value += 4;
		}
	}

	void Renderer::setPixelShaderConstant(int index, const Matrix &M)
	{
		setPixelShaderConstantF(index + 0, M[0]);
		setPixelShaderConstantF(index + 1, M[1]);
		setPixelShaderConstantF(index + 2, M[2]);
		setPixelShaderConstantF(index + 3, M[3]);
	}

	void Renderer::setPixelShaderConstant(int index, const Point &point)
	{
		float float4[4];

		float4[0] = point.x;
		float4[1] = point.y;
		float4[2] = point.z;
		float4[3] = 1;

		setPixelShaderConstantF(index, float4);
	}

	void Renderer::setPixelShaderConstant(int index, const Vector &vector)
	{
		float float4[4];

		float4[0] = vector.x;
		float4[1] = vector.y;
		float4[2] = vector.z;
		float4[3] = 0;

		setPixelShaderConstantF(index, float4);
	}

	void Renderer::setVertexShaderConstantF(int index, const float value[4], int count)
	{
		for(int i = 0; i < count; i++)
		{
			VertexProcessor::setFloatConstant(index, value);
			value += 4;
		}
	}

	void Renderer::setVertexShaderConstantI(int index, const int value[4], int count)
	{
		for(int i = 0; i < count; i++)
		{
			VertexProcessor::setIntegerConstant(index, value);
			value += 4;
		}
	}

	void Renderer::setVertexShaderConstantB(int index, const bool *boolean, int count)
	{
		for(int i = 0; i < count; i++)
		{
			VertexProcessor::setBooleanConstant(index, *boolean);
			boolean += 4;
		}
	}

	void Renderer::setVertexShaderConstantF(int index, const Matrix &M)
	{
		setVertexShaderConstantF(index + 0, M[0]);
		setVertexShaderConstantF(index + 1, M[1]);
		setVertexShaderConstantF(index + 2, M[2]);
		setVertexShaderConstantF(index + 3, M[3]);
	}

	void Renderer::setVertexShaderConstantF(int index, const Point &point)
	{
		VertexProcessor::setFloatConstant(index, point);
	}

	void Renderer::setVertexShaderConstantF(int index, const Vector &vector)
	{
		VertexProcessor::setFloatConstant(index, vector);
	}

	void Renderer::setModelMatrix(const Matrix &M)
	{
		VertexProcessor::setModelMatrix(M);
	}

	void Renderer::setViewMatrix(const Matrix &V)
	{
		VertexProcessor::setViewMatrix(V);
		updateViewMatrix = true;
	}

	void Renderer::setBaseMatrix(const Matrix &B)
	{
		VertexProcessor::setBaseMatrix(B);
		updateBaseMatrix = true;
	}

	const Matrix &Renderer::getModelTransform()
	{
		return VertexProcessor::getModelTransform(viewport);
	}

	const Matrix &Renderer::getViewTransform()
	{
		return VertexProcessor::getModelTransform(viewport);
	}

	void Renderer::setFOV(float FOV)
	{
		VertexProcessor::setFOV(FOV);
		updateProjectionMatrix = true;
	}

	void Renderer::setNearClip(float nearClip)
	{
		VertexProcessor::setNearClip(nearClip);
		updateProjectionMatrix = true;
	}

	void Renderer::setFarClip(float farClip)
	{
		VertexProcessor::setFarClip(farClip);
		updateProjectionMatrix = true;
	}

	void Renderer::setClipFlags(int flags)
	{
		if(!clipper) throw INTERNAL_ERROR;

		clipper->setClipFlags(flags);
	}

	void Renderer::setClipPlane(int index, const float plane[4])
	{
		if(index < 0 || index >= 6) throw Error("User-defined clipping plane index out of range [0, 5]");

		this->plane[index][0] = plane[0];
		this->plane[index][1] = plane[1];
		this->plane[index][2] = plane[2];
		this->plane[index][3] = plane[3];

		updateClipPlanes = true;
	}

	const float *Renderer::getClipPlane(int index)
	{
		if(index < 0 || index >= 6) throw Error("User-defined clipping plane index out of range [0, 5]");

		return plane[index];
	}
}