/******************************************************************************/
/*                                                                            */
/*                               LOAD/SAVE GAME                               */
/*                                                                            */
/******************************************************************************/

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "cpumain.h"
#include "files.h"
#include "sasound.h"
#include "control.h" // trackball
#include <sys/stat.h> // mkdir ! (linux)
#include <zlib.h> // now uses zlib all the time for the savegames

#include "profile.h" // reset_ingame_timer()
#include "gui.h" // setup/release_gui
#include "rgui.h" // raine file selector...

#include "timer.h" // update_timers
#include "dejap.h" // default eeproms in raine.dat
/*

Todo:

- Change ram area, so there is a savedata entry in each game driver (then it is
  not a special case in here).
- Add savedata and any callbacks needed for sound chip emulators and U68020.
- Make save_data_list into a linked list, for more expandable code.
- Think of something useful to save in the global header, currently it is empty.
- Remove old load support, after the next release or two.

*/

#include "raine.h"
#include "savegame.h"

#include "starhelp.h"
#include "mz80help.h"
#include "68020.h"

#include "config.h"
#include "m68k.h"
#include "memory.h"
#include "newcpu.h"

#include "games.h"		// Games List

#include "ingame.h"

// ---- Saved Data Types -------------------------------

#define SAVE_RAM              ASCII_ID('R','A','M',0x00)
#define SAVE_MOUSE            ASCII_ID('M','O','U','S')
// SAVE_MOUSE is especially usefull for the demos...
#define SAVE_END              ASCII_ID('E','N','D',0x00)

// -----------------------------------------------------

typedef struct SAVE_DATA
{
   UINT32 id;			// Unique ASCII_ID for this data
   UINT8 *source;		// Pointer to the data in memory
   UINT32 size;			// Size of the data in bytes
} SAVE_DATA;

static struct SAVE_DATA save_data_list[0x40];

int SaveDataCount;
int UseCompression,SaveSlot;
int savegame_version;

/* Basic file i/o functions */

static void fputl(UINT32 nb, gzFile file) {
  gzwrite(file,&nb,4);
}

void AddSaveData(UINT32 id, UINT8 *src, UINT32 size)
{
  int n;
  for (n=0; n<SaveDataCount; n++) {
    if (save_data_list[n].id == id) {
      save_data_list[n].source = src;
      save_data_list[n].size   = size;
      return;
    }
  }
  save_data_list[SaveDataCount].id     = id;
  save_data_list[SaveDataCount].source = src;
  save_data_list[SaveDataCount].size   = size;
  SaveDataCount++;
}

// -----------------------------------------------------

#define CALLBACK_LOAD      (0x00000001)
#define CALLBACK_SAVE      (0x00000002)
#define CALLBACK_CORE      (0x00000004)

typedef struct SAVE_CALLBACK
{
   UINT32 flags;			// Unique ASCII_ID for this data
   void (*proc)();		// Callback routine ptr
} SAVE_CALLBACK;

static struct SAVE_CALLBACK save_callback_list[0x40];

UINT32 SaveCallbackCount;

void AddLoadCallback(void *callback)
{
   save_callback_list[SaveCallbackCount].flags     = CALLBACK_LOAD;
   save_callback_list[SaveCallbackCount].proc      = callback;
   SaveCallbackCount++;
}
void AddSaveCallback(void *callback)
{
   save_callback_list[SaveCallbackCount].flags     = CALLBACK_SAVE;
   save_callback_list[SaveCallbackCount].proc      = callback;
   SaveCallbackCount++;
}

void AddLoadCallback_Internal(void *callback)
{
   save_callback_list[SaveCallbackCount].flags     = CALLBACK_LOAD|CALLBACK_CORE;
   save_callback_list[SaveCallbackCount].proc      = callback;
   SaveCallbackCount++;
}

void AddSaveCallback_Internal(void *callback)
{
   save_callback_list[SaveCallbackCount].flags     = CALLBACK_SAVE|CALLBACK_CORE;
   save_callback_list[SaveCallbackCount].proc      = callback;
   SaveCallbackCount++;
}

void ProcessCallbackList(UINT32 flags)
{
   UINT32 ta;

   for(ta=0; ta<SaveCallbackCount; ta++){
      if(save_callback_list[ta].flags == flags){
         if(save_callback_list[ta].proc)
            save_callback_list[ta].proc();
      }
   }
}

// -----------------------------------------------------

