#include <iostream>

#include "Person.h"
#include "List.h"
#include "CollisionHandler.h"
#include "CommandManager.h"
#include "Command.h"
#include "glfuncs.h"
#include "bullet.h"
#include "level.h"
#include "ShowOneSprite.h"
#include "Screen.h"
#include "Weapon.h"


using namespace std;

/*Person::Person()
:_xVelocity(0),_yVelocity(0),_maxVel(850),_jumping(true), _wallJump(0), 
_gravity(3), _accel(40), _groundDrag(-1.2), _airDrag(-0.3),
_xDirection( LEFT ),
_yDirection( MIDDLE )
{
	// FIXME  make this general
	_id = "goudy";
	_type = PERSON;

	_collidable = true;

	// FIXME  these should be set based on the levels spawn point
	_box.x = 400;
	_box.y = 400;

	// FIXME this filename should be set based on the type of person it is eventually
	_sprite = Sprite::loadSprite("stickman.png");
	_box.w = _sprite->width();
	_box.h = _sprite->height();

	_previousX = _box.x;
	_previousY = _box.y;

//	CollisionHandler::instance()->registerObject(this);
}*/

Person::Person(List* personInfo, int x, int y)
:Object(personInfo,x,y),
 _xVelocity(0),
 _yVelocity(0),
 _maxXVel(850),
 _invincible(false),
// _jumping(true),

 _maxYVel(800),
 _maxFallRate(700),

 _crouching( false ),
// _wallJump(0), 
 _gravity(2), 
 _accel(40),
 _airAccel(20),
 _groundDrag(-2.5), 
 _airDrag(-0.3),
 _xDirection( LEFT ),
 _yDirection( MIDDLE ), 
 _state( JUMPING ), 
 _health(2), 
 _jumpTimer(0),
 _maxJumpTime(15),
 _dropItem(""),
 _primaryWeapon(NULL),
 _punchWeapon(NULL),
 _punchTimer(0),
 _wallFallTime(0)
{
	_collidable = true;

	load(personInfo);

	_previousX = _box.x;
	_previousY = _box.y;
}

void Person::load(List *personInfo, bool fromConstructor)
{
	if(!fromConstructor)
		Object::load(personInfo,fromConstructor);

	while( !personInfo->empty())
	{
		List * current = personInfo->firstList();
		
		if(current->firstString() == "maxXVel")
			_maxXVel = current->rest()->firstInt();

		if(current->firstString() == "maxYVel")
			_maxYVel = current->rest()->firstInt();

		if(current->firstString() == "maxFallRate")
			_maxFallRate = current->rest()->firstInt();
		
		if(current->firstString() == "gravity")
			_gravity = current->rest()->firstInt();

		if(current->firstString() == "accel")
			_accel = current->rest()->firstInt();

		if(current->firstString() == "airAccel")
			_airAccel = current->rest()->firstInt();

		if(current->firstString() == "groundDrag")
			_groundDrag = current->rest()->firstDouble();

		if(current->firstString() == "airDrag")
			_airDrag = current->rest()->firstDouble();

		if(current->firstString () == "sprite")
			_sprite = (ShowOneSprite *)Sprite::loadSprite( current->rest()->firstString() );

		if(current->firstString () == "maxJumpTime")
			_maxJumpTime = current->rest()->firstInt();
		
		if(current->firstString () == "health")
			_health = current->rest()->firstInt();

		if(current->firstString () == "dropItem")
			_dropItem = current->rest()->firstString();

		if(current->firstString () == "primaryWeapon")
			_primaryWeapon = new Weapon(current->rest()->firstString(),this);
		
		if(current->firstString () == "punchWeapon")
			_punchWeapon = new WeaponPunch(current->rest()->firstString(),this);

		if(current->firstString () == "dX")
			_box.x += current->rest()->firstInt();
		if(current->firstString () == "dY")
			_box.y += current->rest()->firstInt();

		personInfo = personInfo ->rest();
	}



	_box.w = _sprite->box().w;
	_box.h = _sprite->box().h;

}

Person::~Person()
{
	if(_primaryWeapon)
		delete _primaryWeapon;
	if(_punchWeapon)
		delete _punchWeapon;
}

Object *Person::copy()
{
	Person *copy = new Person(*this);
	copy->_primaryWeapon = _primaryWeapon->copy(copy);
	return copy;
}

void Person::setPos(int x, int y)
{
	Object::setPos(x,y);
	_previousX = x;
	_previousY = y;
}

