/*
	Asteroids
    Copyright (C) 2006  Darragh Coy.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/*##############################################################################

Module name: Player
Purpose:     Player movement, firing, drawing and collision detection

##############################################################################*/

#include "Player.h"

/*******************************************************************************
Private module prototypes: Not for outside use!
*******************************************************************************/

   bool Player_CollisionCheck ( GameData_t * gamedata );   // Collision checking

/*******************************************************************************
Variables
*******************************************************************************/

   // The points for the player's ship (original): Important that these are
   // in clockwise order!!

   const Vec2D_t shipPointsOriginal[] =
         {
           { CENTER_X + 12 , CENTER_Y      },
           { CENTER_X - 8  , CENTER_Y - 8  },
           { CENTER_X      , CENTER_Y      },
           { CENTER_X - 8  , CENTER_Y + 8  }
         };

   // Bounding sphere radius around the player. use for rough determination of
   // collisions before using the precise per vertex method..

   const int playerCollisionRadius = 12;

   // Number of points in the ship: calculate automatically so changes are easily made!

   const int numShipPoints = sizeof(shipPointsOriginal) / sizeof(Vec2D_t);

   // The screen center vector: use when rotating

   const Vec2D_t screenCenter = { CENTER_X , CENTER_Y };

   // The points for the player's ship (rotated)

   Vec2D_t shipPointsRotated[numShipPoints];

/*******************************************************************************

Name:       Player_Move
Purpose:    Rotates the player's ship, moves all other objects if the player
            is moving and does collision checking..
Parameters: The gamedata structure
Returns:    None

*******************************************************************************/

void Player_Move ( GameData_t * gamedata )
{
    // Get pointer to the player

    Player_t * pPlayer = &(gamedata->player);

    /***************************************************************************
    Keyboard movement
    ***************************************************************************/

    // Do clockwise / counter clockwise change in orientation if left/right
    // keys are pressed down

    if ( GLWin_Keydown[VK_LEFT] )
    {
         // Rotate counter clockwise

         pPlayer->orientation += PLAYER_TURN_SPEED * Timer_FrameTime();
    }
    else if ( GLWin_Keydown[VK_RIGHT] )
    {
         // Rotate clockwise

         pPlayer->orientation -= PLAYER_TURN_SPEED * Timer_FrameTime();
    }

    // Do forwards movement if key is pressed

    if ( GLWin_Keydown[VK_UP] )
    {
         // Create a unit vector in move direction:

         Vec2D_t moveVec;

         moveVec.x = cos( pPlayer->orientation * TO_RADIANS );
         moveVec.y = sin( pPlayer->orientation * TO_RADIANS );

         // Scale it by move speed and adjust based on the speed of last frame

         moveVec.x *= PLAYER_MOVE_SPEED;
         moveVec.y *= PLAYER_MOVE_SPEED;

         moveVec.x *= Timer_FrameTime();
         moveVec.y *= Timer_FrameTime();

         // Add this to the player's current velocity

         Vec2D_Add( & (pPlayer->velocity) , & moveVec );
    }

    /***************************************************************************
    Do friction to reduce magnitude of player movement vector
    Adjust friction amount also based on frame time. Everything in this game
    should ideally be framerate independent- friction included
    ***************************************************************************/

    // If frame time is zero seconds then no friction. If 1 second then normal
    // speed reduction per second, if 2 seconds then twice as much speed reduction
    // and so on.. this works correctly regardless of the frame rate of the game

    pPlayer->velocity.x *= powf( PLAYER_FRICTION, Timer_FrameTime() );
    pPlayer->velocity.y *= powf( PLAYER_FRICTION, Timer_FrameTime() );

    /***************************************************************************
    Rotation of player ship
    ***************************************************************************/

    // Set all of the ships rotated points as the original points initially

    memcpy( &shipPointsRotated , &shipPointsOriginal , sizeof shipPointsOriginal );

    // Rotate every point about the screen center at current orientation angle

    for ( int i = 0 ; i < numShipPoints ; i++ )
    {
        // Rotate this point

        Vec2D_Rot( &shipPointsRotated[i] , (Vec2D_t*) &screenCenter ,  pPlayer->orientation );
    }

    /***************************************************************************
    Movement of player ship: the ship doesn't actually move, but everything else
    around it does- cheap hack to give the illusion of infinite space!
    ***************************************************************************/

    // Move all stars in opposite direction of movement vector to simulate
    // the player moving across them

    Stars_Shift
       (
       -pPlayer->velocity.x * Timer_FrameTime() ,
        pPlayer->velocity.y * Timer_FrameTime()
        );

    // Move all asteroids in opposite direction

    Asteroid_Shift
       (
       gamedata,
       -pPlayer->velocity.x * Timer_FrameTime() ,
        pPlayer->velocity.y * Timer_FrameTime()
       );

    // Move all bullets in opposite direction

    Bullet_Shift
       (
       gamedata,
       -pPlayer->velocity.x * Timer_FrameTime(),
        pPlayer->velocity.y * Timer_FrameTime()
       );

    // Move all particles in oppposite direction

    Particle_Shift
       (
       -pPlayer->velocity.x * Timer_FrameTime(),
        pPlayer->velocity.y * Timer_FrameTime()
       );

    /***************************************************************************
    If time since last fire is under the minimum time between fires then
    add the time the last frame took to the time since the last fire
    ***************************************************************************/

    if ( pPlayer->timeSinceFire < PLAYER_FIRE_DELAY )
    {
         // Havent reached minimum elapsed time till next fire. Add last
         // frame time to the count

         pPlayer->timeSinceFire += Timer_FrameTime();
    }

    /***************************************************************************
    If space bar pressed and time since last fire >= minimum fire delay then
    spawn a bullet:
    ***************************************************************************/

    if ( GLWin_Keydown[VK_SPACE] && pPlayer->timeSinceFire >= PLAYER_FIRE_DELAY )
    {
         // Space bar pressed and can fire: set time since last fire to 0

         pPlayer->timeSinceFire = 0;

         // Spawn a bullet at screen center going in direction of player:

         Bullet_Create(gamedata,(Vec2D_t*)&screenCenter,-pPlayer->orientation);
    }

    /***************************************************************************
    Do the collision checking with asteroids:
    ***************************************************************************/

    if ( Player_CollisionCheck(gamedata) )
    {
       /************************************************************************
         Player collided with asteroid: now dead
       ************************************************************************/

       // Set player to dead

       gamedata->player.playerAlive = false;

       // Spawn an explosion at player's position: i.e screen center

       Particle_Explosion((Vec2D_t*)&screenCenter);

       // If current player score greater than highest then save

       if ( gamedata->player.score > highScore )
       {
            highScore = gamedata->player.score;
       }

    } // endif player in collision with asteroid

}

