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

#include "splash_system.hpp"
#include <Darkstar/Graphic System/Effect/effect_manager.hpp>
#include <Darkstar/Resource System/Textures/textureManager.hpp>

using ve::SplashSystem;

SplashSystem::SplashSystem(const std::string& effect_name, const std::string& texture_name, 
						   const unsigned int frame_count, const unsigned int radius, 
						   const unsigned int number_of_splashes, const math::Vect3& start_position, 
						   const float splash_speed , const float splash_size)

{
	splash_effect = gs::EffectManager::ptr()->load(effect_name);
	splash_texture = rs::TextureManager::ptr()->load(texture_name);

	splash_frame_count = frame_count;
	splash_texture_x_shift = 1.0f / (float)splash_frame_count;

	this->splash_size = splash_size;

	shared_array<short> index_data(new short[6*number_of_splashes]);

	for(unsigned int i = 0, k = 0; i < 6*number_of_splashes; i += 6, k += 4)
	{
		index_data[i] = k;
		index_data[i + 1] = k + 1;
		index_data[i + 2] = k + 2;
		index_data[i + 3] = k + 2;
		index_data[i + 4] = k + 3;
		index_data[i + 5] = k;
	};

	shared_ptr<rs::InputLayout> input_layout(new rs::InputLayout());
	input_layout->addElement("POSITION", 0, 0, 0, rs::DFMT_R32G32B32A32_FLOAT);
	input_layout->addElement("TEXCOORD", 0, 0, 16, rs::DFMT_R32G32_FLOAT);
	input_layout->create(splash_effect->getEffectInputSignature().code.get(), splash_effect->getEffectInputSignature().size);

	shared_ptr<rs::VertexBuffer> vertex_buffer(new rs::VertexBuffer(input_layout));
	vertex_buffer->createNextStream(6*sizeof(float), 4*number_of_splashes);

	shared_ptr<rs::IndexBuffer> index_buffer(new rs::IndexBuffer());
	index_buffer->createWithData(6*number_of_splashes, rs::DSEIB_16, index_data.get());

	shared_ptr<rs::Material> splash_material(new rs::Material());
	splash_material->setEffect(splash_effect);
	splash_material->setTechnique("base");
	splash_material->setCullMode(shared_ptr<gs::CullModeRenderState>(new gs::CullModeRenderState(gs::DCULL_BACK)));
	splash_material->addTexture("splash_texture", splash_texture);

	shared_ptr<gs::BlendRenderState> splash_blend(new gs::BlendRenderState());
	splash_blend->setSrcBlend(gs::DBLEND_SRC_ALPHA);
	splash_blend->setBlendOp(gs::DBLENDOP_ADD);
	splash_blend->setDstBlend(gs::DBLEND_INV_SRC_ALPHA);
	splash_blend->setBlendEnable(0, true);
	splash_blend->setSrcBlendAlpha(gs::DBLEND_ZERO);
	splash_blend->setDstBlendAlpha(gs::DBLEND_ZERO);
	splash_blend->setBlendOpAlpha(gs::DBLENDOP_ADD);

	splash_material->setBlendState(splash_blend);

	splash_model.reset(new rs::StaticModel());
	splash_model->addNextMesh(shared_ptr<rs::Mesh>(new rs::Mesh(vertex_buffer, index_buffer, splash_material)));

	splash_number = number_of_splashes;
	radius_of_generation = radius;

	srand(GetTickCount());

	for(unsigned int i = 0; i < number_of_splashes; ++i)
	{
		Splash new_splash = _generateNewSplash(start_position);
		new_splash.current_frame = (float)(rand() % frame_count);
		splashes.push_back(new_splash);
	};

	this->splash_speed = splash_speed;
};

SplashSystem::~SplashSystem()
{

};

SplashSystem::Splash SplashSystem::_generateNewSplash(math::Vect3 camera_position)
{
	Splash new_splash;
	new_splash.current_frame = 0;
	int half_rand_max = RAND_MAX / 2;
	new_splash.position.x = (float)(rand()) / RAND_MAX + rand() % (radius_of_generation-1);
	new_splash.position.x *= rand() > half_rand_max ? 1 : -1;
	new_splash.position.y = splash_size;
	new_splash.position.z = (float)(rand()) / RAND_MAX + rand() % (radius_of_generation-1);
	new_splash.position.z *= rand() > half_rand_max ? 1 : -1;
	new_splash.position.x += camera_position.x;
	new_splash.position.z += camera_position.z;
	return new_splash;
};

void SplashSystem::update(const float fps, const shared_ptr<gs::Camera>& camera)
{
	math::Vect3 camera_position = camera->getPosition();

	math::Vect3 camera_normal = -camera->getTarget() + camera->getPosition();
	camera_normal.y = 0;
	camera_normal.normalize();

	math::Vect3 splash_normal = -math::vect3_sing_z;

	float cos_angle = dot(camera_normal, splash_normal);
	float angle = acosf(cos_angle);
	if(camera_normal.x > 0)
		angle = -angle;

	math::Matr4 view_projection = camera->getViewProjection();
	math::Matr4 world;
	math::Matr4 world_view_projection;

	shared_ptr<rs::VertexBuffer> vertex_buffer = splash_model->getMesh(0)->getVertexBuffer();
	SplashVertex* vb = (SplashVertex*)vertex_buffer->mapStream(0, rs::DEMT_WRITE_DISCARD);

	std::vector<Splash>::iterator it;
	unsigned int i = 0;
	for(it = splashes.begin(); it != splashes.end(); ++it, ++i)
	{
		// update current frame
		it->current_frame += (splash_frame_count) / (fps*splash_speed);
		unsigned int current_frame_int = (int)floorf(it->current_frame);

		if(current_frame_int >= splash_frame_count || (it->position - camera_position).length() >= radius_of_generation)
			*it = _generateNewSplash(camera_position);

		float splash_texture_xshift = (int)floorf(it->current_frame)*splash_texture_x_shift;

		// update vertex buffer
	
		world = math::matr4_sing;
		world.ApplyTranslation(it->position);
		world.ApplyRotation(Vect3(0, angle, 0));

		world_view_projection = view_projection * world;

		unsigned int index = i*4;
		vb[index].position = world_view_projection*math::Vect4(-splash_size/2, splash_size, 0.0f, 1.0f);
		vb[index].tex_coord = math::Vect2(splash_texture_xshift, 0);

		vb[index+1].position = world_view_projection*math::Vect4(splash_size/2, splash_size, 0.0f, 1.0f);
		vb[index+1].tex_coord = math::Vect2(splash_texture_x_shift + splash_texture_xshift, 0);

		vb[index+2].position = world_view_projection*math::Vect4(splash_size/2, -splash_size, 0.0f, 1.0f);
		vb[index+2].tex_coord = math::Vect2(splash_texture_x_shift + splash_texture_xshift, 1.0f);

		vb[index+3].position = world_view_projection*math::Vect4(-splash_size/2, -splash_size, 0.0f, 1.0f);
		vb[index+3].tex_coord = math::Vect2(splash_texture_xshift, 1.0f);
	};
	vertex_buffer->unmapStream(0);
};

void SplashSystem::draw(const shared_ptr<gs::Camera>& camera)
{
	for(unsigned int i = 0; i < splash_model->getMeshesNumber(); ++i)
	{
		splash_model->drawMesh(i, camera);
	};
};