#include "Marionette.h"
#include "GameData.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)));

	// Multipliers for when specific limb is attacked
	// Ones with no assigned value are 0
	valueMap[name + "Head"] = 15;
	valueMap[name + "Chest"] = 10;
	valueMap[name + "Pelvis"] = 20;
	valueMap[name + "Right Humerus"] = 7;
	valueMap[name + "Left Humerus"] = 7;
	valueMap[name + "Right Radius"] = 5;
	valueMap[name + "Left Radius"] = 5;
	valueMap[name + "Right Femur"] = 7;
	valueMap[name + "Leftt Femur"] = 7;
	valueMap[name + "Right Tibia"] = 5;
	valueMap[name + "Leftt Tibia"] = 5;

	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 (applet->game->match.get_health(playerNumber) > 0) {
	if (elapsedTime < downTime) {
		elapsedTime = elapsedTime + ts;
	} else {
		// keeps the body upright
		Vector3 motion = this->velocity(name() + "Head");
		if (1 > motion.y) { // && (touchingGround || wasTouchingGround)) {
			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;
	}
	} else {
		for (int i = 0; i < strings.size(); i++) {
			strings[i]->limpForce();
		}
	}
}

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::clearMoves()
{
	for(int i = 0; i < allMoves.size(); i++)
	{
		delete allMoves[i];
	}

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

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;
	}

	this->currentMove = NULL;
	savedInfo.close();
	
	return allNames;
}

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

	doMove = true;
}

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

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::selectMoveById(int id)
{
	currentMove = moveMap[id];
	Configuration *c = currentMove->getConfiguration(0);
	this->applyConfiguration(c);
	return currentMove;
}

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

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;
}

Move *Marionette::getMove(int index)
{
	if (index >= 0 && index < allMoves.size()) {
		return allMoves[index];
	}
	return NULL;
}

Move *Marionette::getCurrentMove(void)	{	return currentMove;	}

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::release(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;
	}

	if (this->name() != other->name()) {
		Vector3 mvel = this->velocity(myPart);
		Vector3 vel = other->velocity(otherPart);
		if (abs(mvel.x) + abs(mvel.y) + abs(mvel.z) < abs(vel.x) + abs(vel.y) + abs(vel.z)) {
			vel *= .1f;
			if ((abs(vel.x) >= .5 || abs(vel.y) >= .5 || abs(vel.z) >= .5) && opponent->isAttacking() && elapsedTime >= downTime) {
				int aval = attackValue(myPart);
				applet->incrementGameScore((int)abs(vel.average() * (100 * aval)), 1-playerNumber);
				applet->game->match.decr_health(playerNumber, (int)abs(vel.average() * aval));
				this->release(false);
				int amp = applet->game->match.get_health(playerNumber) <= 0 ? 6 : 4;
				this->applyForce(myPart, vel * amp);
				recovery = .5f;
				opponent->resetStrike();
			} else {
				Vector3 kyori = -(other->part(otherPart)->frame().translation - this->part(myPart)->frame().translation);
				this->applyForce(myPart, Vector3(kyori.x, 0, kyori.z) * 2);
			}
		}
	}
	return true;
}

int Marionette::attackValue(std::string limbname) 
{
	return valueMap[limbname];
}

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