/*
	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/FCDEffect.h"
#include "FCDocument/FCDEffectCode.h"
#include "FCDocument/FCDEffectParameter.h"
#include "FCDocument/FCDEffectParameterSampler.h"
#include "FCDocument/FCDEffectParameterSurface.h"
#include "FCDocument/FCDEffectPass.h"
#include "FCDocument/FCDEffectPassShader.h"
#include "FCDocument/FCDEffectProfileFX.h"
#include "FCDocument/FCDEffectStandard.h"
#include "FCDocument/FCDEffectTechnique.h"
#include "FCDocument/FCDExtra.h"
#include "FCDocument/FCDImage.h"
#include "FCDocument/FCDMaterial.h"
#include "FCDocument/FCDTexture.h"
#include "FUtils/FUDaeSyntax.h"
#include "FCTestExportImport.h"

static const float sampleMatrix[16] = { 0.5f, 0.1f, 0.7f, 2.0f, 1.11f, 0.5e-2f, 111.0f, 0.5f, 0.0f, 0.0f, 0.557f, -10.02f, 0.001f, 12.0f, 1.02e-3f, 0.0f };
static fm::string wantedImageId = "test_image";
static fm::string wantedImage2Id = "test_image";
static fm::string surfaceParameterImageId;

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

	bool FillMaterialLibrary(FULogFile& fileOut, FCDMaterialLibrary* library)
	{
		// Create an empty material
		FCDMaterial* material = library->AddEntity();
		material->SetNote(FS("EmptyMaterial244"));

		// Create an effect and attach it to a new material
		FCDEffect* effect = library->GetDocument()->GetEffectLibrary()->AddEntity();
		material = library->AddEntity();
		material->SetEffect(effect);
		effect->SetNote(FS("EmptyEffect.. for now!"));

		FillEffectStandard(fileOut, (FCDEffectStandard*) effect->AddProfile(FUDaeProfileType::COMMON));
		FillEffectFX(fileOut, (FCDEffectProfileFX*) effect->AddProfile(FUDaeProfileType::CG));
		return true;
	}

	bool FillEffectStandard(FULogFile& fileOut, FCDEffectStandard* profile)
	{
		FailIf(profile == NULL);
		profile->SetLightingType(FCDEffectStandard::PHONG);
		profile->SetDiffuseColor(FMVector4(1.0f, 0.0f, -2.0f, 1.0f));
		PassIf(IsEquivalent(profile->GetDiffuseColor(), FMVector4(1.0f, 0.0f, -2.0f, 1.0f)));
		profile->SetSpecularColor(FMVector4(0.0f, 1.0f, 0.4f, 1.0f));
		profile->SetIndexOfRefraction(21.0f);
		profile->SetShininess(40.0f);

		// Retrieve two images created earlier
		FCDImage* image1 = profile->GetDocument()->FindImage(wantedImageId);
		FCDImage* image2 = profile->GetDocument()->FindImage(wantedImage2Id);
		PassIf(image1 != NULL && image2 != NULL); // Dependency! Must fill the image library first

		// Two simple bump textures
		FCDTexture* texture1 = profile->AddTexture(FUDaeTextureChannel::BUMP);
		texture1->SetImage(image2);
		FCDETechnique* technique1 = texture1->GetExtra()->GetDefaultType()->AddTechnique(DAEMAYA_MAYA_PROFILE);
		technique1->AddParameter(DAEMAYA_TEXTURE_WRAPU_PARAMETER, 2.0f);
		FCDTexture* texture2 = profile->AddTexture(FUDaeTextureChannel::BUMP);
		texture2->SetImage(image1);
		FCDETechnique* technique2 = texture2->GetExtra()->GetDefaultType()->AddTechnique(DAEMAYA_MAYA_PROFILE);
		technique2->AddParameter(DAEMAYA_TEXTURE_WRAPV_PARAMETER, -1.2f);

		// A bit more extra support.
		FCDEType* extraType = profile->GetExtra()->AddType("Curious");
		PassIf(extraType != NULL);
		FCDETechnique* extraTechnique = extraType->AddTechnique("Kiddy");
		PassIf(extraTechnique != NULL);
		FCDENode* extraNode = extraTechnique->AddParameter("Causes", "trouble");
		PassIf(extraNode != NULL);

		// The third texture is a filter texture and will remain empty.
		UNUSED(FCDTexture* texture3 =)profile->AddTexture(FUDaeTextureChannel::FILTER);
		return true;
	}

	bool FillEffectFX(FULogFile& fileOut, FCDEffectProfileFX* profile)
	{
		FailIf(profile == NULL);

		// Generate some code: both from a code file and using inline code.
		FCDEffectCode* code = profile->AddCode();
		code->SetCode(FS("Some CG code for you to parse!"));
		code->SetSubId("TouchesAKey");
		FCDEffectCode* code2 = profile->AddCode();
		code2->SetFilename(FS("HowDoTheyDoIt.dd"));
		code2->SetSubId("PowerOfWill");

		// Create a technique and dump a few passes in there.
		FCDEffectTechnique* technique = profile->AddTechnique();
		technique->SetName(FC("100 percent reason to remember the name."));
		FCDEffectPass* pass1 = technique->AddPass();
		pass1->SetPassName(FC("First Pass"));
		FCDEffectPass* pass2 = technique->AddPass();
		pass2->SetPassName(FC("Second Pass"));

		// Dump a few parameters around
		FCDEffectParameterBool* booleanParameter = (FCDEffectParameterBool*) profile->AddEffectParameter(FCDEffectParameter::BOOLEAN);
		booleanParameter->SetSemantic("BOOL_P");
		booleanParameter->SetValue(true);
		FCDEffectParameterFloat2* floatParameter = (FCDEffectParameterFloat2*) technique->AddEffectParameter(FCDEffectParameter::FLOAT2);
		floatParameter->SetSemantic("FLOAT2_P");
		floatParameter->SetValue(FMVector2(1.5f, 5.2f));

		// Add some annotations to the float parameter.
		floatParameter->AddAnnotation(FS("UIMin"), FCDEffectParameter::FLOAT, 0.2f);
		floatParameter->AddAnnotation(FS("UIMax"), FCDEffectParameter::INTEGER, 5);
		floatParameter->AddAnnotation(FS("UIWidget"), FCDEffectParameter::STRING, FS("RolloverSlider"));
		floatParameter->AddAnnotation(FS("UIValid"), FCDEffectParameter::BOOLEAN, true);

		// Add a sampler/surface parameter pair
		FCDEffectParameterSurface* surface = (FCDEffectParameterSurface*) profile->AddEffectParameter(FCDEffectParameter::SURFACE);
		FCDImage* surfaceImage = profile->GetDocument()->GetImageLibrary()->GetEntity(0);
		surface->AddImage(surfaceImage);
		surface->SetReference("TestSF");
		surface->SetSurfaceType("RANDOMIZE");
		surface->SetFormat("Plurial");
		surface->SetInitMethod(new FCDEffectParameterSurfaceInitFrom());
		surfaceParameterImageId = surfaceImage->GetDaeId();

		FCDFormatHint* fhint = surface->AddFormatHint();
		fhint->channels = FCDFormatHint::CHANNEL_RGB;
		fhint->precision = FCDFormatHint::PRECISION_MID;
		fhint->range = FCDFormatHint::RANGE_SNORM;
		fhint->options.push_back(FCDFormatHint::OPT_NORMALIZED3);

		FCDEffectParameterSampler* sampler = (FCDEffectParameterSampler*) profile->AddEffectParameter(FCDEffectParameter::SAMPLER);
		sampler->SetSurface(surface);
		sampler->SetSamplerType(FCDEffectParameterSampler::SAMPLER2D);
		sampler->SetReference("Gotit");
		return true;
	}

	bool CheckMaterialLibrary(FULogFile& fileOut, FCDMaterialLibrary* library)
	{
		// There should be two materials within the material library: one is empty, the other is not.
		PassIf(library->GetEntityCount() == 2);
		PassIf(library->GetDocument()->GetEffectLibrary()->GetEntityCount() == 1);

		FCDMaterial* emptyMaterial = NULL,* material = NULL;
		for (size_t i = 0; i < library->GetEntityCount(); ++i)
		{
			FCDMaterial* m = library->GetEntity(i);
			if (m->GetEffect() == NULL) { PassIf(emptyMaterial == NULL); emptyMaterial = m; }
			else { PassIf(material == NULL); material = m; }
		}
		PassIf(emptyMaterial != NULL && material != NULL);

		// Verify the empty material. It should only have a note.
		PassIf(emptyMaterial->GetNote() == FC("EmptyMaterial244"));

		// Verify the other material and its effect.
		FCDEffect* effect = material->GetEffect();
		PassIf(library->GetDocument()->GetEffectLibrary()->GetEntity(0) == effect);
		PassIf(effect->GetNote() == FC("EmptyEffect.. for now!"));
		PassIf(effect->GetProfileCount() == 2);

		CheckEffectStandard(fileOut, (FCDEffectStandard*) effect->FindProfile(FUDaeProfileType::COMMON));
		CheckEffectFX(fileOut, (FCDEffectProfileFX*) effect->FindProfile(FUDaeProfileType::CG));
		return true;
	}

	bool CheckEffectStandard(FULogFile& fileOut, FCDEffectStandard* profile)
	{
		FailIf(profile == NULL);
		PassIf(profile->GetLightingType() == FCDEffectStandard::PHONG);
		PassIf(IsEquivalent(profile->GetDiffuseColor(), FMVector4(1.0f, 0.0f, -2.0f, 1.0f)));
		PassIf(IsEquivalent(profile->GetSpecularColor(), FMVector4(0.0f, 1.0f, 0.4f, 1.0f)));
		PassIf(IsEquivalent(profile->GetShininess(), 40.0f));
		PassIf(IsEquivalent(profile->GetIndexOfRefraction(), 21.0f));

		// There should be two textures in the bump channel.
		PassIf(profile->GetTextureCount(FUDaeTextureChannel::BUMP) == 2);
		FCDTexture* texture1 = NULL,* texture2 = NULL;
		for (size_t i = 0; i < 2; ++i)
		{
			FCDTexture* texture = profile->GetTexture(FUDaeTextureChannel::BUMP, i);
			FailIf(texture == NULL);
			if (texture1 == NULL) texture1 = texture;
			else if (texture2 == NULL) texture2 = texture;
			else Fail;
		}
		PassIf(texture1 != NULL && texture2 != NULL);

		// Verify the texture images
		FCDImage* image1 = profile->GetDocument()->FindImage(wantedImageId);
		FCDImage* image2 = profile->GetDocument()->FindImage(wantedImage2Id);
		PassIf(image1 != NULL && image2 != NULL);
		PassIf(texture1->GetImage() == image2);
		PassIf(texture2->GetImage() == image1);

		// Verify the placement parameters
		FCDETechnique* technique1 = texture1->GetExtra()->GetDefaultType()->FindTechnique(DAEMAYA_MAYA_PROFILE);
		FCDETechnique* technique2 = texture2->GetExtra()->GetDefaultType()->FindTechnique(DAEMAYA_MAYA_PROFILE);
		PassIf(technique1 != NULL && technique2 != NULL);
		FCDENode* node1 = technique1->FindParameter(DAEMAYA_TEXTURE_WRAPU_PARAMETER);
		FCDENode* node2 = technique2->FindParameter(DAEMAYA_TEXTURE_WRAPV_PARAMETER);
		PassIf(node1 != NULL && node2 != NULL);
		PassIf(IsEquivalent(FUStringConversion::ToFloat(node1->GetContent()), 2.0f));
		PassIf(IsEquivalent(FUStringConversion::ToFloat(node2->GetContent()), -1.2f));

		// Profile-level extra support verification.
		FCDEType* extraType = profile->GetExtra()->FindType("Curious");
		PassIf(extraType != NULL);
		FCDETechnique* extraTechnique = extraType->FindTechnique("Kiddy");
		PassIf(extraTechnique != NULL);
		FCDENode* extraNode = extraTechnique->FindParameter("Causes");
		PassIf(extraTechnique != NULL);
		PassIf(IsEquivalent(extraNode->GetContent(), FC("trouble")));

		// There should be an empty texture in the filter channel
		PassIf(profile->GetTextureCount(FUDaeTextureChannel::FILTER) == 1);
		FCDTexture* texture3 = profile->GetTexture(FUDaeTextureChannel::FILTER, 0);
		FailIf(texture3 == NULL || texture3->GetImage() != NULL);
		return true;
	}

	bool CheckEffectFX(FULogFile& fileOut, FCDEffectProfileFX* profile)
	{
		FailIf(profile == NULL);
		PassIf(profile->GetTechniqueCount() == 1);
		PassIf(profile->GetCodeCount() == 2);

		// Verify the code inclusions.
		FCDEffectCode* inlineCode = NULL,* filenameCode = NULL;
		for (size_t i = 0; i < profile->GetCodeCount(); ++i)
		{
			FCDEffectCode* c = profile->GetCode(i);
			switch (c->GetType())
			{
			case FCDEffectCode::INCLUDE: PassIf(filenameCode == NULL); filenameCode = c; break;
			case FCDEffectCode::CODE: PassIf(inlineCode == NULL); inlineCode = c; break;
			default: Fail; break;
			}
		}
		PassIf(inlineCode->GetCode() == FS("Some CG code for you to parse!"));
		PassIf(inlineCode->GetSubId() == "TouchesAKey");
		PassIf(filenameCode->GetFilename().find(FC("HowDoTheyDoIt.dd")) != fstring::npos);
		PassIf(filenameCode->GetSubId() == "PowerOfWill");

		// Grab the technique and check its name and passes, which are ordered.
		FCDEffectTechnique* technique = profile->GetTechnique(0);
		PassIf(technique->GetName() == FC("100 percent reason to remember the name."));
		PassIf(technique->GetPassCount() == 2);
		FCDEffectPass* pass1 = technique->GetPass(0);
		PassIf(pass1 != NULL);
		FCDEffectPass* pass2 = technique->GetPass(1);
		PassIf(pass2 != NULL);
		PassIf(pass1->GetPassName() == FC("First Pass"));
		PassIf(pass2->GetPassName() == FC("Second Pass"));

		// Verify the few parameters littered around.
		PassIf(profile->GetEffectParameterCount() == 3);
		FCDEffectParameterBool* booleanParameter = NULL;
		FCDEffectParameterSampler* samplerParameter = NULL;
		FCDEffectParameterSurface* surfaceParameter = NULL;
		for (size_t i = 0; i < 3; ++i)
		{
			FCDEffectParameter* p = profile->GetEffectParameter(i);
			if (p->GetObjectType() == FCDEffectParameterBool::GetClassType()) { FailIf(booleanParameter != NULL); booleanParameter = (FCDEffectParameterBool*) p; }
			else if (p->GetObjectType() == FCDEffectParameterSampler::GetClassType()) { FailIf(samplerParameter != NULL); samplerParameter = (FCDEffectParameterSampler*) p; }
			else if (p->GetObjectType() == FCDEffectParameterSurface::GetClassType()) { FailIf(surfaceParameter != NULL); surfaceParameter = (FCDEffectParameterSurface*) p; }
			else Fail;
		}
		FailIf(booleanParameter == NULL || samplerParameter == NULL || surfaceParameter == NULL);
		PassIf(booleanParameter->GetType() == FCDEffectParameter::BOOLEAN);
		PassIf(booleanParameter->GetValue() == true);
		PassIf(technique->GetEffectParameterCount() == 1);
		FCDEffectParameter* float2Parameter = technique->GetEffectParameter(0);
		FailIf(float2Parameter == NULL);
		PassIf(float2Parameter->GetType() == FCDEffectParameter::FLOAT2);
		PassIf(IsEquivalent(((FCDEffectParameterFloat2*) float2Parameter)->GetValue()->x, 1.5f));
		PassIf(IsEquivalent(((FCDEffectParameterFloat2*) float2Parameter)->GetValue()->y, 5.2f));

		// Verify the float2 parameter annotations
		FCDEffectParameterAnnotation* floatAnnotation = NULL,* integerAnnotation = NULL,* stringAnnotation = NULL,* booleanAnnotation = NULL;
		for (size_t i = 0; i < float2Parameter->GetAnnotationCount(); ++i)
		{
			FCDEffectParameterAnnotation* a = float2Parameter->GetAnnotation(i);
			if (a->type == FCDEffectParameter::FLOAT) { PassIf(floatAnnotation == NULL); floatAnnotation = a; }
			else if (a->type == FCDEffectParameter::INTEGER) { PassIf(integerAnnotation == NULL); integerAnnotation = a; }
			else if (a->type == FCDEffectParameter::STRING) { PassIf(stringAnnotation == NULL); stringAnnotation = a; }
			else if (a->type == FCDEffectParameter::BOOLEAN) { PassIf(booleanAnnotation == NULL); booleanAnnotation = a; }
			else Fail;
		}
		PassIf(floatAnnotation != NULL && integerAnnotation != NULL && stringAnnotation != NULL && booleanAnnotation != NULL);
		PassIf(*floatAnnotation->name == FC("UIMin") && IsEquivalent(FUStringConversion::ToFloat(*floatAnnotation->value), 0.2f));
		PassIf(*integerAnnotation->name == FC("UIMax") && IsEquivalent(FUStringConversion::ToInt32(*integerAnnotation->value), 5));
		PassIf(*stringAnnotation->name == FC("UIWidget") && IsEquivalent(*stringAnnotation->value, FS("RolloverSlider")));
		PassIf(*booleanAnnotation->name == FC("UIValid") && IsEquivalent(FUStringConversion::ToBoolean(*booleanAnnotation->value), true));

		// Check the sampler/surface parameter pair
		PassIf(surfaceParameter->GetType() == FCDEffectParameter::SURFACE);
		PassIf(surfaceParameter->GetImageCount() == 1);
		PassIf(surfaceParameter->GetImage(0)->GetDaeId() == surfaceParameterImageId);
		PassIf(surfaceParameter->GetReference() == "TestSF");
		PassIf(surfaceParameter->GetFormat() == "Plurial");
		PassIf(IsEquivalent(surfaceParameter->GetSurfaceType(), "RANDOMIZE"));

		FCDFormatHint* fhint = surfaceParameter->GetFormatHint();
		FailIf(fhint == NULL);
		PassIf(fhint->channels == FCDFormatHint::CHANNEL_RGB);
		PassIf(fhint->options.size() == 1 && fhint->options.front() == FCDFormatHint::OPT_NORMALIZED3);
		PassIf(fhint->precision == FCDFormatHint::PRECISION_MID);
		PassIf(fhint->range == FCDFormatHint::RANGE_SNORM);

		PassIf(samplerParameter->GetType() == FCDEffectParameter::SAMPLER);
		PassIf(samplerParameter->GetSurface()->GetReference() == surfaceParameter->GetReference());
		PassIf(samplerParameter->GetSamplerType() == FCDEffectParameterSampler::SAMPLER2D);
		PassIf(samplerParameter->GetReference() == "Gotit");
		return true;
	}

	bool FillImageLibrary(FULogFile& fileOut, FCDImageLibrary* library)
	{
		FailIf(library == NULL || library->GetEntityCount() > 0);
		FCDImage* image1 = library->AddEntity();
		FCDImage* image2 = library->AddEntity();
		FCDImage* image3 = library->AddEntity();

		image1->SetDaeId(wantedImageId);
		image1->SetFilename(FS("./Texture1.jpg"));
		image2->SetDaeId(wantedImage2Id);
		image2->SetFilename(FC("./Texture3D.jpg"));
		image2->SetWidth(256);
		image2->SetHeight(135);
		image3->SetWidth(33);
		image3->SetDepth(521);

		FailIf(image1->GetDaeId() == image2->GetDaeId());
		return true;
	}

	bool CheckImageLibrary(FULogFile& fileOut, FCDImageLibrary* library)
	{
		FailIf(library == NULL || library->GetEntityCount() != 3);

		// Retrieve the three images, verify that they match the id/filenames that we created.
		FCDImage* image1 = NULL,* image2 = NULL,* image3 = NULL;
		for (size_t i = 0; i < 3; ++i)
		{
			FCDImage* image = library->GetEntity(i);
			if (IsEquivalent(image->GetDaeId(), wantedImageId)) { FailIf(image1 != NULL); image1 = image; }
			else if (IsEquivalent(image->GetDaeId(), wantedImage2Id)) { FailIf(image2 != NULL); image2 = image; }
			else { FailIf(image3 != NULL); image3 = image; }
		}
		PassIf(image1 != NULL && image2 != NULL && image3 != NULL);

		// Verify the depth/width/height.
		PassIf(image1->GetWidth() == 0 && image1->GetHeight() == 0 && image1->GetDepth() == 0);
		PassIf(image2->GetWidth() == 256 && image2->GetHeight() == 135 && image2->GetDepth() == 0);
		PassIf(image3->GetWidth() == 33 && image3->GetHeight() == 0 && image3->GetDepth() == 521);

		// Verify the filenames. They should be absolute filenames now, so look for the wanted substrings.
		PassIf(strstr(TO_STRING(image1->GetFilename()).c_str(), "Texture1.jpg") != NULL);
		PassIf(strstr(TO_STRING(image2->GetFilename()).c_str(), "Texture3D.jpg") != NULL);
		return true;
	}
};

