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


		#include "binding_helpers.h"
		#include "core/material_to_shader_tree.h"
		#include "core/render_data.h"
#include "gpu/gpu_material.h"

using namespace GS::Render;
		using namespace GS::Script;


//------------------------------------------------------------------------------
SQInteger			MaterialGetDiffuse(HSQUIRRELVM vm)
{
	__SQ_GETSINGLESAFEPTR(m, Material, typetag_Material)
	__SQ_RETURNVECTORW(m->diffuse)
}
SQInteger			MaterialSetDiffuse(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETSAFEPTR(m, Material, typetag_Material)
	__SQ_GETVECTORW(c)
	__SQ_GETEND
	m->diffuse = c;
	__SQ_RETURN
}
SQInteger			MaterialGetSpecular(HSQUIRRELVM vm)
{
	__SQ_GETSINGLESAFEPTR(m, Material, typetag_Material)
	__SQ_RETURNVECTORW(m->specular)
}
SQInteger			MaterialSetSpecular(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETSAFEPTR(m, Material, typetag_Material)
	__SQ_GETVECTORW(c)
	__SQ_GETEND
	m->specular = c;
	__SQ_RETURN
}
SQInteger			MaterialGetSelf(HSQUIRRELVM vm)
{
	__SQ_GETSINGLESAFEPTR(m, Material, typetag_Material)
	__SQ_RETURNVECTORW(m->self)
}
SQInteger			MaterialSetSelf(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETSAFEPTR(m, Material, typetag_Material)
	__SQ_GETVECTORW(c)
	__SQ_GETEND
	m->self = c;
	__SQ_RETURN
}
SQInteger			MaterialGetAmbient(HSQUIRRELVM vm)
{
	__SQ_GETSINGLESAFEPTR(m, Material, typetag_Material)
	__SQ_RETURNVECTORW(m->ambient)
}
SQInteger			MaterialSetAmbient(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETSAFEPTR(m, Material, typetag_Material)
	__SQ_GETVECTORW(c)
	__SQ_GETEND
	m->ambient = c;
	__SQ_RETURN
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
SQInteger			MaterialGetGlossiness(HSQUIRRELVM vm)
{
	__SQ_GETSINGLESAFEPTR(m, Material, typetag_Material)
	__SQ_RETURNFLOAT(m->glossiness)
}
SQInteger			MaterialSetGlossiness(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETSAFEPTR(m, Material, typetag_Material)
	__SQ_GETFLOAT(v)
	__SQ_GETEND
	m->glossiness = v;
	__SQ_RETURN
}
SQInteger			MaterialGetOpacity(HSQUIRRELVM vm)
{
	__SQ_GETSINGLESAFEPTR(m, Material, typetag_Material)
	__SQ_RETURNFLOAT(m->opacity)
}
SQInteger			MaterialSetOpacity(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETSAFEPTR(m, Material, typetag_Material)
	__SQ_GETFLOAT(v)
	__SQ_GETEND
	m->opacity = v;
	__SQ_RETURN
}
SQInteger			MaterialGetAlphaThreshold(HSQUIRRELVM vm)
{
	__SQ_GETSINGLESAFEPTR(m, Material, typetag_Material)
	__SQ_RETURNFLOAT(m->athreshold)
}
SQInteger			MaterialSetAlphaThreshold(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETSAFEPTR(m, Material, typetag_Material)
	__SQ_GETFLOAT(v)
	__SQ_GETEND
	m->athreshold = v;
	__SQ_RETURN
}
SQInteger			MaterialGetDepthBias(HSQUIRRELVM vm)
{
	__SQ_GETSINGLESAFEPTR(m, Material, typetag_Material)
	__SQ_RETURNFLOAT(m->depth_bias)
}
SQInteger			MaterialSetDepthBias(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETSAFEPTR(m, Material, typetag_Material)
	__SQ_GETFLOAT(v)
	__SQ_GETEND
	m->depth_bias = v;
	__SQ_RETURN
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
SQInteger			MaterialGetName(HSQUIRRELVM vm)
{
	__SQ_GETSINGLESAFEPTR(mat, Material, typetag_Material)
	__SQ_RETURNSTRING(mat->name.c_str())
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
SQInteger			MaterialFlagGet(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETSAFEPTR(mat, Material, typetag_Material)
	__SQ_GETINT(flag)
	__SQ_GETEND
	__SQ_RETURNBOOL(asbool(mat->renderword & flag))
}
SQInteger			MaterialFlagSet(HSQUIRRELVM vm)
{
	__SQ_GETSTART(3)
	__SQ_GETSAFEPTR(mat, Material, typetag_Material)
	__SQ_GETINT(flag)
	__SQ_GETBOOL(state)
	__SQ_GETEND
	if	(state)
			mat->renderword |= flag;
	else	mat->renderword &= ~flag;
	__SQ_RETURN
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
SQInteger			MaterialGetBlendOperator(HSQUIRRELVM vm)
{
	__SQ_GETSINGLESAFEPTR(mat, Material, typetag_Material)
	__SQ_RETURNINT(mat->blendop)
}
SQInteger			MaterialSetBlendOperator(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETSAFEPTR(mat, Material, typetag_Material)
	__SQ_GETINT(op)
	__SQ_GETEND
	mat->blendop = op;
	__SQ_RETURN
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
SQInteger			MaterialGetTexture(HSQUIRRELVM vm)
{
	__SQ_GETSTART(2)
	__SQ_GETSAFEPTR(mat, Material, typetag_Material)
	__SQ_GETINT(slot)
	__SQ_GETEND
	if	((slot < 0) || (slot >= GS::Core::Material::max_texture_stage))
		return sq_throwerror(vm, "Invalid material texture slot index.");
	__SQ_RETURNSAFEPTR(mat->texture_table[slot].c_ptr(), typetag_Texture)
}
SQInteger			MaterialSetTexture(HSQUIRRELVM vm)
{
	__SQ_GETSTART(3)
	__SQ_GETSAFEPTR(mat, Material, typetag_Material)
	__SQ_GETINT(slot)
	__SQ_GETSAFEPTR(tex, Texture, typetag_Texture)
	__SQ_GETEND
	if	((slot < 0) || (slot >= GS::Core::Material::max_texture_stage))
		return sq_throwerror(vm, "Invalid material texture slot index.");
	mat->texture_table[slot] = tex;
	__SQ_RETURN
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
SQInteger			MaterialClone(HSQUIRRELVM vm)
{
	__LOG_W__ << "[SQ] MaterialClone: Called from Squirrel.\n";

	__SQ_GETSINGLESAFEPTR(mat, Material, typetag_Material)

	__LOG_W__ << "[SQ] MaterialClone: Got material pointer: " << (void*)mat << "\n";

	if (!mat)
	{
		__LOG_E__ << "[SQ] MaterialClone: Input material is NULL!\n";
		return sq_throwerror(vm, "Input material is NULL");
	}

	__LOG_W__ << "[SQ] MaterialClone: Material name: '" << mat->name << "'\n";
	__LOG_W__ << "[SQ] MaterialClone: Material refcount: " << mat->GetRefCount() << "\n";

	// Cast to GPU::Material to ensure we're working with the right type
	__LOG_W__ << "[SQ] MaterialClone: Attempting dynamic_cast to GPU::Material...\n";
	GS::GPU::Material *gpu_mat = dynamic_cast<GS::GPU::Material*>(mat);

	if (!gpu_mat)
	{
		__LOG_E__ << "[SQ] MaterialClone: Material is not a GPU::Material! Cannot clone.\n";
		return sq_throwerror(vm, "Material type not supported for cloning");
	}

	__LOG_W__ << "[SQ] MaterialClone: dynamic_cast succeeded, got GPU::Material at " << (void*)gpu_mat << "\n";

	// WORKAROUND: Clone manually without calling virtual methods
	// (calling virtual methods crashes for unknown reason)
	__LOG_W__ << "[SQ] MaterialClone: Cloning manually (bypassing virtual Clone())...\n";

	Material *cloned = NULL;

	try
	{
		__LOG_W__ << "[SQ] MaterialClone: Accessing renderer reference...\n";
		GS::GPU::Renderer &rend = gpu_mat->renderer;
		__LOG_W__ << "[SQ] MaterialClone: Renderer at: " << (void*)&rend << "\n";

		__LOG_W__ << "[SQ] MaterialClone: Allocating new GPU::Material...\n";
		cloned = new GS::GPU::Material(rend);
		__LOG_W__ << "[SQ] MaterialClone: Allocation succeeded: " << (void*)cloned << "\n";

		// Copy properties manually
		__LOG_W__ << "[SQ] MaterialClone: Copying name...\n";
		cloned->name = gpu_mat->name + "_clone";

		__LOG_W__ << "[SQ] MaterialClone: Copying BasicMaterial properties...\n";
		*((GS::Core::BasicMaterial *)cloned) = *((GS::Core::BasicMaterial *)gpu_mat);

		__LOG_W__ << "[SQ] MaterialClone: Copying shader reference...\n";
		((GS::GPU::Material*)cloned)->shader = gpu_mat->shader;

		__LOG_W__ << "[SQ] MaterialClone: Copying texture table...\n";
		for (uint n = 0; n < GS::Core::Material::max_texture_stage; ++n)
		{
			cloned->texture_table[n] = gpu_mat->texture_table[n];
		}

		__LOG_W__ << "[SQ] MaterialClone: Manual clone complete!\n";
	}
	catch (...)
	{
		__LOG_E__ << "[SQ] MaterialClone: EXCEPTION caught during manual clone!\n";
		if (cloned)
			delete cloned;
		return sq_throwerror(vm, "Exception during clone");
	}

	__LOG_W__ << "[SQ] MaterialClone: Clone() returned: " << (void*)cloned << "\n";

	if (!cloned)
	{
		__LOG_E__ << "[SQ] MaterialClone: Clone() returned NULL!\n";
		return sq_throwerror(vm, "Failed to clone material");
	}

	__LOG_W__ << "[SQ] MaterialClone: Returning managed pointer to Squirrel...\n";
	// Use managed pointer so Squirrel will handle the reference counting
	__SQ_RETURNMANAGEDSAFEPTR(cloned, typetag_Material)
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
SQInteger			MaterialGetShader(HSQUIRRELVM vm)
{
	__SQ_GETSINGLESAFEPTR(mat, Material, typetag_Material)
	__SQ_RETURNSAFEPTR(mat->GetShader(), typetag_MaterialShader)
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				RegisterMaterialBinding(HSQUIRRELVM vm)
{
/*#
	Topic: Material
	Type: Material
	Type: MaterialShader
#*/

/*#
	Section: MaterialRenderAttributes
	Desc: Render attributes functions
#*/
	/*#
		Func: MaterialGetDiffuse
		Proto: Vector:Material material
		Desc: Get material diffuse color.
	#*/
	sq_register(vm, MaterialGetDiffuse, "MaterialGetDiffuse", _SC(".x"));
	/*#
		Func: MaterialSetDiffuse
		Proto: void:Material material,Vector diffuse_color
		Desc: Set material diffuse color.
	#*/
	sq_register(vm, MaterialSetDiffuse, "MaterialSetDiffuse", _SC(".xx"));
	/*#
		Func: MaterialGetSpecular
		Proto: Vector:Material material
		Desc: Get material specular color.
	#*/
	sq_register(vm, MaterialGetSpecular, "MaterialGetSpecular", _SC(".x"));
	/*#
		Func: MaterialSetSpecular
		Proto: void:Material material,Vector specular_color
		Desc: Set material specular color.
		Note: This value is combined with the light source own specular color and intensity.
	#*/
	sq_register(vm, MaterialSetSpecular, "MaterialSetSpecular", _SC(".xx"));
	/*#
		Func: MaterialGetSelf
		Proto: Vector:Material material
		Desc: Get material self color.
	#*/
	sq_register(vm, MaterialGetSelf, "MaterialGetSelf", _SC(".x"));
	/*#
		Func: MaterialSetSelf
		Proto: void:Material material,Vector self_color
		Desc: Set material self color.
	#*/
	sq_register(vm, MaterialSetSelf, "MaterialSetSelf", _SC(".xx"));
	/*#
		Func: MaterialGetAmbient
		Proto: Vector:Material material
		Desc: Get material ambient color.
	#*/
	sq_register(vm, MaterialGetAmbient, "MaterialGetAmbient", _SC(".x"));
	/*#
		Func: MaterialSetAmbient
		Proto: void:Material material,Vector ambient_color
		Desc: Set material ambient color.
	#*/
	sq_register(vm, MaterialSetAmbient, "MaterialSetAmbient", _SC(".xx"));

	/*#
		Func: MaterialGetGlossiness
		Proto: float:Material material
		Desc: Get material glossiness.
	#*/
	sq_register(vm, MaterialGetGlossiness, "MaterialGetGlossiness", _SC(".x"));
	/*#
		Func: MaterialSetGlossiness
		Proto: void:Material material,float glossiness
		Desc: Set material glossiness.
	#*/
	sq_register(vm, MaterialSetGlossiness, "MaterialSetGlossiness", _SC(".xn"));
	/*#
		Func: MaterialGetOpacity
		Proto: float:Material material
		Desc: Get material opacity.
	#*/
	sq_register(vm, MaterialGetOpacity, "MaterialGetOpacity", _SC(".x"));
	/*#
		Func: MaterialSetOpacity
		Proto: void:Material material,float opacity
		Desc: Set material opacity.
		See: MaterialSetBlendOperator
	#*/
	sq_register(vm, MaterialSetOpacity, "MaterialSetOpacity", _SC(".xn"));
	/*#
		Func: MaterialGetAlphaThreshold
		Proto: float:Material material
		Desc: Get material alpha threshold.
	#*/
	sq_register(vm, MaterialGetAlphaThreshold, "MaterialGetAlphaThreshold", _SC(".x"));
	/*#
		Func: MaterialSetAlphaThreshold
		Proto: void:Material material,float threshold
		Desc: Set material alpha threshold.
	#*/
	sq_register(vm, MaterialSetAlphaThreshold, "MaterialSetAlphaThreshold", _SC(".xn"));
	/*#
		Func: MaterialGetDepthBias
		Proto: float:Material material
		Desc: Get material depth bias.
	#*/
	sq_register(vm, MaterialGetDepthBias, "MaterialGetDepthBias", _SC(".x"));
	/*#
		Func: MaterialSetDepthBias
		Proto: void:Material material,float depth_bias
		Desc: Set material depth bias.
	#*/
	sq_register(vm, MaterialSetDepthBias, "MaterialSetDepthBias", _SC(".xn"));
	/*#
		Func: MaterialGetTexture
		Proto: Texture:Material material,int slot
		Desc: Get texture at a given material slot.
	#*/
	sq_register(vm, MaterialGetTexture, "MaterialGetTexture", _SC(".xn"));
	/*#
		Func: MaterialSetTexture
		Proto: void:Material material,int slot,Texture texture
		Desc: Set texture at a given material slot.
	#*/
	sq_register(vm, MaterialSetTexture, "MaterialSetTexture", _SC(".xnx"));
	/*#
		Func: MaterialGetShader
		Proto: MaterialShader:Material material
		Desc: Return the material shader for a given material.
		Note: A material shader encapsulates all the variants of a shader required to render a material.
	#*/
	sq_register(vm, MaterialGetShader, "MaterialGetShader", _SC(".x"));

/*#
	Section: MaterialGeneric
	Desc: Generic functions
#*/
	/*#
		Func: MaterialGetName
		Proto: string:Material mat
		Desc: Get material name.
	#*/
	sq_register(vm, MaterialGetName, "MaterialGetName", _SC(".x"));

	/*#
		Func: MaterialClone
		Proto: Material:Material mat
		Desc: Clone a material (create an independent copy in memory).
		Note: The cloned material has all the same textures, properties, and configuration as the original, but changes to the clone won't affect the original material.
		Example: local mat_clone = MaterialClone(original_material)
	#*/
	sq_register(vm, MaterialClone, "MaterialClone", _SC(".x"));

/*#
	Section: MaterialFlag
	Desc: Rendering flag functions
#*/
	/*#
		Func: MaterialSetRenderFlag
		Proto: void:Material mat,MaterialRenderFlag flag,bool state
		Desc: Add or remove a material render flag.
		Example:
// Set the first material of a geometry to be double-sided.
local mat = GeometryGetMaterialFromIndex(geo, 0)
MaterialSetRenderFlag(mat, MaterialRenderDoubleSided, true)
	#*/
	sq_register(vm, MaterialFlagSet, "MaterialFlagSet", _SC(".xib"));
	sq_register(vm, MaterialFlagSet, "MaterialSetRenderFlag", _SC(".xib"));
	/*#
		Func: MaterialTestRenderFlag
		Proto: bool:Material mat,MaterialRenderFlag flag
		Desc: Test a material render flag.
	#*/
	sq_register(vm, MaterialFlagGet, "MaterialFlagGet", _SC(".xi"));
	sq_register(vm, MaterialFlagGet, "MaterialTestRenderFlag", _SC(".xi"));

	/*#
		Func: MaterialSetBlendOperator
		Proto: void:Material mat,MaterialBlendOperator op
		Desc: Set the material blend operator. The blend operator controls how a material is composited onto screen.
		Example:
// Set the first material of a geometry to use additive blending.
local mat = GeometryGetMaterialFromIndex(geo, 0)
MaterialSetBlendOperator(mat, MaterialBlendAdditive)
	#*/
	sq_register(vm, MaterialSetBlendOperator, "MaterialSetBlendOperator", _SC(".xi"));
	/*#
		Func: MaterialGetBlendOperator
		Proto: MaterialBlendOperator:Material mat
		Desc: Get the material blend operator.
		See: MaterialSetBlendOperator
	#*/
	sq_register(vm, MaterialGetBlendOperator, "MaterialGetBlendOperator", _SC(".x"));

	//

	sq_pushroottable(vm);
	sq_pushstring(vm, "NullMaterial", -1); CObject::Push(vm, NULL, typetag_Material); sq_newslot(vm, -3, true);

	using GS::Core::Material;

	/*#
		Enum: MaterialBlendOperator
		Values: MaterialBlendNone,MaterialBlendAlpha,MaterialBlendAdditive
	#*/
	sq_pushstring(vm, "MaterialBlendNone", -1); sq_pushinteger(vm, Material::Blend_None); sq_newslot(vm, -3, true);
	sq_pushstring(vm, "MaterialBlendAlpha", -1); sq_pushinteger(vm, Material::Blend_Alpha); sq_newslot(vm, -3, true);
	sq_pushstring(vm, "MaterialBlendAdditive", -1); sq_pushinteger(vm, Material::Blend_Add); sq_newslot(vm, -3, true);

	/*#
		Enum: MaterialRenderFlag
		Values: MaterialRenderUnlit,MaterialRenderSmooth,MaterialRenderNormalMapTangent,MaterialRenderNoFog,MaterialRenderDoubleSided,MaterialRenderWire,MaterialRenderVertexColor,MaterialRenderParralax,MaterialRenderToonShading,MaterialRenderNoDepthWrite,MaterialRenderNoDepthTest,MaterialRenderAlphaSoftZ,MaterialRenderAlphaInShadow
	#*/
	sq_pushstring(vm, "MaterialRenderUnlit", -1); sq_pushinteger(vm, Material::Render_Unlit); sq_newslot(vm, -3, true);
	sq_pushstring(vm, "MaterialRenderSmooth", -1); sq_pushinteger(vm, Material::Render_Smooth); sq_newslot(vm, -3, true);
	sq_pushstring(vm, "MaterialRenderNormalMapTangent", -1); sq_pushinteger(vm, Material::Render_NormalTangent); sq_newslot(vm, -3, true);
	sq_pushstring(vm, "MaterialRenderNoFog", -1); sq_pushinteger(vm, Material::Render_NoFog); sq_newslot(vm, -3, true);

	sq_pushstring(vm, "MaterialRenderDoubleSided", -1); sq_pushinteger(vm, Material::Render_DoubleSided); sq_newslot(vm, -3, true);
	sq_pushstring(vm, "MaterialRenderWire", -1); sq_pushinteger(vm, Material::Render_Wire); sq_newslot(vm, -3, true);
	sq_pushstring(vm, "MaterialRenderVertexColor", -1); sq_pushinteger(vm, Material::Render_VertexColor); sq_newslot(vm, -3, true);
	sq_pushstring(vm, "MaterialRenderParralax", -1); sq_pushinteger(vm, Material::Render_ParralaxDisp); sq_newslot(vm, -3, true);
	sq_pushstring(vm, "MaterialRenderToonShading", -1); sq_pushinteger(vm, Material::Render_Toon); sq_newslot(vm, -3, true);

	sq_pushstring(vm, "MaterialRenderNoDepthWrite", -1); sq_pushinteger(vm, Material::Render_NoZWrite); sq_newslot(vm, -3, true);
	sq_pushstring(vm, "MaterialRenderNoDepthTest", -1); sq_pushinteger(vm, Material::Render_NoZTest); sq_newslot(vm, -3, true);

	sq_pushstring(vm, "MaterialRenderAlphaSoftZ", -1); sq_pushinteger(vm, Material::Render_AlphaSoftZ); sq_newslot(vm, -3, true);
	sq_pushstring(vm, "MaterialRenderAlphaInShadow", -1); sq_pushinteger(vm, Material::Render_AlphaInShadow); sq_newslot(vm, -3, true);

	sq_pop(vm, 1);
}
//------------------------------------------------------------------------------
