#include "Marionette.h"
#include "Demo.h"
#include <time.h>

#include <fstream>
#include <sstream>
#include <iostream>

#define RECOVERY_TIME 1.0f

using namespace std;

Marionette::Marionette(const std::string& name, int pnum, Demo* d) : StickFigure(name), applet(d) {
	EntityRef pp = PullPoint::create(name + "Right Hand", this->part(name + "Right Hand"));
	World::world()->insert(pp, this->part(name + "Right Hand")->frame());
	strings.append(static_cast<PullPoint*> (&(*pp)));

	pp = PullPoint::create(name + "Right Radius", this->part(name + "Right Radius"));
	World::world()->insert(pp, this->part(name + "Right Radius")->frame());
	strings.append(static_cast<PullPoint*> (&(*pp)));

	pp = PullPoint::create(name + "Left Hand", this->part(name + "Left Hand"));
	World::world()->insert(pp, this->part(name + "Left Hand")->frame());
	strings.append(static_cast<PullPoint*> (&(*pp)));

	pp = PullPoint::create(name + "Left Radius", this->part(name + "Left Radius"));
	World::world()->insert(pp, this->part(name + "Left Radius")->frame());
	strings.append(static_cast<PullPoint*> (&(*pp)));

	pp = PullPoint::create(name + "Left Foot", this->part(name + "Left Foot"));
	World::world()->insert(pp, this->part(name + "Left Foot")->frame());
	strings.append(static_cast<PullPoint*> (&(*pp)));

	pp = PullPoint::create(name + "Right Foot", this->part(name + "Right Foot"));
	World::world()->insert(pp, this->part(name + "Right Foot")->frame());
	strings.append(static_cast<PullPoint*> (&(*pp)));

	pp = PullPoint::create(name + "Pelvis", this->part(name + "Pelvis"));
	World::world()->insert(pp, this->part(name + "Pelvis")->frame());
	strings.append(static_cast<PullPoint*> (&(*pp)));

	pp = PullPoint::create(name + "Left Femur", this->part(name + "Left Femur"));
	World::world()->insert(pp, this->part(name + "Left Femur")->frame());
	strings.append(static_cast<PullPoint*> (&(*pp)));

	pp = PullPoint::create(name + "Right Femur", this->part(name + "Right Femur"));
	World::world()->insert(pp, this->part(name + "Right Femur")->frame());
	strings.append(static_cast<PullPoint*> (&(*pp)));
	
	pp = PullPoint::create(name + "Left Tibia", this->part(name + "Left Tibia"));
	World::world()->insert(pp, this->part(name + "Left Tibia")->frame());
	strings.append(static_cast<PullPoint*> (&(*pp)));

	pp = PullPoint::create(name + "Right Tibia", this->part(name + "Right Tibia"));
	World::world()->insert(pp, this->part(name + "Right Tibia")->frame());
	strings.append(static_cast<PullPoint*> (&(*pp)));

	playerNumber = pnum;
	ppIndex = -1;
	recovery = RECOVERY_TIME;

	currentMove = NULL;
	//Don't start by playing the selected move... cuz it's NULL
	doMove = false;
	downTime = 0.3f;
	elapsedTime = downTime;
	touchingGround = true;
}

void Marionette::setOpponent(Marionette *opp)
{
	opponent = opp;
	for (int i = 0; i < allMoves.size(); i++) {
		if (allMoves[i]->getTargetName() != "NONE") {
			allMoves[i]->setTarget(opponent->part(opponent->name() + allMoves[i]->getTargetName()));
		}
	}
}

void Marionette::onSimulation(G3D::SimTime ts) 
{
	
	if (elapsedTime < downTime) {
		elapsedTime = elapsedTime + ts;
	} else {
		// keeps the body upright
		Vector3 motion = this->velocity(name() + "Head");
		if (1 > motion.y) { // && (touchingGround || wasTouchingGround)) { this was for gravity...
			this->applyForce(name() + "Head", Vector3(0, 0.35f, 0));
			this->setAngularVelocity(name() + "Head", Vector3::ZERO);
		}
	}

	if (strike < RECOVERY_TIME) strike += ts;

	this->setAngularVelocity(name() + "Chest", Vector3::zero());
	if (recovery >= RECOVERY_TIME) {
	if(doMove)
	{
		if(currentMove->inConfiguration())
		{
			currentMove->nextPose();
			if(currentMove->isActive())
			{
				if (!currentMove->isStriking()) {
					applyConfiguration(currentMove->getCurrentConfiguration());
				} else {
					strike = 0;
				}
			}
			else
			{
				doMove = false;
			}
		}
	}
	wasTouchingGround = touchingGround;
	touchingGround = false;
	} else {
		recovery += ts;
	}
}

