#include "GameScene.h"
#include "sqlite3.h"
#include "constants.h"
#include "ScoreScene.h"
#include <algorithm>
#include "Bot.h"
#include "Bullet.h"

USING_NS_CC;

#define RANDOM_PROB  10
#define DZ			1
#define DELTA		-100

const Vec2 bgPos = Vec2(400,350);

Scene* GameScene::scene(int players, int mod, bool friendlyfire)
{
	Scene *scene = CCScene::create();

    // 'layer' is an autorelease object
    GameScene *layer = GameScene::create();
    // add layer as a child to scene
	
	
	scene->addChild(layer,ZO_GAMESCENE);

	GameScene::initAdditionalViews(scene,layer);
	layer->additionalInit(players,mod, friendlyfire);

    // return the scene
    return scene;
}

Scene* GameScene::scene()
{
    // 'scene' is an autorelease object
    Scene *scene = CCScene::create();
	
	

    // 'layer' is an autorelease object
    GameScene *layer = GameScene::create();
    // add layer as a child to scene
	
	
	scene->addChild(layer,ZO_GAMESCENE);

	GameScene::initAdditionalViews(scene,layer);
	layer->additionalInit(1,0,false);

    // return the scene
    return scene;
}

void GameScene::additionalInit(int players, int mod, bool friendlyfire)
{
	loadDB();


	Player* pl = (Player*) createUnit(0,Vec2(400,450),PLAYER1_TEAM);
	_players.push_back(pl);

	int gen = 3;
	int genMeduse = 2;
	if (players==2)
	{
		int team = PLAYER1_TEAM;
		if (friendlyfire)
			team = PLAYER2_TEAM;

		pl = (Player*) createUnit(6,Vec2(400,350),team);

		gen = 6;
		genMeduse = 4;
		_count = 90;
		_limit = 20;
		_deadCount = -6;
		_generationIndex = 2;
		_limitStep = 10;
		_players.push_back(pl);
	}
	else
	{
		_count = 45;
		_limit = 10;
		_deadCount = -3;
		_generationIndex = 2;
		_limitStep = 5;

		_hud->showPlayer(1,false);
	}

	_endless = false;
	if (mod == 1)
	{
		_endless = true;
	}


	for (unsigned i=0;i<genMeduse;i++)
		generateMeduse();
	
	for (unsigned i=0;i<gen;i++)
		UpdateGeneration();
	
}

void GameScene::initAdditionalViews(Scene* scene, GameScene* game)
{
	HUDScene* hud = HUDScene::create();
	scene->addChild(hud,ZO_HUD);

	game->setHUD(hud);
}

// on "init" you need to initialize your instance
bool GameScene::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Layer::init() )
    {
        return false;
    }
	_endGame = false;
	_goodEnd = false;
	g_pause = false;
	_score = 0;
    Size visibleSize = Director::sharedDirector()->getVisibleSize();
    Point origin = Director::sharedDirector()->getVisibleOrigin();

	Sprite* bg = Sprite::create("HUD\\bg.jpg");
	bg->setPosition(bgPos);
	addChild(bg,0);
	
	_listener = EventListenerKeyboard::create();
	_listener->onKeyPressed = CC_CALLBACK_2(GameScene::OnKeyPressed, this);
	_listener->onKeyReleased = CC_CALLBACK_2(GameScene::OnKeyReleased, this);

	_eventDispatcher->addEventListenerWithSceneGraphPriority(_listener, this);

	this->schedule(schedule_selector(GameScene::updateFunc));

    return true;
}

