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

#include <Darkstar/Graphic System/Effect/effect_manager.hpp>
#include <Darkstar/Graphic System/Render State/render_state_manager.hpp>
#include <Darkstar/Graphic System/Direct3D/direct3d_include.hpp>
#include <Darkstar/Kernel/parser.hpp>
#include <Darkstar/Graphic System/Effect/shader_compiler.hpp>

using namespace gs;
using ker::DFile;
using ker::Log;
using ker::Parser;
using ker::Section;
using ker::PairInfo;
using ker::SystemTime;
using ker::Blob;

EffectManager::EffectManager()
{
	load_last_effect_success = false;
};

EffectManager::~EffectManager()
{

};

void EffectManager::_fillContibutionMacrosField(std::list<_Technique>::iterator& it)
{
	unsigned int size = static_cast<unsigned int>(it->tech->macros.size());
	std::vector<Macro>::iterator mit;
	if(size != 0)
	{
		it->tech->macros[0].contribution = 1;
		for(unsigned int i = 1; i < size; ++i)
		{
			unsigned int i1 = i-1;
			it->tech->macros[i].contribution = it->tech->macros[i1].contribution*(it->tech->macros[i1].max_value - it->tech->macros[i1].min_value + 1);
		};
	};
};

void EffectManager::_getMacroAdditionalInfo(std::list<_Technique>::iterator& it, unsigned int& num_concrete_effect, unsigned int& num_ps_macros, unsigned int& num_vs_macros, std::vector<short>& cur_values)
{
	unsigned int size = static_cast<unsigned int>(it->tech->macros.size());
	std::vector<Macro>::iterator mit;
	if(size != 0)
	{
		unsigned int size1 = size-1;
		num_concrete_effect = it->tech->macros[size1].contribution*(it->tech->macros[size1].max_value - it->tech->macros[size1].min_value + 1);
		for(unsigned int i = 0; i < size; ++i)
			cur_values[i] = it->tech->macros[i].cur_value;
		for(mit = it->tech->macros.begin(); mit != it->tech->macros.end(); ++mit)
		{
			mit->cur_value = mit->min_value;
			if(mit->type == MT_SHARED)
			{
				++num_ps_macros;
				++num_vs_macros;
				continue;
			};
			if(mit->type == MT_PIXEL)
			{
				++num_ps_macros;
				continue;
			};
			if(mit->type == MT_VERTEX)
			{
				++num_vs_macros;
				continue;
			};
		};	
	};
};

