#include "DevLib.h"
/******************************************************************************
 * Player.cpp                                                                 *
 ******************************************************************************
 * Project      : Futura (DevLib Demo)                                        *
 * License      : LGPL (full notice can be found at root directory)           *
 * Created by   : Arnaud Storq (norecess@devlib-central.org)                  *
 ******************************************************************************/
#include "Player.h"
#include "Level.h"
#include "Game.h"
#include "Application.h"

// ---------------------------------------------------------------------------- DEFINE
#define ACCELX           0.04f
#define ACCELY           0.02f
#define ACCELZ           0.05f

#define ACCELX_SPEED         10.0f
#define ACCELZ_SPEED         100.0f
#define ACCELX_SPEED_TIMER   8.0f
#define ACCELZ_SPEED_TIMER   1.0f
#define ACCELY_SPEED         1.5f
#define JUMP_LENGTH        0.025f
#define JUMP_COUNT          5

#define FALL_SPEED          -0.003f

#define ACCELY_NOJUMP        -1.0f

// ---------------------------------------------------------------------------- CREATE PLAYER
Player *Player::createPlayer( Ship *ship )
{
    if ( !ship )
        return 0;

    return new Player( ship );
}

// ---------------------------------------------------------------------------- CONSTRUCTOR
Player::Player( Ship *ship )
{
    m_ship = ship;

    reset( );

    m_score = 0;

    m_lifeCount = 2;
}

// ---------------------------------------------------------------------------- DESTRUCTOR
Player::~Player( )
{
}

// ---------------------------------------------------------------------------- KEY PRESS
void Player::keyPress( Key key )
{
    switch( key )
    {
    case KEY_LEFT:
        m_accelX = -ACCELX;
        break;

    case KEY_RIGHT:
        m_accelX = ACCELX;
        break;

    case KEY_SPACE:
        if ( m_freezeTime >= 0.0f )
        {
            if ( m_accelY == ACCELY_NOJUMP )
            {
                if ( m_jumpCount > 0 )
                {
                    --m_jumpCount;
                    
                    m_accelY = 0.0f;

                    Application::getInstance( )->getJump( )->play( );
                }
            }
        }
        break;
    }
}

// ---------------------------------------------------------------------------- RESET
void Player::reset( )
{
    m_posX = ( Application::getInstance( )->getLevel( )->getTrackWidth( ) * 0.5f ) * Application::getInstance( )->getLevel( )->getTileSize( );
    m_posY = 0.0f;
    m_posZ = 0.0f;

    m_rotX = 0.0f;
    m_rotY = 0.0f;
    m_rotZ = 0.0f;

    m_freezeAddTime = false;
    m_freezeTime = -3.0f * Application::getInstance( )->getTickTime( );

    m_accelX = 0.0f;
    m_accelY = ACCELY_NOJUMP;
    m_accelZ = 0.0f;

    m_fallY = 0.0f;

    m_lostTime = 0.0f;

    m_hasLost = false;
    m_hasFinishedLevel = false;
    
    m_jumpCount = Application::getInstance( )->getLevel( )->getStartJumpCount( );

    m_speed = Application::getInstance( )->getTileSpeed( TILE_NORMAL );
}