void NewSave(PACKFILE *fout)
{
   int ta;

   ProcessCallbackList(CALLBACK_SAVE);
   ProcessCallbackList(CALLBACK_SAVE|CALLBACK_CORE);

   // global header

   pack_mputl(SAVE_FILE_TYPE_2, fout);
   pack_iputl(0x00000000, fout);

   // ram header

   pack_mputl(SAVE_RAM, fout);
   pack_iputl(RAMSize, fout);
   pack_fwrite(RAM, RAMSize, fout);

   // user data header

   for(ta=0;ta<SaveDataCount;ta++){
      pack_mputl(save_data_list[ta].id, fout);
      pack_iputl(save_data_list[ta].size, fout);
      pack_fwrite(save_data_list[ta].source, save_data_list[ta].size, fout);
   }

   if (GameMouse) {
     pack_mputl(SAVE_MOUSE,fout);
     pack_iputw(p1_trackball_x,fout);
     pack_iputw(p1_trackball_y,fout);
   }

   pack_mputl(SAVE_END, fout);
}

void do_save_state(char *name) {
   PACKFILE *fout;
   char str[256],*disp_str;

   if(RAMSize){
     if (strstr(name,SLASH)) // The name is already a path
       strcpy(str,name);
     else
       sprintf(str,"%ssavegame" SLASH "%s",dir_cfg.exe_path,name);
     disp_str = strstr(str,"savegame");
     if (!disp_str) disp_str = str;
     if (!(fout=pack_fopen(str,UseCompression ? F_WRITE_PACKED : F_WRITE))){
      print_ingame(120,"Unable to save: %s",disp_str);
      return;
     }

     sa_pause_sound();
     stop_cpu_main();

     NewSave(fout);

     pack_fclose(fout);

     print_ingame(120,"Saved to: %s",disp_str);
     if (!raine_cfg.req_pause_game) {
       start_cpu_main();
       sa_unpause_sound();
       reset_ingame_timer();
     }

   }
   else{
      print_ingame(120,"Game does not support Saving.");
   }

   print_debug("END: GameSave()\n");
}

void GameSave(void)
{
   char str[256];

   print_debug("BEGIN: GameSave()\n");

   sprintf(str,"%s.sv%d",current_game->main_name,SaveSlot);
   do_save_state(str);
}

void GameSaveName(void)
{
  char str[256];

   print_debug("BEGIN: GameSaveName()\n");

   sprintf(str,"savegame" SLASH "%s" SLASH,current_game->main_name);
   mkdir_rwx(str);
   sa_pause_sound();
   stop_cpu_main();
   setup_gui();
   if (raine_file_select("Save game",str,NULL))
     do_save_state(str);
   release_gui();
}

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

void read_safe_data(UINT8 *dest, UINT32 dest_size, UINT32 data_size, PACKFILE *fin)
{
   UINT32 ta,tb;

   if(dest_size==data_size){
      pack_fread(dest,data_size,fin);
      return;
   }
   if(dest_size>data_size){
      print_debug("Actual size is bigger than load data!\n");
      if(data_size>0) pack_fread(dest,data_size,fin);
      return;
   }
   if(dest_size<data_size){
      print_debug("Actual size is smaller than load data!\n");
      if(dest_size>0) pack_fread(dest,dest_size,fin);
      for(ta=dest_size;ta<data_size;ta++){	// Slowly skip the excess data
         tb = pack_getc(fin);
      }
      return;
   }
}

void NewLoad(PACKFILE *fin)
{
   int ta,tb,load_done;
   UINT32 t_size,t_id;

   // global header

   t_size = pack_igetl(fin);

   load_done=0;

   do{

   // read header

   t_id   = pack_mgetl(fin);
   t_size = pack_igetl(fin);

   switch(t_id){
      case SAVE_RAM:
         read_safe_data(RAM,RAMSize,t_size,fin);
      break;
      case SAVE_END:
         print_debug("Load Completed Successfully\n");
         load_done=1;
      break;
      case EOF:
         print_debug("Load Completed, but 'END.' marker was not found\n");
         load_done=1;
      break;
      case SAVE_MOUSE: // Only saved when GameMouse is true
      	p1_trackball_x = t_size & 0xffff;
	p1_trackball_y = t_size >> 16;
	break;
      default:
         tb=0;
         for(ta=0;ta<SaveDataCount && tb==0;ta++){
            if(save_data_list[ta].id == t_id){
               read_safe_data(save_data_list[ta].source,save_data_list[ta].size,t_size,fin);
               tb=1;
            }
         }
         if(tb==0){
            print_debug("Unexpected ID in savefile: %08x/%08x\n",t_id,t_size);
            read_safe_data(NULL,0,t_size,fin);
         }
      break;
   }

   }while(!load_done);
   ProcessCallbackList(CALLBACK_LOAD|CALLBACK_CORE);
   ProcessCallbackList(CALLBACK_LOAD);
}

