#include "main_code.h"

#ifdef TARGET_ANDROID
#include "android_api.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>

#include "include/Engine.h"

#include "main_code.h"

//Better move those to external config or something...
const cardinal CONST_LASER_TIMER = 200;
const int CONST_HIT_SCORE_POINTS = 100;
const float CONST_TIME_SCALE = 0.01f;

const float CONST_PLAYER_ACCELERATION = 3.f;
const float CONST_VELOCITY_FADE = 0.95f;
const float CONST_LASER_VELOCITY = 100.f;

const vec2 CONST_PLAYER_HALF_SIZE(30.f, 30.f);
const vec2 CONST_LASER_HALF_SIZE(10.f, 10.f);


const vec2 CONST_AST_POS1(40, 40);
const vec2 CONST_AST_POS2(40, 280);
const vec2 CONST_AST_POS3(440, 160);



float HealthToScale(int health)
{
	return 0.5f + health * 0.3f;
}

float HealthToHitDistance(int health)
{
	const float CONST_HIT_DISTANCE = 15.f;

	return max(CONST_HIT_DISTANCE * HealthToScale(health), 10.f);
}

void TMyApplication::InnerInit()
{
	
	*Console<<"Inner init go!\n";

#ifdef TARGET_ANDROID
    ST::PathToResources = "";
#endif
#ifdef TARGET_WIN32
#ifdef NDEBUG
	ST::PathToResources = "assets/";
#else
	ST::PathToResources = "../../../assets/";
#endif
#endif
#ifdef TARGET_IOS
	ST::PathToResources = "assets/";
#endif

	RandomGenerator.seed(static_cast<unsigned int>(std::time(0)));

	TapIsDown = false;

	ResourceManager->TexList.AddTexture(CONST_CONSOLE_TEX_NAME);

	ResourceManager->ShaderManager.AddShader("DefaultShader", "gui_transparent.vertex", "gui_transparent.fragment");
    
    Renderer->PushShader("DefaultShader");

	ResourceManager->FontManager.AddFont("droid_sans14", "droid_sans14_font_bitmap.bmp32", "droid_sans14_font_charmap.txt");
	ResourceManager->FontManager.PushFont("droid_sans14");

	ResourceManager->TexList.Serialize(*FileToPropertyTree("textures.xml"));

	Renderer->PushProjectionMatrix(Renderer->GetScreenWidth(), Renderer->GetScreenHeight());

	Level = 1;
	Score = 0;

	InitLevel();
	
	Player.RenderPair.first.SamplerMap[CONST_STRING_TEXTURE_UNIFORM] = "shipTexture";
	Player.RenderPair.second.Data = MakeDataTriangleList(Player.Pos - CONST_PLAYER_HALF_SIZE, Player.Pos + CONST_PLAYER_HALF_SIZE);
	Player.RenderPair.second.RefreshBuffer();

	BackgroundRenderPair.first.SamplerMap[CONST_STRING_TEXTURE_UNIFORM] = "backgroundTexture";
	BackgroundRenderPair.second.Data = MakeDataTriangleList(vec2(0,0), vec2(Renderer->GetScreenWidth(), Renderer->GetScreenHeight()));
	BackgroundRenderPair.second.RefreshBuffer();

	*Console<<"Inner init end!\n";

	glDepthFunc(GL_LEQUAL);
	
}

void TMyApplication::InnerDeinit()
{
}


void TMyApplication::InnerDraw()
{
    
	glClearColor(0,0,0,1);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	//Draw background
	TRenderParamsSetter renderParamSetter(BackgroundRenderPair.first);

	Renderer->DrawTriangleList(BackgroundRenderPair.second);


	//Draw asteroids
	BOOST_FOREACH(CAsteroidStruct& asteroid, Asteroids)
	{
		TRenderParamsSetter renderParamSetter(asteroid.RenderPair.first);

		Renderer->DrawTriangleList(asteroid.RenderPair.second);
	}


	//Draw player
	if (GameState != GS_LOST)
	{
		TRenderParamsSetter renderParamSetter(Player.RenderPair.first);
		Renderer->DrawTriangleList(Player.RenderPair.second);
	}

	//Draw lasers
	BOOST_FOREACH(CLaserStruct& laser, Lasers)
	{
		TRenderParamsSetter renderParamSetter(laser.RenderPair.first);

		Renderer->DrawTriangleList(laser.RenderPair.second);
	}


	//Draw texts
	glBindTexture(GL_TEXTURE_2D, ResourceManager->TexList[ResourceManager->FontManager.GetCurrentFontTextureName()]);

	Renderer->DrawTriangleList(LevelScoreText);
	Renderer->DrawTriangleList(LevelMessageText);
	
}


