//
// Tools_SmoothSkin_Processor_Wii.cpp
//
// Created by KOR - 09 January 2007
//
// Copyright (C) 2007 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header.
#include "MeshProcessing/Tools_SmoothSkin_Processor_Wii.h"

// Asura headers
#include "../Wii/ChunkSystem/Asura_Platform_Chunk_ResourceFile.h"
#include "Animation/Asura_Hierarchy.h"
#include "ChunkSystem/Asura_Chunk_ResourceFile.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
//#include "Debugging/Asura_Logfile.h"

// Tools headers
#include "MeshProcessing/Asura_TriStripper.h"
#include "MeshProcessing/Tools_SmoothSkin_Processor.h"
#include "Wii/Tools_Wii_DisplayList.h"

Tools_SmoothSkin_Wii_Processor::PROJECT_EXPORT_COLOUR_FN Tools_SmoothSkin_Wii_Processor::s_pfnColourExportFunction = NULL;
Tools_SmoothSkin_Wii_Processor::PROJECT_ID_OVERRIDE_FN Tools_SmoothSkin_Wii_Processor::s_pfnIDOverrideFunction = NULL;
Tools_SmoothSkin_Processor_Wii_Strip* Tools_SmoothSkin_Wii_Processor::m_pxStrips;
bool Tools_SmoothSkin_Wii_Processor::s_bIncludeNormals = true;

class Tools_WiiSmoothSkinDisplayLists : public Tools_Wii_DisplayListFactory
{
protected:
	virtual u_int SizePerVert()
	{
		return 4  + (Tools_SmoothSkin_Wii_Processor::GetIncludeNormals()? 2 : 0) + (Tools_SmoothSkin_Wii_Processor::GetProjectExportColourFunction() ? 2 : 0);
	};
	// what vertex format does this display list use
	virtual u_char StripType()
	{
		return 4; //GX_VTXFMT4
	}
	// store one vertex worth of data
	virtual void StoreVert(void *&pV, u_int uIndex)
	{
		Store(pV, (u_short)uIndex); // GXPosition1x16(m_pusIndices[uIndex])
		if (Tools_SmoothSkin_Wii_Processor::GetIncludeNormals())
		{
			Store(pV, (u_short)uIndex); // GXNormal1x16(m_pusIndices[uIndex]);
		}
		if(Tools_SmoothSkin_Wii_Processor::GetProjectExportColourFunction())
		{
			Store(pV, (u_short)uIndex); // GXColor1x16(m_pusIndices[uIndex]);
		}
		Store(pV, (u_short)uIndex); // GXTexCoord1x16(m_pusIndices[uIndex]);
	}
public:
};

static Tools_WiiSmoothSkinDisplayLists s_pxDefaultDisplayListFactory;
Tools_Wii_DisplayListFactory * const Tools_SmoothSkin_Wii_Processor::s_pxDefaultDisplayListFactory = &::s_pxDefaultDisplayListFactory;
Tools_Wii_DisplayListFactory *Tools_SmoothSkin_Wii_Processor::s_pxDisplayListFactory = Tools_SmoothSkin_Wii_Processor::s_pxDefaultDisplayListFactory;