void Person::collide( Object* o )
{
	if(	(o->getType() == TILE)|| (o->getType() == DOOR) ||
		((_type == PLAYER)&&(o->getType() == ENEMY ))||
		((_type == ENEMY)&&(o->getType() == PLAYER ))
		)
	{
		int x,y;
		if( _previousX != _box.x){

			glfuncs::intersectX(o->getBox(),_box,&x);

			//Stairs/ramps special case
			//if we moved our box up by tileHeight, and we don't hit anything
			//then we're on stairs
			if( _type == PLAYER && 
				!touchingAbove(Level::tileHeight) && 
				touchingAbove(Level::tileHeight-1) )
			{
				_box.y-=Level::tileHeight;
				_previousY -= Level::tileHeight;
			}
			else
			{
				_box.x += x;
				_xVelocity = 0;
			}
		}
		if( _previousY != _box.y){

			glfuncs::intersectY(o->getBox(),_box,&y);
			_box.y += y;
			_yVelocity = 0;
		}
	}
}

void Person::draw()
{
	ShowOneSprite* s = (ShowOneSprite*)_sprite;
	s->setDirection( _xDirection );
	
	switch( _state )
	{
	case STANDING:
		s->show( 0 );
		break;
	case RUNNING:
		s->show( 1 );
		break;
	case JUMPING:
		s->show( 2 );
		break;
	case FALLING:
		s->show( 3 );
		break;
	case WALLFALLING:
		s->show( 4 );
		break;
	case CROUCHING:
		s->show( 5 );
		break;
	case PUNCHING:
		s->show( 6 );
		break;
	case DEAD:
		s->show( 7 );
		break;
	default:
		s->show( 0 );
		break;
	}
	s->draw(_box.x,_box.y);
}

void Person::update()
{
	_sprite->update();

	if(_primaryWeapon)
		_primaryWeapon->update();
	if(_punchWeapon)
		_punchWeapon->update();

	//If you had the chance to walljump, but moved away from the wall, you're now just falling
	/*if((_wallJump * _xVelocity) > 0)
	{
		_wallJump = 0;
		_jumping = true;
	}*/

	//if(_yVelocity > 0)
	//{
	//	//you are falling, you shouldn't be allowed to jump until you hit something
	//	_jumping = true;
	//}
	
	_box.x += (_xVelocity/Screen::FRAMERATE);
	CollisionHandler::instance()->checkCollision(this);
	_previousX = _box.x;

	
	_box.y += (_yVelocity/Screen::FRAMERATE);
	CollisionHandler::instance()->checkCollision(this);
	_previousY = _box.y;

	//A little bit of drag force
	if(_xVelocity != 0)
	{
		if(!touchingBelow())
			incrVelocity((_xVelocity/abs(_xVelocity)) * _airDrag * 1000/Screen::FRAMERATE, 0);
		else
			incrVelocity((_xVelocity/abs(_xVelocity)) * _groundDrag * 1000/Screen::FRAMERATE, 0);
	}
	//_yVelocity -= _yVelocity *0.3;

	// breakVel is the least amount of velocity the person can have 
	// before moving at least 1 pixel
	int breakVel = Screen::FRAMERATE;
	if(_state !=DEAD){
		if(_state == PUNCHING && _punchTimer > 0)
		{
			_punchTimer--;
		}
		else if( _punchTimer == 0)
		{
			if( abs(_yVelocity) < breakVel )
			{
				if( _state != JUMPING )
					_state = STANDING;
			} 
			if( _yVelocity < -breakVel )
			{
				_state = JUMPING;
			} else if(_crouching)
			{
				_state = CROUCHING;
			} else if( _yVelocity > breakVel )
			{
				_state = FALLING;
				if(touchingLeft()||touchingRight())
				{
					//FIXME ?
					//MAJICK NUMBER
					if(_wallFallTime > 7)		
						_state = WALLFALLING;
					_wallFallTime++;
				}
			} else if(abs(_xVelocity) > breakVel)
			{
				_state = RUNNING;
			}

			//FIXME ?
			//MAJICK NUMBER
			if(_state != WALLFALLING && _wallFallTime > 8)
				_wallFallTime = 0;
		}
	}

	incrVelocity(0,_gravity * 1000/Screen::FRAMERATE);
}

void Person::incrVelocity(int xVel, int yVel)
{
	if(abs(_xVelocity + xVel) < _maxXVel)
		_xVelocity += xVel;
	else
		_xVelocity < 0 ? _xVelocity = -_maxXVel : _xVelocity = _maxXVel;


	if(abs(_yVelocity + yVel) < _maxFallRate)
		_yVelocity += yVel;
	else
		_yVelocity < 0 ? _yVelocity = -_maxFallRate : _yVelocity = _maxFallRate; 
}

int Person::getAccel()
{
	if(!touchingBelow())
		return _airAccel;

	return _accel;
}

