//	Darkstar Engine  (c) UnderWorld Interactive Studio (all coding by Svarog    2007-2008)
//
//      Данный файл является частью Darkstar Engine. Любое использование кода файла
//      без согласия компании, является нарушением закона об авторском праве (в слу-
//      чае если не оговорено обратное).
//
//      Имя файла: material_loader.cpp
//      Краткое описание: loader for darkstar material
//

#include <Darkstar/Resource System/Material/material_loader.hpp>
#include <Darkstar/Kernel/parser.hpp>
#include <Darkstar/Kernel/logh.hpp>
#include <Darkstar/Resource System/Textures/textureManager.hpp>
#include <Darkstar/Graphic System/Effect/effect_manager.hpp>
#include <Darkstar/Kernel/FileSystem/dfile.hpp>
#include <Darkstar/Graphic System/Render State/blend_render_state.hpp>
#include <Darkstar/Graphic System/Render State/cull_render_state.hpp>

using rs::Material;
using rs::BaseMaterialParameter;
using rs::TextureMaterialParameter;
using rs::FloatArrayMaterialParameter;
using ker::Parser;
using ker::Log;
using ker::Section;
using ker::PairInfo;
using rs::TextureManager;
using gs::EffectManager;
using rs::BaseTexture;
using gs::Effect;
using rs::MaterialLoader;
using ker::DFile;
using gs::BlendRenderState;
using gs::DBLEND;
using gs::DBLENDOP;
using gs::DCULL;
using gs::CullModeRenderState;

MaterialLoader::MaterialLoader()
{

};

MaterialLoader::~MaterialLoader()
{

};

bool getBlendFromString(const std::string& str, DBLEND& blend)
{
	if(str == "zero")
	{
		blend = gs::DBLEND_ZERO;
		return true;
	};
	if(str == "one")
	{
		blend = gs::DBLEND_ONE;
		return true;
	};
	if(str == "src_color")
	{
		blend = gs::DBLEND_SRC_COLOR;
		return true;
	};
	if(str == "inv_src_color")
	{
		blend = gs::DBLEND_INV_SRC_COLOR;
		return true;
	};
	if(str == "src_alpha")
	{
		blend = gs::DBLEND_SRC_ALPHA;
		return true;
	};
	if(str == "inv_src_alpha")
	{
		blend = gs::DBLEND_INV_SRC_ALPHA;
		return true;
	};
	if(str == "dst_alpha")
	{
		blend = gs::DBLEND_DEST_ALPHA;
		return true;
	};
	if(str == "inv_dst_alpha")
	{
		blend = gs::DBLEND_INV_DEST_ALPHA;
		return true;
	};
	if(str == "dst_color")
	{
		blend = gs::DBLEND_DEST_COLOR; 
		return true;
	};
	if(str == "inv_dst_color")
	{
		blend = gs::DBLEND_INV_DEST_COLOR;
		return true;
	};
	if(str == "src_alpha_sat")
	{
		blend = gs::DBLEND_SRC_ALPHA_SAT;
		return true;
	};
	if(str == "blend_factor")
	{
		blend = gs::DBLEND_BLEND_FACTOR;
		return true;
	};
	if(str == "inv_blend_factor")
	{
		blend = gs::DBLEND_INV_BLEND_FACTOR;
		return true;
	};
	if(str == "src1_color")
	{
		blend = gs::DBLEND_SRC1_COLOR;
		return true;
	};
	if(str == "inv_src1_color")
	{
		blend = gs::DBLEND_INV_SRC1_COLOR;
		return true;
	};
	if(str == "src1_alpha")
	{
		blend = gs::DBLEND_SRC1_ALPHA;
		return true;
	};
	if(str == "inv_src1_alpha")
	{
		blend = gs::DBLEND_INV_SRC_ALPHA;
		return true;
	};
	return false;
};

bool getBlendOpFromString(const std::string& str, DBLENDOP& op)
{
	if(str == "add")
	{
		op = gs::DBLENDOP_ADD;
		return true;
	};
	if(str == "subtract")
	{
		op = gs::DBLENDOP_SUBTRACT;
		return true;
	};
	if(str == "revsubtract")
	{
		op = gs::DBLENDOP_REVSUBTRACT;
		return true;
	};
	if(str == "min")
	{
		op = gs::DBLENDOP_MIN;
		return true;
	};
	if(str == "max")
	{
		op = gs::DBLENDOP_MAX;
		return true;
	};
	return false;
};

