/* 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	"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", "If value is true, use an alternate naming scheme(file base and numeric) for screen snapshots.", MDFNST_BOOL, "0"},
  { "dfmd5", "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", "Path override for screen snapshots.", MDFNST_STRING, "" },
  { "path_sav", "Path override for save games and nonvolatile memory.", MDFNST_STRING, "" },
  { "path_state", "Path override for save states.", MDFNST_STRING, "" },
  { "path_movie", "Path override for movies.", MDFNST_STRING, "" },
  { "path_cheat", "Path override for cheats.", MDFNST_STRING, "" },
  { "path_palette", "Path override for custom palettes.", MDFNST_STRING, "" },
  { NULL }
};


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;
  }
  MDFNGameInfo = NULL;
  MDFN_StateEvilEnd();
  if(CDInUse)
  {
   CDIF_Close();
   CDInUse = 0;
  }
 }
}


extern MDFNGI EmulatedNES, EmulatedGBA, EmulatedGB, EmulatedLynx, EmulatedNGP, EmulatedPCE, EmulatedPCFX;
static MDFNGI *Systems[7] = { &EmulatedNES, &EmulatedGB, &EmulatedLynx, &EmulatedNGP, &EmulatedPCE, &EmulatedPCFX, &EmulatedGBA };

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)
 {
  sysname = "pce";

 } 

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

   if(!(Systems[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);
}




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

	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));
	}

	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<7; x++)
	{
	 int t;

	 if(!Systems[x]->Load) continue;
         MDFNGameInfo = Systems[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 == 6)
	  {
           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, MDFNSetting *DriverSettings)
{
        MDFNI_printf(_("Starting Mednafen %s\n"), MEDNAFEN_VERSION);
        MDFN_indent(1);

	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)
 	 MDFN_MergeSettings(DriverSettings);
	for(unsigned int x = 0; x < sizeof(Systems) / sizeof(MDFNGI *); x++)
	 MDFN_MergeSettings(Systems[x]);
        if(!MFDN_LoadSettings(basedir))
	 return(0);
        return 1;
}

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

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, int16 **SoundBuf, int32 *SoundBufSize, int skip, float soundmultiplier)
{
 FSettings.soundmultiplier = soundmultiplier;

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

 MDFNGameInfo->Emulate(pXBuf, LineWidths, 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, int type, void *ptr, int attrib)
{
 if(MDFNGameInfo)
 {
  MDFNGameInfo->SetInput(port, type, ptr, attrib);
 }
}