void do_load_state(char *name) {
  PACKFILE *fin;
  char str[256],*disp_str;

   if(RAMSize){
     if (strstr(name,SLASH)) // The name is already a path
       strcpy(str,name);
     else
       sprintf(str,"%ssavegame" SLASH "%s",dir_cfg.exe_path,name);
   disp_str = strstr(str,"savegame");
   if (!disp_str) disp_str = str;
   if(!(fin=pack_fopen(str,F_READ_PACKED))){
     // printf("errno %d %s for %s flags %s\n",errno,strerror(errno),str,F_READ_PACKED);
     if(!(fin=pack_fopen(str,F_READ))){
        print_ingame(120,"Unable to load: %s", disp_str);
        return;
     }
   }

   savegame_version = pack_mgetl(fin);

   sa_pause_sound();
   stop_cpu_main();

   switch(savegame_version){
      case SAVE_FILE_TYPE_0:
	print_ingame(120,"savegame" SLASH "%s.sv%d is too ancient!", current_game->main_name, SaveSlot);
         pack_fclose(fin);
         return;
      break;
      case SAVE_FILE_TYPE_1:
      case SAVE_FILE_TYPE_2:
         NewLoad(fin);
	 update_timers();
      break;
      default:
	print_ingame(120,"savegame" SLASH "%s.sv%d is not recognised", current_game->main_name, SaveSlot);
         pack_fclose(fin);
         return;
      break;
   }

   pack_fclose(fin);

   print_ingame(120,"Loaded from: %s", disp_str);

   RefreshBuffers=1;

   if (!raine_cfg.req_pause_game) {
     start_cpu_main();
     sa_unpause_sound();
     reset_ingame_timer();
   }

   }
   else{
      print_ingame(120,"Game does not support Loading.");
   }

   reset_ingame_timer();

   print_debug("END: GameLoad()\n");
}

void GameLoad(void)
{
   char str[256];

   print_debug("BEGIN: GameLoad()\n");

   sprintf(str,"%s.sv%d", current_game->main_name, SaveSlot);
   do_load_state(str);
}

void GameLoadName(void)
{
  char str[256];

   print_debug("BEGIN: GameLoadName()\n");

   sprintf(str,"savegame" SLASH "%s" SLASH,current_game->main_name);
   sa_pause_sound();
   stop_cpu_main();
   setup_gui();
   if (raine_file_select("Load game",str,NULL))
     do_load_state(str);
   release_gui();
}

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

#define MAX_EPROM	(4)

typedef struct EPR_DATA
{
   UINT8 *source;		// Pointer to the data in memory
   UINT32 size;			// Size of the data in bytes
   UINT8 flags;			// Flags
} EPR_DATA;

static struct EPR_DATA eeprom_list[MAX_EPROM];

static int eeprom_count;
UINT8 *default_eeprom;
UINT16 default_eeprom_size;

void clear_eeprom_list(void)
{
   eeprom_count=0;
   default_eeprom = NULL;
   default_eeprom_size = 0;
}

void add_eeprom(UINT8 *source, UINT32 size, UINT8 flags)
{
   int ta;

   ta = eeprom_count;

   eeprom_list[ta].source = source;
   eeprom_list[ta].size   = size;
   eeprom_list[ta].flags  = flags;

   memset(eeprom_list[ta].source,0xff,eeprom_list[ta].size);

   eeprom_count++;
}

static char *epr_ext[MAX_EPROM]=
{
   ".epr",
   ".ep0",
   ".ep1",
   ".ep2",
};

int load_eeprom(void)
{
   char str[256];
   int ta,ret=0;

   for(ta=0;ta<eeprom_count;ta++){

   sprintf(str,"%ssavedata" SLASH "%s%s", dir_cfg.exe_path, current_game->main_name, epr_ext[ta]);
   ret = load_file(str, eeprom_list[ta].source, eeprom_list[ta].size);
   if(!ret){
     memset(eeprom_list[ta].source,0xff,eeprom_list[ta].size);
     if (default_eeprom && default_eeprom_size) {
       memcpy(eeprom_list[ta].source,default_eeprom,default_eeprom_size);
       continue;
     }
   }

   }
   return ret;
}

void save_eeprom(void)
{
   char str[256];
   int ta;

   for(ta=0;ta<eeprom_count;ta++){
      sprintf(str,"%ssavedata" SLASH "%s%s", dir_cfg.exe_path, current_game->main_name, epr_ext[ta]);
      save_file(str, eeprom_list[ta].source, eeprom_list[ta].size);
   }
}

void next_save_slot(void)
{
   SaveSlot++;

   if(SaveSlot>9)
      SaveSlot=0;

   print_ingame(120,"Save Slot %01d Selected",SaveSlot);
}