bool Tools_SmoothSkin_Wii_Processor::OutputToChunk(const Tools_SmoothSkin_Processor* pxSrcProcessor, HANDLE hFile) const
{
	const Asura_Hierarchy_Skin* pxSkin = pxSrcProcessor->GetInputSkin();
	Asura_Assert(pxSkin, "No skin");
	if(!pxSkin) { return false; }

	if(!s_pxDisplayListFactory)
	{
		Asura_Assert(0, "Missing display list factory");
	}

	// Because we store bone index in a u_char, this imposes a 255 bone limit.
	Asura_Assert(pxSkin->NumberOfBones <= 255, "Too many bones for Wii exporter");

	const u_int uNumStrippedVertices = pxSrcProcessor->GetNumVertices();
	const u_int uNumMaterials = pxSrcProcessor->GetNumMaterials();
	const u_int uNumElements = pxSrcProcessor->GetNumElements();

	Asura_Chunk_Memory_Stream xStream;

	Asura_String xName = "Stripped";
	xName += pxSkin->Name;
	const u_int uFlags = s_bIncludeNormals ? ASURA_WII_SMOOTHSKIN_CHUNK_FLAG_HAS_NORMALS : 0;
	xStream.WriteResourceChunk_Start(ASURA_RESOURCEFILE_TYPE_PLATFORMSPECIFIC, ASURA_RESOURCEFILE_TYPE_WII_SMOOTHSKIN, ASURA_WII_SMOOTHSKIN_CHUNK_VERSION, uFlags, xName);

	//Asura_OutputToDebugger(("Exporting %smooth skin s\n", pxSkin->Name.GetString()));

	// Calculate the amount of bone information to output. This is an upper limit - some of the less significant weights may be dropped.
	unsigned int uNonZeroBoneInfo = 0;
	for(u_int uVertex = 0; uVertex < uNumStrippedVertices; ++uVertex)
	{
		const Tools_SmoothSkin_Vertex* pxVertex = &pxSrcProcessor->GetVertex(uVertex);
		uNonZeroBoneInfo += pxVertex->GetNumWeightings();
	}

	const Asura_Hash_ID uHashID = Asura_GetHashID(pxSkin->Name.GetString());
	xStream << uHashID;
	xStream << uNumStrippedVertices;
	xStream << uNonZeroBoneInfo;
	xStream << uNumMaterials;

	// Iterate through materials
	for(u_int uMaterial = 0; uMaterial < uNumMaterials; ++uMaterial)
	{
		const Tools_SmoothSkin_Material& xMaterial = pxSrcProcessor->GetMaterial(uMaterial);

		// Create display list for this material.
		s_pxDisplayListFactory->BeginList();

		for(u_int uElement = 0; uElement < uNumElements; ++uElement)
		{
			const Tools_SmoothSkin_SubElement& xElement = xMaterial.GetSubElement(uElement);
			if(xElement.GetNumTris() == 0) { continue; }

			Tools_SmoothSkin_Processor_Wii_Strip xElementStrip;

			Tools_TriStripper::Initialise();
			Tools_TriStripper::SetOutputType( Tools_TriStripper::OUTPUT_TRISTRIP );
			xElementStrip.m_uNumIndices = Tools_TriStripper::CreateStrips(xElement.GetNumTris(), xElement.GetIndices(), &xElementStrip.m_pusIndices);
			s_pxDisplayListFactory->AddStrip(xElementStrip.m_pusIndices, xElementStrip.m_uNumIndices);
			Tools_TriStripper::Shutdown();
		}
		void *pvDisplayList = NULL;
		u_int uDisplayListSize = 0;
		const u_int uNumTris = s_pxDisplayListFactory->EndList(pvDisplayList, uDisplayListSize);

		// Write out this material.
		const u_int uMaterialID = xMaterial.GetMaterialIDWithinSkin();
		xStream << uMaterialID;
		xStream << uDisplayListSize;
		xStream << uNumTris;
		//Asura_OutputToDebugger(("Material ID %u, display list size %u, num tris %u\n", uMaterialID, uDisplayListSize, uNumTris));
		xStream.Write(uDisplayListSize, pvDisplayList);

		if (pvDisplayList)
		{
			delete[] pvDisplayList;
		}
	}

	if(s_pfnColourExportFunction)
	{
		// Write out colours
		s_pfnColourExportFunction(xStream, pxSrcProcessor);
	}
	else
	{
		// No colour information to export
		const u_int uZero = 0;
		xStream << uZero;
	}

	// Write out verts
	for(u_int uVertex = 0; uVertex < uNumStrippedVertices; ++uVertex)
	{
		const Tools_SmoothSkin_Vertex* pxVertex = &pxSrcProcessor->GetVertex(uVertex);
		const Asura_Vector_3& xPos = pxVertex->GetPos();
		const Asura_Vector_3& xNormal = pxVertex->GetNormal();
		// Position is stored as shorts with 10 fractional bits. 2^10 = 1024
		Asura_Assert(xPos.x <= 32.0f && xPos.x >= -32.0f, "Smooth skin position out of range.");
		Asura_Assert(xPos.y <= 32.0f && xPos.y >= -32.0f, "Smooth skin position out of range.");
		Asura_Assert(xPos.z <= 32.0f && xPos.z >= -32.0f, "Smooth skin position out of range.");
		xStream << static_cast<short>(xPos.x * 1024.0f);
		xStream << static_cast<short>(xPos.y * 1024.0f);
		xStream << static_cast<short>(xPos.z * 1024.0f);

		if (s_bIncludeNormals)
		{
			// Normals: stored as chars with 6 fractional bits. 2^6 = 64
			Asura_Assert(xNormal.x <= 1.0f && xNormal.x >= -1.0f, "Smooth skin normal out of range.");
			Asura_Assert(xNormal.y <= 1.0f && xNormal.y >= -1.0f, "Smooth skin normal out of range.");
			Asura_Assert(xNormal.z <= 1.0f && xNormal.z >= -1.0f, "Smooth skin normal out of range.");
			xStream << static_cast<char>(xNormal.x * 64.0f);
			xStream << static_cast<char>(xNormal.y * 64.0f);
			xStream << static_cast<char>(xNormal.z * 64.0f);
			// Pad to 4 bytes.
			xStream.WritePadding(1);
		}
	}
	// Write out texture co-ords
	for(u_int uVertex = 0; uVertex < uNumStrippedVertices; ++uVertex)
	{
		const Tools_SmoothSkin_Vertex* pxVertex = &pxSrcProcessor->GetVertex(uVertex);
		const Asura_Vector_2& xTexCoords = pxVertex->GetTexCoords();
		// Texture co-ordinates are stored as shorts with 10 fractional bits. 2^10 = 1024
		Asura_Assert(xTexCoords.x <= 32.0f && xTexCoords.x >= -32.0f, "Smooth skin vertex U out of range.");
		Asura_Assert(xTexCoords.y <= 32.0f && xTexCoords.y >= -32.0f, "Smooth skin vertex V out of range.");
		xStream << static_cast<short>(xTexCoords.x * 1024.0f);
		xStream << static_cast<short>(xTexCoords.y * 1024.0f);
	}
	// Write out weights
	unsigned int uBonesWritten = 0; 
	for(u_int uVertex = 0; uVertex < uNumStrippedVertices; ++uVertex)
	{
		const Tools_SmoothSkin_Vertex* pxVertex = &pxSrcProcessor->GetVertex(uVertex);
		const u_char* pucIndices = pxVertex->GetBoneIndices();
		const float* pfWeights = pxVertex->GetBoneWeights();
		// Here's the nasty bit. We have to keep the total weight at exactly one...
		// ... but we are converting to chars, so we are naturally going to lose some accuracy.
		// Therefore, we'll have to adjust the figures until they match.
		u_char aucWeights[4];
		u_char ucTotal = 0;
		int iMaxBone = -1;
		for(int i = 0; i < 4; ++i)
		{
			if(pfWeights[i] > 0.0f)
			{
				aucWeights[i] = static_cast<u_char>(pfWeights[i] * 255.0f);
				ucTotal = static_cast<u_char>(ucTotal + aucWeights[i]);
				++iMaxBone;
			}
			else
			{
				aucWeights[i] = 0;
			}
		}
		if(ucTotal != 0xff)
		{
			// Recalculate weights
			// Desired total is 255
			int i;
			ucTotal = 0xff;
			for(i = 0; i < iMaxBone; ++i)
			{
				// Subtract from desired total
				ucTotal = static_cast<u_char>(ucTotal - aucWeights[i]);
			}
			// Set the last bone to be the remaining amount
			aucWeights[i] = ucTotal;
		}
		for(u_int u = 0; u < 4; ++u)
		{					
			if(aucWeights[u] > 0)
			{
				const u_char ucIndex = pucIndices[u];
				xStream << ucIndex;
				xStream << aucWeights[u];
				++uBonesWritten;
			}
		}
	}
	// Some of the smaller bone weights may have got lost. Pad out.
	// TODO: precalculate weights, so we don't have to do this
	if(uBonesWritten < uNonZeroBoneInfo)
	{
		xStream.WritePadding(2 * (uNonZeroBoneInfo - uBonesWritten));
	}

	xStream.WriteResourceChunk_End();

	const void* pvData = xStream.GetStartAddress();
	const u_int uChunkSize = xStream.GetPosition();

	unsigned long uBytesWritten = 0;
	// Write the data to the file...
	WriteFile(hFile, pvData, uChunkSize, &uBytesWritten, NULL);

	Asura_Assert(uChunkSize == uBytesWritten, "Did not write out expected number of bytes");

	return true;
}