bool getCullModeFromString(const std::string& str, DCULL& cull)
{
	if(str == "front")
	{
		cull = gs::DCULL_FRONT;
		return true;
	};
	if(str == "back")
	{
		cull = gs::DCULL_BACK;
		return true;
	};
	if(str == "none")
	{
		cull = gs::DCULL_NONE;
		return true;
	};
	return false;
};

std::map<std::string, shared_ptr<Material> > MaterialLoader::load(const std::string& filename)
{
	std::map<std::string, shared_ptr<Material> > materials;
	DFile file(filename);
	if(!file.getIsLoaded())
	{
		lwar << "Cannot open file " << filename;
		return materials;
	};
	Parser material_parser(file, true);
	if(!material_parser.getIsParsedSuccessfully())
	{
		lwar << "Cannot parse material file";
		return materials;
	};
	shared_ptr<Section> root = material_parser.getRootSection();
	std::map<std::string, PairInfo>::iterator val_it;
	for(unsigned int i = 0; i < (unsigned int)root->sections.size(); ++i)
	{
		shared_ptr<Material> new_material(new Material);
		shared_ptr<Section> material_section = root->sections[i];
		for(unsigned int j = 0; j < (unsigned int)material_section->sections.size(); ++j)
		{
			shared_ptr<Section> material_param = material_section->sections[j];
			if(material_param->section_name == "effect")
			{
				shared_ptr<Effect> effect = EffectManager::ptr()->load(material_param->section_value);
				new_material->setEffect(effect);
				if(material_param->values.size() != 0)
				{
					val_it = material_param->values.find("technique");
					if(val_it != material_param->values.end())
						new_material->setTechnique(val_it->second.value);
				};
				continue;
			};
			if(material_param->section_name == "map")
			{
				if((val_it = material_param->values.find("file")) == material_param->values.end())
				{
					lwar << "Error in material file. In line " << material_param->section_start_line << " in each map section must contains file param";
					materials.clear();
					return materials;
				};
				shared_ptr<BaseTexture> texture = TextureManager::ptr()->load(val_it->second.value);
				new_material->addTexture(material_param->section_value, texture);
				continue;
			};
			if(material_param->section_name == "macro")
			{
				if((val_it = material_param->values.find("value")) == material_param->values.end())
				{
					lwar << "Error in material file. In line " << material_param->section_start_line << " in each macro section must contains value param";
					materials.clear();
					return materials;
				};
				new_material->addFlag(material_param->section_value, (unsigned int)atoi(val_it->second.value.c_str()));
				continue;
			};
			if(material_param->section_name == "param")
			{
				std::string type, value;
				val_it = material_param->values.find("type");
				if(val_it == material_param->values.end())
				{
					lwar << "Error in material file. In line " << material_param->section_start_line << " in each param section must contains type param";
					materials.clear();
					return materials;
				};
				type = val_it->second.value;
				val_it = material_param->values.find("value");
				if(val_it == material_param->values.end())
				{
					lwar << "Error in material file. In line " << material_param->section_start_line << " in each param section must contains value param";
					materials.clear();
					return materials;
				};
				value = val_it->second.value;
				unsigned int dim = 0;
				shared_array<float> values;
				if(type == "vect4")
				{
					dim = 3;
					values = _getFloatArrayValues(value, dim);
					new_material->addVect4(material_param->section_value, math::Vect4(values.get()));
				}
				else
				{
					if(type == "vect3")
					{
						dim = 4;
						values = _getFloatArrayValues(value, dim);
						new_material->addVect3(material_param->section_value, math::Vect3(values.get()));
					}
					else
					{
						if(type == "vect2")
						{
							values = _getFloatArrayValues(value, dim);
							dim = 2;
							new_material->addVect2(material_param->section_value, math::Vect2(values.get()));
						}
						else
						{
							dim = _getFloatArrayDimension(type);
							if(dim != 1)
								values = _getFloatArrayValues(value, dim);
							else
							{
								values.reset(new float[1]);
								values[0] = (float)atof(value.c_str());
							};
							new_material->addFloatArray(material_param->section_value, values, dim);
						};
					};
				};
				if(!values)
				{
					lwar << "Error in material file. In line " << material_param->section_start_line << " param is not correct";
					materials.clear();
					return materials;
				};
				continue;
			};
			if(material_param->section_name == "blend")
			{
				shared_ptr<BlendRenderState> blend_state(new BlendRenderState);
				val_it = material_param->values.find("dst_blend");
				if(val_it != material_param->values.end())
				{
					DBLEND blend;
					if(!getBlendFromString(val_it->second.value, blend))
					{
						lwar << "Error in file. String " << val_it->second.line_in_file;
						materials.clear();
						return materials;
					};
					blend_state->setDstBlend(blend);
				};

				val_it = material_param->values.find("src_blend");
				if(val_it != material_param->values.end())
				{
					DBLEND blend;
					if(!getBlendFromString(val_it->second.value, blend))
					{
						lwar << "Error in file. String " << val_it->second.line_in_file;
						materials.clear();
						return materials;
					};
					blend_state->setSrcBlend(blend);
				};

				val_it = material_param->values.find("src_blend_alpha");
				if(val_it != material_param->values.end())
				{
					DBLEND blend;
					if(!getBlendFromString(val_it->second.value, blend))
					{
						lwar << "Error in file. String " << val_it->second.line_in_file;
						materials.clear();
						return materials;
					};
					blend_state->setSrcBlendAlpha(blend);
				};

				val_it = material_param->values.find("dst_blend_alpha");
				if(val_it != material_param->values.end())
				{
					DBLEND blend;
					if(!getBlendFromString(val_it->second.value, blend))
					{
						lwar << "Error in file. String " << val_it->second.line_in_file;
						materials.clear();
						return materials;
					};
					blend_state->setDstBlendAlpha(blend);
				};

				val_it = material_param->values.find("blend_op");
				if(val_it != material_param->values.end())
				{
					DBLENDOP op;
					if(!getBlendOpFromString(val_it->second.value, op))
					{
						lwar << "Error in file. String " << val_it->second.line_in_file;
						materials.clear();
						return materials;
					};
					blend_state->setBlendOp(op);
				};

				val_it = material_param->values.find("blend_op_alpha");
				if(val_it != material_param->values.end())
				{
					DBLENDOP op;
					if(!getBlendOpFromString(val_it->second.value, op))
					{
						lwar << "Error in file. String " << val_it->second.line_in_file;
						materials.clear();
						return materials;
					};
					blend_state->setBlendOpAlpha(op);
				};

				for(unsigned int k = 0; k < gs::render_target_num; ++k)
				{
					char buf[20];
					sprintf_s(buf, "blend_enable[%d]", k);
					val_it = material_param->values.find(buf);
					if(val_it != material_param->values.end())
					{
						if(val_it->second.value == "true")
							blend_state->setBlendEnable(k, true);
						else
							if(val_it->second.value == "false")
								blend_state->setBlendEnable(k, false);
							else
							{
								lwar << "Error in file " << filename << " error in line " << val_it->second.line_in_file;
								materials.clear();
								return materials;
							};
					};
				};
				new_material->setBlendState(blend_state);
			};
			if(material_param->section_name == "cull")
			{
				shared_ptr<CullModeRenderState> cull_mode(new CullModeRenderState);
				val_it = material_param->values.find("mode");
				if(val_it != material_param->values.end())
				{
					DCULL cull;
					if(!getCullModeFromString(val_it->second.value, cull))
					{
						lwar << "Error in file " << filename << " error in line " << val_it->second.line_in_file;
						materials.clear();
						return materials;
					};
					cull_mode->setCullMode(cull);
				};
				new_material->setCullMode(cull_mode);
			};
		};
		materials.insert(std::pair<std::string, shared_ptr<Material> >(material_section->section_value, new_material));
	};
	return materials;
};

// each value must be in (float, float, float,..., float) format
shared_array<float> MaterialLoader::_getFloatArrayValues(const std::string& value, const unsigned int number)
{
	shared_array<float> values(new float[number]);
	if(value[0] != '(' || value[value.length()-1] != ')')
		return values;
	std::stringstream vstream(value.substr(1, value.length()-1));
	unsigned int number_of_floats = 0;
	std::string next_float;
	while(std::getline(vstream, next_float, ','))
	{
		if(number_of_floats > number)
		{
			std::cout << "File format error. Not correct number of values in param section";
			return shared_array<float>();
		};
		next_float = next_float.substr(next_float.find_first_not_of(' '));
		float val = (float)atof(next_float.c_str());
		values[number_of_floats] = val;
		++number_of_floats;
	};
	return values;
};

unsigned int MaterialLoader::_getFloatArrayDimension(const std::string& type)
{
	if(type == "float")
		return 1;
	unsigned int dim;
	sscanf_s(type.c_str(), "float%d", &dim);
	return dim;
};






	