/*
	Copyright (C) 2005-2007 Feeling Software Inc.
	Portions of the code are:
	Copyright (C) 2005-2007 Sony Computer Entertainment America
	
	MIT License: http://www.opensource.org/licenses/mit-license.php
*/

#include "StdAfx.h"
#include "FCDocument/FCDController.h"
#include "FCDocument/FCDGeometry.h"
#include "FCDocument/FCDGeometryMesh.h"
#include "FCDocument/FCDGeometryPolygons.h"
#include "FCDocument/FCDGeometryPolygonsInput.h"
#include "FCDocument/FCDGeometrySource.h"
#include "FCDocument/FCDGeometrySpline.h"
#include "FCDocument/FCDMorphController.h"
#include "FCDocument/FCDSceneNode.h"
#include "FCDocument/FCDSkinController.h"
#include "FCTestExportImport.h"

static const float positionData[12] = { 0.0f, 0.0f, 3.0f, 5.0f, 0.0f, -2.0f, -3.0f, 4.0f, -2.0f, -3.0f, -4.0f, -2.0f };
static const float colorData[12] = { 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f };
static const float dummyData[10] = { 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f };
static const uint32 positionIndices[12] = { 0, 1, 2, 0, 2, 3, 0, 3, 1, 3, 2, 1 };
static const uint32 colorIndices[12] = { 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2 };

static const float sampleBindPose1[16] = { 1.0f, 0.4f, 0.4f, 0.0f, 7.77f, 0.0f, 0.3f, 2.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f };
static const float sampleBindPose2[16] = { 0.3f, 0.0f, -0.3f, -21.0f, 0.96f, 0.0f, 2.0f, 2.5f, 0.0f, -5.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f };
static fm::string splineId, meshId, jointId1, jointId2;

namespace FCTestExportImport
{
	static const char* szTestName = "FCTestExportImportGeometry";

	bool FillGeometryLibrary(FULogFile& fileOut, FCDGeometryLibrary* library)
	{
		FCDGeometry* geometry = library->AddEntity();
		PassIf(geometry->GetType() == FCDEntity::GEOMETRY);
		FailIf(geometry->IsMesh());
		FailIf(geometry->IsSpline());
		meshId = geometry->GetDaeId();
		FailIf(meshId.empty());

		// Creates a mesh to export
		FCDGeometryMesh* mesh = geometry->CreateMesh();
		FailIf(geometry->IsSpline());
		PassIf(geometry->IsMesh());
		PassIf(geometry->GetMesh() == mesh);
		PassIf(geometry->GetSpline() == NULL);
		FillGeometryMesh(fileOut, mesh);

		// Create a spline to export
		geometry = library->AddEntity();
		geometry->CreateMesh();
		FCDGeometrySpline* spline = geometry->CreateSpline();
		PassIf(geometry->IsSpline());
		FailIf(geometry->IsMesh());
		PassIf(geometry->GetMesh() == NULL);
		PassIf(geometry->GetSpline() == spline);
		FillGeometrySpline(fileOut, spline);
		splineId = geometry->GetDaeId();
		FailIf(splineId.empty());
		return true;
	}