// ---------------------------------------------------------------------------- UPDATE
void Player::update( )
{
    float elapsedTime = Application::getInstance( )->getElapsedTime( );
    Level *level = Application::getInstance( )->getLevel( );
    
    if ( m_freezeAddTime )
    {
        // Set current gravitation time
        m_freezeTime += elapsedTime;
    }
    m_freezeAddTime = true;

    // If game lost, play fall animation..
    if ( m_hasLost )
    {
        m_posY += m_fallY;
        m_fallY += elapsedTime * FALL_SPEED;
        
        if ( Application::getInstance( )->getTime( ) > ( m_lostTime + 2.0f ) )
        {
            m_lifeCount--;            
            if ( m_lifeCount < 0 )
            {    
                m_lifeCount = -1; // idle
            }
            else
            {
                reset( );
            }
        }

        return;
    }
    
    // Compute ship's movement on X axis
    float addX = elapsedTime * m_accelX * ACCELX_SPEED_TIMER;
    m_posX += addX;

    // Compute ship's movement on Z axis
    if ( m_freezeTime >= 0.0f )
    {
        m_speed = elapsedTime * level->getTrackSpeed( ) * m_accelZ * ACCELZ_SPEED_TIMER;
        m_posZ += m_speed;

        m_score = 16 * (int) m_posZ;
    }

    // Test if level ended
    {
        float sizeX = 0.0f;
        float sizeY = 0.0f;
        float sizeZ = 0.0f;
        m_ship->getMesh( )->getSize( sizeX, sizeY, sizeZ );

        float collisionLength = m_ship->getScale( ) * sizeZ * 0.65f;
        
        int y = (int) ( ( m_posZ + collisionLength ) / level->getTileSize( ) );
    
        if ( y >= level->getTrackHeight( ) )
        {
            m_hasFinishedLevel = true;
            return;
        }
    }

    // If jumping..
    if ( m_accelY != ACCELY_NOJUMP )
    {
        // Compute new ship's Y position
        m_accelY += elapsedTime * ACCELY_SPEED;
        m_posY = sin( m_accelY ) * JUMP_LENGTH;
        
        // If jump animation is finished..
        if ( m_posY < 0.0f )
        {
            // Disable jumping
            m_accelY = ACCELY_NOJUMP;
            
            // Stick ship to playzone
            m_posY = 0.0f;
        }
    }
    
    // Check collisions..
    while ( checkCollisions( ) &&  !m_hasLost )
    {
        for ( unsigned int col = 0; col < 1; ++col )
        {
            CollideType collideType = m_collideList[ col ].type;
            TileType collideTileType = m_collideList[ col ].tileType;

            switch( collideType )
            {
            case COLLIDE_FRONTLEFT:
            case COLLIDE_FRONTRIGHT:
                {
                    if ( collideTileType == TILE_CUBE )
                    {
                        m_accelZ = -ACCELZ;

                        m_posZ -= m_speed;
                    }
                }
                break;

            case COLLIDE_LEFT:
            case COLLIDE_RIGHT:
                {
                    if ( ( m_accelY == ACCELY_NOJUMP ) || 
                        ( m_accelY != ACCELY_NOJUMP ) && ( collideTileType == TILE_CUBE ) )
                    {
                        // Cancel new X position
                        m_posX -= addX;

                        // Reverse and slow down the acceleration
                        m_accelX *= -0.25f;
                    }
                }
                break;
            }
        }

        // Check if the ship has fallen onto a valid tile
        if ( ( getCurrentTile( ) == TILE_EMPTY ) && ( m_accelY == ACCELY_NOJUMP ) )
        {
            Application::getInstance( )->getDeath( )->play( );
            m_lostTime = Application::getInstance( )->getTime( );
            
            m_hasLost = true;
        }
    }

    // Compute speed
    if ( m_accelY == ACCELY_NOJUMP )
    {
        TileType tileType = getCurrentTile( );
        if ( ( tileType != TILE_EMPTY ) && ( tileType != TILE_CUBE ) )
        {
            m_accelZ = Application::getInstance( )->getTileSpeed( tileType );
        }
    }

    // Reduce accelerations to zero
    m_accelX += ( 0.0f - m_accelX ) / ACCELX_SPEED;
    m_accelZ += ( Application::getInstance( )->getTileSpeed( TILE_NORMAL ) - m_accelZ ) / ACCELZ_SPEED;
}

// ---------------------------------------------------------------------------- GET CURRENT TILE
TileType Player::getCurrentTile( )
{
    Level *level = Application::getInstance( )->getLevel( );
    
    float sizeX = 0.0f;
    float sizeY = 0.0f;
    float sizeZ = 0.0f;
    m_ship->getMesh( )->getSize( sizeX, sizeY, sizeZ );

    float shipMiWidth = m_ship->getScale( ) * sizeX * 0.5f;
    
    float collisionLength = m_ship->getScale( ) * sizeZ * 0.65f;
    
    // Check if over the playzone
    if ( ( m_posX < shipMiWidth ) || ( m_posX > ( level->getTrackWidth( ) * level->getTileSize( ) - shipMiWidth ) ) )
    {
        return TILE_EMPTY;
    }

    int x = (int) ( m_posX / level->getTileSize( ) );
    int y = (int) ( ( m_posZ + collisionLength ) / level->getTileSize( ) );

    return level->getTrackTile( x, y );
}

