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

#include <Darkstar/Nature/Sky/star_database.hpp>
#include <Darkstar/Graphic System/Effect/effect_manager.hpp>
#include <Darkstar/Resource System/Textures/textureManager.hpp>
#include <Darkstar/Kernel/logh.hpp>
#include <Darkstar/Kernel/Timetable/time.hpp>

using gs::Effect;
using gs::EffectManager;
using rs::BaseTexture;
using rs::StaticModel;
using rs::TextureManager;
using rs::InputLayout;
using rs::VertexBuffer;
using rs::IndexBuffer;
using rs::Material;
using ker::Log;

using namespace nature;
using namespace math;

StarDatabase::StarDatabase(const std::string& database_name, const std::string& star_texture_name, const std::string& star_effect_name, const float site_latitude)
{
	this->site_latitude = site_latitude;
	star_texture = TextureManager::ptr()->load(star_texture_name);
	star_effect = EffectManager::ptr()->load(star_effect_name);

	visible_stars_buffer.reset(new StaticModel());

	// set correct blend render state values
	star_blend_render_state.reset(new gs::BlendRenderState());
	star_blend_render_state->setBlendEnable(0, true);
	star_blend_render_state->setBlendOp(gs::DBLENDOP_ADD);
	star_blend_render_state->setBlendOpAlpha(gs::DBLENDOP_ADD);
	star_blend_render_state->setDstBlend(gs::DBLEND_INV_SRC_ALPHA);
	star_blend_render_state->setSrcBlend(gs::DBLEND_SRC_ALPHA);
	star_blend_render_state->setDstBlendAlpha(gs::DBLEND_ZERO);
	star_blend_render_state->setSrcBlendAlpha(gs::DBLEND_ZERO);
	// create material for stars
	star_material.reset(new rs::Material());
	star_material->addTexture("star_texture", star_texture);
	star_material->setBlendState(star_blend_render_state);
	star_material->setEffect(star_effect);
	star_material->setTechnique("base");
	
	is_database_correct = false;

	if(EffectManager::ptr()->getIsLoadLastEffect())
	{
		star_input_layout.reset(new rs::InputLayout());
		star_input_layout->addElement("POSITION", 0, 0, 0, rs::DFMT_R32G32B32_FLOAT);
		star_input_layout->addElement("TEXCOORD", 0, 0, 12, rs::DFMT_R32G32_FLOAT);
		if(!star_input_layout->create(star_effect->getEffectInputSignature().code.get(), star_effect->getEffectInputSignature().size))
		{
			lwar << "Cannot create input layout for star with effect " << star_effect_name;
			return;
		};
	}
	else
	{
		lwar << "Cannot load effect " << star_effect_name;
		return;
	};

	if(!_initStarsDatabase(database_name))
	{
		lwar << "Cannot init star database";
		return;
	};
	if(!_updateVisibleStarsBuffer())
	{
		lwar << "Cannot create buffer for visible stars";
		return;
	};
	is_database_correct = true;
};

StarDatabase::~StarDatabase()
{

};