Marionette::~Marionette()
{
	for(int i = 0; i < allMoves.size(); i++)
	{
		delete allMoves[i];
	}
}

void Marionette::initPullPoints(const G3D::CoordinateFrame &c)
{
	this->setFrame(c);
	for(int i = 0; i < strings.size(); ++i) {
		strings[i]->setFrame(this->part(strings[i]->name())->frame());
	}
	defaultStance = new Configuration(strings, this);
}

void Marionette::setFrame(const G3D::CoordinateFrame &c)
{
	Entity::setFrame(c);
	for(int i = 0; i < strings.size(); ++i) {
		strings[i]->moveTo(this->part(strings[i]->name())->frame().translation);
	}
}

PullPoint* Marionette::nextPullPoint() {
	ppIndex = (ppIndex + 1) % strings.size();
	return strings[ppIndex];
}

void Marionette::saveMoves(int pid)
{
	ostringstream filename;
	filename << "conf\\p" << pid << ".moves";
	ofstream moveFile(filename.str().c_str());
	
	//Output the total number of moves
	moveFile << allMoves.size() << endl << endl;

	//Output each move seperately
	for(int i = 0; i < allMoves.size(); i++)
	{
		//Declare new move
		moveFile << "MOVE" << endl;

		// Print move ID
		moveFile << allMoves[i]->getId() << "   ";

		//Print the name of the move
		moveFile << allMoves[i]->name() << endl;

		//Print the number of parts this move has
		moveFile << allMoves[i]->numPoses() << endl;

		//Print each part of the move
		for(int j = 0; j < allMoves[i]->numPoses(); j++)
		{
			moveFile << "   " << j << endl;

			//There will be n parts (one for each string)
			Array<Vector3> partses = allMoves[i]->getConfiguration(j)->getPositions();
			for(int p = 0; p < partses.size(); p++){
				moveFile << "   " 
					<< partses[p].x << " " 
					<< partses[p].y << " "	
					<< partses[p].z << " " 
					<< endl;
			}
		}

		// Ew...
		string alimb = (allMoves[i]->getAttackLimb()!=NULL ? allMoves[i]->getAttackLimb()->name().substr(this->name().length()): "NONE");
		string::size_type space = alimb.find(" ");
		if (string::npos != space) {
			alimb.replace(space, 1, "_");
		}
		// Double Ew...
		string t = (allMoves[i]->getTarget()!=NULL ? allMoves[i]->getTarget()->name().substr(opponent->name().length()) : "NONE");
		space = t.find(" ");
		if (string::npos != space) {
			t.replace(space, 1, "_");
		}

		moveFile << "   " << alimb << endl;
		moveFile << "   " << t << endl;
		//This is just for formatting
		moveFile << endl;
	}

	moveFile.close();
}

Array<char* > Marionette::loadMoves(int pid)
{
	this->clearMoves();

	ostringstream filename;
	filename << "conf\\p" << pid << ".moves";
	ifstream savedInfo(filename.str().c_str());

	int numMoves;
	savedInfo >> numMoves; 

	for(int i = 0; i < numMoves; i++)
	{
	
		string move;
		savedInfo >> move;
				
		string name;
		int id;

		savedInfo >> id;
		savedInfo >> name;
		Move* currentMove = new Move(name.c_str(), id);
		allNames.append(currentMove->name());
		maxMoveId = id > maxMoveId ? id : maxMoveId;
				
		int numPoses;
		savedInfo >> numPoses;
	
		for(int j = 0; j < numPoses; j++)
		{
			int poseNum;
			savedInfo >> poseNum;

			Array<Vector3> currPose;

			for(int p = 0; p < strings.size(); p++)
			{
				float x, y, z;

				savedInfo >> x >> y >> z;
				Vector3 currentVec(x, y, z);

				currPose.append(currentVec);
			}

			Configuration *temp = new Configuration(this);
			temp->setPositions(currPose);
			currentMove->addConfiguration(temp);
		}

		string alimb, t;
		savedInfo >> alimb >> t;
		
		// Hack...
		string::size_type space = alimb.find("_");
		if (string::npos != space) {
			alimb.replace(space, 1, " ");
		}
		// Hack E. McFunk...
		space = t.find("_");
		if (string::npos != space) {
			t.replace(space, 1, " ");
		}

		for (int i = 0; i < strings.size(); i++) {
			if (strings[i]->name() == this->name() + alimb) {
				currentMove->setAttackLimb(strings[i]);
			}
		}

		currentMove->setTargetName(t);

		allMoves.append(currentMove);
		moveMap[currentMove->getId()] = currentMove;
	}

	savedInfo.close();
	
	return allNames;
}

void Marionette::playMove()
{
	currentMove->reset();
	currentMove->activate();
	applyConfiguration(currentMove->getCurrentConfiguration());

	doMove = true;
}