void GameScene::OnKeyPressed(EventKeyboard::KeyCode keyCode, Event* event)
{
	if (_endGame)
		return;

	if (_players.size()>0)
	{
		switch (keyCode)
		{
		case cocos2d::EventKeyboard::KeyCode::KEY_A:
			_players[0]->addDirection(dir_left,&_rects);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_D:
			_players[0]->addDirection(dir_right,&_rects);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_T:
			_players[0]->Attack(ATTACK_UP);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_G:
			_players[0]->Attack(ATTACK_FORWARD);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_B:
			_players[0]->Attack(ATTACK_DOWN);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_Y:
			_players[0]->Attack(ATTACK_SPECIAL);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_H:
			_players[0]->Attack(ATTACK_THROW);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_S:
			_players[0]->addDirection(dir_down,&_rects);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_W:
			_players[0]->addDirection(dir_up,&_rects);
			break;
		default:
		break;
		}
	}

	if (_players.size()>1)
	{
		switch (keyCode)
		{
		case cocos2d::EventKeyboard::KeyCode::KEY_LEFT_ARROW:
			_players[1]->addDirection(dir_left,&_rects);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_RIGHT_ARROW:
			_players[1]->addDirection(dir_right,&_rects);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_M:
			_players[1]->Attack(ATTACK_DOWN);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_K:
			_players[1]->Attack(ATTACK_FORWARD);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_I:
			_players[1]->Attack(ATTACK_UP);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_L:
			_players[1]->Attack(ATTACK_THROW);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_O:
			_players[1]->Attack(ATTACK_SPECIAL);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_DOWN_ARROW:
			_players[1]->addDirection(dir_down,&_rects);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_UP_ARROW:
			_players[1]->addDirection(dir_up,&_rects);
			break;
		default:
		break;
		}
	}
}

void GameScene::OnKeyReleased(EventKeyboard::KeyCode keyCode, Event* event)
{
	if (_endGame)
		return;


	if (_players.size()>0)
	{
		switch (keyCode)
		{
		case cocos2d::EventKeyboard::KeyCode::KEY_A:
			_players[0]->addDirection(-dir_left,&_rects);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_D:
			_players[0]->addDirection(-dir_right,&_rects);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_S:
			_players[0]->addDirection(-dir_down,&_rects);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_W:
			_players[0]->addDirection(-dir_up,&_rects);
			break;
		default:
		break;
		}
	}

	if (_players.size()>1)
	{
		switch (keyCode)
		{
		case cocos2d::EventKeyboard::KeyCode::KEY_LEFT_ARROW:
			_players[1]->addDirection(-dir_left,&_rects);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_RIGHT_ARROW:
			_players[1]->addDirection(-dir_right,&_rects);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_DOWN_ARROW:
			_players[1]->addDirection(-dir_down,&_rects);
			break;
		case cocos2d::EventKeyboard::KeyCode::KEY_UP_ARROW:
			_players[1]->addDirection(-dir_up,&_rects);
			break;
		default:
		break;
		}
	}
}

void GameScene::generateMeduse()
{
	int side = pow(-1,rand()%2);
	Vec2 pos = Vec2(g_gameSize.getMidX() - side*(g_gameSize.size.width)/2 - side*DELTA , rand()%(1+ int(g_gameSize.size.height/2)) + g_gameSize.getMinY());
	Vec2 direction = Vec2(side,0);

	Item* item = createItem(2,pos,BOT_TEAM,direction,20);
	_meduses.push_back(item);
}

void GameScene::updateFunc(float dt)
{
	if (_hud->_soundState)
	{
		soundMaster.play();
	}

	static bool paused = false;
	if (g_pause)
	{
		if (!paused)
		{
			_actions = getActionManager()->pauseAllRunningActions();
			paused=true;
		}
		return;
	}

	if (paused)
	{
		getActionManager()->resumeTargets(_actions);
		_actions.clear();
		paused = false;
	}

	static float time = 0;
	time+=dt;

	UpdateDead();
	UpdateRectsAndZOrder();
	UpdateMovement();
	UpdateAttacks(dt);
	UpdateHUD();
}

void GameScene::UpdateDead()
{
	for (unsigned i=_players.size();i>0;i--)
	{
		if (_players[i-1]->isDead())
		{
			_players.erase(_players.begin() + i-1);
		}
	}

	if (_players.empty())
	{
		_goodEnd = false;
		_endGame = true; 
		badEndGame();
	}

	for (unsigned i=_units.size();i>0;i--)
	{
		if (_units[i-1]->isDead())
		{
			Unit* unit = _units[i-1];
			int item_prob = rand()%RANDOM_PROB;
			if (item_prob==0)
			{
				createItem(rand()%2, unit->getRect()->center,unit->GetTeam());
			}

			removeUnit(i-1);
		}
	}

	for (unsigned i=_meduses.size();i>0;i--)
	{
		if(_meduses[i-1]->isUsed())
		{
			_meduses.erase(_meduses.begin()+i-1);
			generateMeduse();
		}
	}

	for (unsigned i=_items.size();i>0;i--)
	{
		if(_items[i-1]->isUsed())
		{
			removeItem(i-1);
		}
	}
}

