/*
	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: Saves.cpp
Purpose:     Loading and saving of high scores as well as save games

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

#include "Saves.h"

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

   // Save file header structure
   
   struct SaveHeader_t
   {
      unsigned char file_id;          // 8-bit file identifier
      unsigned char file_checksum;    // 8-bit checksum for rest of the file
      float         file_version;     // What version of the game does save belong to ?
   };
   
   // Hi score file structure
   
   struct ScoreFile_t
   {
      unsigned int file_id;                    // 8-bit file identifier
      char         name[PLAYER_NAME_LENGHT+1]; // Name of player who got the highest score
      unsigned int score;                      // Actual score
   }; 
   

   // Calculate 8-bit checksum of data
   
   unsigned char Saves_Checksum( char * data  , unsigned int size );                     

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

   //..

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

Name:       Saves_SaveUser 
Purpose:    Attempts to save user data to a file. User data file just stores
            best score- nothing else...
Parameters: Pointer to gamedata
Returns:    none

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

void Saves_SaveUser ( struct GameData_t * gamedata )
{
     /**************************************************************************
     Ensure data directory exists. If it does this function wont do anything anyway
     **************************************************************************/   
     
     CreateDirectory(USER_DATA_DIR,0);     

     /**************************************************************************
     Makeup a file path and name from user data directory and user name
     **************************************************************************/        
     
     // Check first if user name is empty. If so then abort
     
     if ( playerName[0] == '\0' ) return;
     
     // Makeup a full path to save file using users 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
     
     /**************************************************************************
     Makeup the user data file structure. Just pop in the file id and best score..
     **************************************************************************/  
     
     UserFile_t userData;
     
     userData.file_id     = USER_FILE_ID;
     userData.bestScore   = gamedata->player.scoreHighest;
     
     /**************************************************************************
     Open up a save file. Abort on failure
     **************************************************************************/          
          
     ofstream outFile(fileName,ios::binary | ios::out);

     if ( ! outFile.is_open() )
     {
          // Close the file
          
          outFile.close();    
          
          // Abort save
          
          return;
     }  
     
     /**************************************************************************
     Write user data to the file
     **************************************************************************/               

     // Write to the file
     
     outFile.write((char*)&userData,sizeof userData);

     /**************************************************************************
     If there was an error then remove the file.
     **************************************************************************/
     
     if ( ! outFile.good() )
     {
          // Delete the file
          
          remove(fileName);
     } 
     
     /**************************************************************************
     Close up the file
     **************************************************************************/     
     
     outFile.close();

}

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

Name:       Saves_LoadUser 
Purpose:    Attempts to load user data from a file. User data file just stores
            best score- nothing else...
Parameters: Pointer to gamedata
Returns:    none

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

void Saves_LoadUser ( struct GameData_t * gamedata )
{
     /**************************************************************************
     Makeup a file path and name from user data directory and user name
     **************************************************************************/        
     
     // Check first if user name is empty. If so then abort
     
     if ( playerName[0] == '\0' ) return;
     
     // Makeup a full path to save file using users 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
     
     /**************************************************************************
     Attempt to open save file. Abort save on error
     **************************************************************************/
     
     ifstream inFile(fileName,ios::binary | ios::in);

     if ( ! inFile.is_open() )
     {
          // Close the file
          
          inFile.close();    
          
          // Abort load
          
          return;
     } 

     /**************************************************************************
     Attempt to Read file structure:
     **************************************************************************/          
     
     UserFile_t userData;
     
     inFile.read((char*)&userData,sizeof userData);   
     
     /**************************************************************************
     If file i/o error then delete corrupt file and abort
     **************************************************************************/                
     
     if ( ! inFile.good() )
     {
          // Error occured. Delete corrupt file
          
          remove(fileName);
          
          // Close file
          
          inFile.close();
          
          // Abort
          
          return;
     }
     
     /**************************************************************************
     If file header ok then set current player's high score: otherwise delete
     corrupt file
     **************************************************************************/      
     
     if ( userData.file_id == USER_FILE_ID )
     {
         // Set current user high score
         
         gamedata->player.scoreHighest = userData.bestScore;
     
     }
     else
     {
         // Invalid file. Delete corrupt file
          
         remove(fileName);
     }
     
     /**************************************************************************
     Close up the file
     **************************************************************************/      
     
     inFile.close();
}

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

