#include "StdAfx.h"
#include "../res/resource.h"
#include "caftestreader.h"
#include "ChunkFileReader.h"
#include "ChunkFileWriter.h"
#include "BSplineOpen.h"
#include "BSplineApprTest.h"
#include "MotionOptimizerView.h"
#include "ChunkFileWriter.h"
#include "BSplineVec3dPacked.h"
#include "DlgApprParams.h"
#include "BSplineApprTest.h"
#include "crc32.h"
#include "CgfUtils.h"

// list of important chunks' CRC32's: these chunks are treated as those that require
// higher quality of approximation. The first number is the CtrlID, the second is the importance
std::map<unsigned,float> g_mapImportance;

void AddImportance (unsigned nControllerID, float fImportance)
{
	g_mapImportance.insert (std::map<unsigned,float>::value_type(nControllerID, fImportance));
}
void AddImportance (const char* szControllerName, float fImportance)
{
	AddImportance(Crc32Gen::GetCRC32 (szControllerName), fImportance);
}
void InitImportanceMap()
{
	AddImportance("Bip01 L Thigh", 5);
	AddImportance("Bip01 R Thigh", 5);
	AddImportance("Bip01 L Calf", 2.5);
	AddImportance("Bip01 R Calf", 2.5);
	
	AddImportance("Bip01", 5);
	AddImportance("Bip01 Pelvis", 4);
	AddImportance("Bip01 Spine", 3);
	AddImportance("Bip01 Spine1", 3);
	AddImportance("Bip01 Spine2", 3);
}


// finds and returns the importance of the given controller by id.
// returns 1 if the importance is unknown (assume the normal importance)
float isImportantCtrlID (unsigned nCtrlID)
{
	if (g_mapImportance.empty())
		InitImportanceMap();

	std::map<unsigned, float>::const_iterator it = g_mapImportance.find (nCtrlID);
	if (it != g_mapImportance.end())
		return it->second;
	else
		return 1.0f;
}