void Marionette::applyConfiguration(Configuration* pose)
{
	pose->applyConfiguration(strings);
}

int Marionette::addMove(char* name)
{
	Move* addedMove = new Move(name, maxMoveId);
	maxMoveId++;

	allMoves.append(addedMove);
	allNames.append(name);

	currentMove = addedMove;
	this->addConfiguration();
	return allMoves.size() - 1;
}

int Marionette::addMove(Move *m)
{
	maxMoveId = (m->getId() > maxMoveId) ? m->getId() : maxMoveId;
	allMoves.append(m);
	allNames.append(m->name());
	return allMoves.size() - 1;
}

int Marionette::addConfiguration()
{
	Configuration* c = new Configuration(strings, this);
	currentMove->addConfiguration(c);
	return currentMove->numPoses();
}

Move* Marionette::selectMove(int index)
{
		
	if(index >= 0 && index < allMoves.size()){
		
		currentMove = allMoves[index];
		Configuration *c = currentMove->getConfiguration(0);
		this->applyConfiguration(c);

		return currentMove;
		
	}

	return NULL;
}

Move* Marionette::getMoveById(int id)
{
	return moveMap[id];
}

void Marionette::deleteMove(int sel)
{
	allMoves.fastRemove(sel);
}

void Marionette::clearMoves()
{
	for(int i = 0; i < allMoves.size(); i++)
	{
		delete allMoves[i];
	}

	allMoves.clear();
	allNames.clear();
	moveMap.clear();
}

void Marionette::setConfiguration()
{
	Configuration* c = new Configuration(strings, this);
	currentMove->setCurrentConfiguration(c);
}

int Marionette::deleteConfiguration()
{
	return currentMove->deleteCurrentConfiguration();
}

Configuration* Marionette::selectConfiguration(int index)
{
	currentMove->selectConfiguration(index);
	this->applyConfiguration(currentMove->getCurrentConfiguration());
	return currentMove->getConfiguration(index);
}

void Marionette::setPullPoint(int index)
{
	ppIndex = index;
}

Array<PullPoint*> Marionette::getStrings(void)
{
	return strings;
}

Array<std::string> Marionette::getPartNames(void)
{
	Array<std::string> result;
	for (int i = 0; i < partArray.size(); i++) {
		result.append(partArray[i]->name());
	}
	return result;
}

Entity::Part* Marionette::hackPart(const std::string &partName)
{
	return this->part(partName);
}

void Marionette::playMove(int id)
{
	currentMove = allMoves[id];
}

void Marionette::setAttackLimb(PullPoint* pullpoint)
{
	currentMove->setAttackLimb(pullpoint);
}

void Marionette::setMoveTarget(Entity::Part *target)
{
	currentMove->setTarget(target);
}

int Marionette::moveCount(void)
{
	return allMoves.size();
}

bool Marionette::isMoving()
{
	return doMove;
}

void Marionette::stopMoving()
{
	doMove = false;
}

void Marionette::turnTo(CoordinateFrame cf) {
	for(int i = 0; i < strings.size(); ++i) {
		Vector3 rel = this->frame().pointToObjectSpace(strings[i]->frame().translation);
		strings[i]->setFrame(CoordinateFrame(cf.pointToWorldSpace(rel)));
	}
	this->setFrame(cf);
}

bool Marionette::isAttacking(void)
{
	return (strike < RECOVERY_TIME);
}

void Marionette::setDefaultConfiguration(Configuration *pose) {
	defaultStance = pose;
}

Configuration* Marionette::getDefaultConfiguration(void)
{
	return defaultStance;
}

void Marionette::realese(bool fall)
{
	if (fall) {
		elapsedTime = 0;
	}
	for (int i = 0; i < strings.size(); i++) {
		strings[i]->limpForce();
	}
}

bool Marionette::onCollision(const std::string &myPart, const dojo::EntityRef &other, const std::string &otherPart, const Vector3 &wsLocation)
{
	if (other->name() == "Map") {
		touchingGround = true;
		return true;
	}

	Vector3 vel = other->velocity(otherPart);
	if (this->name() != other->name() && this->velocity(myPart) > other->velocity(otherPart)) {
		vel *= .1f;
		if ((abs(vel.x) >= .5 || abs(vel.y) >= .5 || abs(vel.z) >= .5) && opponent->isAttacking() && elapsedTime >= downTime) {
			applet->incrementGameScore(1);
			this->realese(false);
			this->applyForce(myPart, vel);
			recovery = 0;
			opponent->resetStrike();
		} else {
			Vector3 kyori = -(other->part(otherPart)->frame().translation - this->part(myPart)->frame().translation);
			this->applyForce(myPart, Vector3(kyori.x, 0, kyori.z));
		}
	}
	return true;
}

void Marionette::resetStrike(void) {
	strike = RECOVERY_TIME;
}