	bool FillGeometryMesh(FULogFile& fileOut, FCDGeometryMesh* mesh)
	{
		FCDGeometrySource* posSource = mesh->AddVertexSource();
		FailIf(posSource == NULL);
		posSource->SetName(FC("TestPositionSource"));
		posSource->SetSourceType(FUDaeGeometryInput::POSITION);
		posSource->SetData(FloatList(positionData, 12), 3);

		FCDGeometrySource* colorSource = mesh->AddSource();
		FailIf(colorSource == NULL);
		colorSource->SetName(FC("TestColorSource"));
		colorSource->SetSourceType(FUDaeGeometryInput::COLOR);
		colorSource->SetData(FloatList(colorData, 12), 4);

		FCDGeometrySource* dummySource = mesh->AddSource();
		FailIf(dummySource == NULL);
		dummySource->SetName(FC("TestDummySource"));
		dummySource->SetSourceType(FUDaeGeometryInput::EXTRA);
		dummySource->SetData(FloatList(dummyData, 10), 3);
		PassIf(FillExtraTree(fileOut, dummySource->GetExtra(), false));

		FCDGeometryPolygons* polys1 = mesh->AddPolygons();
		FailIf(polys1 == NULL || polys1->GetInputCount() != 1);
		FCDGeometryPolygons* polys2 = mesh->AddPolygons();
		FailIf(polys2 == NULL || polys2->GetInputCount() != 1);
		FCDGeometryPolygonsInput* pInput1 = polys1->AddInput(colorSource, 1);
		PassIf(polys1->GetInputCount() == 2);
		PassIf(pInput1 != NULL && pInput1->GetSource() == colorSource && pInput1->GetOffset() == 1);
		FCDGeometryPolygonsInput* pInput2 = polys2->AddInput(colorSource, 1);
		PassIf(polys2->GetInputCount() == 2);
		PassIf(pInput2 != NULL && pInput2->GetSource() == colorSource && pInput2->GetOffset() == 1);
		FCDGeometryPolygonsInput* pInput3 = polys1->AddInput(dummySource, 2);
		PassIf(pInput3 != NULL && pInput3->GetSource() == dummySource && pInput3->GetOffset() == 2);

		// Write some extra tree in there too.
		FillExtraTree(fileOut, polys1->GetExtra(), true);
		FillExtraTree(fileOut, polys2->GetExtra(), true);

		// Fill in some indices in order to form a tetrahedron
		polys1->AddFace(3); polys1->AddFace(3); polys1->AddFace(3); polys1->AddFace(3);
		FailIf(polys1->FindInput(posSource) == NULL);
		polys1->FindInput(posSource)->SetIndices(positionIndices, 12);
		FailIf(polys1->FindInput(posSource) == NULL);
		polys1->FindInput(colorSource)->SetIndices(colorIndices, 12);
		return true;
	}

	bool FillGeometrySpline(FULogFile& fileOut, FCDGeometrySpline* spline)
	{
		spline->SetType(FUDaeSplineType::BEZIER);
		FCDBezierSpline* splineData = (FCDBezierSpline*) spline->AddSpline();
		PassIf(splineData->IsType(FCDBezierSpline::GetClassType()));
		/*
		FCDCVs cvs;
		cvs.push_back(FMVector3(1.0f, 2.0f, 4.0f));
		cvs.push_back(FMVector3::XAxis);
		spline->SetCVs(cvs);
		PassIf(spline->GetCVCount() == 2);

		FCDKnots knots;
		knots.push_back(1.4);
		knots.push_back(7.4);
		spline->SetKnots(knots);
		PassIf(spline->GetKnotCount() == 2); */
		return true;
	}

	bool CheckGeometryLibrary(FULogFile& fileOut, FCDGeometryLibrary* library)
	{
		PassIf(library->GetEntityCount() == 2);

		// Find the one mesh and the one spline geometries.
		FCDGeometryMesh* mesh = NULL; FCDGeometrySpline* spline = NULL;
		for (size_t i = 0; i < 2; ++i)
		{
			FCDGeometry* g = library->GetEntity(i);
			if (g->IsMesh()) mesh = g->GetMesh();
			else if (g->IsSpline()) spline = g->GetSpline();
			else Fail;
		}
		FailIf(mesh == NULL || spline == NULL);

		CheckGeometryMesh(fileOut, mesh);
		CheckGeometrySpline(fileOut, spline);
		return true;
	}