// Reads the CAF file, pushing all found motions into an array of ApprTest which
// optimizes them
CCafTestReader::CCafTestReader(CChunkFileReader* pReader, CBSplineApprTest::Params paramsPos, CBSplineApprTest::Params paramsRot, bool isOpen):
	m_nSpline (0),
	m_pFileReader (pReader),
	m_ParamsPos (paramsPos),
	m_ParamsRot (paramsRot),
	m_isOpen(isOpen),
	m_b8Bit (false)
{
	if (strncmp(pReader->getFileHeader().Signature, FILE_SIGNATURE, sizeof(FILE_SIGNATURE)))
		throw "Invalid file signature";

	if (pReader->getFileHeader().FileType != FileType_Anim)
		throw "Not an animation file";

	if (pReader->getFileHeader().Version != AnimFileVersion)
		throw "Unsupported animation file version";

	int numControllerChunks = pReader->numChunksOfType(ChunkType_Controller);

	int nControllerSize = 0, nOtherSize = 0;
	int nControllerChunkIdx = 0; // the current controller chunk idx

	for (int nChunk = 0; nChunk < pReader->numChunks(); ++nChunk)
	{
		const CHUNK_HEADER& chunkHeader = pReader->getChunkHeader(nChunk);
		int nChunkSize = pReader->getChunkSize(nChunk);
		const void* pChunk = pReader->getChunkData(nChunk);
		switch (chunkHeader.ChunkType)
		{
		case ChunkType_Controller:
		switch (chunkHeader.ChunkVersion)
		{
		case CONTROLLER_CHUNK_DESC_0826::VERSION:
			{
				const CONTROLLER_CHUNK_DESC_0826& chunk = *(const CONTROLLER_CHUNK_DESC_0826*)pReader->getChunkData(nChunk);

				//if (nChunkSize < sizeof (CONTROLLER_CHUNK_DESC_0826))
				//	throw "Truncated Chunk description header";

				nControllerSize += nChunkSize;

				float fCurveness = 0;

				if (chunk.type == CTRL_CRYBONE
						&& chunk.nKeys > 3
						)
				{
					optimizeCtrlBone (nChunk, chunk, nChunkSize);
				}
				else
				if (chunk.type == CTRL_BSPLINE_1C
					||chunk.type == CTRL_BSPLINE_2C
					||chunk.type == CTRL_BSPLINE_1O
					||chunk.type == CTRL_BSPLINE_2O)
				{
					readCtrlBSpline (chunk, nChunkSize);
				}

				nControllerChunkIdx ++;
			}
			break;

		case CONTROLLER_CHUNK_DESC_0827::VERSION:
			{
				const CONTROLLER_CHUNK_DESC_0827& chunk = *(const CONTROLLER_CHUNK_DESC_0827*)pReader->getChunkData(nChunk);

				//if (nChunkSize < sizeof (CONTROLLER_CHUNK_DESC_0827))
				//	throw "Truncated Chunk description header";

				nControllerSize += nChunkSize;

				if (chunk.numKeys > 3)
					optimizeCtrlBone (nChunk, chunk, nChunkSize);

				nControllerChunkIdx ++;
			}
			break;

		default:
			;
			//throw "Unsupported controller chunk version";
		}
		break;

		case ChunkType_BoneAnim:
			nOtherSize += nChunkSize;
			LogToDbg("ChunkType_BoneAnim: %d bytes (%d)\n", nChunkSize, nOtherSize);
		break;

		case ChunkType_BoneNameList:
			readNameEntities(chunkHeader, pChunk, nChunkSize);
			nOtherSize += nChunkSize;
			LogToDbg("ChunkType_BoneNameList: %d bytes (%d)\n", nChunkSize, nOtherSize);
		break;

		case ChunkType_Timing:
			nOtherSize += nChunkSize;
			LogToDbg("ChunkType_Timing: %d bytes (%d)\n", nChunkSize, nOtherSize);
		break;

		case ChunkType_SceneProps:
			nOtherSize += nChunkSize;
			LogToDbg("ChunkType_SceneProps: %d bytes (%d)\n", nChunkSize, nOtherSize);
		break;

		case ChunkType_BoneMesh:
			nOtherSize += nChunkSize;
			LogToDbg("ChunkType_BoneMesh: %d bytes (%d)\n", nChunkSize, nOtherSize);
		break;

		default:
		{
			nOtherSize += nChunkSize;
			LogToDbg("Other Chunk (%d): %d bytes (%d)\n", chunkHeader.ChunkType, nChunkSize, nOtherSize);
			assert (nOtherSize >= 0);
		}
		break;
		}
	}
	LogToDbg("File read, %d bytes of controller chunks, %d bytes of other chunks\n", nControllerSize, nOtherSize);
}

void CCafTestReader::optimizeCtrlBone (int nChunk, const CONTROLLER_CHUNK_DESC_0826& chunk, int nChunkSize)
{
	if (nChunkSize < int(sizeof (CONTROLLER_CHUNK_DESC_0826) + chunk.nKeys * sizeof(CryBoneKey)))
		throw "Truncated chunk data";

	// read all the bones into approximator
	const CryBoneKey* pKeys = (const CryBoneKey*)(&chunk+1);
	
	std::vector<int> vTimes;
	std::vector<Vec3d> vPos;
	std::vector<Vec3d> vRot;

	vTimes.resize (chunk.nKeys);
	vPos.resize(chunk.nKeys);
	vRot.resize(chunk.nKeys);
	
	CryQuat qPrev (1,0,0,0); // must be identity

	float fCurveness = 0;
	for (int nKey = 0; nKey < chunk.nKeys; ++nKey)
	{
		vTimes[nKey] = pKeys[nKey].time;
		
		CryQuat qCurr = pKeys[nKey].relquat;
		
		if ((qCurr|qPrev) < 0)
			qCurr = -qCurr;

		//FIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXMEFIXME
		vRot[nKey] = log (qCurr);//.v;
		fCurveness += vRot[nKey].GetLength();

		qPrev = qCurr;

		vPos[nKey] = pKeys[nKey].relpos;
	}

	float fImportance = isImportantCtrlID(chunk.nControllerId);

	m_mapCtrlSpline.insert (IntIntMap::value_type(nChunk, m_arrSplineApproximators.size()));
	CBSplineApprTest::Params paramsPos = m_ParamsPos;
	m_arrSplineApproximators.push_back(new CBSplineApprTest(vTimes, vPos, paramsPos, m_isOpen));
	CBSplineApprTest::Params paramsRot = m_ParamsRot;
	paramsRot.fPrecision *= fImportance;
	m_arrSplineApproximators.push_back(new CBSplineApprTest(vTimes, vRot, paramsRot, m_isOpen));

	m_arrSplineCtrlID.push_back(chunk.nControllerId);
	m_arrSplineCtrlID.push_back(chunk.nControllerId);
}