/*******************************************************************************

Name:       Player_CollisionCheck
Purpose:    Does the collision checking for the player. uses the precise per-vertex
            collision checking rather than bounding spheres. Here accuracy is important
            because ideally the game should be always fair to the player. If the
            player is actually outside the asteroid and the game decides otherwise then
            it is hardly good for the playability of the game!
Parameters: Pointer to gamedata
Returns:    True if a collision is found, false if not..

*******************************************************************************/

bool Player_CollisionCheck ( GameData_t * gamedata )
{
   // Pointer to the current asteroid

   Asteroid_t * pAsteroid = gamedata->asteroids;

   /****************************************************************************
   Check against every active asteroid in the game
   ****************************************************************************/

   for ( int astNum = 0 ; astNum < AST_MAX_ASTEROIDS * 2 ; astNum++ )
   {
       /************************************************************************
       If this asteroid is not active then move onto the next one..
       ************************************************************************/

       if ( ! pAsteroid->asteroidAlive ){ pAsteroid++; continue; }

       /************************************************************************
       Get the distance from the center of the asteroid to the center of the screen.
       The center of the screen is always the center of the ship since it doesn't r
       actuall move..
       ************************************************************************/

       float dist = Vec2D_Distance((Vec2D_t*)&screenCenter,&(pAsteroid->center));

       /************************************************************************
       If the distance is less than the collision radius of the player's ship
       and the collision radius of the asteroid combined, then there may be a
       collision. If not though there is no need to do the check- move on
       ************************************************************************/

       if ( ! (dist < playerCollisionRadius + pAsteroid->collisionRadius) )
       {
            // There is no chance of the collision happening. Don't bother to
            // go on checking...

            pAsteroid++; continue;
       }

       /************************************************************************
       Ok so the rough collision detection has passed: now check if each of the
       ships points are inside the asteroid using the asteroid collision detection
       method. If a collision is found then return true:
       ************************************************************************/

       for ( int i = 0 ; i < numShipPoints ; i++ )
       {
           // Check if this point is inside the asteroid: if so then return true

           if ( Asteroid_Inside( shipPointsRotated + i , pAsteroid ) )
           {
                // Inside: collision, so return true

                return true;
           }
       }

       // Move onto the next asteroid

       pAsteroid++;

   } // End of check against every asteroid loop

   /****************************************************************************
   If we have reached this far then no collision has been found. Return false:
   ****************************************************************************/

   return false;
}

/*******************************************************************************

Name:       Player_Init
Purpose:    Initialises the player data
Parameters: GameData
Returns:    None

*******************************************************************************/

void Player_Init ( GameData_t * gamedata )
{
     // Reset player memory to 0

     memset(&gamedata->player,0,sizeof gamedata->player);

     // Set player angle to 90

     gamedata->player.orientation = 90.0;

     // Set player as alive

     gamedata->player.playerAlive = true;
}

/*******************************************************************************

Name:       Player_Draw
Purpose:    Draws the player's ship
Parameters: None
Returns:    None

*******************************************************************************/

