// Author: Juan Carlos De Abreu (jcabreur@gmail.com)

#include "actor.h"
#include "Model/ModelManagement.h"
#include "math/random/random.h"
#include "Media/Sound/soundManager.h"
#include "Media/Sound/sound.h"

BEGIN_TEG_NAMESPACE();

namespace ai {

IActor::IActor() {}

IActor::IActor(math::Vec2<int> position) {
	this->position = position;
	moving = false;
	idleCount = 0;
}

IActor::~IActor() { }

Children::Children() : withThief(false) {}

Children::~Children() {}

void Children::nextMove() {

}

Thief::Thief() {}

Thief::~Thief() {}

void Thief::nextMove() {

}

Worker::Worker(math::Vec2<int> position, int type) : IActor(position) {
	this->type = type;
}

Worker::~Worker() { }

void Worker::nextMove() {}

void Worker::makeMove(Tablero& t) {
	// Ir al objetivo..

	if (path.size() == 0) {
		// Crear un nuevo camino
		moving = false;
	} else {
		math::Vec2<int> back = path.back();
		// Verificar si se puede mover a esa posicion..
		if (t.state[back.y][back.x] == Tablero::EMPTY || t.state[back.y][back.x] == Tablero::MARK) {

			dir = ((position.y > back.y)<<3) + ((position.y < back.y)<<2) + ((position.x > back.x)<<1) + (position.x < back.x);

			path.pop_back();
			t.state[position.y][position.x] = Tablero::EMPTY;
			position = back;
			t.state[position.y][position.x] = Tablero::WORKER;
		} else {
			idleCount++;
			// Verificar si hay un deadlock
			if (idleCount >= 5) {
				idleCount = 0;
				moving = false;
			}
		} 
	}
}

class LessFunctor {
public:
	math::Vec2<int> dest_;
	LessFunctor(math::Vec2<int> dest) { dest_ = dest;}

