#include "VertexProcessor.hpp"

#include "Viewport.hpp"
#include "Math.hpp"
#include "VertexBuffer.hpp"
#include "VS_2_0Shader.hpp"
#include "VertexPipeline.hpp"

namespace swShader
{
	VertexProcessor::State::State()
	{
		shaderFile = 0;
		pipelineState = 0;
		stamp = 0;
	}

	VertexProcessor::State::State(const State &state)
	{
		shaderFile = strdup(state.shaderFile);
		pipelineState = state.pipelineState;
		updateStamp();
	}

	VertexProcessor::State &VertexProcessor::State::operator=(const State &state)
	{
		free(shaderFile);
		shaderFile = strdup(state.shaderFile);
		pipelineState = state.pipelineState;
		updateStamp();

		return *this;
	}

	VertexProcessor::State::~State()
	{
		free(shaderFile);
		shaderFile = 0;
	}

	bool VertexProcessor::State::operator==(const State &state) const
	{
		if(stamp != state.stamp) return false;
		if(!shaderFile ^ !state.shaderFile) return false;
		if(shaderFile && state.shaderFile && strcmp(shaderFile, state.shaderFile) != 0) return false;
		if(pipelineState != state.pipelineState) return false;

		return true;
	}

	bool VertexProcessor::State::operator!=(const State &state) const
	{
		return !(*this == state);
	}

	void VertexProcessor::State::setShaderFile(const char *shaderFile)
	{
		free(this->shaderFile);
		this->shaderFile = strdup(shaderFile);
	}

	void VertexProcessor::State::setPipelineState(int state)
	{
		if(pipelineState != state)
		{
			pipelineState = state;
			updateStamp();
		}
	}

	void VertexProcessor::State::updateStamp()
	{
		stamp = pipelineState;
	}

	VertexProcessor::VertexProcessor()
	{
		vertexBuffer = 0;

		shaderFile = 0;
		shaderCache = new FIFOCache<State, VertexShader>(64);
		shader = 0;

		clearCache();

		W = 0;
		H = 0;

		tanFOV = tan(rad(90 / 2));   // FOV = 90 degrees
		nearClip = 1;
		farClip = 1000;

		M = 1;
		V = 1;
		B = Matrix(1, 0, 0, 0,
		           0, 0, 1, 0,
		           0, 1, 0, 0,
		           0, 0, 0, 1);
		P = 0;

		PB = P * B;
		PBV = PB * V;
		PBVM = PBV * M;

		for(int i = 0; i < 8; i++)
		{
			lightEnable[i] = false;
			specularEnable[i] = false;
			lightPosition[i] = 0;
		}

		updateProcessor = true;
		updateMatrix = true;
		updateModelMatrix = true;
		updateViewMatrix = true;
		updateBaseMatrix = true;
		updateProjectionMatrix = true;
		updateShader = true;
		updateLighting = true;
	}

	VertexProcessor::~VertexProcessor()
	{
		delete[] shaderFile;
		shaderFile = 0;

		delete shaderCache;
		shaderCache = 0;
	}

	void VertexProcessor::setShader(const char *shaderFile)
	{
		if(this->shaderFile != shaderFile || (this->shaderFile && shaderFile && strcmp(this->shaderFile, shaderFile) != 0))
		{
			delete[] this->shaderFile;
			this->shaderFile = strdup(shaderFile);
			updateProcessor = true;
			updateShader = true;
		}
	}

	void VertexProcessor::setVertexBuffer(const VertexBuffer *VB)
	{
		if(!VB) throw Error("No vertex buffer specified");

		this->vertexBuffer = VB;

		updateProcessor = true;
		updateShader = true;
	}

	void VertexProcessor::setFloatConstant(int index, const float value[4])
	{
		VertexShader::setFloatConstant(index, value);
	}

	void VertexProcessor::setIntegerConstant(int index, const int value[4])
	{
		VertexShader::setIntegerConstant(index, value);
	}

	void VertexProcessor::setBooleanConstant(int index, bool boolean)
	{
		VertexShader::setBooleanConstant(index, boolean);
	}

	void VertexProcessor::setFloatConstant(int index, const Vector &V)
	{
		VertexShader::setFloatConstant(index, V);
	}

	void VertexProcessor::setFloatConstant(int index, const Point &P)
	{
		VertexShader::setFloatConstant(index, P);
	}

	void VertexProcessor::setFloatConstant(int index, const Matrix &M)
	{
		VertexShader::setFloatConstant(index, M);
	}

	void VertexProcessor::setModelMatrix(const Matrix &M)
	{
		this->M = M;

		updateProcessor = true;
		updateMatrix = true;
		updateModelMatrix = true;
	}