// ---------------------------------------------------------------------------- CHECK COLLISIONS
bool Player::checkCollisions( )
{
    Level *level = Application::getInstance( )->getLevel( );

    bool res = false;

    m_collideList.clear( );

    float levelWidth = (float) level->getTrackWidth( );
    float levelHeight = (float) level->getTrackHeight( );

    float sizeX = 0.0f;
    float sizeY = 0.0f;
    float sizeZ = 0.0f;
    m_ship->getMesh( )->getSize( sizeX, sizeY, sizeZ );

    float shipMiWidth = m_ship->getScale( ) * sizeX * 0.5f;

    float collisionLength = m_ship->getScale( ) * sizeZ * 2.5f;

    CollideItem item;
    
    item.tileType = level->getTrackTile( (int) ( ( m_posX - shipMiWidth ) / level->getTileSize( ) ), (int) ( m_posZ / level->getTileSize( ) ) );
    if ( ( item.tileType != TILE_NORMAL )
      && ( item.tileType != TILE_SLOW )
      && ( item.tileType != TILE_FAST )
      && ( item.tileType != TILE_VERYFAST )
      && !( ( item.tileType == TILE_EMPTY ) && ( m_accelY != ACCELY_NOJUMP ) ) )
    {
        item.type = COLLIDE_LEFT;
        m_collideList.push_back( item );
    }

    item.tileType = level->getTrackTile( (int) ( ( m_posX + shipMiWidth ) / level->getTileSize( ) ), (int) ( m_posZ / level->getTileSize( ) ) );
    if ( ( item.tileType != TILE_NORMAL )
        && ( item.tileType != TILE_SLOW )
        && ( item.tileType != TILE_FAST )
        && ( item.tileType != TILE_VERYFAST )
        && !( ( item.tileType == TILE_EMPTY ) && ( m_accelY != ACCELY_NOJUMP ) ) )
    {
        item.type = COLLIDE_RIGHT;
        m_collideList.push_back( item );
    }

    item.tileType = level->getTrackTile( (int) ( ( m_posX - shipMiWidth ) / level->getTileSize( ) ), (int) ( ( m_posZ + collisionLength ) / level->getTileSize( ) ) );
    if ( ( item.tileType != TILE_NORMAL )
        && ( item.tileType != TILE_SLOW )
        && ( item.tileType != TILE_FAST )
        && ( item.tileType != TILE_VERYFAST )
        && !( ( item.tileType == TILE_EMPTY ) && ( m_accelY != ACCELY_NOJUMP ) ) )
    {
        if ( item.tileType != TILE_EMPTY )
        {
            item.type = COLLIDE_FRONTLEFT;
            m_collideList.push_back( item );
        }
    }

    item.tileType = level->getTrackTile( (int) ( ( m_posX + shipMiWidth ) / level->getTileSize( ) ), (int) ( ( m_posZ + collisionLength ) / level->getTileSize( ) ) );
    if ( ( item.tileType != TILE_NORMAL )
        && ( item.tileType != TILE_SLOW )
        && ( item.tileType != TILE_FAST )
        && ( item.tileType != TILE_VERYFAST )
        && !( ( item.tileType == TILE_EMPTY ) && ( m_accelY != ACCELY_NOJUMP ) ) )
    {
        if ( item.tileType != TILE_EMPTY )
        {
            item.type = COLLIDE_FRONTRIGHT;
            m_collideList.push_back( item );
        }
    }

    // Check if over the playzone
    if ( ( m_posX < shipMiWidth ) && ( m_accelY == ACCELY_NOJUMP ) )
    {
        item.type = COLLIDE_LEFT;
        item.tileType = TILE_EMPTY;
        m_collideList.push_back( item );
    }
    
    if ( ( m_posX > ( level->getTrackWidth( ) * level->getTileSize( ) - shipMiWidth ) ) && ( m_accelY == ACCELY_NOJUMP ) )
    {
        item.type = COLLIDE_RIGHT;
        item.tileType = TILE_EMPTY;
        m_collideList.push_back( item );
    }

    return ( m_collideList.size( ) != 0 );
}

// ---------------------------------------------------------------------------- HAS LOST
bool Player::hasLost( )
{ 
    if ( m_hasLost && ( Application::getInstance( )->getTime( ) > ( m_lostTime + 2.0f ) ) )
        return true;
        
    return false;
}