Name:       Saves_SaveGame 
Purpose:    Attempts to save the game data to a file
Parameters: Pointer to gamedata
Returns:    none

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

void Saves_SaveGame ( struct GameData_t * gamedata )
{
     /**************************************************************************
     Ensure data directory exists. If it does this function wont do anything anyway
     **************************************************************************/   
     
     CreateDirectory(SAVE_DATA_DIR,0);     
     
     /**************************************************************************
     Makeup a file path and name from user data directory and user name
     **************************************************************************/        
     
     // Check first if user name is empty. If so then issue a warning message
     // and abort. The program cannot magically guess which save file needs loading!
     
     if ( playerName[0] == '\0' )
     {
        // No user name entered: issue warning and abort loading
        
        GLWin_Message
           (
           "Cannot save your user save game because you have not \n"
           "Entered your user name. ",
           "Error loading game. "
           );
           
           return;
     }
     
     // Makeup a full path to save file using users name and save directory

	 #define FILE_NAME_LENGTH ( sizeof(SAVE_DATA_DIR) + PLAYER_NAME_LENGHT + 1 )
     
		char fileName[FILE_NAME_LENGTH] = {0};
     
		strcpy_s(fileName,FILE_NAME_LENGTH,SAVE_DATA_DIR	);
		strcat_s(fileName,FILE_NAME_LENGTH,playerName		);

	 #undef FILE_NAME_LENGTH
     
     /**************************************************************************
     Attempt to open save file. Abort save on error
     **************************************************************************/
     
     ofstream outFile(fileName,ios::binary | ios::out);

     if ( ! outFile.is_open() )
     {
          // Error happened when saving: inform user
          
          GLWin_Message("Error saving game. Could not open save file.", "Error");
          
          // Close the file
          
          outFile.close();    
          
          // Abort save
          
          return;
     }
     
     /**************************************************************************
     Make up and write the file header to the save file
     **************************************************************************/     
     
     // Makeup header with checksum and file ID
     
     SaveHeader_t header;
     
     header.file_id       = SAVE_FILE_ID;
     header.file_version  = GAME_VERSION;
     header.file_checksum = Saves_Checksum((char*)gamedata,sizeof(GameData_t));
     
     // Write to the file
     
     outFile.write((char*)&header,sizeof header);
     
     /**************************************************************************
     Write the gamedata to the file
     **************************************************************************/       

     outFile.write((char*)gamedata,sizeof(GameData_t));
     
     /**************************************************************************
     If there was an error then remove the file and display a warning.
     Otherwise inform user that the game was saved successfully
     **************************************************************************/
     
     if ( ! outFile.good() )
     {
          // Something went wrong. Display error and delete the file.
          
          GLWin_Message
              (
              "Error writing to save file. Disk may be full or corrupted.",
              "Save file error"
              );
          
          // Delete the file
          
          remove(fileName);
     } 
     else
     {
          // Save successfull     
     
          GLWin_Message
              (
              "Game data saved successfully..",
              "Save done"
              );
     }

     /**************************************************************************
     Close up the file
     **************************************************************************/         
     
     outFile.close();     
}

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

Name:       Saves_LoadGame 
Purpose:    Attempts to load the game data from a save file
Parameters: Pointer to gamedata
Returns:    none

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

