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

#include <Darkstar/Visual Effects/hdr/hdr.hpp>
#include <Darkstar/Graphic System/Direct3D/graphicDriver.hpp>
#include <Darkstar/Graphic System/Effect/effect_manager.hpp>
#include <Darkstar/Kernel/logh.hpp>

using namespace gs;
using namespace rs;
using namespace ve;
using ker::Log;

HDR::HDR(const std::string& hdr_operator_name, const std::string& hdr_down_sampler_name, const unsigned int screen_width, const unsigned int screen_height, const DRTAATYPE& aa_type)
{
	is_hdr_correct = false;

	hdr_operator = EffectManager::ptr()->load(hdr_operator_name);
	if(!EffectManager::ptr()->getIsLoadLastEffect())
	{
		lwar << "Cannot load effect for hdr. HDR operator " << hdr_operator_name << " is not correct";
		return;
	};
	hdr_operator->setCurTechnique("base");

	hdr_down_sampler = EffectManager::ptr()->load(hdr_down_sampler_name);
	if(!EffectManager::ptr()->getIsLoadLastEffect())
	{
		lwar << "Cannot load effect for hdr. HDR down sampler operator " << hdr_down_sampler_name << " is not correct";
		return;
	};
	hdr_down_sampler->setCurTechnique("base");

	boost::function2<void, const unsigned int, const unsigned int> new_function;
	new_function = boost::bind(&ve::HDR::setScreenSize, this, _1, _2);
	GraphicDriver::ptr()->addMessageListenerOnResize(new_function);

	is_hdr_correct = _createAll(screen_width, screen_height, aa_type);
};

HDR::~HDR()
{

};

void HDR::setScreenSize(const unsigned int new_screen_width, const unsigned int new_screen_height)
{
	is_hdr_correct = _createAll(new_screen_width, new_screen_height, aa_type);
};

void HDR::setHDROperator(const shared_ptr<Effect>& new_hdr_operator)
{
	if(!new_hdr_operator)
	{
		lwar << "Cannot set NULL HDR operator";
		return;
	};
	hdr_operator = new_hdr_operator;
};

void HDR::setHDRDownSampler(const shared_ptr<Effect>& new_hdr_down_sampler)
{
	if(!new_hdr_down_sampler)
	{
		lwar << "Cannot set NULL HDR down sampler operator";
		return;
	};
	hdr_down_sampler = new_hdr_down_sampler;
};

const shared_ptr<RenderTarget>& HDR::getSceneRenderTarget() const
{
	return scene_render_target;
};

const shared_ptr<RenderTarget>& HDR::getStrechedScene(unsigned int level) const
{
	return scene_down_samples[level];
};

const math::Vect2& HDR::getStrechedSceneSize(unsigned int level) const
{
	return tex_size[level];
};

const shared_ptr<gs::RenderTarget>& HDR::getAverageLuminosity() const
{
	return average_luminosity;
};

const shared_ptr<Quad>& HDR::getFullscreenQuad(const unsigned int level) const
{
#ifdef GD_D3D10
	return d3d10_fullscreen_quad;
#endif
#ifdef GD_D3D9
	return d3d9_fullscreen_quads[level];
#endif
};

bool HDR::getIsHDRCorrect() const
{
	return is_hdr_correct;
};

bool HDR::_createAll(const unsigned int new_screen_width, const unsigned int new_screen_height, const gs::DRTAATYPE& new_aa_type)
{
	scene_render_target.reset(new RenderTarget());
	if(!scene_render_target->create2D(new_screen_width, new_screen_height, DRTFMT_R16G16B16A16_FLOAT, new_aa_type, true))
	{
		lwar << "Cannot create scene render target with width " << new_screen_width << " and height = " << new_screen_height;
		return false;
	};

	unsigned int tmp_width = new_screen_width;
	unsigned int tmp_height = new_screen_height;
	while(tmp_width != 1 || tmp_height != 1)
	{
		inv_tex_size.push_back(math::Vect2(1.0f/(float)tmp_width, 1.0f/(float)tmp_height));
		tmp_width >>= 1;
		tmp_height >>= 1;
		if(tmp_height == 0)
			tmp_height = 1;
		if(tmp_width == 0)
			tmp_width = 1;
		shared_ptr<RenderTarget> next_ds(new RenderTarget());
		if(!next_ds->create2D(tmp_width, tmp_height, DRTFMT_R16G16B16A16_FLOAT, new_aa_type, true))
		{
			lwar << "Cannot create next down sample render target with width = " << tmp_width << " and heigth = " << tmp_height;
			return false;
		};
		scene_down_samples.push_back(next_ds);
		tex_size.push_back(math::Vect2((float)tmp_width, (float)tmp_height));
	};

	average_luminosity.reset(new RenderTarget());
	if(!average_luminosity->create2D(1, 1, DRTFMT_R16_FLOAT, WITHOUT_AA, true))
	{
		lwar << "Cannot create texture for average luminosity";
		return false;
	};
#ifdef GD_D3D10
	d3d10_fullscreen_quad.reset(new Quad(hdr_operator));
	d3d10_fullscreen_quad->addMaterialTexture("scene_texture", scene_render_target->getTexture());
	d3d10_fullscreen_quad->addMaterialTexture("average_lumin", scene_down_samples[scene_down_samples.size()-1]->getTexture());
#endif
#ifdef GD_D3D9
	shared_ptr<rs::Quad> backbuffer_quad(new Quad(-1.0f - (1.0f / (new_screen_width)), 1.0f - (1.0f / (new_screen_height)), 2, hdr_operator));
	d3d9_fullscreen_quads.push_back(backbuffer_quad);
	for(unsigned int i = 0; i < static_cast<unsigned int>(tex_size.size()); ++i)
	{
		shared_ptr<rs::Quad> next_quad;
		next_quad.reset(new Quad(-1.0f - (1.0f / (tex_size[i].x)), 1.0f + (1.0f / (tex_size[i].y)), 2, hdr_operator));
		d3d9_fullscreen_quads.push_back(next_quad);
	};
#endif
	aa_type = new_aa_type;
	return true;
};