	bool CheckGeometryMesh(FULogFile& fileOut, FCDGeometryMesh* mesh)
	{
		// Verify the mesh and its sources
		PassIf(mesh->GetSourceCount() == 3);
		FCDGeometrySource* posSource = NULL,* colorSource = NULL,* dummySource = NULL;
		for (size_t i = 0; i < 3; ++i)
		{
			FCDGeometrySource* source = mesh->GetSource(i);
			FailIf(source == NULL);
			switch (source->GetType())
			{
			case FUDaeGeometryInput::POSITION: posSource = source; PassIf(source->GetName() == FC("TestPositionSource")); break;
			case FUDaeGeometryInput::COLOR: colorSource = source; PassIf(source->GetName() == FC("TestColorSource")); break;
			case FUDaeGeometryInput::EXTRA: dummySource = source; PassIf(source->GetName() == FC("TestDummySource")); break;
			default: Fail; break;
			}
		}
		FailIf(posSource == NULL || colorSource == NULL || dummySource == NULL);
		PassIf(IsEquivalent(posSource->GetData(), posSource->GetDataCount(), positionData, 12));
		PassIf(posSource->GetStride() == 3);
		PassIf(IsEquivalent(colorSource->GetData(), colorSource->GetDataCount(), colorData, 12));
		PassIf(colorSource->GetStride() == 4);
		PassIf(IsEquivalent(dummySource->GetData(), dummySource->GetDataCount(), dummyData, 10));
		PassIf(dummySource->GetStride() == 3);
		PassIf(CheckExtraTree(fileOut, dummySource->GetExtra(), false));

		// Find the non-empty polygon set and verify that one of the polygon set is, in fact, empty.
		FCDGeometryPolygons* polys1 = NULL,* polysEmpty = NULL;
		for (size_t i = 0; i < mesh->GetPolygonsCount(); ++i)
		{
			FCDGeometryPolygons* p = mesh->GetPolygons(i);
			if (p->GetFaceCount() == 0) { PassIf(polysEmpty == NULL); polysEmpty = p; }
			else { PassIf(polys1 == NULL); polys1 = p; }

			CheckExtraTree(fileOut, p->GetExtra(), true);
		}
		PassIf(polys1 != NULL && polysEmpty != NULL);

		// Check that we have the wanted tetrahedron in the non-empty polygon set.
		PassIf(polys1->GetFaceCount() == 4);
		PassIf(polys1->GetHoleCount() == 0);
		PassIf(polys1->GetFaceVertexCount(0) == 3 && polys1->GetFaceVertexCount(1) == 3 && polys1->GetFaceVertexCount(2) == 3 && polys1->GetFaceVertexCount(3) == 3);
		FCDGeometryPolygonsInput* posInput = polys1->FindInput(posSource);
		FailIf(posInput == NULL || posInput->GetIndexCount() != 12);
		FCDGeometryPolygonsInput* colorInput = polys1->FindInput(colorSource);
		FailIf(colorInput == NULL || colorInput == posInput || colorInput->GetIndexCount() != 12);
		PassIf(IsEquivalent(posInput->GetIndices(), 12, positionIndices, 12));
		PassIf(IsEquivalent(colorInput->GetIndices(), 12, colorIndices, 12));
		return true;
	}

	bool CheckGeometrySpline(FULogFile& UNUSED(fileOut), FCDGeometrySpline* UNUSED(spline))
	{/*
		// Verify the spline and its data
		FailIf(spline->GetCVCount() != 2);
		FailIf(spline->GetKnotCount() != 2);
		PassIf(IsEquivalent(*spline->GetCV(0), FMVector3(1.0f, 2.0f, 4.0f)));
		PassIf(IsEquivalent(*spline->GetCV(1), FMVector3::XAxis));
		PassIf(IsEquivalent(spline->GetKnot(0), 1.4));
		PassIf(IsEquivalent(spline->GetKnot(1), 7.4));*/
		return true;
	}

	bool FillControllerLibrary(FULogFile& fileOut, FCDControllerLibrary* library)
	{
		FailIf(library == NULL);

		// Create a morph controller on the previously created spline.
		FCDController* morpher = library->AddEntity();
		PassIf(FillControllerMorph(fileOut, morpher->CreateMorphController()));

		// Create a skin controller on the previously created mesh.
		FCDController* skin = library->AddEntity();
		skin->SetNote(FS("A nicey skinny controller. "));
		PassIf(FillControllerSkin(fileOut, skin->CreateSkinController()));
		return true;
	}