void Person::jump(xDirection xDir)
{
	//Don't allow player or scripts to jump if they are already jumping.
	/*if(!_jumping && _jumpReset)
	{
		//If you're jumping straight off the ground
		if(_wallJump == 0)
		{
			
		}
		//If you're jumping off of some wall
		else if(_wallJump/abs(_wallJump) == xDir)
		{
			_xVelocity = _wallJump * .8 * _maxXVel;
			_yVelocity = -.9 * _maxYVel;
		}
		_jumping = true;
		_jumpTimer = 0;
		_jumpReset = false;
	}
	else if(_jumping && _jumpTimer < _maxJumpTime && _wallJump == 0)
	{
		_yVelocity = -_maxYVel;
		_jumpTimer ++;
	}*/

	//we're on top of something, and not directly below something, we can jump
	if( !_crouching &&_jumpReset && touchingBelow() && !touchingAbove() )
	{
		_yVelocity = -_maxYVel;
		//_jumping = true;
		_jumpTimer = 0;
		_jumpReset = false;
		_wallJumpReset = false;
	}
	//when we hit something above us the jump resets
	else if( touchingAbove() )
	{
		_jumpTimer = _maxJumpTime;
	}
	// we're touching a wall and have changed direction in the last timestep.
	// we can walljump
	else if(  (_wallJumpReset&& _xDirection == RIGHT && _prevXDirection == LEFT && touchingLeft() ) 
			||(_wallJumpReset && _xDirection == LEFT && _prevXDirection == RIGHT && touchingRight() )
			)
	{
		_xVelocity = (int)_xDirection * .8 * _maxXVel;
		_yVelocity = -_maxYVel;
		_jumpReset = false;
		_wallJumpReset = false;
	}
	else if(!_jumpReset && _jumpTimer < _maxJumpTime)
	{
		_yVelocity = -0.25 * _maxYVel;
		_jumpTimer ++;
	}

}

void Person::live()
{
	_invincible = false;
	_state = STANDING;
}

void Person::jumpReset()
{
	_jumpReset = true;
	_wallJumpReset = true;
	_jumpTimer = _maxJumpTime;
	_sprite->reset(2);
}

void Person::shoot()
{
	if(_primaryWeapon)
		_primaryWeapon->shoot();

	if(_punchWeapon && !_crouching)
	{
		_punchWeapon->shoot();
		_state = PUNCHING;
		//FIXME?
		//MAGIC number
		_punchTimer = 1;
	}
}

/**
* Make the person crouch
*/
void Person::crouch()
{
	if(!_crouching && touchingBelow())
	{
		_crouching = true;
		_box.h /= 2;
		_box.y += _box.h;
		_previousY = _box.y;
		_previousX = _box.x;
		_sprite->setHeight( _box.h );
	}
}
/**
* Uncrouches the person
*/
void Person::uncrouch()
{
	if(  _crouching && !touchingAbove( _box.h ) )
	{
		_crouching = false;
		_box.y -= _box.h;
		_previousY = _box.y;
		_previousX = _box.x;
		_box.h *= 2;
		_sprite->setHeight( _box.h );
	}
}

/**
* Schedules the calling object for deletion.
*/
void Person::die()
{
	_collidable = false;
	Level::instance()->scheduleDelete( this );

	if(_dropItem != "") {
		Object *newObject = Object::loadObject(_dropItem,0,0);
		SDL_Rect rect = newObject->getBox();
		newObject->setPos(_box.x, _box.y + _box.h - rect.h);
		Level::instance()->registerObject( newObject );
	}
}

bool Person::touchingAbove(int pels)
{
	_box.y-=pels;
	vector<Object *> objs = CollisionHandler::ObjsColliding( this , true);
	_box.y+=pels;
	return !objs.empty();
}
bool Person::touchingBelow(int pels)
{
	_box.y+=pels;
	vector<Object *> objs = CollisionHandler::ObjsColliding( this , true);
	_box.y-=pels;
	return !objs.empty();
}
bool Person::touchingLeft(int pels)
{
	_box.x-=pels;
	vector<Object *> objs = CollisionHandler::ObjsColliding( this , true);
	_box.x+=pels;
	return !objs.empty();
}
bool Person::touchingRight(int pels)
{
	_box.x+=pels;
	vector<Object *> objs = CollisionHandler::ObjsColliding( this , true);
	_box.x-=pels;
	return !objs.empty();
}

void Person::addHealth(int dHealth)
{
	if( !_invincible || dHealth >= 0 ) {
		_health += dHealth;

		// Check that the person is still alive
		if( _health <= 0 )
		{	
			//SOUNDFX
			if(_type == ENEMY)
				CommandManager::instance()->execute(new PlaySoundCommand("sounds/death_male08.wav"));
			else if(_type == PLAYER)
				CommandManager::instance()->execute(new PlaySoundCommand("sounds/death_male02.wav"));

			die();
		}
	}
}