bool EffectManager::_compileTechnique(std::list<_Technique>::iterator& it)
{
	std::vector<Macro>::iterator mit;
	// number of macros in pixel shader
	unsigned int num_ps_macros = 0;
	// number of macros in vertex shader
	unsigned int num_vs_macros = 0;
	// number of concrete effects, i.e. number of effects that will be generate with different macros values
	unsigned int num_concrete_effect = 1;
	unsigned int size = static_cast<unsigned int>(it->tech->macros.size());
	std::vector<short> cur_values(size);
	// retrieve additional info about effect, for example number of concrete effects or macros's default values
	_getMacroAdditionalInfo(it, num_concrete_effect, num_ps_macros, num_vs_macros, cur_values);
	
	// code of pixel shader
	shared_array<char> pcode;
	// code of vertex shader
	shared_array<char> vcode;
	// size of pixel shader code
	unsigned int pc_size;
	// size of vertex shader code
	unsigned int vc_size;

	// use two different files for vertex and pixel shaders
	if(!it->files.is_the_same)
	{
		// retrieve size of vertex and pixel shader codes
		pc_size = it->files.pfile->getSize();
		vc_size = it->files.vfile->getSize();
		try
		{
			pcode.reset(new char[pc_size]);
			vcode.reset(new char[vc_size]);
		}
		catch(std::bad_alloc&)
		{
			lwar << "Cannot find enough memory to allocate";
			return false;
		};
		// read data from files
		if(!it->files.pfile->read(pc_size, pcode.get()))
		{
			lwar << "Cannot read " << pc_size << " bytes from file " << it->files.pfile->getFilename();
			return false;
		};
		if(!it->files.vfile->read(vc_size, vcode.get()))
		{
			lwar << "Cannot read " << vc_size << " bytes from file " << it->files.vfile->getFilename();
			return false;
		};
	}
	// if files which contains code of vertex and pixel shader the same
	else
	{
		pc_size = it->files.pfile->getSize();
		vc_size = pc_size;
		try
		{
			pcode.reset(new char[pc_size]);
			vcode = pcode;
		}
		catch(std::bad_alloc&)
		{
			lwar << "Cannot find enough memory to allocate";
			return false;
		};
		if(!it->files.pfile->read(pc_size, pcode.get()))
		{
			lwar << "Cannot read " << pc_size << " bytes from file " << it->files.pfile->getFilename();
			return false;
		};
	};

	// information about shader, such variables and input params
	shared_ptr<ShaderReflection> vs_ref, ps_ref;
	// number of input parameters in vertex shader
	input_sig_num = 0;

	// pixel and vertex shader macros in platform specific form
	shared_array<ShaderMacro> psmacros;
	shared_array<ShaderMacro> vsmacros;
	// macros definitions
	std::vector<shared_array<char> > definitions;
	try
	{
		psmacros.reset(new ShaderMacro[num_ps_macros+1]);
		vsmacros.reset(new ShaderMacro[num_vs_macros+1]);
	}
	catch(std::bad_alloc&)
	{
		lwar << "Cannot find enough memory to allocate";
		return false;
	};
	// this value will be contain maximum number of constants in one concrete effect overall
	unsigned int max_const = 0;
	// this value will be contain current number of constants in one concrete effect
	unsigned int num_const = 0;

	// compile code and size of vertex and pixel shader, it will be use for write to file
	std::vector<Blob> vc_code;
	std::vector<Blob> pc_code;
	// tmp blob for vertex or pixel shader code and size
	Blob new_blob;
	// for each concrete effect
	for(unsigned int i = 0; i < num_concrete_effect; ++i)
	{
		// form specific lists of macros for each shader
		unsigned int ps_macros_count = 0;
		unsigned int vs_macros_count = 0;
		for(mit = it->tech->macros.begin(); mit != it->tech->macros.end(); ++mit)
		{
			shared_array<char> buf(new char[4]);
			// both shader contain this macro
			if(mit->type == MT_SHARED)
			{
				psmacros[ps_macros_count].name = mit->name.c_str();
				_itoa_s(mit->cur_value, buf.get(), 4, 10);
				definitions.push_back(buf);
				psmacros[ps_macros_count].definition = buf.get();
				vsmacros[vs_macros_count] = psmacros[ps_macros_count];
				++vs_macros_count;
				++ps_macros_count;
				continue;
			};
			// only pixel shader contains this macro
			if(mit->type == MT_PIXEL)
			{
				psmacros[ps_macros_count].name = mit->name.c_str();
				_itoa_s(mit->cur_value, buf.get(), 4, 10);
				definitions.push_back(buf);
				psmacros[ps_macros_count].definition = buf.get();
				++ps_macros_count;
				continue;
			};
			// only vertex shader contains this macro
			if(mit->type == MT_VERTEX)
			{
				vsmacros[vs_macros_count].name = mit->name.c_str();
				_itoa_s(mit->cur_value, buf.get(), 4, 10);
				definitions.push_back(buf);
				vsmacros[ps_macros_count].definition = buf.get();
				++vs_macros_count;
				continue;
			};
		};
		// fill the last element in macros
		psmacros[num_ps_macros].name = NULL;
		psmacros[num_ps_macros].definition = NULL;
		vsmacros[num_vs_macros] = psmacros[num_ps_macros];

		// compile pixel shader
		ShaderCompiler ps_compiler;
		if(!ps_compiler.compileShader(pcode, pc_size, psmacros, it->pixel_shader_function, it->pixel_shader_target))
		{
			lwar << "Cannot compile shader " << it->pixel_shader_name;
			return false;
		};
		// get compiled code
		new_blob.data = ps_compiler.getCompiledCode();
		new_blob.data_size = ps_compiler.getCodeSize();
		// save code and size for future write to file
		pc_code.push_back(new_blob);
		// create pixel shader
		shared_ptr<PixelShader> pixel_shader(new PixelShader);
		if(!pixel_shader->create(new_blob.data, new_blob.data_size))
		{
			lwar << "Cannot create pixel shader with name " << it->pixel_shader_name;
			return false;
		};
		// reflect shader
		shared_ptr<ShaderReflection> loc_ps_ref(new ShaderReflection(new_blob.data, new_blob.data_size));

		// compile vertex shader
		ShaderCompiler vs_compiler;
		if(!vs_compiler.compileShader(vcode, vc_size, vsmacros, it->vertex_shader_function, it->vertex_shader_target))
		{
			lwar << "Cannot compile shader " << it->vertex_shader_name;
			return false;
		};
		// save code and size for future write to file
		new_blob.data = vs_compiler.getCompiledCode();
		new_blob.data_size = vs_compiler.getCodeSize();
		// create vertex shader
		vc_code.push_back(new_blob);
		shared_ptr<VertexShader> vertex_shader(new VertexShader);
		if(!vertex_shader->create(new_blob.data, new_blob.data_size))
		{
			lwar << "Cannot create vertex shader with name " << it->vertex_shader_name;
			return false;
		};
		// reflect shader
		shared_ptr<ShaderReflection> loc_vs_ref(new ShaderReflection(new_blob.data, new_blob.data_size));

		// make new concrete effect
		ConcreteEffect ne;
		ne.ps = pixel_shader;
		ne.vs = vertex_shader;
		it->tech->ce.push_back(ne);

		// if current number of input parameters in vertex shader more than max, then retrieve
		// new input signature blob
		if(loc_vs_ref->getInputParametersNum() > input_sig_num)
		{
			tmp_input_signature.data = loc_vs_ref->getInputSignatureData();
			tmp_input_signature.data_size = loc_vs_ref->getInputSignatureSize();
			input_sig_num = loc_vs_ref->getInputParametersNum();
		};
		// save current vertex shader reflection for future compatibility check
		tmp_vertex_shader_reflection.push_back(loc_vs_ref);

		num_const = 0;
		num_const += (loc_vs_ref->getVariablesNum() + loc_ps_ref->getVariablesNum() + loc_vs_ref->getBoundResourcesNum() + loc_ps_ref->getBoundResourcesNum());
		if(num_const >= max_const)
		{
			vs_ref = loc_vs_ref;
			ps_ref = loc_ps_ref;
		};
		// follow to the next cur_values
		for(unsigned int j = 0; j < size; ++j)
		{
			it->tech->macros[j].cur_value += 1;
			if(it->tech->macros[j].cur_value > it->tech->macros[j].max_value)
				it->tech->macros[j].cur_value = it->tech->macros[j].min_value;
			else
				break;
		};
	};
	// create constant table from reflections that give us maximum number of constant together
	it->tech->const_table.reset(new ConstantTable(vs_ref, ps_ref));
	for(unsigned int i = 0; i < size; ++i)
		it->tech->macros[i].cur_value = cur_values[i];
	// write effect code to file for optimization
	compiled_effect_file->writeConstantTables(it->tech->const_table, vc_code, pc_code);
	return true;
};	