void CCafTestReader::optimizeCtrlBone (int nChunk, const CONTROLLER_CHUNK_DESC_0827& chunk, int nChunkSize)
{
	if (nChunkSize < int(sizeof (CONTROLLER_CHUNK_DESC_0827) + chunk.numKeys * sizeof(CryKeyPQLog)))
		throw "Truncated chunk data";

	// read all the bones into approximator
	const CryKeyPQLog* pKeys = (const CryKeyPQLog*)(&chunk+1);
	
	std::vector<int> vTimes;
	std::vector<Vec3d> vPos;
	std::vector<Vec3d> vRot;

	vTimes.resize (chunk.numKeys);
	vPos.resize(chunk.numKeys);
	vRot.resize(chunk.numKeys);
	
	float fCurveness = 0;
	for (unsigned nKey = 0; nKey < chunk.numKeys; ++nKey)
	{
		vTimes[nKey] = pKeys[nKey].nTime;
		vRot[nKey] = pKeys[nKey].vRotLog;
		vPos[nKey] = pKeys[nKey].vPos;
	}

	float fImportance = isImportantCtrlID(chunk.nControllerId);

	m_mapCtrlSpline.insert (IntIntMap::value_type(nChunk, m_arrSplineApproximators.size()));
	CBSplineApprTest::Params paramsPos = m_ParamsPos;
	m_arrSplineApproximators.push_back(new CBSplineApprTest(vTimes, vPos, paramsPos, m_isOpen));
	CBSplineApprTest::Params paramsRot = m_ParamsRot;
	paramsRot.fPrecision *= fImportance;
	m_arrSplineApproximators.push_back(new CBSplineApprTest(vTimes, vRot, paramsRot, m_isOpen));

	m_arrSplineCtrlID.push_back(chunk.nControllerId);
	m_arrSplineCtrlID.push_back(chunk.nControllerId);
}


CCafTestReader::~CCafTestReader(void)
{
}

void CCafTestReader::onDraw (CDC* pDC, CMotionOptimizerView* pView)
{
	RECT rc;
	pView->GetClientRect(&rc);
	setSize (pView);

	if (m_arrSplineApproximators.empty())
	{
		char sz[1000];
		sprintf (sz, "No Data to display. %d approximated splines, %d packed splines.", m_arrSplineApproximators.size(), m_arrSplinesPacked.size());
		pDC->ExtTextOut (10,rc.bottom-24, 0, NULL, sz, NULL);
	}
	else
	{
		if (m_nSpline >= 0 && m_nSpline < (int)m_arrSplineCtrlID.size())
		{
			char szTrack[128];
			
			unsigned nCtrlID = 0;
			if ((int)m_arrSplineCtrlID.size() > m_nSpline)
				nCtrlID = m_arrSplineCtrlID[m_nSpline];
			std::string sName = m_mapNames[nCtrlID];

			sprintf (szTrack, "%s %d (\"%s\", crc 0x%X)", (m_nSpline & 1)?"Rotation":"Position", m_nSpline/2, sName.c_str(), nCtrlID);

			pDC->ExtTextOut (10,rc.bottom-24, 0, NULL, szTrack, NULL);
		}

		if (m_nSpline >= 0 && m_nSpline < (int)m_arrSplineApproximators.size())
			m_arrSplineApproximators[m_nSpline]->onDraw (pDC, pView);
	}
	
	if (m_nSpline >= 0 && m_nSpline < (int)m_arrSplinesPacked.size())
	{
		draw(pDC, m_arrSplinesPacked[m_nSpline]);
	}
}