	void VertexProcessor::setViewMatrix(const Matrix &V)
	{
		this->V = V;

		updateProcessor = true;
		updateMatrix = true;
		updateViewMatrix = true;
	}

	void VertexProcessor::setBaseMatrix(const Matrix &B)
	{
		this->B = B;

		updateProcessor = true;
		updateMatrix = true;
		updateBaseMatrix = true;
	}

	void VertexProcessor::setFOV(float FOV)
	{
		tanFOV = tan(rad(FOV / 2));

		updateProcessor = true;
		updateMatrix = true;
		updateProjectionMatrix = true;
	}

	void VertexProcessor::setNearClip(float nearClip)
	{
		this->nearClip = nearClip;

		updateProcessor = true;
		updateMatrix = true;
		updateProjectionMatrix = true;
	}

	void VertexProcessor::setFarClip(float farClip)
	{
		this->farClip = farClip;

		updateProcessor = true;
		updateMatrix = true;
		updateProjectionMatrix = true;
	}

	void VertexProcessor::setLightEnable(int light, bool lightEnable)
	{
		if(light < 0 || light >= 8) throw Error("Light index (%d) out of range [0, 7]", light);

		this->lightEnable[light] = lightEnable;

		updateProcessor = true;
		updateLighting = true;
	}

	void VertexProcessor::setSpecularEnable(int light, bool specularEnable)
	{
		if(light < 0 || light >= 8) throw Error("Light index (%d) out of range [0, 7]", light);

		this->specularEnable[light] = specularEnable;

		updateProcessor = true;
		updateLighting = true;
	}

	void VertexProcessor::setAmbientLight(const Color<float> &ambientLight)
	{
		VertexPipeline::setAmbientLight(ambientLight);
	}

	void VertexProcessor::setLightPosition(int light, const Point &lightPosition)
	{
		if(light < 0 || light >= 8) throw Error("Light index (%d) out of range [0, 7]", light);

		this->lightPosition[light] = lightPosition;

		updateProcessor = true;
		updateLighting = true;
	}

	void VertexProcessor::setLightColor(int light, const Color<float> &lightColor)
	{
		VertexPipeline::setLightColor(light, lightColor);
	}

	void VertexProcessor::setLightAttenuation(int light, float constant, float linear, float quadratic)
	{
		VertexPipeline::setLightAttenuation(light, constant, linear, quadratic);
	}

	void VertexProcessor::setLightRange(int light, float lightRange)
	{
		VertexPipeline::setLightRange(light, lightRange);
	}

	void VertexProcessor::setMaterialEmission(const Color<float> &emission)
	{
		VertexPipeline::setMaterialEmission(emission);
	}

	void VertexProcessor::setMaterialAmbient(const Color<float> &materialAmbient)
	{
		VertexPipeline::setMaterialAmbient(materialAmbient);
	}

	void VertexProcessor::setMaterialDiffuse(const Color<float> &diffuseColor)
	{
		VertexPipeline::setMaterialDiffuse(diffuseColor);
	}

	void VertexProcessor::setMaterialSpecular(const Color<float> &specularColor)
	{
		VertexPipeline::setMaterialSpecular(specularColor);
	}

	void VertexProcessor::setMaterialShininess(const float specularPower[4])
	{
		VertexPipeline::setMaterialShininess(specularPower);
	}

	void VertexProcessor::setViewport(const Viewport *viewport)
	{
		updateTransform(viewport);
	}

	const Matrix &VertexProcessor::getModelTransform(const Viewport *viewport)
	{
		updateTransform(viewport);
		return PBVM;
	}

	const Matrix &VertexProcessor::getViewTransform(const Viewport *viewport)
	{
		updateTransform(viewport);
		return PBV;
	}

	FVFFlags VertexProcessor::getOutputFormat()
	{
		update();   // Makes sure we have a shader

		return shader->getOutputFormat() | FVF_RHW;
	}

	XVertex VertexProcessor::processVertex(int v)
	{
		update();

		// Search in FIFO cache
		const int i = v % 32;
		
		if(cacheIndex[i] == v)
		{
			return vertexCache[i];
		}

		// Vertex not found in cache, process it
		shader->setOutputVertex(&vertexCache[i]);
		shader->process(v);
		cacheIndex[i] = v;
		return vertexCache[i];
	}

	void VertexProcessor::clearCache()
	{
		for(int i = 0; i < 32; i++)
		{
			cacheIndex[i] = -1;
		}
	}

	bool VertexProcessor::isFixedFunction()
	{
		return shaderFile == 0;
	}