shared_ptr<Effect> EffectManager::load(const std::string& effect_name)
{
	tmp_vertex_shader_reflection.clear();
	tmp_input_signature.data = NULL;
	tmp_input_signature.data_size = 0;
	compiled_effect_file.reset(new CompiledEffectFile());

	input_sig_num = 0;
	load_last_effect_success = false;
	_tech.clear();

	stdext::hash_map<std::string, weak_ptr<Effect> >::iterator cit = effect_cash.find(effect_name);
	if(cit != effect_cash.end())
		if(!cit->second.expired())
			return cit->second.lock();

	shared_ptr<Effect> eff;
	EffectParser effect_parser;
	if(!effect_parser.parseFile(effect_name))
	{
		lwar << "Cannot parse effect file " << effect_name;
		return eff;
	};
	_tech = effect_parser.getFileData();

	std::list<_Technique>::iterator it;
	// find last write time in all files which are use in this effect
	shared_ptr<SystemTime> stime = effect_parser.getEffectFileLastWriteTime();
	for(it = _tech.begin(); it != _tech.end(); ++it)
	{
		if(it->pixel_shader_name != it->vertex_shader_name)
		{
			shared_ptr<DFile> pfile(new DFile(it->pixel_shader_name));
			shared_ptr<DFile> vfile(new DFile(it->vertex_shader_name));
			if(!pfile->getIsLoaded() || !vfile->getIsLoaded())
			{
				lwar << "Cannot open shader file with name: " << pfile->getFilename() << " or " << vfile->getFilename();
				return eff;
			};
			if((*stime) < (*pfile->getLastWriteTime()))
				stime = pfile->getLastWriteTime();
			if((*stime) < (*vfile->getLastWriteTime()))
				stime = vfile->getLastWriteTime();
			it->files.pfile = pfile;
			it->files.vfile = vfile;
			it->files.is_the_same = false;
		}
		else
		{
			if(it->pixel_shader_function == it->vertex_shader_function)
			{
				lwar << "Cannot compile pixel and vertex shader from one file and one main function";
				return eff;
			};
			shared_ptr<DFile> file(new DFile(it->pixel_shader_name));
			if(!file->getIsLoaded())
			{
				lwar << "Cannot open shader file with name: " << file->getFilename();
				return eff;
			};
			if((*stime) < (*file->getLastWriteTime()))
				stime = file->getLastWriteTime();
			it->files.pfile = file;
			it->files.vfile = file;
			it->files.is_the_same = true;
		};
		for(unsigned int i = 0; i < (unsigned int)it->include_files.size(); ++i)
		{
			DFile incl_file(it->include_files[i]);
			if(!incl_file.getIsLoaded())
			{
				lwar << "Cannot open include file " << it->include_files[i];
				continue;
			};
			if((*stime) < (*incl_file.getLastWriteTime()))
				stime = incl_file.getLastWriteTime();
		};
	};

	shared_ptr<Effect> new_effect(new Effect);
	weak_ptr<Effect> weak_new_effect(new_effect);

	unsigned int dot_pos = (unsigned int)effect_name.find_last_of('.');
	if(dot_pos == std::string::npos)
		dot_pos = (unsigned int)effect_name.length();
	std::string compiled_effect_file_name = effect_name.substr(0, dot_pos) + ".dcef" + GRAPHIC_PLATFORM;
	DFile compiled_effect(compiled_effect_file_name);

	for(it = _tech.begin(); it != _tech.end(); ++it)
		_fillContibutionMacrosField(it);

	if(compiled_effect.getIsLoaded())
	{
		if((*stime) < (*compiled_effect.getLastWriteTime()))
		{
			if(compiled_effect_file->load(compiled_effect, _tech, new_effect))
			{
				load_last_effect_success = true;
				effect_cash.insert(std::pair<std::string, weak_ptr<Effect> >(effect_name, weak_new_effect));
				return new_effect;
			}
			else
				new_effect.reset(new Effect);
		};
	};

	std::string cefn_full = effect_parser.getEffectFilePath() + compiled_effect_file_name;
	compiled_effect_file->openForWrite(cefn_full);

	for(it = _tech.begin(); it != _tech.end(); ++it)
	{
		if(!_compileTechnique(it))
		{
			lwar << "Cannot compile effect " << effect_name;
			compiled_effect_file->deleteFile();
			return eff;
		};
		new_effect->addNewTechnique(it->tech_name, it->tech);
	};

	if(!_checkInputSignature())
	{
		lwar << "Input signature in effect " << effect_name << " not correct";
		compiled_effect_file->deleteFile();
		return eff;
	};

	compiled_effect_file->writeInputSignature(tmp_input_signature);

	InputSignature is;
	is.size = tmp_input_signature.data_size;
	is.code.reset(new char[is.size]);
	memcpy(is.code.get(), tmp_input_signature.data, is.size);
	new_effect->setEffectInputSignature(is);

	load_last_effect_success = true;
	return new_effect;
};

bool EffectManager::getIsLoadLastEffect() const
{
	return load_last_effect_success;
};
		