void GameScene::UpdateRectsAndZOrder()
{
	std::sort(_units.begin(),_units.end(),compareUnitByZOrder);
	std::sort(_items.begin(),_items.end(),compareItemByZOrder);

	for (unsigned i=0;i<_units.size();i++)
	{
		_units[i]->UpdateRect();
		//_units[i]->UpdateZOrder(2*i);
	}

	for (unsigned i=0;i<_items.size();i++)
	{
		_items[i]->UpdateRect();
	}

	int count = _items.size() + _units.size();
	int i=0;
	int j=0;
	int k=0;
	while (k<count)
	{
		float pos1=-2*g_gameSize.getMaxY(),pos2=-2*g_gameSize.getMaxY();
		
		if(j<_units.size())
			pos1 = _units[j]->getRect()->center.y;

		if (i<_items.size())
			pos2 = _items[i]->getRect()->center.y;

		if (pos1>pos2)
		{
			_units[j]->UpdateZOrder(2*k+DZ);
			j++;
		}
		else
		{
			_items[i]->UpdateZOrder(2*k+DZ);
			i++;
		}
		k++;
	}
}

void GameScene::UpdateMovement()
{
	for (unsigned i=0;i<_units.size();i++)
	{
		Unit* unit = _units[i];
		unit->UpdateMove(&_rects);
	}

	for (unsigned j=0;j<_players.size();j++)
	{
		bool _throw = _players[j]->UpdateThrow(&_meduses);
		_hud->showThrow(j,_throw);
	}
}

void GameScene::UpdateAttacks(float dt)
{
	std::vector<ARect> rects;

	for (unsigned i=0;i<_units.size();i++)
	{
		_units[i]->UpdateCalldown(dt);
		ARect r =_units[i]->GetAttackRect();
		if (r.prop!=NULL)
		{
			if (r.prop->type == ATTACK_THROW)
			{
				Bullet* bul =(Bullet*) createItem(r.prop->damage,r.center,r.team,r.direction,r.prop->range);
				bul->init(r.prop->count,r.direction, r.prop->attackSize);
			}
			else
			{
				rects.push_back(r);
			}
		}
	}

	for (unsigned i=0;i<_units.size();i++)
	{
		Unit* unit = _units[i];

		for (unsigned j=0;j<rects.size();j++)
		{
			unit->Attacked(&(rects[j]));
		}

		for (unsigned j=_items.size();j>0;j--)
		{
			if (!_items[j-1]->isUsed())
			{
				unit->CheckItemPickUp(_items[j-1]);
			}
		}

		unit->CheckHealth();
	}
}

void GameScene::UpdateHUD()
{
	for (unsigned i=0;i<_players.size();i++)
	{
		_hud->showHealth(i,_players[i]->getRatio());
		_hud->showAP(i,_players[i]->getAP());
	}

}


void GameScene::setHUD(HUDScene* hud)
{
	_hud = hud;

	_hud->endButtonRef(this,menu_selector(GameScene::endButtonPressed));
}