	bool VertexProcessor::specularEnabled()
	{
		return specularEnable[0] |
		       specularEnable[1] |
		       specularEnable[2] |
		       specularEnable[3] |
		       specularEnable[4] |
		       specularEnable[5] |
		       specularEnable[6] |
		       specularEnable[7];
	}

	void VertexProcessor::updateTransform(const Viewport *viewport)
	{
		if(!updateProcessor) return;
		if(!updateMatrix) return;

		if(viewport)
		{
			updateProjectionMatrix |= (W != viewport->getWidth()) | (H != viewport->getHeight());

			if(updateProjectionMatrix)
			{
				W = viewport->getWidth();
				H = viewport->getHeight();

				const float D = 0.5f * W / tanFOV;
				const float F = farClip;
				const float N = nearClip;
				const float Q = F / (F - N);

				P = Matrix(D / W,  0,     0.5f,  0,
						0,      D / H, 0.5f,  0,
						0,      0,     Q,    -Q * N,
						0,      0,     1,     0);

				PB = P * B;
				PBV = PB * V;
				PBVM = PBV * M;

				updateProjectionMatrix = false;
				updateBaseMatrix = false;
				updateViewMatrix = false;
				updateModelMatrix = false;
			}
		}

		if(updateBaseMatrix)
		{
			PB = P * B;
			PBV = PB * V;
			PBVM = PBV * M;

			updateBaseMatrix = false;
			updateViewMatrix = false;
		}

		if(updateViewMatrix)
		{
			PBV = PB * V;
			PBVM = PBV * M;

			updateViewMatrix = false;
			updateModelMatrix = false;
		}

		if(updateModelMatrix)
		{
			PBVM = PBV * M;

			updateModelMatrix = false;
		}

		if(isFixedFunction())
		{
			VertexPipeline::setTransform(PBVM);

			// Camera position in model coordinates
			VertexPipeline::setCameraPosition(!PBVM * Point(0));
		}

		if(!updateShader && !updateLighting)
		{
			updateProcessor = false;
		}

		updateMatrix = false;
	}

	const VertexProcessor::State &VertexProcessor::status() const
	{
		static State state;

		state.setShaderFile(shaderFile);

		return state;
	}

	void VertexProcessor::update()
	{
		updateTransform();

		if(!updateProcessor) return;

		if(updateShader)
		{
			State state = status();

			shader = shaderCache->query(state);

			if(!shader)   // Create one
			{
				if(shaderFile)
				{
					shader = new VS_2_0Shader(shaderFile);
				}
				else
				{
					shader = new VertexPipeline();
				}

				shaderCache->add(state, shader);
			}

			if(!shader) throw INTERNAL_ERROR;

			shader->loadConstants();

			if(vertexBuffer)
			{
				                                     shader->setPositionStream(vertexBuffer->position[0]);
				if(vertexBuffer->hasNormal())        shader->setNormalStream(vertexBuffer->normal[0]);
				if(vertexBuffer->hasDiffuse())       shader->setColorStream(vertexBuffer->color[0], 0);
				if(vertexBuffer->hasSpecular())      shader->setColorStream(vertexBuffer->color[1], 1);
				if(vertexBuffer->numTexCoords() > 0) shader->setTexCoordStream(vertexBuffer->texCoord[0], 0);
				if(vertexBuffer->numTexCoords() > 1) shader->setTexCoordStream(vertexBuffer->texCoord[1], 1);
				if(vertexBuffer->numTexCoords() > 2) shader->setTexCoordStream(vertexBuffer->texCoord[2], 2);
				if(vertexBuffer->numTexCoords() > 3) shader->setTexCoordStream(vertexBuffer->texCoord[3], 3);
				if(vertexBuffer->numTexCoords() > 4) shader->setTexCoordStream(vertexBuffer->texCoord[4], 4);
				if(vertexBuffer->numTexCoords() > 5) shader->setTexCoordStream(vertexBuffer->texCoord[5], 5);
				if(vertexBuffer->numTexCoords() > 6) shader->setTexCoordStream(vertexBuffer->texCoord[6], 6);
				if(vertexBuffer->numTexCoords() > 7) shader->setTexCoordStream(vertexBuffer->texCoord[7], 7);
			}

			updateShader = false;
		}

		if(updateLighting && isFixedFunction())
		{
			const Matrix &T = !M;

			VertexPipeline::resetLights();

			for(int i = 0; i < 8; i++)
			{
				if(lightEnable[i])
				{
					// Light position in model coordinates
					VertexPipeline::setLightPosition(i, T * lightPosition[i]);
				}
			}

			updateLighting = false;
		}

		if(!updateMatrix)
		{
			updateProcessor = false;
		}
	}
}