void CCafTestReader::onMouseWheel (UINT nFlags, short zDelta, CPoint pt, CMotionOptimizerView* pView)
{
	if (m_arrSplineApproximators.empty() && m_arrSplinesPacked.empty())
		m_nSpline = 0;
	else
	{
		if (zDelta > 0)
			m_nSpline++;
		else
			m_nSpline--;
		tforceRangeIncl<int>(m_nSpline, 0, tmax(m_arrSplineApproximators.size(), m_arrSplinesPacked.size())-1);
	}
}


void CCafTestReader::onCommand (int nCommand)
{
	switch (nCommand)
	{
	case ID_FILE_EXPORT_CAF:
		onExportCaf ();
		break;
	case ID_REOPTIMIZE:
		{
			CBSplineApprTest_AutoPtr pApprTest = m_arrSplineApproximators[m_nSpline];
			CDlgApprParams dlgParams(AfxGetMainWnd(), pApprTest->m_Params);
			if (dlgParams.DoModal() == IDOK)
			{
				pApprTest->Reoptimize (dlgParams.m_Params);
			}
		}
		break;
	}
}


// sets the name identifying the CAF file
void CCafTestReader::setOriginName (const char* szName)
{
	m_sOriginName = szName;
}


// Exports optimized CAF file by copying all unknown chunks from input file to output file
// and replacing the controller chunks with packed controllers
void CCafTestReader::onExportCaf(void)
{
	CFileDialog fileDlg(FALSE, "caf", "*.caf", OFN_PATHMUSTEXIST|OFN_OVERWRITEPROMPT, "Crytek Animation Files (*.caf)|*.caf|All Files (*.*)|*.*", AfxGetMainWnd());
	char szTitle[200];
	sprintf (szTitle, "Save optimized '%s' file as...", m_sOriginName.c_str());
	fileDlg.m_ofn.lpstrTitle = szTitle;
	if (fileDlg.DoModal() == IDOK)
	{
		try
		{
			exportCaf (fileDlg.GetPathName());
		}
		catch (CChunkFileWriter::Error& e)
		{
			AfxMessageBox(e.c_str(), MB_OK|MB_ICONSTOP);
		}
	}
}

void CCafTestReader::exportCaf(const char* szPath)
{
	CChunkFileWriter writer (szPath, FileType_Anim, AnimFileVersion);

	for (int nChunk = 0; nChunk < m_pFileReader->numChunks(); ++nChunk)
	{
		const CHUNK_HEADER& chunkHeader = m_pFileReader->getChunkHeader(nChunk);
		int nChunkSize = m_pFileReader->getChunkSize(nChunk);
		const void* pChunkData = m_pFileReader->getChunkData(nChunk);
		switch (chunkHeader.ChunkType)
		{
		case ChunkType_Controller:
			if (m_mapCtrlSpline.find(nChunk) != m_mapCtrlSpline.end())
			{
				CBSplineApprTest* pSplinePos = m_arrSplineApproximators[m_mapCtrlSpline[nChunk]];
				CBSplineApprTest* pSplineRot = m_arrSplineApproximators[m_mapCtrlSpline[nChunk]+1];

				unsigned nControllerId;
				switch (chunkHeader.ChunkVersion)
				{
				case CONTROLLER_CHUNK_DESC_0826::VERSION:
					nControllerId = ((const CONTROLLER_CHUNK_DESC_0826*)pChunkData)->nControllerId;
					break;
				case CONTROLLER_CHUNK_DESC_0827::VERSION:
					nControllerId = ((const CONTROLLER_CHUNK_DESC_0827*)pChunkData)->nControllerId;
					break;
				default:
					nControllerId = 0;
					LogToDbg("Unknown chunk: cannot deduce controller id");
					assert (0);
					break;
				}

				writeController (chunkHeader, nControllerId, writer, pSplinePos->getSpline(), pSplineRot->getSpline());
			}
			else // otherwise keep the chunk
			{
//				writer.addChunk(chunkHeader);
//				writer.write (m_pFileReader->getChunkData(nChunk), nChunkSize);
			}
		break;

		case ChunkType_Mesh:
			// ignore mesh chunks
			LogToDbg("Ignoring mesh chunk (%d bytes)\n", nChunkSize);
			break;

		default:
			writer.addChunk(chunkHeader);
			writer.write (m_pFileReader->getChunkData(nChunk), nChunkSize);
			break;
		}
	}
}