void TMyApplication::InnerUpdate(cardinal dt)
{
	float scaledTime = CONST_TIME_SCALE * dt;

	//Update Asteroids
	BOOST_FOREACH(CAsteroidStruct& asteroid, Asteroids)
	{

		float difAngle = asteroid.AngularVelocity * scaledTime;
		
		asteroid.Angle += difAngle;

		mat3 rotationMatrix = CreateZRotationMatrix(difAngle);

		MoveDataTriangleList(asteroid.RenderPair.second.Data, -vec3(asteroid.CenterPos, 0));
		RotateDataTriangleList(asteroid.RenderPair.second.Data, rotationMatrix);
		MoveDataTriangleList(asteroid.RenderPair.second.Data, vec3(asteroid.CenterPos, 0));


		vec2 dif = asteroid.Velocity * scaledTime;

		vec2 newCenterPos = NormalizePos(asteroid.CenterPos + dif);
		
		dif = newCenterPos - asteroid.CenterPos;

		asteroid.CenterPos = newCenterPos;

		MoveDataTriangleList(asteroid.RenderPair.second.Data, vec3(dif, 0));

		asteroid.RenderPair.second.RefreshBuffer();
	}



	UpdatePlayerPosition(scaledTime);
	


	//Shoot laser
	LaserTimer.Update(dt);

	if (LaserTimer.IsOver())
	{
		LaserTimer.SetTimer(CONST_LASER_TIMER);
		
		if (GameState == GS_PLAY)
		{
			AddNewLaser();
		}
	}

	//Update lasers
	BOOST_FOREACH(CLaserStruct& laser, Lasers)
	{
		vec2 dif = CONST_LASER_VELOCITY *laser.Dir * scaledTime;
		MoveDataTriangleList(laser.RenderPair.second.Data, vec3(dif, 0));
		laser.Pos += dif;

		laser.RenderPair.second.RefreshBuffer();
	}

	//Do all checks...
	ProcessLaserHit();

	ProcessPlayerCollisions();

	ClearUnusedLasers();

	if (Asteroids.size() == 0 && GameState == GS_PLAY)
	{
		GameState = GS_WON;
		RefreshScoreText();
	}


}


void TMyApplication::InnerOnTapDown(vec2 p)
{
	TapIsDown = true;
	LastTap = p;
}
	
void TMyApplication::InnerOnTapUp(vec2 p)
{
	TapIsDown = false;

	
	if (GameState == GS_LOST)
	{
		Level = 1;
		Score = 0;
		InitLevel();
	}
	else if (GameState == GS_WON)
	{
		Level++;
		InitLevel();
	}
}

void TMyApplication::InnerOnTapUpAfterMove(vec2 p)
{
	TapIsDown = false;
}
	
void TMyApplication::InnerOnMove(vec2 shift)
{
	LastTap -= shift;
}

void TMyApplication::InitLevel()
{
	GameState = GS_PLAY;

	LaserTimer.SetTimer(CONST_LASER_TIMER);

	Asteroids.clear();
	Lasers.clear();

	CreateRandomAsteroid(CONST_AST_POS1, Level);
	CreateRandomAsteroid(CONST_AST_POS2, Level);
	CreateRandomAsteroid(CONST_AST_POS3, Level);

	Player.Angle = 0;
	Player.Pos = vec2(Renderer->GetScreenWidth()/2, Renderer->GetScreenHeight()/2);

	RefreshScoreText();

}