void HDR::beginRenderObjects()
{
	GraphicDriver::ptr()->begin(scene_render_target, GraphicDriver::ptr()->getDepthStencilSurface());
};

void HDR::drawQuad()
{
	shared_ptr<Camera> camera(new Camera());
	GraphicDriver::ptr()->beginWithColor(GraphicDriver::ptr()->getBackBuffer());
	GraphicDriver::ptr()->setViewport(0, 0);
	shared_ptr<rs::Quad> fullscreen_quad;
#ifdef GD_D3D10
	fullscreen_quad = d3d10_fullscreen_quad;
#endif

#ifdef GD_D3D9
	fullscreen_quad = d3d9_fullscreen_quads[0];
#endif
	for(unsigned int i = 0; i < fullscreen_quad->getModel()->getMeshesNumber(); ++i)
	{
		shared_ptr<Material> mat = fullscreen_quad->getModel()->getMesh(i)->getMaterial();
		mat->setEffect(hdr_operator);
		mat->setTechnique("base");
		mat->addTexture("scene_texture", scene_render_target->getTexture());
		fullscreen_quad->getModel()->drawMesh(i, camera);
	};
	GraphicDriver::ptr()->end();
};

void HDR::end()
{
	GraphicDriver::ptr()->end();
};

void HDR::endWithCalculations()
{
	GraphicDriver::ptr()->end();
	calculateAverageLuminocity();
};

void HDR::calculateAverageLuminocity()
{
	shared_ptr<Camera> camera(new Camera());
	shared_ptr<rs::Quad> fullscreen_quad;
	for(unsigned int i = 0; i < scene_down_samples.size(); ++i)
	{
		GraphicDriver::ptr()->beginWithColor(scene_down_samples[i]);
		GraphicDriver::ptr()->clear();
		GraphicDriver::ptr()->setViewport((unsigned int)tex_size[i].x, (unsigned int)tex_size[i].y);

#ifdef GD_D3D10
		fullscreen_quad = d3d10_fullscreen_quad;
#endif

#ifdef GD_D3D9
		fullscreen_quad = d3d9_fullscreen_quads[i + 1];
#endif

		for(unsigned int j = 0; j < fullscreen_quad->getModel()->getMeshesNumber(); ++j)
		{
			shared_ptr<Material> mat = fullscreen_quad->getModel()->getMesh(j)->getMaterial();
			hdr_down_sampler->setCurTechnique("base");
			hdr_down_sampler->setVect2("inv_texture_size", &inv_tex_size[i]);
			mat->setEffect(hdr_down_sampler);
			mat->setTechnique("base");
			mat->addTexture("scene_texture", i > 0 ? scene_down_samples[i-1]->getTexture() : scene_render_target->getTexture());
			fullscreen_quad->getModel()->drawMesh(j, camera);
		};

		GraphicDriver::ptr()->end();
	};
	unsigned int i = static_cast<unsigned int>(scene_down_samples.size()-1);
	GraphicDriver::ptr()->beginWithColor(average_luminosity);
	GraphicDriver::ptr()->clear();
	GraphicDriver::ptr()->setViewport((unsigned int)tex_size[i].x, (unsigned int)tex_size[i].y);

#ifdef GD_D3D10
	fullscreen_quad = d3d10_fullscreen_quad;
#endif

#ifdef GD_D3D9
	fullscreen_quad = d3d9_fullscreen_quads[d3d9_fullscreen_quads.size()-1];
#endif
	for(unsigned int j = 0; j < fullscreen_quad->getModel()->getMeshesNumber(); ++j)
	{
		shared_ptr<Material> mat = fullscreen_quad->getModel()->getMesh(j)->getMaterial();
		hdr_down_sampler->setVect2("inv_texture_size", &inv_tex_size[i]);
		mat->setEffect(hdr_down_sampler);
		mat->setTechnique("retrieve_r32");
		mat->addTexture("scene_texture", scene_down_samples[i]->getTexture());
		fullscreen_quad->getModel()->drawMesh(j, camera);
	};

	GraphicDriver::ptr()->end();
};