/*
 * Avatar.cpp
 *
 *  Created on: 2009-07-10
 *      Author: wasyl
 */

#include "Avatar.h"

#include "../prep/Common.h"
#include "../prep/res/Resources.h"
#include "../misc/Controls.h"
#include "../prep/misc/Vector2D.h"
#include "../misc/Physics.h"
#include "../prep/misc/Random.h"
#include "Level.h"
#include "Game.h"
#include "Carryable.h"
#include "Container.h"
#include "Particle.h"
#include "../misc/Config.h"

Avatar::Avatar()
{
	Resources::load(mSprite, "media/guysheet.png");
	Resources::load(mSndJump, "media/jump.wav");
	Resources::load(mSndDie, "media/die.wav");
	Resources::load(mSndLand, "media/land.wav");
	Resources::load(mSndThrow, "media/throw.wav");

	mPos = Vec(400, 00);
	mLeft = Controls::get().left;
	mRight = Controls::get().right;
	mDown = Controls::get().down;
	mUp = Controls::get().up;
	mJump = Controls::get().jump;
	mAction = Controls::get().action;

	mDoubleJump = true;
	mState = STANDING;
	mCarried = NULL;

	WinState ws = {NEITHER, ""};
	mWinState = ws;

	lastTime = 0.0;
}

Avatar::~Avatar()
{
}

void Avatar::animate(double dt)
{
	double now = lastTime + dt;
	int framesNow = int(now * 8);
	int framesThen = int(lastTime * 8);
	for (int i = 0; i < framesNow - framesThen; i++)
	{
		mAnim = (mAnim + 1) % 6;
		if (mAnim % 2 == 1)
			al_play_sample(mSndLand, 0.9, 0.5, 1.0, ALLEGRO_PLAYMODE_ONCE, NULL);
	}
}

Rect Avatar::bbox()
{
	return Rect(mPos.x + 1.0, mPos.y + 2.0, mPos.x + 19.0, mPos.y + 20.0);
}

void Avatar::handleCollisions(double dt)
{
	// collisions
	Collision col = Physics::collisions(this);
	int retries = 0;
	while (col.dir != Collision::NONE && retries++ < 20)
	{
		if (col.penetr > 12.0)
		{
			die("were crushed by a hunk of wall");
			return;
		}

		switch (col.dir)
		{
		case Collision::LEFT:
		case Collision::RIGHT:
			// no walking into walls
			Physics::eject(this, col);
			mVel.x = 0.0;
			break;

		case Collision::DOWN:
			// landed in a floor
			if (mState == FALLING && mVel.y >= 0.0)
			{
				mState = STANDING;
				al_play_sample(mSndLand, 1.1, 0.5, 1.0, ALLEGRO_PLAYMODE_ONCE, NULL);
			}

			Physics::eject(this, col);
			break;

		case Collision::UP:
			Physics::eject(this, col);
			if (mVel.y < 0.0)
				mVel.y = 0.0;
			break;

		default:
			break;
		}

		col = Physics::collisions(this);
	}
}

void Avatar::toss()
{
	if (!mCarried)
		return;

	al_play_sample(mSndThrow, 1.0, 0.5, 1.0, ALLEGRO_PLAYMODE_ONCE, NULL);
	Game::get()->count(Game::THROWS, 1);

	if (mState == RUNNING)
		mCarried->toss(Vec(mFlip ? 300.0 : -300.0, -140.0));
	else if (mState == STANDING)
	{
		if (*mUp)
			mCarried->toss(Vec(mFlip ? 100.0 : -100.0, -240.0));
		else
			mCarried->toss(Vec(mFlip ? 200.0 : -200.0, -100.0));
	}
	else if (mState == FALLING)
	{
		mCarried->toss(Vec(mFlip ? 200.0 : -200.0, -100.0));
	}
	mCarried = NULL;
}