CAsteroidStruct TMyApplication::CreateAsteroid(vec2 pos, vec2 dir, int health)
{
	static const float CONST_TEX_COORD_SCALE = 0.01f;
	static const float CONST_ANGULAR_VELOCITY_SCALE = 0.001f;

	static const boost::random::uniform_int_distribution<> velocityDistribution(5, 10);
	
	static const boost::random::uniform_int_distribution<> angularVelocityDistribution(0, 10);

	static const boost::random::uniform_int_distribution<> vertexNumberDistribution(5, 10);
	static const boost::random::uniform_int_distribution<> vertexShiftDistribution(20, 30);

	CAsteroidStruct result;

	result.Health = health;

	result.CenterPos = pos;

	result.Angle = 0;
	
	result.AngularVelocity = angularVelocityDistribution(RandomGenerator) * CONST_ANGULAR_VELOCITY_SCALE;
	
	result.Velocity = velocityDistribution(RandomGenerator) * dir;

	int numberOfVertices = vertexNumberDistribution(RandomGenerator);
	
	std::vector<float> distArr(numberOfVertices);
	
	for (int i=0; i<numberOfVertices; i++)
	{
		distArr[i] = vertexShiftDistribution(RandomGenerator) * HealthToScale(health);
	}

	//First iteration
	vec2 prevShift = distArr[0] * vec2(1,0);

	vec2 prevTexCoordShift = prevShift * CONST_TEX_COORD_SCALE;

	//Other iterations
	for (int i = 1; i <= numberOfVertices; i++)
	{
		float angle = i * 2 * pi / static_cast<float>(numberOfVertices);

		vec2 shift = distArr[i % numberOfVertices] * vec2(cosf(angle), sinf(angle));

		vec2 texCoordShift = shift * CONST_TEX_COORD_SCALE;

		result.RenderPair.second.Data.Vec3CoordArr[CONST_STRING_POSITION_ATTRIB].push_back(vec3(pos, 0));
		result.RenderPair.second.Data.Vec3CoordArr[CONST_STRING_POSITION_ATTRIB].push_back(vec3(pos + prevShift, 0));
		result.RenderPair.second.Data.Vec3CoordArr[CONST_STRING_POSITION_ATTRIB].push_back(vec3(pos + shift, 0));

		result.RenderPair.second.Data.Vec2CoordArr[CONST_STRING_TEXCOORD_ATTRIB].push_back(vec2(0.5f, 0.5f));
		result.RenderPair.second.Data.Vec2CoordArr[CONST_STRING_TEXCOORD_ATTRIB].push_back(vec2(0.5f, 0.5f) + prevTexCoordShift);
		result.RenderPair.second.Data.Vec2CoordArr[CONST_STRING_TEXCOORD_ATTRIB].push_back(vec2(0.5f, 0.5f) + texCoordShift);

		prevShift = shift;
		prevTexCoordShift = texCoordShift;
	}

	result.RenderPair.first.SamplerMap[CONST_STRING_TEXTURE_UNIFORM] = "asteroidTexture";

	result.RenderPair.second.RefreshBuffer();

	return result;
}


void TMyApplication::CreateRandomAsteroid(vec2 pos, int health)
{

	static const boost::random::uniform_int_distribution<> angleDistribution(0, 36);

	float randomAngle =  angleDistribution(RandomGenerator) * pi / 18.f;

	Asteroids.push_back(CreateAsteroid(pos, vec2(cosf(randomAngle), sinf(randomAngle)), health));
}

void TMyApplication::CreateAsteroidParts(vec2 pos, int newHealth)
{
	static const boost::random::uniform_int_distribution<> CreateAsteroidParts(2, 5);

	static const boost::random::uniform_int_distribution<> angleDistribution(0, 36);

	float randomAngle =  angleDistribution(RandomGenerator) * pi / 18.f;

	int number = CreateAsteroidParts(RandomGenerator);

	for (int i = 0; i < number; i++)
	{
		float partAngle = randomAngle + i * 2 * pi / static_cast<float>(number);
		Asteroids.push_back(CreateAsteroid(pos, vec2(cosf(partAngle), sinf(partAngle)), newHealth));
	}

}

vec2 TMyApplication::NormalizePos(vec2 pos)
{
	vec2 result = pos;

	while (result.v[0] < 0)
	{
		result.v[0] += Renderer->GetScreenWidth();
	}

	while (result.v[1] < 0)
	{
		result.v[1] += Renderer->GetScreenHeight();
	}

	while (result.v[0] >= Renderer->GetScreenWidth())
	{
		result.v[0] -= Renderer->GetScreenWidth();
	}

	while (result.v[1] >= Renderer->GetScreenHeight())
	{
		result.v[1] -= Renderer->GetScreenHeight();
	}

	return result;
}

void TMyApplication::RefreshPlayerPos()
{
	Replace6PointsInTriangleList(Player.RenderPair.second.Data, 0, (-1) * CONST_PLAYER_HALF_SIZE, CONST_PLAYER_HALF_SIZE);

	mat3 rotationMatrix = CreateZRotationMatrix(Player.Angle);

	RotateDataTriangleList(Player.RenderPair.second.Data, rotationMatrix);

	MoveDataTriangleList(Player.RenderPair.second.Data, vec3(Player.Pos, 0));

	Player.RenderPair.second.RefreshBuffer();

}

