/* Mednafen - Multi-system Emulator
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include        <string.h>
#include	<stdio.h>
#include	<stdlib.h>
#include	<stdarg.h>
#include	<errno.h>
#include	<sys/types.h>
#include	<sys/stat.h>
#include	<unistd.h>
#include	<trio/trio.h>
#include	<assert.h>

#include	"types.h"
#include	"mednafen.h"
#include	"netplay.h"
#include	"netplay-driver.h"
#include	"general.h"
#include	"endian.h"
#include        "memory.h"

#include	"state.h"
#include	"movie.h"
#include        "video.h"
#include	"file.h"
#include	"wave.h"
#include	"cdromif.h"
#include	"mempatcher.h"

#include	"minilzo.h"

static MDFNSetting MednafenSettings[] =
{
  { "snapname", gettext_noop("If value is true, use an alternate naming scheme(file base and numeric) for screen snapshots."), MDFNST_BOOL, "0"},
  { "dfmd5", gettext_noop("Include the MD5 hash of the loaded game in the filenames of the data file(save states, SRAM backups) Mednafen creates."), MDFNST_BOOL, "1" },
  { "path_snap", gettext_noop("Path override for screen snapshots."), MDFNST_STRING, "" },
  { "path_sav", gettext_noop("Path override for save games and nonvolatile memory."), MDFNST_STRING, "" },
  { "path_state", gettext_noop("Path override for save states."), MDFNST_STRING, "" },
  { "path_movie", gettext_noop("Path override for movies."), MDFNST_STRING, "" },
  { "path_cheat", gettext_noop("Path override for cheats."), MDFNST_STRING, "" },
  { "path_palette", gettext_noop("Path override for custom palettes."), MDFNST_STRING, "" },
  { NULL }
};

static char *PortDeviceCache[16];
static void *PortDataCache[16];
static uint32 PortDataLenCache[16];

MDFNGI *MDFNGameInfo = NULL;
static bool CDInUse = 0;
static float LastSoundMultiplier;

void MDFNI_CloseGame(void)
{
 if(MDFNGameInfo)
 {
  #ifdef NETWORK
  if(MDFNnetplay)
   MDFNI_NetplayStop();
  #endif
  MDFNMOV_Stop();
  MDFNGameInfo->CloseGame();
  if(MDFNGameInfo->name)
  {
   free(MDFNGameInfo->name);
   MDFNGameInfo->name=0;
  }
  MDFNMP_Kill();

  MDFNGameInfo = NULL;
  MDFN_StateEvilEnd();
  #ifdef NEED_CDEMU
  if(CDInUse)
  {
   CDIF_Close();
   CDInUse = 0;
  }
  #endif
 }
}

int MDFNI_NetplayStart(uint32 local_players, uint32 netmerge, const std::string &nickname, const std::string &game_key, const std::string &connect_password)
{
 return(NetplayStart((const char**)PortDeviceCache, PortDataLenCache, local_players, netmerge, nickname, game_key, connect_password));


}



extern MDFNGI EmulatedNES, EmulatedGBA, EmulatedGB, EmulatedLynx, EmulatedNGP, EmulatedPCE, EmulatedPCFX;

// The order of systems in this struct MUST match the GISYS_* defines in git.h! (FIXME so this is no longer required)
MDFNGI *MDFNSystems[] = 
{ 
 #ifdef WANT_NES_EMU
 &EmulatedNES,
 #endif

 #ifdef WANT_GB_EMU
 &EmulatedGB, 
 #endif

 #ifdef WANT_GBA_EMU
 &EmulatedGBA,
 #endif

 #ifdef WANT_PCE_EMU
 &EmulatedPCE,
 #endif

 #ifdef WANT_LYNX_EMU
 &EmulatedLynx, 
 #endif

 #ifdef WANT_PCFX_EMU
 &EmulatedPCFX, 
 #endif

 #ifdef WANT_NGP_EMU
 &EmulatedNGP,
 #endif
};

int MDFNSystemCount = sizeof(MDFNSystems) / sizeof(MDFNGI *);

#ifdef NEED_CDEMU
MDFNGI *MDFNI_LoadCD(const char *sysname, const char *devicename)
{
 MDFNI_CloseGame();

 LastSoundMultiplier = 1;

 int ret = CDIF_Open(devicename);
 if(!ret)
 {
  MDFN_PrintError(_("Error opening CD."));
  return(0);
 }

 if(sysname == NULL)
 {
  uint8 sector_buffer[2048];

  memset(sector_buffer, 0, sizeof(sector_buffer));

  sysname = "pce";

  for(int32 track = CDIF_GetFirstTrack(); track <= CDIF_GetLastTrack(); track++)
  {
   CDIF_Track_Format format;
   if(CDIF_GetTrackFormat(track, format) && format == CDIF_FORMAT_MODE1)
   {
    CDIF_ReadSector(sector_buffer, CDIF_GetTrackStartPositionLBA(track), 1);
    if(!strncmp("PC-FX:Hu_CD-ROM", (char*)sector_buffer, strlen("PC-FX:Hu_CD-ROM")))
    {
     sysname = "pcfx";
     break;
    }
   }
  }

 } 

 for(int x = 0; x < MDFNSystemCount; x++)
 {
  if(!strcasecmp(MDFNSystems[x]->shortname, sysname))
  {
   if(!MDFNSystems[x]->LoadCD)
   {
    MDFN_PrintError(_("Specified system \"%s\" doesn't support CDs!"), sysname);
    return(0);
   }
   MDFNGameInfo = MDFNSystems[x];

   if(!(MDFNSystems[x]->LoadCD()))
   {
    CDIF_Close();
    MDFNGameInfo = NULL;
    return(0);
   }
   CDInUse = 1;

   MDFNSS_CheckStates();
   MDFNMOV_CheckMovies();

   MDFN_ResetMessages();   // Save state, status messages, etc.
   //MDFN_indent(-1);
   MDFN_StateEvilBegin();
   return(MDFNGameInfo);
  }
 }
 MDFN_PrintError(_("Unrecognized system \"%s\"!"), sysname);
 return(0);
}
#endif



MDFNGI *MDFNI_LoadGame(const char *name)
{
        MDFNFILE *fp;
	int x;
	struct stat stat_buf;

	#ifdef NEED_CDEMU
	if(strlen(name) > 4 && !strcasecmp(name + strlen(name) - 4, ".cue"))
	{
	 return(MDFNI_LoadCD(NULL, name));
	}
	
	if(!stat(name, &stat_buf) && !S_ISREG(stat_buf.st_mode))
	{
	 return(MDFNI_LoadCD(NULL, name));
	}
	#endif

	MDFNI_CloseGame();

	LastSoundMultiplier = 1;

	MDFNGameInfo = NULL;

	MDFN_printf(_("Loading %s...\n\n"),name);

	MDFN_indent(1);

        GetFileBase(name);

	fp=MDFN_fopen(name, MDFN_MakeFName(MDFNMKF_IPS,0,0).c_str(),"rb",0);

	if(!fp)
        {
 	 MDFN_PrintError(_("Error opening \"%s\": %s"), name, strerror(errno));
	 MDFNGameInfo = NULL;
	 return 0;
	}

	for(x=0; x<MDFNSystemCount; x++)
	{
	 int t;

	 if(!MDFNSystems[x]->Load) continue;
         MDFNGameInfo = MDFNSystems[x];
         MDFNGameInfo->soundchan = 0;
         MDFNGameInfo->soundrate = 0;
         MDFNGameInfo->name=0;
         MDFNGameInfo->rotated = 0;
	 t = MDFNGameInfo->Load(name, fp);

	 if(t == 0)
	 {
	  MDFN_fclose(fp);
	  MDFN_indent(-1);
	  MDFNGameInfo = NULL;
	  return(0);
	 }	 
	 else if(t == -1)
	 {
	  if(x == MDFNSystemCount - 1)
	  {
           MDFN_PrintError(_("Unrecognized file format.  Sorry."));
           MDFN_fclose(fp);
           MDFN_indent(-1);
	   MDFNGameInfo = NULL;
           return 0;
	  }
	 }
	 else
	  break;	// File loaded successfully.
	}

        MDFN_fclose(fp);

	MDFNSS_CheckStates();
	MDFNMOV_CheckMovies();

	MDFN_ResetMessages();	// Save state, status messages, etc.

	MDFN_indent(-1);

	if(!MDFNGameInfo->name)
        {
         unsigned int x;
         char *tmp;

         MDFNGameInfo->name = (UTF8 *)strdup(GetFNComponent(name));

         for(x=0;x<strlen((char *)MDFNGameInfo->name);x++)
         {
          if(MDFNGameInfo->name[x] == '_')
           MDFNGameInfo->name[x] = ' ';
         }
         if((tmp = strrchr((char *)MDFNGameInfo->name, '.')))
          *tmp = 0;
        }

        MDFN_StateEvilBegin();
        return(MDFNGameInfo);
}

int MDFNI_Initialize(char *basedir, const std::vector<MDFNSetting> &DriverSettings)
{
        MDFNI_printf(_("Starting Mednafen %s\n"), MEDNAFEN_VERSION);
        MDFN_indent(1);


	memset(PortDataCache, 0, sizeof(PortDataCache));
	memset(PortDataLenCache, 0, sizeof(PortDataLenCache));
	memset(PortDeviceCache, 0, sizeof(PortDeviceCache));

	lzo_init();

	MDFNI_SetBaseDirectory(basedir);

        memset(&FSettings,0,sizeof(FSettings));

	FSettings.SoundVolume=100;
	FSettings.soundmultiplier = 1;
	MDFN_InitFontData();

	// First merge all settable settings, then load the settings from the SETTINGS FILE OF DOOOOM
	MDFN_MergeSettings(MednafenSettings);
	MDFN_MergeSettings(MDFNMP_Settings);

	if(DriverSettings.size())
 	 MDFN_MergeSettings(DriverSettings);

	for(unsigned int x = 0; x < MDFNSystemCount; x++)
	 MDFN_MergeSettings(MDFNSystems[x]);

        if(!MFDN_LoadSettings(basedir))
	 return(0);
        return 1;
}

void MDFNI_Kill(void)
{
 MDFN_SaveSettings();
 //MDFNNES_Kill();

 for(int x = 0; x < sizeof(PortDeviceCache) / sizeof(char *); x++)
 {
  if(PortDeviceCache[x])
  {
   free(PortDeviceCache[x]);
   PortDeviceCache[x] = NULL;
  }
 }
}

void MDFNI_DoRewind(void)
{
 if(MDFNMOV_IsPlaying())
  MDFN_DispMessage((UTF8*)_("Can't rewind during movie playback(yet!)."));
 else if(MDFNnetplay)
  MDFN_DispMessage((UTF8*)_("Silly-billy, can't rewind during netplay."));
 else
  MDFNGameInfo->DoRewind();
}

static uint8 *gbpad;

void MDFNI_SetGBInput(uint8 *yaya)
{
 gbpad = yaya;
}

void MDFNI_Emulate(uint32 *pXBuf, MDFN_Rect *LineWidths, uint32 *pHLOverlayBuf, MDFN_Rect *HLOverlayLineWidths, int16 **SoundBuf, int32 *SoundBufSize, int skip, float soundmultiplier)
{
 FSettings.soundmultiplier = soundmultiplier;

 if(soundmultiplier != LastSoundMultiplier)
 {
  MDFNGameInfo->SetSoundMultiplier(soundmultiplier);
  LastSoundMultiplier = soundmultiplier;
 }

 #ifdef NETWORK
 if(MDFNnetplay)
 {
  NetplayUpdate((const char**)PortDeviceCache, PortDataCache, PortDataLenCache, MDFNGameInfo->InputInfo->InputPorts);
 }
 #endif

 for(int x = 0; x < 16; x++)
  if(PortDataCache[x])
   MDFNMOV_AddJoy(PortDataCache[x], PortDataLenCache[x]);

 MDFNGameInfo->Emulate(pXBuf, LineWidths, pHLOverlayBuf, HLOverlayLineWidths, SoundBuf, SoundBufSize, skip);

 if(SoundBuf && SoundBufSize)
 {
  //if(CDInUse)
  // CDIF_Sound_Mix(*SoundBuf, *SoundBufSize);
  MDFN_WriteWaveData(*SoundBuf, *SoundBufSize); /* This function will just return if sound recording is off. */
 }
}


