#include "PixelProcessor.hpp"

#include "PixelShader.hpp"
#include "XVertex.hpp"
#include "Error.hpp"
#include "Rasterizer.hpp"
#include "PS_2_0Shader.hpp"
#include "PixelPipeline.hpp"

namespace swShader
{
	PixelProcessor::State::State()
	{
		shaderFile = 0;
		pipelineState = 0;
		for(int i = 0; i < 16; i++) samplerState[i] = 0;
		stamp = 0;
	}

	PixelProcessor::State::State(const State &state)
	{
		shaderFile = strdup(state.shaderFile);
		pipelineState = state.pipelineState;
		for(int i = 0; i < 16; i++) samplerState[i] = state.samplerState[i];
		updateStamp();
	}

	PixelProcessor::State &PixelProcessor::State::operator=(const State &state)
	{
		free(shaderFile);
		shaderFile = strdup(state.shaderFile);
		pipelineState = state.pipelineState;
		for(int i = 0; i < 16; i++) samplerState[i] = state.samplerState[i];
		updateStamp();

		return *this;
	}

	PixelProcessor::State::~State()
	{
		free(shaderFile);
		shaderFile = 0;
	}

	bool PixelProcessor::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;

		for(int i = 0; i < 16; i++)
		{
			if(samplerState[i] != state.samplerState[i]) return false;
		}