bool StarDatabase::_initStarsDatabase(const std::string& base_name)
{
	// we will use Bright Star Catalog (BSC) as database
	ker::DFile star_database(base_name);
	if(!star_database.getIsLoaded())
		return false;

	shared_array<char> data;
	try
	{
		data.reset(new char[star_database.getSize() + 1]);
		data[star_database.getSize()] = '\0';
	}
	catch(std::bad_alloc&)
	{
		return false;
	};
	star_database.read(star_database.getSize(), data.get());
	std::stringstream stream;
	stream << data.get();
	data.reset();
	std::string next_line;
	while(std::getline(stream, next_line))
	{
		// right ascension in hours from 0 to 24
		// declination in degrees. In north pole +90 and in south pole -90.
		// In this catalog equatorial coordinate system

		/* byte to byte description of each string in Bright star catalog. Bytes have start from 1
		76- 77  I2     h       RAh      ?Hours RA, equinox J2000, epoch 2000.0 (1)
		78- 79  I2     min     RAm      ?Minutes RA, equinox J2000, epoch 2000.0 (1)
		80- 83  F4.1   s       RAs      ?Seconds RA, equinox J2000, epoch 2000.0 (1)
		84  A1     ---     DE-      ?Sign Dec, equinox J2000, epoch 2000.0 (1)
		85- 86  I2     deg     DEd      ?Degrees Dec, equinox J2000, epoch 2000.0 (1)
		87- 88  I2     arcmin  DEm      ?Minutes Dec, equinox J2000, epoch 2000.0 (1)
		89- 90  I2     arcsec  DEs      ?Seconds Dec, equinox J2000, epoch 2000.0 (1)
		91- 96  F6.2   deg     GLON     ?Galactic longitude (1)
		97-102  F6.2   deg     GLAT     ?Galactic latitude (1)
		103-107  F5.2   mag     Vmag     ?Visual magnitude (1)
		*/
		Star next_star;
		math::Vect3 right_ascension;
		std::string ra_hours = next_line.substr(75, 2);
		right_ascension.x = (float)atof(ra_hours.c_str());

		std::string ra_minutes = next_line.substr(77, 2);
		right_ascension.y = (float)atof(ra_minutes.c_str());

		std::string ra_seconds = next_line.substr(79, 4);
		right_ascension.z = (float)atof(ra_seconds.c_str());

		next_star.right_ascension = right_ascension.x + right_ascension.y/60.0f + right_ascension.z/3600.0f;

		char decl_sing = next_line[83];
		float decl_factor = 1.0f;
		if(decl_sing == '-')
			decl_factor = -1.0f;

		std::string decl_hours = next_line.substr(84, 2);
		math::Vect3 declination;
		declination.x = (float)atof(decl_hours.c_str());

		std::string decl_minutes = next_line.substr(86, 2);
		declination.y = (float)atof(decl_minutes.c_str());

		std::string decl_seconds = next_line.substr(88, 2);
		declination.z = (float)atof(decl_seconds.c_str());

		next_star.declination = declination.x + declination.y/60.0f + declination.z/3600.0f;
		next_star.declination *= decl_factor*deg_to_rad;

		std::string visual_magnitude = next_line.substr(102, 5);
		next_star.visual_magnitude = (float)atof(visual_magnitude.c_str());
		// If one star has visual magnitude - a and another one - b then first is brighter than 
		// second in 2.5^(a-b) times
		if(next_star.visual_magnitude >= 3.5f)
			continue;

		bright_stars_database.push_back(next_star);
	};
	return true;
};

Vect3 StarDatabase::_getStarPositionOnGeosphere(const Star& star)
{
	// convert from equatorial coordinate system to viewer coordinate system
	float cos_lat = cosf(site_latitude);
	float sin_lat = sinf(site_latitude);
	float sin_dec = sinf(star.declination);
	float cos_dec = cosf(star.declination);
	// hour_angle = sidereal_time - right_ascension
	float hour_angle = ker::Time(23, 56, 4).getCurrentDayHour() - star.right_ascension;
	hour_angle *= 15*deg_to_rad;
	float cos_ha = cosf(hour_angle);
	// angle from horizon
	float star_altitude = asinf(sin_lat*sin_dec + cos_lat*cos_dec*cos_ha);
	// star is not visible from this position
	if(star_altitude < 0)
		return vect3_zero;
	float cos_sa = cosf(star_altitude);
	// angle from zenith
	star_altitude = pi/2 - star_altitude;
	// we want to have star_azimuth angle from south
	float star_azimuth;
	float cos_star_azimuth = (cos_lat*sin_dec - sin_lat*cos_dec*cos_ha)/cos_sa;
	float sin_star_azimuth = (-cos_dec*sinf(hour_angle))/cos_sa;
	// at now angle is calculating from north
	star_azimuth = acosf(cos_star_azimuth);
	// calculating from south
	star_azimuth = pi - star_azimuth;
	// positive values corresponds to south-west
	if(sin_star_azimuth > 0)
		star_azimuth = -star_azimuth;
	Vect3 star_direction = computeDirectionOnSphere(star_altitude, star_azimuth);
	return star_direction * 3000.0f;
};