void Avatar::checkWinConditions()
{
	Entities &containers = mWorld->index(ID("cont"));
	if (containers.size() == 0)
		return;

	FOREACH (vector,Entity*,e,containers)
	{
		Container *c = (Container*)(*e);
		if (!c->happy())
			return;
	}
	win();
}

void Avatar::update(double dt)
{
	if (mWinState.state == DIED)
		return;

	double now = lastTime + dt;

	if (mWinState.state == NEITHER)
	{
		Game::get()->count(Game::TIME, dt);
	}

	handleCollisions(dt);

	// falling outside the level
	if (mPos.y > 600.0)
	{
		die("fell of the face of the world");
		return;
	}

	// no floor? toggle to falling down
	if (mState != FALLING)
	{
		Level *lv = Game::get()->level();
		if (!lv->collision(mPos + Vec(10.0, 21.0)))
		{
			if (mState == RUNNING)
			{
				mVel = Vec(mFlip ? 30.0 : -30.0, 0.0);
				mJumpGracePeriod = 0.04;
			}
			else
			{
				mVel = Vec(0.0,0.0);
				mJumpGracePeriod = 0.0;
			}
			mState = FALLING;
		}
	}

	if (mState == FALLING)
	{
		if ((*mLeft) && (!*mRight))
		{
			if (mVel.x > 0.0)
				mVel.x -= (800.0 + mVel.x * 4.0) * dt;
			else
				mVel.x -= (400.0 - mVel.x * 4.0) * dt;
		}
		else if ((*mRight) && (!*mLeft))
		{
			if (mVel.x < 0.0)
				mVel.x += (800.0 - mVel.x * 4.0) * dt;
			else
				mVel.x += (400.0 + mVel.x * 4.0) * dt;
		}
		else if (mVel.x < -50.0 || mVel.x > 50.0)
			mVel.x = (1.0 - dt) * mVel.x;

		if (mVel.x < -200.0)
			mVel.x = -200.0;
		if (mVel.x > 200.0)
			mVel.x = 200.0;

		if (mVel.x < 0.0)
			mFlip = false;
		else if (mVel.x > 0.0)
			mFlip = true;

		mVel.y += dt * 600.0;
		if (mVel.y > 400.0)
			mVel.y = 400.0;
		mPos += mVel * dt;

		if (*mAction) toss();

		mJumpGracePeriod -= dt;
		if (*mJump && mDoubleJump)
		{
			if (mJumpGracePeriod > 0.0)
			{
				al_play_sample(mSndJump, 0.65, 0.5, 1.0, ALLEGRO_PLAYMODE_ONCE, NULL);
				Game::get()->count(Game::JUMPS, 1);
				mVel.y = jumpHeight();
				mVel.x = mFlip ? 111.0 : -111.0;
				mJumpGracePeriod = 0.0;
			}
			else
			{
				al_play_sample(mSndJump, 0.65, 0.5, 1.2, ALLEGRO_PLAYMODE_ONCE, NULL);

				Game::get()->count(Game::DOUBLE_JUMPS, 1);
				mVel.y = jumpHeight() * 0.6;
				if (mVel.x > 140.0)
					mVel.x = 140.0;
				else if (mVel.x < -140.0)
					mVel.x = -140.0;
				mDoubleJump = false;
			}
		}
	}

	// controls when ducking
	if (mState == DUCKING)
	{
		if (!*mDown)
			mState = STANDING;

		if (*mAction)
		{
			if (!mCarried)
			{
				Entities &carryables = mWorld->index(ID("carr"));
				FOREACH(vector,Entity*,c,carryables)
				{
					if ( (*c)->bbox().contains(mPos + Vec(10.0, 10.0)) ||
						(*c)->bbox().contains(mPos + Vec(18.0, 10.0)) ||
						(*c)->bbox().contains(mPos + Vec(2.0, 10.0)))
					{
						Carryable *carry = (Carryable*)(*c);
						if (carry->state() == Carryable::STANDING)
						{
							carry->pickup(this);
							mCarried = carry;
							break;
						}
					}
				}
			}
			else
			{
				//mCarried->setX(mPos.x + (mFlip ? 14 : -14));
				dropCarried();
			}
		}
	}

	// controls when standing
	if (mState == STANDING)
	{
		mDoubleJump = true;
		if ((*mLeft) && (!*mRight))
		{
			mFlip = false;
			mState = RUNNING;
			mAnim = 0;
		}
		else if ((*mRight) && (!*mLeft))
		{
			mFlip = true;
			mState = RUNNING;
			mAnim = 0;
		}
		if (*mDown)
			mState = DUCKING;

		if (*mJump)
		{
			Game::get()->count(Game::JUMPS, 1);
			mJumpGracePeriod = 0.0;
			mState = FALLING;
			mVel = Vec(0.0, jumpHeight());
			al_play_sample(mSndJump, 0.65, 0.5, 1.0, ALLEGRO_PLAYMODE_ONCE, NULL);
		}

		if (*mAction) toss();
	}

	// controls when running
	if (mState == RUNNING)
	{
		mDoubleJump = true;
		if ((*mLeft) && (!*mRight))
		{
			animate(dt);
			mFlip = false;
			mPos.x -= dt * 200.0;
			Game::get()->count(Game::TRAVERSED, dt*200.0);
		}
		else if ((*mRight) && (!*mLeft))
		{
			animate(dt);
			mFlip = true;
			mPos.x += dt * 200.0;
			Game::get()->count(Game::TRAVERSED, dt*200.0);
		}
		else
			mState = STANDING;

		if (*mJump)
		{
			Game::get()->count(Game::JUMPS, 1);
			mState = FALLING;
			mJumpGracePeriod = 0.0;
			mVel = Vec(mFlip ? 111.0 : -111.0, jumpHeight());
			al_play_sample(mSndJump, 0.65, 0.5, 1.0, ALLEGRO_PLAYMODE_ONCE, NULL);
		}

		if (*mAction) toss();
	}

	handleCollisions(dt);

	if (mCarried)
	{
		mCarried->setX(mPos.x);
		mCarried->setY(mPos.y - (mState == DUCKING ? 15.0 : 20.0));
	}

	checkWinConditions();

	lastTime = now;
}

