#include "VertexPipeline.hpp"

#include "Error.hpp"

namespace swShader
{
	float4 VertexPipeline::constant[256] = {0};
	float4 VertexPipeline::transform[4] = {0};
	float4 VertexPipeline::cameraPosition = {0};
	float4 VertexPipeline::lightPosition[8] = {0};
	float4 VertexPipeline::lightColor[8] = {0};
	float4 VertexPipeline::attenuationConstant[8] = {0};
	float4 VertexPipeline::attenuationLinear[8] = {0};
	float4 VertexPipeline::attenuationQuadratic[8] = {0};
	float4 VertexPipeline::lightRange[8] = {0};
	float4 VertexPipeline::ambientLight = {0};
	float4 VertexPipeline::materialEmission = {0};
	float4 VertexPipeline::materialAmbient = {0};
	float4 VertexPipeline::materialDiffuse = {0};
	float4 VertexPipeline::materialSpecular = {0};
	float4 VertexPipeline::materialShininess = {0};

	bool VertexPipeline::lightEnable[8] = {false};

	Operand VertexPipeline::zero(Operand::INTERNAL_CONSTANT, 0);
	Operand VertexPipeline::one(Operand::INTERNAL_CONSTANT, 1);
	Operand VertexPipeline::trans(Operand::INTERNAL_TRANSFORM);
	Operand VertexPipeline::cam(Operand::INTERNAL_CAMERA_POSITION);
	Operand VertexPipeline::light0(Operand::INTERNAL_LIGHT_POSITION, 0);
	Operand VertexPipeline::lcol0(Operand::INTERNAL_LIGHT_COLOR, 0);
	Operand VertexPipeline::att0c(Operand::INTERNAL_ATTENUATION_CONSTANT, 0);
	Operand VertexPipeline::att0l(Operand::INTERNAL_ATTENUATION_LINEAR, 0);
	Operand VertexPipeline::att0q(Operand::INTERNAL_ATTENUATION_QUADRATIC, 0);
	Operand VertexPipeline::range(Operand::INTERNAL_LIGHT_RANGE);
	Operand VertexPipeline::amb(Operand::INTERNAL_AMBIENT_LIGHT);
	Operand VertexPipeline::mamb(Operand::INTERNAL_MATERIAL_AMBIENT);
	Operand VertexPipeline::emiss(Operand::INTERNAL_MATERIAL_EMISSION);
	Operand VertexPipeline::diff(Operand::INTERNAL_MATERIAL_DIFFUSE);
	Operand VertexPipeline::spec(Operand::INTERNAL_MATERIAL_SPECULAR);
	Operand VertexPipeline::pow(Operand::INTERNAL_MATERIAL_SHININESS);

	Operand VertexPipeline::v0(Operand::INPUT_REGISTER, 0);
	Operand VertexPipeline::v1(Operand::INPUT_REGISTER, 1);
	Operand VertexPipeline::v2(Operand::INPUT_REGISTER, 2);

	Operand VertexPipeline::r0(Operand::TEMPORARY_REGISTER, 0);
	Operand VertexPipeline::r1(Operand::TEMPORARY_REGISTER, 1);
	Operand VertexPipeline::r2(Operand::TEMPORARY_REGISTER, 2);
	Operand VertexPipeline::r3(Operand::TEMPORARY_REGISTER, 3);
	Operand VertexPipeline::r4(Operand::TEMPORARY_REGISTER, 4);
	Operand VertexPipeline::r5(Operand::TEMPORARY_REGISTER, 5);
	Operand VertexPipeline::r6(Operand::TEMPORARY_REGISTER, 6);
	Operand VertexPipeline::r7(Operand::TEMPORARY_REGISTER, 7);

	Operand VertexPipeline::oPos(Operand::POSITION_REGISTER);
	Operand VertexPipeline::oT0(Operand::TEXTURE_COORDINATE_REGISTER, 0);
	Operand VertexPipeline::oT1(Operand::TEXTURE_COORDINATE_REGISTER, 1);
	Operand VertexPipeline::oD0(Operand::DIFFUSE_SPECULAR_REGISTER, 0);
	Operand VertexPipeline::oD1(Operand::DIFFUSE_SPECULAR_REGISTER, 1);

	VertexPipeline::VertexPipeline()
	{
	}

	VertexPipeline::~VertexPipeline()
	{
	}

	void VertexPipeline::loadConstants()
	{
	}