void GameScene::loadDB()
{
	sqlite3* database;

	if (sqlite3_open("other/data.sqlite",&database) == SQLITE_OK)
	{	
		sqlite3_stmt* statement;
		sqlite3_stmt* sel;

		if (sqlite3_prepare_v2(database,"SELECT pk FROM attacks",-1,&statement,NULL)==SQLITE_OK)
		{
			while (sqlite3_step(statement)==SQLITE_ROW)
			{
				int pk = sqlite3_column_int(statement,0);
				
				if (sqlite3_prepare_v2(database,"SELECT range,damage,type,count,calldown,sizeX,sizeY, directionX, directionY, stun, pushback, selfdestruction, dt FROM attacks WHERE pk=?",-1,&sel,NULL)==SQLITE_OK)
				{
					sqlite3_bind_int(sel,1,pk);
					if (sqlite3_step(sel) == SQLITE_ROW)
					{
						float range = (float) sqlite3_column_double(sel,0);
						int damage = (int) sqlite3_column_int(sel,1);
						int type = (int) sqlite3_column_int(sel,2);
						int count = (int) sqlite3_column_int(sel,3);
						float calldown = (float) sqlite3_column_double(sel,4);
						float sizeX = (float) sqlite3_column_double(sel,5);
						float sizeY = (float) sqlite3_column_double(sel,6);
						float directionX = (float) sqlite3_column_double(sel,7);
						float directionY = (float) sqlite3_column_double(sel,8);
						bool stun = (bool) sqlite3_column_int(sel,9);
						bool pushback = (bool) sqlite3_column_int(sel,10);
						bool selfdestruction = (bool) sqlite3_column_int(sel,11);
						float dt = (float) sqlite3_column_double(sel,12);

						AttackProp prop(range,damage,type,count,calldown,dt,Vec2(sizeX,sizeY),Vec2(directionX,directionY),stun,pushback,selfdestruction);
						g_properties.push_back(prop);
					}
					sqlite3_reset(sel);
				}
			}
			sqlite3_reset(statement);
		}
		sqlite3_finalize(sel);
		sqlite3_finalize(statement);

		if (sqlite3_prepare_v2(database,"SELECT pk FROM units",-1,&statement,NULL)==SQLITE_OK)
		{
			while (sqlite3_step(statement)==SQLITE_ROW)
			{
				int pk = sqlite3_column_int(statement,0);
				
				if (sqlite3_prepare_v2(database,"SELECT speed,sizeX,sizeY,score,health,critHealth,behavior, attacks FROM units WHERE pk=?",-1,&sel,NULL)==SQLITE_OK)
				{
					sqlite3_bind_int(sel,1,pk);
					if (sqlite3_step(sel) == SQLITE_ROW)
					{
						float speed = (float) sqlite3_column_double(sel,0);
						float sizeX = (float) sqlite3_column_double(sel,1);
						float sizeY = (float) sqlite3_column_double(sel,2);
						int score = (int) sqlite3_column_int(sel,3);
						int health = (int) sqlite3_column_int(sel,4);
						int critHealth = (int) sqlite3_column_int(sel,5);
						int behavior = (int) sqlite3_column_int(sel,6);

						char* c=NULL;
						std::string str;
						c = (char*) sqlite3_column_text(sel,7);
						if (c!=NULL)
							str = c;
						else
							str = "";

						std::vector<int> attacks;
						dispathAttacksFromString(str,&attacks);

						UnitTemplate temp = UnitTemplate(pk,speed,score,health,critHealth,behavior,Vec2(sizeX,sizeY),&attacks);
						g_templates.push_back(temp);
					}
					sqlite3_reset(sel);
				}
			}
			sqlite3_reset(statement);
		}
		sqlite3_finalize(sel);
		sqlite3_finalize(statement);	

		int rc = sqlite3_close(database); 

        while( rc == SQLITE_BUSY) 
        { 
            // set rc to something that will exit the while loop 
            rc = SQLITE_OK; 
            sqlite3_stmt * stmt = sqlite3_next_stmt(database, NULL); 

            if(stmt != NULL) 
            { 
                rc = sqlite3_finalize(stmt); 
                if(rc == SQLITE_OK) 
                { 
                    rc = sqlite3_close(database); 
                } 
            } 
        } 
	}
}


void GameScene::dispathAttacksFromString(std::string& data,std::vector<int>* vec)
{
	std::vector<int> num;
    std::istringstream iss(data);

	std::vector<std::string> tokens;
	std::stringstream ss(data);
    std::string item;

    while (std::getline(ss, item, DELIM)) 
	{
		tokens.push_back(item);
    }
    

	for(int i=0;i<tokens.size();i++)
	{
		vec->push_back(atoi(tokens.at(i).c_str()));
    }
}

/*
void GameScene::dispathBuildDataFromString(std::string& data,std::vector<BuildNeeds>* vec)
{
	std::vector<std::string> elems;

    std::stringstream ss(data);
    std::string item;
    
	while (std::getline(ss, item, DELIM)) 
	{
        elems.push_back(item);
    }
    
	for (unsigned i=0;i<elems.size();i++)
	{
		BuildNeeds need;
		std::stringstream ss1(elems[i]);
		std::string str;
		std::getline(ss1, str, ' ');
		need.index = atoi(str.c_str());

		std::string resData = elems[i];
		int len = str.length() + 1;
		if (len <= resData.length())
		{
			resData.erase(resData.begin(),resData.begin() + str.length() + 1);
			dispathResDataFromString(resData,&(need.resources));
		}

		vec->push_back(need);
	}
}*/