void TMyApplication::AddNewLaser()
{
	CLaserStruct laser;

	float angle = Player.Angle;

	laser.Dir = vec2(cosf(angle), sinf(angle));

	laser.Pos = Player.Pos;

	laser.RenderPair.second.Data = MakeDataTriangleList((-1) * CONST_LASER_HALF_SIZE, CONST_LASER_HALF_SIZE);

	mat3 rotationMatrix = CreateZRotationMatrix(angle);

	RotateDataTriangleList(laser.RenderPair.second.Data, rotationMatrix);

	MoveDataTriangleList(laser.RenderPair.second.Data, vec3(laser.Pos, 0));

	laser.RenderPair.first.SamplerMap[CONST_STRING_TEXTURE_UNIFORM] = "fireTexture";
	
	laser.RenderPair.second.RefreshBuffer();

	Lasers.push_back(laser);
}

void TMyApplication::UpdatePlayerPosition(float scaledTime)
{
	
	if (TapIsDown)
	{
	
		vec2 dir = Normalize(LastTap - Player.Pos);

		Player.Velocity += CONST_PLAYER_ACCELERATION * scaledTime * dir;

		Player.Angle = acosf(DotProduct(dir, vec2(1, 0)));

		if (dir.v[1] < 0)
		{
			Player.Angle = -Player.Angle;
		}
	}

	Player.Pos += Player.Velocity * scaledTime;

	
	Player.Velocity *= powf(CONST_VELOCITY_FADE, clamp(scaledTime, 0.f, 1.5f));

	Player.Pos = NormalizePos(Player.Pos);

	RefreshPlayerPos();

}

void TMyApplication::ProcessLaserHit()
{

	for (auto laserItr = Lasers.begin(); laserItr != Lasers.end(); )
	{
		for (auto astItr = Asteroids.begin(); astItr != Asteroids.end(); )
		{

			float realDistance = HealthToHitDistance(astItr->Health);
			
			if (fabs(laserItr->Pos.v[0] - astItr->CenterPos.v[0]) < realDistance &&
				fabs(laserItr->Pos.v[1] - astItr->CenterPos.v[1]) < realDistance)
			{
				laserItr = Lasers.erase(laserItr);

				if (astItr->Health > 0)
				{
					CreateAsteroidParts(astItr->CenterPos, astItr->Health - 1);
				}

				astItr = Asteroids.erase(astItr);

				Score += CONST_HIT_SCORE_POINTS;
				RefreshScoreText();

				break;

			}


			if (astItr != Asteroids.end())
			{
				astItr++;
			}
		}

		if (laserItr != Lasers.end())
		{
			laserItr++;
		}
	}
	
}

void TMyApplication::ProcessPlayerCollisions()
{

	for (auto astItr = Asteroids.begin(); astItr != Asteroids.end(); astItr++)
	{
		float realDistance = HealthToHitDistance(astItr->Health);

		if (fabs(astItr->CenterPos.v[0] - Player.Pos.v[0]) < realDistance && fabs(astItr->CenterPos.v[1] - Player.Pos.v[1])  < realDistance)
		{
			GameState = GS_LOST;
			RefreshScoreText();
		}
	}
}

void TMyApplication::ClearUnusedLasers()
{
	for (auto itr = Lasers.begin(); itr != Lasers.end(); )
	{
		if (itr->Pos.v[0] < 0 || itr->Pos.v[1] < 0 || itr->Pos.v[0] > Renderer->GetScreenWidth() || itr->Pos.v[1] > Renderer->GetScreenHeight())
		{
			itr = Lasers.erase(itr);
		}

		if (itr != Lasers.end())
		{
			itr++;
		}
	}
}


void TMyApplication::RefreshScoreText()
{
	LevelScoreText = ResourceManager->FontManager.DrawStringToVBO(vec2(10, 20), TTextBasicAreaParams(), "Score: "+ tostr(Score));

	if (GameState == GS_PLAY)
	{
		LevelMessageText = ResourceManager->FontManager.DrawStringToVBO(vec2(10, 300), TTextBasicAreaParams(), "Now playing "+ tostr(Level) + " level");
	}
	else if (GameState == GS_WON)
	{
		LevelMessageText = ResourceManager->FontManager.DrawStringToVBO(vec2(10, 300), TTextBasicAreaParams(), "You won! Tap on screen to start next level");
	}
	else if (GameState == GS_LOST)
	{
		LevelMessageText = ResourceManager->FontManager.DrawStringToVBO(vec2(10, 300), TTextBasicAreaParams(), "You failed! Tap on screen to restart");
	}
}