void CCafTestReader::readCtrlBSpline (const CONTROLLER_CHUNK_DESC_0826& chunk, int nChunkSize)
{
	IBSpline3Packed_AutoPtr pSplineRot,pSplinePos;
	switch (chunk.type)
	{
	case CTRL_BSPLINE_1C:
		pSplineRot = new TBSplineVec3dPacked<false,unsigned char>();
		pSplinePos = new TBSplineVec3dPacked<false,unsigned char>();
		break;
	case CTRL_BSPLINE_2C:
		pSplineRot = new TBSplineVec3dPacked<false,unsigned short>();
		pSplinePos = new TBSplineVec3dPacked<false,unsigned short>();
		break;
	case CTRL_BSPLINE_1O:
		pSplineRot = new TBSplineVec3dPacked<true,unsigned char>();
		pSplinePos = new TBSplineVec3dPacked<true,unsigned char>();
		break;
	case CTRL_BSPLINE_2O:
		pSplineRot = new TBSplineVec3dPacked<true,unsigned short>();
		pSplinePos = new TBSplineVec3dPacked<true,unsigned short>();
		break;
	};

	if (nChunkSize < sizeof(CONTROLLER_CHUNK_DESC_0826) + 2)
		throw "Truncated chunk";

	// data for the positional spline
	char* pData = (char*)(&chunk+1);
	
	// remaining size
	int nSize = nChunkSize-sizeof(CONTROLLER_CHUNK_DESC_0826);
	
	int nPosSplineSize = pSplinePos->unpack (pData, nSize);
	if (!nPosSplineSize)
		throw "Truncated or invalid chunk: cannot read positional packed spline";
	nSize -= nPosSplineSize;
	pData += nPosSplineSize;

	m_arrSplinesPacked.push_back(pSplinePos);

	int nRotSplineSize = pSplineRot->unpack (pData, nSize);
	if (!nRotSplineSize)
		throw "Truncated or invalid chunk: cannot read rotational packed spline";

	m_arrSplinesPacked.push_back(pSplineRot);
}

void CCafTestReader::draw (CDC* pDC, IBSpline3Packed* pSpline)
{
	float fFirstKnots[2] = {pSpline->getKnotTime(0),pSpline->getKnotTime(1)};
	Vec3d vTest = pSpline->getValue ((fFirstKnots[1]+fFirstKnots[0])/2);

	pDC->SelectObject (GetStockObject (WHITE_BRUSH));
	
	m_fMinX = pSpline->getTimeMin();
	m_fMaxX = pSpline->getTimeMax();
	m_fMinY = tmin(pSpline->getCPMin()[0],pSpline->getCPMin()[1],pSpline->getCPMin()[2]);
	m_fMaxY = tmax(pSpline->getCPMax()[0],pSpline->getCPMax()[1],pSpline->getCPMax()[2]);

	int numKnots = pSpline->numKnots();
	int numCPs = pSpline->numCPs();

	char szTitle[200];
	sprintf (szTitle, "%d knots, %d CPs, time: [%g..%g], xyz: [%7.5f..%7.5f]", numKnots, numCPs, m_fMinX, m_fMaxX, m_fMinY, m_fMaxY);
	pDC->ExtTextOut(0,30,0, 0, szTitle, 0);

	/*
	// draw the keys
	for (int nKey = 0; nKey < numKnots; ++nKey)
	{
		float t = pSpline->getKnotTime(nKey);
	
		pDC->SelectObject(&m_penGrid);
		pDC->MoveTo (point (t, m_fMinY));
		pDC->LineTo (point (t, m_fMaxY));
	}

	for (int nCoord = 0; nCoord < 3; ++nCoord)
	{
		float t = pSpline->getKnotTime(0);
		Vec3d v = pSpline->getCP(0);
		pDC->SelectObject (&m_penWeakSplineRGB[nCoord]);
		pDC->MoveTo(point(t, v[nCoord]));
		for (int i = 1; i < pSpline->numKnots(); ++i)
		{
			t = pSpline->getKnotTime(i);
			v = pSpline->getCP(i);
			pDC->LineTo (point(t, v[nCoord]));
		}
	}
	*/
	
	// draw the approximation
	for (int nCoord = 0; nCoord < 3; ++nCoord)
	{
		//pDC->SelectObject(&m_penSpline);
		pDC->SelectObject (m_penRGB + nCoord);
		
		float t = pSpline->getTimeMin(),tmax = pSpline->getTimeMax(), fStep = (tmax-t)/500;
		Vec3d v = pSpline->getValue(t);
		pDC->MoveTo(point (t, v[nCoord]));

		for (t+=0.001f;t <= tmax; t+= fStep)
		{
			v = pSpline->getValue(t);
			pDC->LineTo(point(t,v[nCoord]));
		}
	}
}