Item* GameScene::createItem(int id, cocos2d::Vec2 position, unsigned team, Vec2 direction, float speed)
{
	int type;
	if (id ==0)
		type = ITEM_TYPE_HP;
	else if (id==1)
		type = ITEM_TYPE_AP;
	else if (id==2)
		type = ITEM_TYPE_THROW;
	else
		type = ITEM_TYPE_BULLET;

	Item* item;
	if (id>2)
	{
		item = new Bullet(id,type,team,position,this,direction,speed);
	}
	else
	{
		item = new Item(id,type,team,position,this,direction,speed);
	}
	_items.push_back(item);
	

	return item;
}

void GameScene::removeItem(unsigned index)
{
	Item* remItem = _items[index];
	_items.erase(_items.begin() + index);
	delete remItem;
}


Unit* GameScene::createUnit(int type, Vec2 position, unsigned team)
{
	UnitTemplate* temp = &(g_templates[type]);
	Unit* pUnit;
	if (type == 0)
		pUnit= new Player(temp,position, team,this);
	else if (type == 6)
		pUnit = new Player(temp,position, team, this);
	else
	{
		pUnit = new Bot(temp,position, team, this, &_players);
	}
	
	_units.push_back(pUnit);
	_rects.push_back(pUnit->getRect());

	return pUnit;
}

void GameScene::removeUnit(Unit* remUnit)
{
	for (unsigned i=0;i<_units.size();i++)
	{
		if (_units[i] == remUnit)
			removeUnit(i);
			break;
	}
}

void GameScene::removeUnit(unsigned index)
{
	Unit* remUnit = _units[index];

	_score += remUnit->_template->_score;
	

	for (unsigned i=0;i<_rects.size();i++)
	{
		if (_rects[i]==remUnit->getRect())
			_rects.erase(_rects.begin() + i);
	}
	
	_units.erase(_units.begin() + index);

	if (remUnit->GetTeam() == BOT_TEAM)
		UpdateGeneration();
	delete remUnit;
}

void GameScene::onExit()
{
	soundMaster.stop();

	for (unsigned i=_units.size();i>0;i--)
	{
		Unit* unit = _units[i-1];
		delete unit;
	}

	_players.clear();
	_eventDispatcher->removeEventListener(_listener);
	_hud = NULL;
	stopAllActions();
	removeAllChildren();
	//_eventDispatcher->removeAllEventListeners();

	Layer::onExit();
}

void GameScene::endButtonPressed(cocos2d::Ref* pSender)
{
	if (_endGame)
	{
		Scene* scene = ScoreScene::sceneWithScore(countScore());
		Director::sharedDirector()->replaceScene(scene);
	}
	else
	{
		Director::sharedDirector()->popScene();
	}
}

void GameScene::goodEndGame()
{
	endButtonPressed(NULL);
}

void GameScene::badEndGame()
{
	endButtonPressed(NULL);
}

int GameScene::countScore()
{
	int score=0;

	score +=_score;
	
	if (_goodEnd)
		score+=GOOD_END_SCORE;


	return score;
}

void GameScene::UpdateGeneration()
{
	if (!_endless)
	{
		_deadCount++;
	}

	if (_count == 0 && _units.size() == _players.size())
	{
		_endGame= true;
		_goodEnd = true;
		goodEndGame();
	}

	if (_count ==0)
	{
		return;
	}

	int index =0;
	if ((_limit-_deadCount-1)/_players.size() == 0)
	{
		index = _generationIndex+1;
	}
	else
	{
		if(_deadCount == _limit)
		{
			_deadCount -= _limit;
			_limit += _limitStep;
			_generationIndex++;
		}

		index= rand()%(_generationIndex)+1;
	}

	if (!_endless)
	{
		_count--;
	}

	int side = pow(-1,rand()%2);
	Vec2 pos = Vec2(g_gameSize.getMidX() - side*(g_gameSize.size.width)/2 - side*DELTA , rand()%(1+ int(g_gameSize.size.height/2)) + g_gameSize.getMinY());

	createUnit(index,pos,BOT_TEAM);
}