	bool operator () (const std::pair<math::Vec2<int>, int>& n1, const std::pair<math::Vec2<int>, int>& n2) const {
		const int abs1 = abs(n1.first.x - dest_.x) + abs(n1.first.y - dest_.y);
		const int abs2 = abs(n2.first.x - dest_.x) + abs(n2.first.y - dest_.y);
		const int dist1 = abs1 + n1.second;
		const int dist2 = abs2 + n2.second;
		if (dist1 == dist2) return abs1 > abs2;
		return dist1 > dist2;
	}
};


void Worker::update(Tablero& t) {
	//int maxDeep = 10;
	//int posx = 0, posy = 0;

	// TODO: Verificar si la celda a la que se quiere mover esta ocupada, y de ser asi, esperar..
	// esto puede hacer que caigan en un deadlock
	// Una posible solucion es llevar un contador de cuantos turnos a tenido que esperar, y al pasar un limite
	// generar una nueva posicion destino...

	math::Vec2<int>& thiefPos = ai::ActorManagement::GetInstance().theThief.position;

	destiny.x = (int)RAND(0, t.numberOfColumms);
	destiny.y = (int)RAND(0, t.numberOfRows);


	// Ver si el carajo esta por lo menos cerca...
	const int distToThief = abs(thiefPos.x - position.x) + abs(thiefPos.y - position.y);
	if (ActorManagement::GetInstance().theChild.withThief && distToThief < kMaxDeep) {

		if (!see) {
			SoundManager& soundmanager = SoundManager::instance();

			Sound *soundBebe = soundmanager.GetSound("./Data/sounds/Efectos/Atrapenla.wav");
			soundBebe->SetPosition(3.0f, 0.0f, -30.0f);
			soundBebe->Play(false, false);
		}
		see = true;

		if (distToThief <= 1) {
			ActorManagement::GetInstance().capture = true;

			SoundManager& soundmanager = SoundManager::instance();
			Sound *soundBebe = soundmanager.GetSound("./Data/sounds/Efectos/ninomio.wav");
			soundBebe->SetPosition(3.0f, 0.0f, -30.0f);
			soundBebe->Play(false, false);
		}

		destiny = thiefPos;
		moving = false;
	} else {
		see = false;
	}
	
	if (moving) {
		makeMove(t);
/*	} else {
		// Definir un objetivo
		//  estos objetivos pueden ser cargados luego...
		// Por ahora el siguiente movimiento es es siempre 0, 0

		// TODO: esta es la parte que debe ser sustituida por A*

		// El deep solo es utilizado para saber si puede vee al thief...

		std::queue< std::pair<math::Vec2<int>, int> > q;
		q.push(std::make_pair(math::Vec2<int>(position.x, position.y), 0));
		t.state[position.y][position.x] |= Tablero::MARK;

		std::map< std::pair<int, int>, std::pair<int, int> > prev;

		bool findTheDestiny = false;

		while (!q.empty() && !findTheDestiny) {
			int deep = q.front().second;
			math::Vec2<int> currentPos = q.front().first; q.pop();

//			if (deep < kMaxDeep) {
				const int dx[] = {-1, 1, 0, 0};
				const int dy[] = {0, 0, -1, 1};

				//int start = RAND(0, 3);
				for (int i = 0; i < 4; ++i) {
					const math::Vec2<int> newPos(currentPos.x + dx[i], currentPos.y + dy[i]);
					if (newPos.x >= 0 && newPos.x < t.numberOfColumms && newPos.y >= 0 && newPos.y < t.numberOfRows) {					
						// Verificar si llego a al celda destino..
						if (newPos == destiny) {
							prev[std::make_pair(newPos.x, newPos.y)] = std::make_pair(currentPos.x, currentPos.y); 
							findTheDestiny = true;
							path = GetPath(prev, std::make_pair(destiny.x, destiny.y));
							moving = true;
							makeMove(t);
						} else {
							if (t.state[newPos.y][newPos.x] == Tablero::EMPTY) {
								q.push(std::make_pair(newPos, deep + 1));
								t.state[newPos.y][newPos.x] = Tablero::MARK;
								prev[std::make_pair(newPos.x, newPos.y)] = std::make_pair(currentPos.x, currentPos.y); 
							} 
						}
					}
				}
			}
//		}
	
	}*/

	} else {
		// Definir un objetivo
		//  estos objetivos pueden ser cargados luego...
		// Por ahora el siguiente movimiento es es siempre 0, 0

		// TODO: esta es la parte que debe ser sustituida por A*

		// El deep solo es utilizado para saber si puede vee al thief...

		std::priority_queue< std::pair<math::Vec2<int>, int>, std::vector<std::pair<math::Vec2<int>, int> >, LessFunctor > q(destiny);
		//std::queue< std::pair<math::Vec2<int>, int> > q;
		//const std::pair<math::Vec2<int>, int> start(std::make_pair(math::Vec2<int>(position.x, position.y), 0)); 
		//q.push(start);
		q.push(std::make_pair(math::Vec2<int>(position.x, position.y), 0));
		t.state[position.y][position.x] |= Tablero::MARK;

		std::map< std::pair<int, int>, std::pair<int, int> > prev;

		bool findTheDestiny = false;

		while (!q.empty() && !findTheDestiny) {
			int deep = q.top().second;
			math::Vec2<int> currentPos = q.top().first; q.pop();

//			if (deep < kMaxDeep) {
				const int dx[] = {-1, 1, 0, 0};
				const int dy[] = {0, 0, -1, 1};

				//int start = RAND(0, 3);
				for (int i = 0; i < 4; ++i) {
					const math::Vec2<int> newPos(currentPos.x + dx[i], currentPos.y + dy[i]);
					if (newPos.x >= 0 && newPos.x < t.numberOfColumms && newPos.y >= 0 && newPos.y < t.numberOfRows) {					
						// Verificar si llego a al celda destino..
						if (newPos == destiny) {
							prev[std::make_pair(newPos.x, newPos.y)] = std::make_pair(currentPos.x, currentPos.y); 
							findTheDestiny = true;
							path = GetPath(prev, std::make_pair(destiny.x, destiny.y));
							moving = true;
							makeMove(t);
						} else {
							if (t.state[newPos.y][newPos.x] == Tablero::EMPTY) {
								q.push(std::make_pair(newPos, deep + 1));
								t.state[newPos.y][newPos.x] = Tablero::MARK;
								prev[std::make_pair(newPos.x, newPos.y)] = std::make_pair(currentPos.x, currentPos.y); 
							} 
						}
					}
				}
			}
//		}
	
	}

}

void Children::update(Tablero& t) {
	// TODO: esta es la parte que debe ser sustituida por A*

	// El deep solo es utilizado para saber si puede vee al thief...

	math::Vec2<int>& thiefPos = ai::ActorManagement::GetInstance().theThief.position;

	const int finalPosx = 350;
	const int finalPosy = 10;

	bool playSound = withThief;
	// Ver si el carajo esta por lo menos cerca...
	withThief = false;
	if (abs(thiefPos.x - position.x) + abs(thiefPos.y - position.y) < kMaxDeep) {

		// Ver distancia a la posicion final para saber si termino el juego.
		if (abs(finalPosx - position.x) + abs(finalPosy - position.y) < 10) {
			ActorManagement::GetInstance().finish = true;
			
			SoundManager& soundmanager = SoundManager::instance();
			Sound *soundBebe = soundmanager.GetSound("./Data/sounds/Efectos/vamosacasa.wav");
			soundBebe->SetPosition(3.0f, 0.0f, -30.0f);
			soundBebe->Play(false, false);
		}

		std::queue< std::pair<math::Vec2<int>, int> > q;
		q.push(std::make_pair(math::Vec2<int>(position.x, position.y), 0));
		t.state[position.y][position.x] |= Tablero::MARK;

		std::map< std::pair<int, int>, std::pair<int, int> > prev;

		bool findTheThief = false;
		
		while (!q.empty() && !findTheThief) {
			int deep = q.front().second;
			math::Vec2<int> currentPos = q.front().first; q.pop();

			if (deep < kMaxDeep) {
				const int dx[] = {-1, 1, 0, 0};
				const int dy[] = {0, 0, -1, 1};

				int start = RAND(0, 3);
				for (int i = start; i < start + 4; ++i) {
					const math::Vec2<int> newPos(currentPos.x + dx[i % 4], currentPos.y + dy[i % 4]);
					if (newPos.x >= 0 && newPos.x < t.numberOfColumms && newPos.y >= 0 && newPos.y < t.numberOfRows) {					
						// Verificar si llego a al celda destino..
						if (t.state[newPos.y][newPos.x] == Tablero::THIEF) {
							if (!playSound) {
								SoundManager& soundmanager = SoundManager::instance();

								Sound *soundBebe = soundmanager.GetSound("./Data/sounds/Efectos/VenBebe.wav");
                               soundBebe->SetPosition(3.0f, 0.0f, -30.0f);
                               soundBebe->Play(false, false);
							}

							findTheThief = true;
							withThief = true;
							t.state[newPos.y][newPos.x] |= Tablero::MARK;
							prev[std::make_pair(newPos.x, newPos.y)] = std::make_pair(currentPos.x, currentPos.y);
							path = GetPath(prev, std::make_pair(thiefPos.x, thiefPos.y));
							if (path.size() > 1) {
								t.state[position.y][position.x] = Tablero::EMPTY;
								math::Vec2<int> back = path.back();
								dir = ((position.y > back.y)<<3) + ((position.y < back.y)<<2) + ((position.x > back.x)<<1) + (position.x < back.x);
								position = back;
								t.state[position.y][position.x] = Tablero::CHILD;
								path.clear();
							}
						} else if (t.state[newPos.y][newPos.x] == Tablero::EMPTY) {
							q.push(std::make_pair(newPos, deep + 1));
							t.state[newPos.y][newPos.x] = Tablero::MARK;
							prev[std::make_pair(newPos.x, newPos.y)] = std::make_pair(currentPos.x, currentPos.y);
						}
					}
				}
			}
		}

		//if(!findTheThief) withThief = false;
	} 
}

ActorManagement::ActorManagement() {
	capture = false;
	finish = false;
}

ActorManagement::~ActorManagement() {}

void ActorManagement::InitializeWorkers(Tablero& t) {

	const SmartPointer<IModel>& model1 = LOAD_MODEL("./Data/Models/escenario.obj");
	float minMapX = model1->vMin.x;
	float minMapZ = model1->vMin.z;

	float sizeX = minMapX*2;
	float sizeZ = minMapZ*2;
	float offsetX = sizeX/(float)t.numberOfColumms;
	float offsetZ = sizeZ/(float)t.numberOfRows;

	FILE *f = fopen("ActorDescription.txt", "r");
	int numberOfWorkers;
	fscanf(f, "%d", &numberOfWorkers);
	for (int i = 0; i < numberOfWorkers; ++i) {
		int posx, posy, type;
		fscanf(f, "%d %d %d", &posx, &posy, &type);
		workers.push_back(Worker(math::Vec2<int>(posx, posy), type));
		workerTranslate.push_back(new TranslateNode(minMapX - offsetX*posx, 0.0, minMapZ - offsetZ*posy));
		workerRotate.push_back(new RotateNode(0, 0, 1, 0));
		t.state[posy][posx] = Tablero::WORKER;
	}

	//Leer child info
	int posx, posy;
	fscanf(f, "%d %d %d", &posx, &posy);
	childTranslate = new TranslateNode(minMapX - offsetX*posx, 0.0, minMapZ - offsetZ*posy);
	childRotate = new RotateNode(0, 0, 1, 0);
	theChild.position.x = posx;
	theChild.position.y = posy;
	t.state[posy][posx] = Tablero::CHILD;

	fclose(f);
}

void ActorManagement::UpdateMoves(Tablero& t) {

	const SmartPointer<IModel>& model1 = LOAD_MODEL("./Data/Models/escenario.obj");
	float minMapX = model1->vMin.x;
	float minMapZ = model1->vMin.z;

	float sizeX = minMapX*2;
	float sizeZ = minMapZ*2;
	float offsetX = sizeX/(float)t.numberOfColumms;
	float offsetZ = sizeZ/(float)t.numberOfRows;

	const int kNumberOfWorkers = static_cast<int>(workers.size());
	for (int i = 0; i < kNumberOfWorkers; ++i) {
		workers[i].update(t);
		workerTranslate[i]->set_x(minMapX - offsetX*workers[i].position.x);
		workerTranslate[i]->set_z(minMapZ - offsetZ*workers[i].position.y);

		int dir = workers[i].dir;
		if(dir == 1) workerRotate[i]->set_angle(90);
		else if(dir == 2) workerRotate[i]->set_angle(-90);
		else if(dir == 4) workerRotate[i]->set_angle(0);
		else if(dir == 8) workerRotate[i]->set_angle(180);

		t.restore();
	}
}

void ActorManagement::UpdateChild(Tablero& t) {
	const SmartPointer<IModel>& model1 = LOAD_MODEL("./Data/Models/escenario.obj");
	float minMapX = model1->vMin.x;
	float minMapZ = model1->vMin.z;

	float sizeX = minMapX*2;
	float sizeZ = minMapZ*2;
	float offsetX = sizeX/(float)t.numberOfColumms;
	float offsetZ = sizeZ/(float)t.numberOfRows;
	
	//Update child
	theChild.update(t);
	childTranslate->set_x(minMapX - offsetX*theChild.position.x);
	childTranslate->set_z(minMapZ - offsetZ*theChild.position.y);
	int dir = theChild.dir;
	if(dir == 1) childRotate->set_angle(90);
	else if(dir == 2) childRotate->set_angle(-90);
	else if(dir == 4) childRotate->set_angle(0);
	else if(dir == 8) childRotate->set_angle(180);
	t.restore();
}

};

END_TEG_NAMESPACE();