	bool FillControllerMorph(FULogFile& fileOut, FCDMorphController* controller)
	{
		FailIf(controller == NULL);
		controller->SetMethod(FUDaeMorphMethod::RELATIVE);

		// Retrieve the base spline entity that will be morphed
		// (and for this test only: it'll be the morph targets)
		// Also retrieve the mesh, for similarity checks.
		FCDGeometry* spline = controller->GetDocument()->FindGeometry(splineId);
		FailIf(spline == NULL);
		FCDGeometry* mesh = controller->GetDocument()->FindGeometry(meshId);
		FailIf(mesh == NULL);
		FailIf(controller->IsSimilar(mesh));
		FailIf(controller->IsSimilar(spline));

		controller->SetBaseTarget(spline);
		PassIf(controller->IsSimilar(spline));
		FailIf(controller->IsSimilar(mesh));

		controller->AddTarget(spline, 0.3f);
		controller->AddTarget(spline, 0.6f);
		PassIf(controller->GetTargetCount() == 2);
		return true;
	}

	bool FillControllerSkin(FULogFile& fileOut, FCDSkinController* controller)
	{
		FailIf(controller == NULL);

		// Create two joints.
		FCDSceneNode* visualScene = controller->GetDocument()->GetVisualSceneLibrary()->AddEntity();
		PassIf(visualScene != NULL);
		FCDSceneNode* joint1 = visualScene->AddChildNode();
		PassIf(joint1 != NULL);
		FCDSceneNode* joint2 = joint1->AddChildNode();
		PassIf(joint2 != NULL);
		// In the standard course of operations, we would either load a file
		// with subId Info, or link it in when loading the instance.  Here we have no instance
		fm::string jid1("joint1");
		fm::string jid2("joint2");
		joint1->SetSubId(jid1);
		joint2->SetSubId(jid2);
		jointId1 = joint1->GetSubId();
		jointId2 = joint2->GetSubId();
		FailIf(jointId1.empty() || jointId2.empty());
		controller->AddJoint(joint1->GetSubId(), FMMatrix44::Identity);
		controller->AddJoint(joint2->GetSubId(), FMMatrix44(sampleBindPose1).Inverted());
		controller->SetBindShapeTransform(FMMatrix44(sampleBindPose2));
		PassIf(controller->GetJointCount() == 2);

		// Retrieve and assign the base target
		FCDGeometry* geometricTarget = controller->GetDocument()->FindGeometry(meshId);
		controller->SetTarget(geometricTarget);

		// Set some influences
		PassIf(controller->GetInfluenceCount() == 4);
		FCDSkinControllerVertex* influence = controller->GetVertexInfluence(0);
		FailIf(influence == NULL);
		influence->AddPair(0, 0.5f);
		influence->AddPair(1, 0.5f);

		influence = controller->GetVertexInfluence(3);
		FailIf(influence == NULL);
		influence->AddPair(1, 1.0f);

		influence = controller->GetVertexInfluence(2);
		FailIf(influence == NULL);
		influence->AddPair(0, 0.1f);
		return true;
	}

	bool CheckControllerLibrary(FULogFile& fileOut, FCDControllerLibrary* library)
	{
		FailIf(library == NULL);

		FCDController* morpher = NULL,* skin = NULL;
		for (size_t i = 0; i < library->GetEntityCount(); ++i)
		{
			FCDController* c = library->GetEntity(i);
			if (c->IsMorph()) { PassIf(morpher == NULL); morpher = c; }
			else if (c->IsSkin()) { PassIf(skin == NULL); skin = c; }
			else Fail;
		}
		PassIf(morpher != NULL && skin != NULL);

		// Check the morpher
		FailIf(morpher->IsSkin());
		PassIf(morpher->IsMorph());
		PassIf(morpher->GetMorphController() != NULL);
		PassIf(CheckControllerMorph(fileOut, morpher->GetMorphController()));

		// Check the skin
		PassIf(skin->GetSkinController() != NULL);
		//FailIf(skin->HasMorphController());
		FailIf(skin->IsMorph());
		PassIf(skin->GetNote() == FC("A nicey skinny controller. "));
		PassIf(CheckControllerSkin(fileOut, skin->GetSkinController()));
		return true;
	}