MDFNS FSettings;

static int curindent = 0;

void MDFN_indent(int indent)
{
 curindent += indent;
}

static uint8 lastchar = 0;
void MDFN_printf(const char *format, ...)
{
 char *format_temp;
 char *temp;
 unsigned int x, newlen;

 va_list ap;
 va_start(ap,format);


 // First, determine how large our format_temp buffer needs to be.
 uint8 lastchar_backup = lastchar; // Save lastchar!
 for(newlen=x=0;x<strlen(format);x++)
 {
  if(lastchar == '\n' && format[x] != '\n')
  {
   int y;
   for(y=0;y<curindent;y++)
    newlen++;
  }
  newlen++;
  lastchar = format[x];
 }

 format_temp = (char *)malloc(newlen + 1); // Length + NULL character, duh
 
 // Now, construct our format_temp string
 lastchar = lastchar_backup; // Restore lastchar
 for(newlen=x=0;x<strlen(format);x++)
 {
  if(lastchar == '\n' && format[x] != '\n')
  {
   int y;
   for(y=0;y<curindent;y++)
    format_temp[newlen++] = ' ';
  }
  format_temp[newlen++] = format[x];
  lastchar = format[x];
 }

 format_temp[newlen] = 0;

 temp = trio_vaprintf(format_temp, ap);
 free(format_temp);

 MDFND_Message(temp);
 free(temp);

 va_end(ap);
}