void Avatar::dropCarried()
{
	mCarried->release();
	mCarried = NULL;
}

void Avatar::render()
{
	static const int F_IDLE = 2;
	static const int F_FALL = 7;
	static const int F_DUCK = 6;
	static const int F_CARRYING = 9;

	if (mWinState.state == DIED)
		return;

	int frame;
	if (mState == RUNNING)
		frame = mAnim;
	if (mState == STANDING)
		frame = F_IDLE;
	if (mState == FALLING)
		frame = F_FALL;
	if (mState == DUCKING)
		frame = F_DUCK;
	if (mCarried || mWinState.state == WON)
		frame += F_CARRYING;

	al_draw_bitmap(mSprite->get(frame), mPos.x, mPos.y, mFlip ? ALLEGRO_FLIP_HORIZONTAL : 0);
}

void Avatar::die(std::string comment)
{
	if (mWinState.state != NEITHER)
		return;

	al_play_sample(mSndDie, 1.0, 0.5, 1.0, ALLEGRO_PLAYMODE_ONCE, NULL);
	Config::get().addDeath();

	for (int i = 0; i < 20; i++)
	{
		Particle *p = new Particle(
				"media/puff.png",
				mPos + Vec(randomFloat(-10.0, 10.0), randomFloat(-10.0, 10.0)),
				Vec(0.0,randomFloat(-7.0, -15.0)), 0.8, -0.02, ALLEGRO_INVERSE_ALPHA);
		mWorld->add(p);
	}
	WinState ws = {DIED, comment.c_str()};
	mWinState = ws;

	if (mCarried)
		dropCarried();
}

void Avatar::win()
{
	if (mWinState.state != NEITHER)
		return;

	WinState ws = {WON, ""};
	mWinState = ws;
}