	bool CheckControllerMorph(FULogFile& fileOut, FCDMorphController* controller)
	{
		FailIf(controller == NULL);
		PassIf(controller->GetMethod() == FUDaeMorphMethod::RELATIVE);

		// Check that there are two targets, that the weights are correct, as well as the ids.
		PassIf(controller->GetTargetCount() == 2);
		PassIf(controller->GetBaseTarget() != NULL);
		PassIf(controller->GetBaseTarget()->GetDaeId() == splineId);

		FCDMorphTarget* target1 = controller->GetTarget(0);
		FailIf(target1 == NULL);
		FCDMorphTarget* target2 = controller->GetTarget(1);
		FailIf(target2 == NULL);
		PassIf(target1->GetGeometry() == controller->GetBaseTarget());
		PassIf(target2->GetGeometry() == controller->GetBaseTarget());
		PassIf(IsEquivalent(target1->GetWeight(), 0.6f) || IsEquivalent(target1->GetWeight(), 0.3f));
		PassIf(IsEquivalent(target2->GetWeight(), 0.6f) || IsEquivalent(target2->GetWeight(), 0.3f));
		FailIf(IsEquivalent(target1->GetWeight(), target2->GetWeight()));
		return true;
	}

	bool CheckControllerSkin(FULogFile& fileOut, FCDSkinController* controller)
	{
		FailIf(controller == NULL);

		// Check the base target's identity
		FailIf(controller->GetTarget() == NULL);
		PassIf(controller->GetTarget()->GetType() == FCDEntity::GEOMETRY);
		PassIf(controller->GetTarget()->GetDaeId() == meshId);

		// Retrieve the two joints and verify their ids/bind-pose.
		PassIf(controller->GetJointCount() == 2);
		const FMMatrix44* joint1 = NULL,* joint2 = NULL;
		for (size_t i = 0; i < 2; ++i)
		{
			FCDSkinControllerJoint* joint = controller->GetJoint(i);
			if (joint->GetId() == jointId1) { FailIf(joint1 != NULL); joint1 = &joint->GetBindPoseInverse(); }
			else if (joint->GetId() == jointId2) { FailIf(joint2 != NULL); joint2 = &joint->GetBindPoseInverse(); }
			else Fail;
		}
		FailIf(joint1 == NULL || joint2 == NULL);
		PassIf(IsEquivalent(*joint1, FMMatrix44::Identity));
		FMMatrix44 sbp = FMMatrix44(sampleBindPose1).Inverted();
		PassIf(IsEquivalent(*joint2, FMMatrix44(sampleBindPose1).Inverted()));

		// Verify the influences
		PassIf(controller->GetInfluenceCount() == 4);
		FCDSkinControllerVertex* influence = controller->GetVertexInfluence(0);
		FailIf(influence == NULL);
		PassIf(influence->GetPairCount() == 2);
		PassIf(IsEquivalent(influence->GetPair(0)->jointIndex, 0));
		PassIf(IsEquivalent(influence->GetPair(0)->weight, 0.5f));
		PassIf(IsEquivalent(influence->GetPair(1)->jointIndex, 1));
		PassIf(IsEquivalent(influence->GetPair(1)->weight, 0.5f));

		influence = controller->GetVertexInfluence(1);
		FailIf(influence == NULL);
		PassIf(influence->GetPairCount() == 0);

		influence = controller->GetVertexInfluence(2);
		FailIf(influence == NULL);
		PassIf(influence->GetPairCount() == 1);
		PassIf(IsEquivalent(influence->GetPair(0)->jointIndex, 0));
		PassIf(IsEquivalent(influence->GetPair(0)->weight, 1.0f)); // the weight should have been normalized.

		influence = controller->GetVertexInfluence(3);
		FailIf(influence == NULL);
		PassIf(influence->GetPairCount() == 1);
		PassIf(IsEquivalent(influence->GetPair(0)->jointIndex, 1));
		PassIf(IsEquivalent(influence->GetPair(0)->weight, 1.0f));
		return true;
	}
};