void MDFN_PrintError(const char *format, ...)
{
 char *temp;

 va_list ap;

 va_start(ap, format);

 temp = trio_vaprintf(format, ap);
 MDFND_PrintError(temp);
 free(temp);

 va_end(ap);
}

MDFNException::MDFNException()
{


}

MDFNException::~MDFNException()
{


}

void MDFNException::AddPre(const char *format, ...)
{
 char oldmsg[sizeof(TheMessage)];

 strcpy(oldmsg, TheMessage);

 va_list ap;
 va_start(ap, format);
 trio_vsnprintf(oldmsg, sizeof(TheMessage), format, ap);
 va_end(ap);

 int freelen = sizeof(TheMessage) - strlen(TheMessage);
 strncpy(TheMessage + strlen(TheMessage), oldmsg, freelen);
}

void MDFNException::AddPost(const char *format, ...)
{
 int freelen = sizeof(TheMessage) - strlen(TheMessage);

 if(freelen <= 0)
 {
  puts("ACKACKACK Exception erorrorololoz");
  return;
 }

 va_list ap;

 va_start(ap, format);
 trio_vsnprintf(TheMessage + strlen(TheMessage), freelen, format, ap);
 va_end(ap);
}


void MDFNI_SetPixelFormat(int rshift, int gshift, int bshift, int ashift)
{
 FSettings.rshift = rshift;
 FSettings.gshift = gshift;
 FSettings.bshift = bshift;
 FSettings.ashift = ashift;

 if(MDFNGameInfo)
  MDFNGameInfo->SetPixelFormat(rshift, gshift, bshift);
}