bool StarDatabase::_updateVisibleStarsBuffer()
{
	shared_ptr<rs::VertexBuffer> star_vertex_buffer(new rs::VertexBuffer(star_input_layout));
	shared_ptr<rs::IndexBuffer> star_index_buffer(new rs::IndexBuffer());

	std::vector<StarShaderVertex> vb_data;
	std::vector<unsigned short> ib_data;
	unsigned int cur_vertex_number = 0;
	for(unsigned int i = 0; i < bright_stars_database.size(); ++i)
	{
		// get star position on geosphere
		Vect3 star_position_on_geosphere = _getStarPositionOnGeosphere(bright_stars_database[i]);
		// in assume geosphere as sphere get star normal
		Vect3 star_quad_normal = -star_position_on_geosphere.getNormalize();
		// move star a little to the center of geosphere
		Vect3 star_real_position = star_position_on_geosphere + 30*star_quad_normal;
		// we must rotate source quad (quad in OXY with center in 0 and width - 2) so that its normal
		// concur with normal of geosphere and next we must move it to the correct position
		Quat rotation;
		
		Vect3 rotation_axe = (star_quad_normal - vect3_sing_z)*0.5f;
		rotation.setRotation(pi, rotation_axe);
		StarShaderVertex lu_corner;
		StarShaderVertex ru_corner;
		StarShaderVertex rd_corner;
		StarShaderVertex ld_corner;

		float star_size = bright_stars_database[i].visual_magnitude*(-0.76f) + 7.42f;
		lu_corner.position = rotation.rotate(Vect3(-1, 1, 0)*star_size) + star_real_position;
		ru_corner.position = rotation.rotate(Vect3(1, 1, 0)*star_size) + star_real_position;
		rd_corner.position = rotation.rotate(Vect3(1, -1, 0)*star_size) + star_real_position;
		ld_corner.position = rotation.rotate(Vect3(-1, -1, 0)*star_size) + star_real_position;

		lu_corner.tex_coord = vect2_zero;
		ru_corner.tex_coord = vect2_sing_x;
		rd_corner.tex_coord = vect2_sing;
		ld_corner.tex_coord = vect2_sing_y;

		vb_data.push_back(lu_corner);
		vb_data.push_back(ru_corner);
		vb_data.push_back(rd_corner);
		vb_data.push_back(ld_corner);

		ib_data.push_back(cur_vertex_number);
		ib_data.push_back(cur_vertex_number + 1);
		ib_data.push_back(cur_vertex_number + 2);
		ib_data.push_back(cur_vertex_number + 2);
		ib_data.push_back(cur_vertex_number + 3);
		ib_data.push_back(cur_vertex_number);
		cur_vertex_number += 4;
	};
	if(star_vertex_buffer->createNextStreamWithData(sizeof(StarShaderVertex), static_cast<unsigned int>(vb_data.size()), &vb_data[0]) == -1)
	{
		lwar << "Cannot create next stream with data for star vertex buffer";
		return false;
	};
	
	if(!star_index_buffer->createWithData(static_cast<unsigned int>(ib_data.size()), rs::DSEIB_16, &ib_data[0]))
	{
		lwar << "Cannot create index buffer with data for star database";
		return false;
	};
	
	visible_stars_buffer.reset(new StaticModel());
	visible_stars_buffer->addNextMesh(shared_ptr<rs::Mesh>(new rs::Mesh(star_vertex_buffer, star_index_buffer, star_material)));

	return true;
};

void StarDatabase::setStarTexture(const std::string& new_star_texture_name)
{
	star_texture = TextureManager::ptr()->load(new_star_texture_name);
	star_material->addTexture("star_texture", star_texture);
};

void StarDatabase::setStarEffect(const std::string& new_star_effect_name)
{
	shared_ptr<Effect> tmp_star_effect = EffectManager::ptr()->load(new_star_effect_name);
	if(!EffectManager::ptr()->getIsLoadLastEffect())
	{
		lwar << "Cannot load effect " << new_star_effect_name;
		return;
	};
	shared_ptr<InputLayout> tmp_star_input_layout(new rs::InputLayout());
	tmp_star_input_layout->addElement("POSITION", 0, 0, 0, rs::DFMT_R32G32B32_FLOAT);
	tmp_star_input_layout->addElement("TEXCOORD0_BLOCK", 0, 0, 12, rs::DFMT_R32G32_FLOAT);
	if(!tmp_star_input_layout->create(star_effect->getEffectInputSignature().code.get(), star_effect->getEffectInputSignature().size))
	{
		lwar << "Cannot create input layout for stars with effect " << new_star_effect_name;
		return;
	};

	star_effect = tmp_star_effect;
	star_input_layout = tmp_star_input_layout;

	star_material->setEffect(star_effect);
	star_material->setTechnique("base");
};

void StarDatabase::setSiteLatitude(const float site_latitude)
{
	this->site_latitude = site_latitude;

	if(!_updateVisibleStarsBuffer())
	{
		lwar << "Cannot update visible stars buffer";
		is_database_correct = false;
		return;
	};
};

const shared_ptr<StaticModel>& StarDatabase::getVisibleStarsBuffer() const
{
	return visible_stars_buffer;
};

bool StarDatabase::getIsDatabaseCorrect() const
{
	return is_database_correct;
};