void Player_Draw ( void )
{
  // Begin drawing a fully looped set of lines

  glBegin(GL_LINE_LOOP);

      // Set the draw color to white first

      glColor3f(1.0,1.0,1.0);

      // Pass each of the vertices to opengl

      for ( int i = 0; i < numShipPoints ; i++ )
      {
          glVertex2f( shipPointsRotated[i].x , shipPointsRotated[i].y );
      }

  // Finish up drawing

  glEnd();
}

/*******************************************************************************

Name:       Player_EnterName
Purpose:    Allows player to enter name. Checks with window for the last key
            and adds it to the name if there is enough room. If return key is
            pressed then name entry is aborted.
Parameters: Pointer to gamedata
Returns:    None

*******************************************************************************/

void Player_EnterName ( GameData_t * gamedata )
{
     // If no key entered then return

     if ( GLWin_LastKey == 0 ) return;

     // Get string lenght of player name

     int lenght = strlen(playerName);

     // Allow only three types of keys to be pressed: characters from 'a' to 'z'
     // the return and backspace key.

     if ( GLWin_LastKey >= 'A' && GLWin_LastKey <= 'z' )
     {
        // Character entry: only allow if not at lenght limit

        if ( lenght < PLAYER_NAME_LENGHT )
        {
            *( playerName + lenght) = GLWin_LastKey;
        }

     }
     else if ( GLWin_LastKey == VK_BACK )
     {
        // Delete a character: only allow if string not zero lenght

        if ( lenght > 0 )
        {
            *( playerName + lenght - 1) = 0;
        }
     }
     else if ( GLWin_LastKey == '\r' )
     {
         // Return key: finish name entry

         enteringName = false;

         // If new name is not blank then check for user data file
         // Dont check also if user entered is the same as the last user.

         if ( playerName[0] != '\0' && strcmp(lastPlayerName,playerName) != 0 )
         {
            // Proper name entered. Makeup a full file name including user name
            // and save directory:

			#define FILE_NAME_LENGTH ( sizeof(USER_DATA_DIR) + PLAYER_NAME_LENGHT + 1 )

				char fileName[FILE_NAME_LENGTH] = {0};

				strcpy_s(fileName,FILE_NAME_LENGTH,USER_DATA_DIR	);
				strcat_s(fileName,FILE_NAME_LENGTH,playerName		);

			#undef FILE_NAME_LENGTH

            // Open the file:

            ifstream fileIn(fileName,ios::binary | ios::in);

            // If it exists then load user high score: otherwise display a
            // welcome message.

            if ( fileIn.is_open() )
            {
               // Get the path to the save file for this user

			   #define FILE_NAME_LENGTH ( sizeof(SAVE_DATA_DIR) + PLAYER_NAME_LENGHT + 1 )

				   char saveFileName[sizeof(SAVE_DATA_DIR) + PLAYER_NAME_LENGHT + 1] = {0};

				   strcpy_s(saveFileName,FILE_NAME_LENGTH,SAVE_DATA_DIR		);
				   strcat_s(saveFileName,FILE_NAME_LENGTH,playerName		);

               #undef FILE_NAME_LENGTH

               // Open a save file for the user

               ifstream saveFile(saveFileName,ios::binary | ios::in);

               // If the file exists then ask the user do they want to load the save file.
               // Otherwise restart the game

               if ( saveFile.is_open() )
               {
                    // File exists. Ask user do they want to load:

                    bool loadgame = GLWin_Prompt
                         (
                         "You have a previously saved game.\n"
                         "Do you wish to load this game ? ",
                         "Load game?"
                         );

                    // If load was confirmed then load the save game. Otherwise
                    // start a new game

                    if ( loadgame )
                    {
                         // Load the save game

                         Saves_LoadGame(gamedata);

                         // Pause the game also

                         gamePaused = true;

                    }
                    else
                    {
                        // User doesn't want to load: start a new game

                        Game_Init();
                    }
               }
               else
               {
                   // No save exists: start a new game

                   Game_Init();
                }

               // Close the save file

               saveFile.close();

               // Load current user high score

               Saves_LoadUser(gamedata);
            }
            else
            {
                // Display a welcome message

                GLWin_Message
                   (
                   "Hello new user. This appears to be your first time playing.\n"
                   "Press F6 to save your data at any time and F9 to load.     \n"
                   "The next time you enter your name your score will be retrieved for you.\n",
                   "Welcome!"
                   );

                // Restart the game

                Game_Init();
            }

            // Close the file opened

            fileIn.close();

            // Set the name of the last player as the current

            memcpy(lastPlayerName,playerName,PLAYER_NAME_LENGHT+1);

         } // end if new name not blank and not changed user

         // Special case bug fix: if last user had a name and newly entered
         // user has no name then reset the game. User has changed..

         if ( lastPlayerName[0] != '\0' && playerName[0] == '\0' )
         {
            Game_Init();
         }


         // Discard time passed while entering name by unpausing the timer

         Timer_Unpause();

     } // end if last key was return

     // Set last key pressed as NULL

     GLWin_LastKey = 0;
}