		return true;
	}

	bool PixelProcessor::State::operator!=(const State &state) const
	{
		return !(*this == state);
	}

	void PixelProcessor::State::setShaderFile(const char *shaderFile)
	{
		free(this->shaderFile);
		this->shaderFile = strdup(shaderFile);
	}

	void PixelProcessor::State::setPipelineState(int state)
	{
		if(pipelineState != state)
		{
			pipelineState = state;
			updateStamp();
		}
	}

	void PixelProcessor::State::setSamplerState(int stage, int state)
	{
		if(stage < 0 || stage >= 16) throw Error("Sampler index out of range");

		if(samplerState[stage] != state)
		{
			samplerState[stage] = state;
			updateStamp();
		}
	}

	void PixelProcessor::State::updateStamp()
	{
		stamp = pipelineState;
		for(int i = 0; i < 16; i++) stamp += samplerState[i];
	}

	PixelProcessor::PixelProcessor()
	{
		Context::init();

		rasterizer = new Rasterizer;

		shaderFile = 0;
		shaderCache = new FIFOCache<State, PixelShader>(64);
		shader = 0;
	}

	PixelProcessor::~PixelProcessor()
	{
		delete rasterizer;
		rasterizer = 0;

		delete[] shaderFile;
		shaderFile = 0;
		delete shaderCache;
		shaderCache = 0;
	}

	void PixelProcessor::setFloatConstant(int index, const float value[4])
	{
		PixelShader::setFloatConstant(index, value);
	}

	void PixelProcessor::setFloatConstant(int index, const Vector &V)
	{
		PixelShader::setFloatConstant(index, V);
	}

	void PixelProcessor::setFloatConstant(int index, const Point &P)
	{
		PixelShader::setFloatConstant(index, P);
	}

	void PixelProcessor::setFloatConstant(int index, const Matrix &M)
	{
		PixelShader::setFloatConstant(index, M);
	}

	void PixelProcessor::setShader(const char *shaderFile)
	{
		if(this->shaderFile != shaderFile || (this->shaderFile && shaderFile && strcmp(this->shaderFile, shaderFile) != 0))
		{
			delete[] this->shaderFile;
			this->shaderFile = strdup(shaderFile);
			updateShader = true;
		}
	}

	void PixelProcessor::setRenderTarget(const RenderTarget *renderTarget)
	{
		if(!rasterizer) throw INTERNAL_ERROR;

		rasterizer->setRenderTarget(renderTarget);
	}

	void PixelProcessor::setVertexFormat(const FVFFlags &FVF)
	{
		updateShader |= Context::FVF != FVF;
		Context::FVF = FVF;
	}

	void PixelProcessor::setTextureMap(int stage, Texture *texture)
	{
		if(stage < 0 || stage >= 16) throw Error("Texture stage index out of [0, 15] range: %d", stage);

		sampler[stage].setTextureMap(texture);
	}

	void PixelProcessor::releaseTextures()
	{
		for(int stage = 0; stage < 16; stage++)
		{
			sampler[stage].releaseTexture();
		}
	}

	void PixelProcessor::setTexCoordIndex(int stage, int texCoordIndex)
	{
		if(stage < 0 || stage >= 8) throw Error("Texture stage index out of [0, 7] range: %d", stage);

		updateShader |= sampler[stage].setTexCoordIndex(texCoordIndex);
	}

	void PixelProcessor::setStageOperation(int stage, Sampler::StageOperation stageOperation)
	{
		if(stage < 0 || stage >= 8) throw Error("Texture stage index out of [0, 7] range: %d", stage);

		updateShader |= sampler[stage].setStageOperation(stageOperation);
	}

	void PixelProcessor::setFirstArgument(int stage, Sampler::SourceArgument firstArgument)
	{
		if(stage < 0 || stage >= 8) throw Error("Texture stage index out of [0, 7] range: %d", stage);

		updateShader |= sampler[stage].setFirstArgument(firstArgument);
	}

	void PixelProcessor::setSecondArgument(int stage, Sampler::SourceArgument secondArgument)
	{
		if(stage < 0 || stage >= 8) throw Error("Texture stage index out of [0, 7] range: %d", stage);

		updateShader |= sampler[stage].setSecondArgument(secondArgument);
	}

	void PixelProcessor::setThirdArgument(int stage, Sampler::SourceArgument thirdArgument)
	{
		if(stage < 0 || stage >= 8) throw Error("Texture stage index out of [0, 7] range: %d", stage);

		updateShader |= sampler[stage].setThirdArgument(thirdArgument);
	}

	void PixelProcessor::setFirstModifier(int stage, Sampler::ArgumentModifier firstModifier)
	{
		if(stage < 0 || stage >= 8) throw Error("Texture stage index out of [0, 7] range: %d", stage);

		updateShader |= sampler[stage].setFirstModifier(firstModifier);
	}

	void PixelProcessor::setSecondModifier(int stage, Sampler::ArgumentModifier secondModifier)
	{
		if(stage < 0 || stage >= 8) throw Error("Texture stage index out of [0, 7] range: %d", stage);

		updateShader |= sampler[stage].setSecondModifier(secondModifier);
	}

	void PixelProcessor::setThirdModifier(int stage, Sampler::ArgumentModifier thirdModifier)
	{
		if(stage < 0 || stage >= 8) throw Error("Texture stage index out of [0, 7] range: %d", stage);

		updateShader |= sampler[stage].setThirdModifier(thirdModifier);
	}

	void PixelProcessor::setDestinationArgument(int stage, Sampler::DestinationArgument destinationArgument)
	{
		if(stage < 0 || stage >= 8) throw Error("Texture stage index out of [0, 7] range: %d", stage);

		updateShader |= sampler[stage].setDestinationArgument(destinationArgument);
	}

	void PixelProcessor::setTextureFilter(int stage, Sampler::FilterType textureFilter)
	{
		if(stage < 0 || stage >= 16) throw Error("Texture stage index out of [0, 15] range: %d", stage);

		updateShader |= sampler[stage].setTextureFilter(textureFilter);
	}

	void PixelProcessor::setAddressingMode(int stage, Sampler::AddressingMode addressMode)
	{
		if(stage < 0 || stage >= 16) throw Error("Texture stage index out of [0, 15] range: %d", stage);

		updateShader |= sampler[stage].setAddressingMode(addressMode);
	}

	void PixelProcessor::setDepthCompare(DepthCompareMode depthCompareMode)
	{
		updateShader |= (this->depthCompareMode != depthCompareMode);
		this->depthCompareMode = depthCompareMode;
	}

	void PixelProcessor::setAlphaCompare(AlphaCompareMode alphaCompareMode)
	{
		updateShader |= (this->depthCompareMode != depthCompareMode);
		this->depthCompareMode = depthCompareMode;
	}

	void PixelProcessor::setDepthWriteEnable(bool depthWriteEnable)
	{
		updateShader |= (this->depthWriteEnable != depthWriteEnable);
		this->depthWriteEnable = depthWriteEnable;
	}

	void PixelProcessor::setAlphaTestEnable(bool alphaTestEnable)
	{
		updateShader |= (this->alphaTestEnable != alphaTestEnable);
		this->alphaTestEnable = alphaTestEnable;
	}

	void PixelProcessor::setCullMode(CullMode cullMode)
	{
		updateShader |= (this->cullMode != cullMode);
		this->cullMode = cullMode;
	}

	void PixelProcessor::setShadingMode(ShadingMode shadingMode)
	{
		updateShader |= (this->shadingMode != shadingMode);
		this->shadingMode = shadingMode;
	}

	void PixelProcessor::setSpecularEnable(bool specularEnable)
	{
		updateShader |= (this->specularEnable != specularEnable);
		this->specularEnable = specularEnable;
	}

	void PixelProcessor::setAlphaBlendEnable(bool alphaBlendEnable)
	{
		updateShader |= (this->alphaBlendEnable != alphaBlendEnable);
		this->alphaBlendEnable = alphaBlendEnable;
	}

	void PixelProcessor::setSourceBlendFactor(BlendFactor sourceBlendFactor)
	{
		updateShader |= (this->sourceBlendFactor != sourceBlendFactor);
		this->sourceBlendFactor = sourceBlendFactor;
	}

	void PixelProcessor::setDestBlendFactor(BlendFactor destBlendFactor)
	{
		updateShader |= (this->destBlendFactor != destBlendFactor);
		this->destBlendFactor = destBlendFactor;
	}

	void PixelProcessor::setAlphaReference(int alphaReference)
	{
		this->alphaReference = alphaReference;
	}

	void PixelProcessor::renderPolygon(XVertex **V, int n, FVFFlags FVF)
	{
		update(FVF);
		scanline = shader->executable();

		for(int i = 2; i < n; i++)
		{
			const XVertex &V1 = *V[0];
			const XVertex &V2 = *V[i - 1];
			const XVertex &V3 = *V[i];

			float area = 0.5f * ((V1.x - V2.x) * (V3.y - V1.y) - (V1.y - V2.y) * (V3.x - V1.x));

			if(cullMode != CULL_NONE)
			{
				if(cullMode == CULL_CLOCKWISE)
				{
					if(area <= 0) continue;
				}
				else if(cullMode == CULL_COUNTERCLOCKWISE)
				{
					if(area >= 0) continue;
				}
				else
				{
					throw INTERNAL_ERROR;
				}
			}

			rasterizer->renderTriangle(&V1, &V2, &V3, FVF);
		}
	}

	const PixelProcessor::State &PixelProcessor::status() const
	{
		static State state;

		state.setShaderFile(shaderFile);

		const int a = 0 + BITS(COLOR_LAST);		// colorDepth
		const int b = a + BITS(DEPTH_LAST);		// depthCompareMode
		const int c = b + BITS(ALPHA_LAST);		// alphaCompareMode
		const int d = c + 1;					// depthWriteEnable
		const int e = d + 1;					// alphaTestEnable
		const int f = e + 1;					// alphaBlendEnable
		const int g = f + BITS(SHADING_LAST);	// shadingMode
		const int h = g + 1;					// specularEnable
		const int i = h + BITS(BLEND_LAST);		// sourceBlend
		const int j = i + BITS(BLEND_LAST);		// destinationBlend

		// Test whether status fits in 32-bit
		META_ASSERT(j <= 32);

		state.setPipelineState(colorDepth			<< 0 |
		                       depthCompareMode		<< a |
		                       alphaCompareMode		<< b |
		                       depthWriteEnable		<< c |
		                       alphaTestEnable		<< d |
							   alphaBlendEnable		<< e |
		                       shadingMode			<< f |
							   specularEnable		<< g |
		                       sourceBlendFactor	<< h |
		                       destBlendFactor		<< i);

		for(unsigned int i = 0; i < 16; i++)
		{
			state.setSamplerState(i, sampler[i].status());
		}

		return state;
	}

	void PixelProcessor::update(FVFFlags FVF)
	{
		this->FVF = FVF;

		if(updateShader)
		{
			State state = status();

			shader = shaderCache->query(state);

			if(!shader)   // Create one
			{
				if(shaderFile)
				{
					shader = new PS_2_0Shader(shaderFile);
				}
				else
				{
					shader = new PixelPipeline();
				}

				shaderCache->add(state, shader);
			}

			if(!shader) throw INTERNAL_ERROR;

			shader->loadConstants();

			updateShader = false;
		}
	}
}