void MDFN_DoSimpleCommand(int cmd)
{
 MDFNGameInfo->DoSimpleCommand(cmd);
}

void MDFN_QSimpleCommand(int cmd)
{
 #ifdef NETWORK
 if(MDFNnetplay)
  MDFNNET_SendCommand(cmd, 0);
 else
 #endif
 {
  if(!MDFNMOV_IsPlaying())
  {
   MDFN_DoSimpleCommand(cmd);
   MDFNMOV_AddCommand(cmd);
  }
 }
}

void MDFNI_Power(void)
{
 if(MDFNGameInfo)
  MDFN_QSimpleCommand(MDFNNPCMD_POWER);
}

void MDFNI_Reset(void)
{
 if(MDFNGameInfo)
  MDFN_QSimpleCommand(MDFNNPCMD_RESET);
}


void MDFNI_SetSoundVolume(uint32 volume)
{
 FSettings.SoundVolume=volume;
 if(MDFNGameInfo)
 {
  MDFNGameInfo->SetSoundVolume(volume);
 }
}

void MDFNI_Sound(int Rate)
{
 FSettings.SndRate=Rate;
 if(MDFNGameInfo)
 {
  MDFNGameInfo->Sound(Rate);
 }
}

void MDFNI_ToggleLayer(int which)
{
 if(MDFNGameInfo)
 {
  const char *goodies = MDFNGameInfo->LayerNames;
  int x = 0;
  while(x != which)
  {
   while(*goodies)
    goodies++;
   goodies++;
   if(!*goodies) return; // ack, this layer doesn't exist.
   x++;
  }
  if(MDFNGameInfo->ToggleLayer(which))
   MDFN_DispMessage((UTF8*)_("%s enabled."), _(goodies));
  else
   MDFN_DispMessage((UTF8*)_("%s disabled."), _(goodies));
 }
}

void MDFNI_SetInput(int port, const char *type, void *ptr, uint32 ptr_len_thingy)
{
 if(MDFNGameInfo)
 {
  assert(port < 16);

  PortDataCache[port] = ptr;
  PortDataLenCache[port] = ptr_len_thingy;

  if(PortDeviceCache[port])
  {
   free(PortDeviceCache[port]);
   PortDeviceCache[port] = NULL;
  }

  PortDeviceCache[port] = strdup(type);

  MDFNGameInfo->SetInput(port, type, ptr);
 }
}

int MDFNI_DiskInsert(int oride)
{
 if(MDFNGameInfo && MDFNGameInfo->DiskInsert)
  return(MDFNGameInfo->DiskInsert(oride));
}

int MDFNI_DiskEject(void)
{
 if(MDFNGameInfo && MDFNGameInfo->DiskEject)
  return(MDFNGameInfo->DiskEject());
}

int MDFNI_DiskSelect(void)
{
 if(MDFNGameInfo && MDFNGameInfo->DiskSelect)
  return(MDFNGameInfo->DiskSelect());
}