void Saves_LoadGame ( struct GameData_t * gamedata )
{     
     /**************************************************************************
     Makeup a file path and name from user data directory and user name
     **************************************************************************/        
     
     // Check first if user name is empty. If so then issue a warning message
     // and abort. The program cannot magically guess which save file needs loading!
     
     if ( playerName[0] == '\0' )
     {
        // No user name entered: issue warning and abort loading
        
        GLWin_Message
           (
           "Cannot load your user save game because you have not \n"
           "Entered your user name. ",
           "Error loading game. "
           );
           
           return;
     }
     
     // Makeup a full path to save file using users name and save directory

	 #define FILE_NAME_LENGTH ( sizeof(SAVE_DATA_DIR) + PLAYER_NAME_LENGHT + 1 )
     
		char fileName[FILE_NAME_LENGTH] = {0};
     
		strcpy_s(fileName,FILE_NAME_LENGTH,SAVE_DATA_DIR	);
		strcat_s(fileName,FILE_NAME_LENGTH,playerName		);

	 #undef FILE_NAME_LENGTH
     
     /**************************************************************************
     Attempt to open the file: abort on failure
     **************************************************************************/          
     
     ifstream fileIn(fileName,ios::binary);
     
     if ( ! fileIn.is_open() )
     {
          // Error happened when loading: inform user
          
          GLWin_Message
             (
             "Cannot open user save file. Does it exist yet ?", 
             "Error loading game"
             );
             
          // Close file
          
          fileIn.close();             
          
          // Abort load
          
          return;
     }  
     
     /**************************************************************************
     Attempt to Read file header:
     **************************************************************************/          
     
     SaveHeader_t header;
     
     fileIn.read((char*)&header,sizeof header);
     
     /**************************************************************************
     Validate header
     **************************************************************************/         
     
     // Check I/O Status firstly
     
     if ( ! fileIn.good() )
     {
          // Something went wrong reading. Display error... 
          
          GLWin_Message
             (
             "Error occured whilst reading save file", 
             "Error loading game"
             ); 
             
          // Close file
          
          fileIn.close();                      
       
          // Abort load
          
          return;
      }   
      
      // Check if id is correct
      
      if ( header.file_id != SAVE_FILE_ID )
      {
           // Display warning: not a valid save file
           
           GLWin_Message
             (
             "Save file is not a valid save file.", 
             "Error loading game"
             ); 
             
           // Close file
          
           fileIn.close();                                
           
           // Abort load
           
           return;
      }
      
      // Check save file version matches this game version
      
      if ( header.file_version != GAME_VERSION )
      {
           // Display warning: not a valid save file
           
           GLWin_Message
             (
             "Save file belongs to a different game version. Cannot load.", 
             "Error loading game"
             );   
             
           // Close file
          
           fileIn.close();                              
           
           // Abort load
           
           return;
      }  
      
     /**************************************************************************
     Read game data
     **************************************************************************/         
     
     GameData_t loadData; fileIn.read((char*)&loadData,sizeof loadData);
     
     /**************************************************************************
     Validate game data
     **************************************************************************/      
     
     // Check I/O status
     
     if ( ! fileIn.good() )
     {
          // Something went wrong reading. Display error... 
          
          GLWin_Message
             (
             "Error occured whilst reading save file", 
             "Error loading game"
             );  
             
          // Close file
          
          fileIn.close();                     
       
          // Abort load
          
          return;
      } 
      
     // Get 8-bit checksum for the data read
     
     unsigned char dataChecksum = Saves_Checksum ( (char*)&loadData , sizeof loadData );
     
     // If this checksum does not agree with that of the header then abort:
           
     if ( dataChecksum != header.file_checksum  )
     {                          
          // Data has been changed or corrupted: Display error.
          
          GLWin_Message
             (
             "Data file has been corrupted or made invalid", 
             "Error loading game"
             );     
             
          // Close file
          
          fileIn.close();     
       
          // Abort load
          
          return;
      }  
      
     /**************************************************************************
     If we've got this far then everything is OK. Copy the gamedata read into
     the gamedata located in the game module:
     **************************************************************************/ 
     
     // Save player's current high score first
     
     unsigned highScoreCurrent = gamedata->player.scoreHighest;
     
     // Copy the game data read from the file into the main game data        
     
     memcpy(gamedata,&loadData,sizeof loadData);
     
     // If the high score read from the file is smaller than the high score before
     // the read then restore this score:
            
     if ( highScoreCurrent > gamedata->player.scoreHighest )
     {
          gamedata->player.scoreHighest = highScoreCurrent;
     }
     
     // Show message indicatiing load success
     
         GLWin_Message
             (
             "Save game loaded successfully", 
             "Loading complete"
             );          
       
}

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