	void VertexPipeline::setTransform(const Matrix &M)
	{
		transform[0][0] = M[0][0];
		transform[0][1] = M[0][1];
		transform[0][2] = M[0][2];
		transform[0][3] = M[0][3];

		transform[1][0] = M[1][0];
		transform[1][1] = M[1][1];
		transform[1][2] = M[1][2];
		transform[1][3] = M[1][3];

		transform[2][0] = M[2][0];
		transform[2][1] = M[2][1];
		transform[2][2] = M[2][2];
		transform[2][3] = M[2][3];

		transform[3][0] = M[3][0];
		transform[3][1] = M[3][1];
		transform[3][2] = M[3][2];
		transform[3][3] = M[3][3];
	}

	void VertexPipeline::setCameraPosition(const Point &P)
	{
		cameraPosition[0] = P.x;
		cameraPosition[1] = P.y;
		cameraPosition[2] = P.z;
		cameraPosition[3] = 1;
	}

	void VertexPipeline::setAmbientLight(const Color<float> &ambientLight)
	{
		VertexPipeline::ambientLight[0] = ambientLight.r;
		VertexPipeline::ambientLight[1] = ambientLight.g;
		VertexPipeline::ambientLight[2] = ambientLight.b;
		VertexPipeline::ambientLight[3] = ambientLight.a;
	}

	void VertexPipeline::setLightPosition(int light, const Point &P)
	{
		if(light < 0 || light >= 8) throw INTERNAL_ERROR;

		lightPosition[light][0] = P.x;
		lightPosition[light][1] = P.y;
		lightPosition[light][2] = P.z;
		lightPosition[light][3] = 1;

		lightEnable[light] = true;
	}

	void VertexPipeline::setLightColor(int light, const Color<float> &lightColor)
	{
		if(light < 0 || light >= 8) throw INTERNAL_ERROR;

		VertexPipeline::lightColor[light][0] = lightColor.r;
		VertexPipeline::lightColor[light][1] = lightColor.g;
		VertexPipeline::lightColor[light][2] = lightColor.b;
		VertexPipeline::lightColor[light][3] = lightColor.a;
	}

	void VertexPipeline::setLightAttenuation(int light, float constant, float linear, float quadratic)
	{
		if(light < 0 || light >= 8) throw INTERNAL_ERROR;

		VertexPipeline::attenuationConstant[light][0] = constant;
		VertexPipeline::attenuationConstant[light][1] = constant;
		VertexPipeline::attenuationConstant[light][2] = constant;
		VertexPipeline::attenuationConstant[light][3] = constant;

		VertexPipeline::attenuationLinear[light][0] = linear;
		VertexPipeline::attenuationLinear[light][1] = linear;
		VertexPipeline::attenuationLinear[light][2] = linear;
		VertexPipeline::attenuationLinear[light][3] = linear;

		VertexPipeline::attenuationQuadratic[light][0] = quadratic;
		VertexPipeline::attenuationQuadratic[light][1] = quadratic;
		VertexPipeline::attenuationQuadratic[light][2] = quadratic;
		VertexPipeline::attenuationQuadratic[light][3] = quadratic;
	}

	void VertexPipeline::setLightRange(int light, float lightRange)
	{
		if(light < 0 || light >= 8) throw INTERNAL_ERROR;

		VertexPipeline::lightRange[light][0] = lightRange;
		VertexPipeline::lightRange[light][1] = lightRange;
		VertexPipeline::lightRange[light][2] = lightRange;
		VertexPipeline::lightRange[light][3] = lightRange;
	}

	void VertexPipeline::resetLights()
	{
		for(int i = 0; i < 8; i++)
		{
			lightEnable[i] = false;
		}
	}

	void VertexPipeline::setMaterialEmission(const Color<float> &emission)
	{
		VertexPipeline::materialEmission[0] = emission.r;
		VertexPipeline::materialEmission[1] = emission.g;
		VertexPipeline::materialEmission[2] = emission.b;
		VertexPipeline::materialEmission[3] = emission.a;
	}

	void VertexPipeline::setMaterialAmbient(const Color<float> &materialAmbient)
	{
		VertexPipeline::materialAmbient[0] = materialAmbient.r;
		VertexPipeline::materialAmbient[1] = materialAmbient.g;
		VertexPipeline::materialAmbient[2] = materialAmbient.b;
		VertexPipeline::materialAmbient[3] = materialAmbient.a;
	}

	void VertexPipeline::setMaterialDiffuse(const Color<float> &diffuseColor)
	{
		VertexPipeline::materialDiffuse[0] = diffuseColor.r;
		VertexPipeline::materialDiffuse[1] = diffuseColor.g;
		VertexPipeline::materialDiffuse[2] = diffuseColor.b;
		VertexPipeline::materialDiffuse[3] = diffuseColor.a;
	}

