/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include "gpu/gpu_material.h"
		#include "gpu/gpu_renderer.h"
		#include "core/material_to_shader_tree.h"
		#include "core/shader_tree_convert_static_texture_block_to_dynamic.h"
		#include "core/shader_tree_to_shader.h"
		#include "core/shader_tree.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GPU;


//------------------------------------------------------------------------------
bool				Material::ShouldReloadOnDependencyChange(const char *n) const
{	return (name == n) || (shader->GetName() == n);	}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Material::LoadTextureTable(Render::ResourceFactory &rf, const Core::Material &m)
{
	bool r = true;
	for (uint n = 0; n < Core::Material::max_texture_stage; ++n)
	{
		texture_table[n] = rf.LoadTexture(m.texstage[n].t);
		if	(texture_table[n].IsNull())
			r = false;
	}
	return r;
}
static bool			LoadCoreShader(Render::ResourceFactory &rf, const Core::Material &m, Core::Shader &core_shader, const char *name)
{
	using namespace Core;

	if	(name == NULL)
	{
		// TODO convert material to shader directly (avoid creating an AST).
		ShaderTree shader_tree;
		if	(!MaterialToShaderTree::Convert(m, shader_tree) || !ConvertShaderTreeToShader(shader_tree, core_shader))
			return false;
	}
	else
	{
		using namespace NML;

		// Load shader/shader tree.
		File file;
		if	(!Parser::Load(name, file))
			return false;

		if	(Tag *tag = file.GetTag("Shader"))
		{
			if	(!core_shader.FromMetaTag(*tag))
				return false;
			core_shader.name = m.shader;
		}
		else if (Tag *tag = file.GetTag("ShaderMap"))
		{
			ShaderTree shader_tree;
			if	(!shader_tree.FromMetaTag(*tag))
				return false;

			ConvertStaticToDynamicTextureBlocks(shader_tree, m);

			if	(!ConvertShaderTreeToShader(shader_tree, core_shader))
				return false;
		}
		else
			__ERR__(__LOG_E__ << "No shader or shader tree definition found in '" << m.shader << "'.\n", false)
	}
	return true;
}
bool				Material::Create(Render::ResourceFactory &rf, const Core::Material &m)
{
	__LOG_H__ << "Load render material '" << m.name << "'.\n";
	name = m.name;

	using namespace Core;

	// Transfer basic material informations to the render data.
	*((BasicMaterial *)this) = ((BasicMaterial &)m);

	// Load texture table.
	LoadTextureTable(rf, m);

	// Load core shader.
	MaterialShaderStaticParm parm;

	parm.no_lighting = asbool(m.renderword & Core::Material::Render_Unlit);
	parm.use_skinning = asbool(m.renderword & Core::Material::Render_Skinned);
	parm.use_alpha_test = asbool(m.renderword & Core::Material::Render_AlphaTest);
	parm.use_depth_bias = asbool(m.depth_bias);

	Core::Shader core_shader;
	bool load_core_shader = LoadCoreShader(rf, m, core_shader, m.shader);

	// Drop current material shader.
	shader = NULL;

	// Look for a compatible material shader in cache.
	if	(load_core_shader)
		ListForeachPtr(MaterialShader *, s, renderer.material_shaders)
			if	((s->GetName() == core_shader.name) && (s->GetStaticParm() == parm))
			{
				__LOG_V__ << "Reusing cached material shader for material '" << m.name << "'.\n";

				shader = s;
				return true;
			}

	// Build a new one otherwise.
	shader = new MaterialShader(renderer);
	if	(shader.IsNull())
		return false;

	if	(!load_core_shader || !shader->Create(rf, core_shader, parm))
	{
		if	(!LoadCoreShader(rf, m, core_shader, "@core/builtin/shader/shader_error.nsa"))
			return false;
		if	(!shader->Create(rf, core_shader, parm))
			return false;
	}
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Render::Material *Material::Clone() const
{
	Material *cloned = new Material(renderer);

	// __LOG_W__ << "[MaterialClone] Step 1c: 'new' returned successfully!\n";
	if (!cloned)
	{
		__LOG_E__ << "[MaterialClone] ERROR: Failed to allocate memory for cloned material!\n";
		return NULL;
	}
	// __LOG_W__ << "[MaterialClone] Step 1: Material instance created at " << (void*)cloned << "\n";

	// Copy name (with "_clone" suffix to distinguish it)
	// __LOG_W__ << "[MaterialClone] Step 2: Copying name...\n";
	cloned->name = name + "_clone";
	// __LOG_W__ << "[MaterialClone] Step 2: Name copied: '" << cloned->name << "'\n";

	// Copy basic material properties (diffuse, specular, self, ambient, glossiness, etc.)
	// __LOG_W__ << "[MaterialClone] Step 3: Copying BasicMaterial properties...\n";
	*((Core::BasicMaterial *)cloned) = *((Core::BasicMaterial *)this);
	// __LOG_W__ << "[MaterialClone] Step 3: BasicMaterial properties copied.\n";

	// Copy shader reference
	// __LOG_W__ << "[MaterialClone] Step 4: Copying shader reference...\n";
	cloned->shader = shader;
	// __LOG_W__ << "[MaterialClone] Step 4: Shader reference copied.\n";

	// Copy texture table (smart pointers, so textures are shared, not duplicated)
	// __LOG_W__ << "[MaterialClone] Step 5: Copying texture table...\n";
	for (uint n = 0; n < Core::Material::max_texture_stage; ++n)
	{
		cloned->texture_table[n] = texture_table[n];
	}
	// __LOG_W__ << "[MaterialClone] Step 5: Texture table copied.\n";

	// __LOG_W__ << "[MaterialClone] SUCCESS: Material cloned successfully as '" << cloned->name << "'.\n";

	return cloned;
}
//------------------------------------------------------------------------------