Name:       Saves_SaveScore
Purpose:    Saves the high score to a file
Parameters: None
Returns:    None

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

void Saves_SaveScore()
{
     /**************************************************************************
     Ensure data directory exists. If it does this function wont do anything anyway
     **************************************************************************/   
     
     CreateDirectory(SCORE_FILE_DIR,0);     

     /**************************************************************************
     Attempt to open the file: abort on failure
     **************************************************************************/          
     
     ofstream fileOut(SCORE_FILE_NAME,ios::binary);
     
     if ( ! fileOut.is_open() )
     {
          // Close file
          
          fileOut.close();             
          
          // Abort save
          
          return;
     }
     
     /**************************************************************************
     Makeup a score file structure:
     **************************************************************************/           
     
     ScoreFile_t score;
     
     memcpy(score.name,highScoreName,PLAYER_NAME_LENGHT+1);
     
     score.file_id = SCORE_FILE_ID;
     score.score   = highScore;

     /**************************************************************************
     Attempt to write score to file:
     **************************************************************************/  
     
     fileOut.write((char*)&score,sizeof score);
     
     /**************************************************************************
     If something went wrong then try and delete the file
     **************************************************************************/       
     
     if ( ! fileOut.good() )
     {
          // Delete the file
          
          remove(SCORE_FILE_NAME);
     }      

     /**************************************************************************
     Close the file
     **************************************************************************/  
     
     fileOut.close();    
}

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

Name:       Saves_LoadScore
Purpose:    Attempts to read the high score from the score file
Parameters: None
Returns:    None

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

void Saves_LoadScore()
{
     /**************************************************************************
     Attempt to open the file: abort on failure
     **************************************************************************/          
     
     ifstream fileIn(SCORE_FILE_NAME,ios::binary);
     
     if ( ! fileIn.is_open() )
     {
          // Close file
          
          fileIn.close();             
          
          // Abort load
          
          return;
     }
     
     /**************************************************************************
     Declare a score file structure and try to read
     **************************************************************************/  
     
     ScoreFile_t score; fileIn.read((char*)&score,sizeof score);
     
     /**************************************************************************
     If something went wrong or score file ID invalid then abort.
     Otherwise set current high score
     **************************************************************************/      
     
     if ( ( ! fileIn.good() ) || score.file_id != SCORE_FILE_ID )
     {          
          // Something is wrong: Close file
          
          fileIn.close();             
          
          // Abort load
          
          return;
     }
     else
     {
         // Everything OK: set high score
         
         highScore = score.score;
         
         // Set name of the high score holder
         
         memcpy(highScoreName,score.name,PLAYER_NAME_LENGHT+1); 
     }
     
     /**************************************************************************
     Close the file
     **************************************************************************/
            
     fileIn.close();    
}

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

Name:       Saves_Checksum
Purpose:    Calculates an 8-bit checksum for the range of data provided
Parameters: Pointer to data, size of data in bytes
Returns:    Checksum

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

unsigned char Saves_Checksum ( char * data  , unsigned int size )
{
    // Checksum
    
    unsigned char checksum = 0;
         
    // Calculate the checksum:
                 
    while ( size )
    {
		// Calculate new value 
		
		unsigned new_value = checksum + (*data);
    
		// Assign:
		
		checksum = (unsigned char)(new_value&0xff);
		
		// Move on in data:
      
		size--; data++;
    }
    
    // Return the checksum
    
    return checksum;
}