	void VertexPipeline::setMaterialSpecular(const Color<float> &specularColor)
	{
		VertexPipeline::materialSpecular[0] = specularColor.r;
		VertexPipeline::materialSpecular[1] = specularColor.g;
		VertexPipeline::materialSpecular[2] = specularColor.b;
		VertexPipeline::materialSpecular[3] = specularColor.a;
	}

	void VertexPipeline::setMaterialShininess(const float specularPower[4])
	{
		VertexPipeline::materialShininess[0] = specularPower[0];
		VertexPipeline::materialShininess[1] = specularPower[1];
		VertexPipeline::materialShininess[2] = specularPower[2];
		VertexPipeline::materialShininess[3] = specularPower[3];
	}

	void VertexPipeline::encode()
	{
		#ifndef NDEBUG
			setEchoFile("VertexPipeline.asm");
		#endif

		for(int i = 0; i < 16; i++) vDcl[i] = false;

		try
		{
			pushad();
			freeAll();

			pipeline();

			writeOutput();

			emms();
			popad();
			ret();
		}
		catch(const Error &error)
		{
			throw Error("Fatal vertex pipeline assembler error: ") << error;
		}
		catch(...)
		{
			throw INTERNAL_ERROR;
		}

		code = finalize();
	}

	void VertexPipeline::pipeline()
	{
		VS_2_0();

		DEF(zero, 0, 0, 0, 0);
		DEF(one, 1, 1, 1, 1);

		DCL_POSITION0(v0);
		DCL_TEXCOORD0(v1);
		DCL_NORMAL0(v2);

		// Transform
		M4X4(r0, v0, trans);

		MOV(r6, zero);	// Diffuse component
		MOV(r7, zero);	// Specular component

		for(int i = 0; i < 8; i++)
		{
			if(!lightEnable[i]) continue;

			// Attenuation
			SUB(r1, light0+i, v0);
			DP3(r4, r1, r1);
			RSQ(r4, r4);
			MUL(r5, r4, r4);
			MAD(r4, r4, att0l+i, att0q+i);
			MAD(r4, r5, att0c+i, r4);
			RCP(r4, r4);
			MUL(r4, r5, r4);

			// Diffuse
			NRM(r1, r1);
			DP3(r2, r1, v2);
			MAX(r2, r2, zero);
			MUL(r2, r2, diff);
			MUL(r2, r2, lcol0+i);
			MUL(r2, r2, r4);
			ADD(r6, r6, r2);

			// Specular
			SUB(r3, cam, v0);
			NRM(r3, r3);
			ADD(r3, r3, r1);
			NRM(r3, r3);
			DP3(r3, r3, v2);
			POW(r3, r3, pow);
			MUL(r3, r3, spec);
			MUL(r3, r3, lcol0+i);
			MUL(r3, r3, r4);
			MAX(r3, r3, zero);
			ADD(r7, r7, r3);
		}

		// Ambient
		MAD(r6, mamb, amb, r6);

		MIN(r6, r6, one);
		MIN(r7, r7, one);

		MOV(oPos, r0);
		MOV(oT0, v1);
		MOV(oD0, r6);
		MOV(oD1, r7);
	}

	void *VertexPipeline::reference(const Operand &reg)
	{
		switch(reg.type)
		{
		case Operand::INTERNAL_CONSTANT:
			return &constant[reg.index];
		case Operand::INTERNAL_TRANSFORM:
			return &transform[reg.index];
		case Operand::INTERNAL_CAMERA_POSITION:
			return &cameraPosition;
		case Operand::INTERNAL_LIGHT_POSITION:
			return &lightPosition[reg.index];
		case Operand::INTERNAL_LIGHT_COLOR:
			return &lightColor[reg.index];
		case Operand::INTERNAL_ATTENUATION_CONSTANT:
			return &attenuationConstant[reg.index];
		case Operand::INTERNAL_ATTENUATION_LINEAR:
			return &attenuationLinear[reg.index];
		case Operand::INTERNAL_ATTENUATION_QUADRATIC:
			return &attenuationQuadratic[reg.index];
		case Operand::INTERNAL_LIGHT_RANGE:
			return &lightRange[reg.index];
		case Operand::INTERNAL_AMBIENT_LIGHT:
			return &ambientLight;
		case Operand::INTERNAL_MATERIAL_EMISSION:
			return &materialEmission;
		case Operand::INTERNAL_MATERIAL_AMBIENT:
			return &materialAmbient;
		case Operand::INTERNAL_MATERIAL_DIFFUSE:
			return &materialDiffuse;
		case Operand::INTERNAL_MATERIAL_SPECULAR:
			return &materialSpecular;
		case Operand::INTERNAL_MATERIAL_SHININESS:
			return &materialShininess;
		default:
			return VS_2_0Assembler::reference(reg);
		}
	}
}