// array of name entities
void CCafTestReader::readNameEntities (const CHUNK_HEADER& chunkHeader, const void* pChunk, unsigned nChunkSize)
{
	std::vector<const char*> arrNames;
	if (!LoadBoneNameList(chunkHeader, pChunk, nChunkSize, arrNames))
		LogToDbg("Cannot parse the names");
	else
	for (unsigned nEntity = 0; nEntity < arrNames.size(); ++nEntity)
	{
		unsigned nControllerID = Crc32Gen::GetCRC32 (arrNames[nEntity]);
		m_mapNames.insert (NameEntityMap::value_type (nControllerID, arrNames[nEntity]));
	}
}

// packs and writes out the given spline representation
void CCafTestReader::writeSpline (
	class CChunkFileWriter& writer,
	BSplineVec3d * pSpline)
{
	IBSpline3Packed_AutoPtr pSplinePacked;
	
	if (pSpline->isOpen())
		if (m_b8Bit)
			pSplinePacked = new TBSplineVec3dPacked <true, unsigned char>;
		else
			pSplinePacked = new TBSplineVec3dPacked <true, unsigned short>;
	else
		if (m_b8Bit)
			pSplinePacked = new TBSplineVec3dPacked <false, unsigned char>;
		else
			pSplinePacked = new TBSplineVec3dPacked <false, unsigned short>;

	pSplinePacked->copy (pSpline);
	int nBufferSize = pSplinePacked->getPackedSize ();
	char* pBuffer = new char[nBufferSize];
	pSplinePacked->pack (pBuffer);
	writer.write (pBuffer, nBufferSize);
	delete[] pBuffer;
}

// Writes out a packed representation of the given motion chunk.
// The optimized splines for positional and rotational data must be passed.
void CCafTestReader::writeController (
	const ChunkHeader& chunkHeader,
	unsigned nControllerId,
	class CChunkFileWriter& writer,
	BSplineVec3d* pSplinePos,
	BSplineVec3d* pSplineRot)
{
	assert (pSplinePos->isOpen() == pSplineRot->isOpen());
	writer.addChunk(ChunkType_Controller, CONTROLLER_CHUNK_DESC_0826::VERSION, chunkHeader.ChunkID);
	CONTROLLER_CHUNK_DESC_0826 chunkNew;
	chunkNew.chdr          = writer.getChunk();
	chunkNew.nControllerId = nControllerId;
	chunkNew.nKeys         = pSplinePos->numKnots() + pSplineRot->numKnots();
	chunkNew.type          = pSplinePos->isOpen()?CTRL_BSPLINE_2O:CTRL_BSPLINE_2C;

	writer.write (chunkNew);
	// write the pos and then rot spline
	writeSpline (writer, pSplinePos);
	writeSpline (writer, pSplineRot);
}
