/* Mednafen - Multi-system Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2002 Xodnizel
 *
 * 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 <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

#include <locale.h>

#ifdef WIN32
#include <windows.h>
#endif

#include "main.h"

#include "input.h"
#include "joystick.h"
#include "video.h"
#include "sound.h"
#ifdef NETWORK
#include "netplay.h"
#endif

char *PCE_STR = "pce" ;

bool pending_save_state, pending_snapshot, pending_save_movie;
static volatile Uint32 MainThreadID = 0;
static bool ffnosound;

static const char *CSD_xres = "Full-screen horizontal resolution.";
static const char *CSD_yres = "Full-screen vertical resolution.";
static const char *CSD_xscale = "The scaling factor for the X axis.";
static const char *CSD_yscale = "The scaling factor for the Y axis.";
static const char *CSD_xscalefs = "The scaling factor for the X axis in fullscreen mode.";
static const char *CSD_yscalefs = "The scaling factor for the Y axis in fullscreen mode.";
static const char *CSD_scanlines = "Enable scanlines with specified transparency.";
static const char *CSD_stretch = "Stretch to fill screen.";
static const char *CSD_videoip = "Enable bilinear interpolation.";
static const char *CSD_special = "Enable specified special video scaler.";

static MDFNSetting DriverSettings[] =
{
  #ifdef NETWORK
  { "nethost", "Network play server hostname.", MDFNST_STRING, "mednafen.com" },
  { "netport", "Port to connect to on the server.", MDFNST_UINT, "4046", "1", "65535" },
  { "netpassword", "Password to connect to the netplay server.", MDFNST_STRING, "" },
  { "netlocalplayers", "Number of local players for network play.", MDFNST_UINT, "1", "1", "8" },
  { "netnick", "Nickname to use for network play chat.", MDFNST_STRING, "" },
  { "netgamekey", "Key to hash with the MD5 hash of the game.", MDFNST_STRING, "" },
  { "netmerge", "Merge input to this player # on the server.", MDFNST_UINT, "0" },
  #endif

  { "nes.xres", CSD_xres, MDFNST_UINT, "640" },
  { "nes.yres", CSD_yres, MDFNST_UINT, "480" },
  { "nes.xscale", CSD_xscale, MDFNST_FLOAT, "2" },
  { "nes.yscale", CSD_yscale, MDFNST_FLOAT, "2" },
  { "nes.xscalefs", CSD_xscalefs, MDFNST_FLOAT, "2" },
  { "nes.yscalefs", CSD_yscalefs, MDFNST_FLOAT, "2" },
  { "nes.scanlines", CSD_scanlines, MDFNST_UINT, "0" },
  { "nes.stretch", CSD_stretch, MDFNST_BOOL, "0" },
  { "nes.videoip", CSD_videoip, MDFNST_BOOL, "0" },
  { "nes.special", CSD_special, MDFNST_STRING, "none", NULL, NULL, MDFND_ValidateSpecialScalerSetting },

  { "gb.xres", CSD_xres, MDFNST_UINT, "800" },
  { "gb.yres", CSD_yres, MDFNST_UINT, "600" },
  { "gb.xscale", CSD_xscale, MDFNST_FLOAT, "4" },
  { "gb.yscale", CSD_yscale, MDFNST_FLOAT, "4" },
  { "gb.xscalefs", CSD_xscalefs, MDFNST_FLOAT, "4" },
  { "gb.yscalefs", CSD_yscalefs, MDFNST_FLOAT, "4" },
  { "gb.scanlines", CSD_scanlines, MDFNST_UINT, "0" },
  { "gb.stretch", CSD_stretch, MDFNST_BOOL, "0" },
  { "gb.videoip", CSD_videoip, MDFNST_BOOL, "0" },
  { "gb.special", CSD_special, MDFNST_STRING, "none", NULL, NULL, MDFND_ValidateSpecialScalerSetting },

  { "gba.xres", CSD_xres, MDFNST_UINT, "800" },
  { "gba.yres", CSD_yres, MDFNST_UINT, "600" },
  { "gba.xscale", CSD_xscale, MDFNST_FLOAT, "3" },
  { "gba.yscale", CSD_yscale, MDFNST_FLOAT, "3" },
  { "gba.xscalefs", CSD_xscalefs, MDFNST_FLOAT, "3" },
  { "gba.yscalefs", CSD_yscalefs, MDFNST_FLOAT, "3" },
  { "gba.scanlines", CSD_scanlines, MDFNST_UINT, "0" },
  { "gba.stretch", CSD_stretch, MDFNST_BOOL, "0" },
  { "gba.videoip", CSD_videoip, MDFNST_BOOL, "0" },
  { "gba.special", CSD_special, MDFNST_STRING, "none", NULL, NULL, MDFND_ValidateSpecialScalerSetting },

  { "lynx.xres", CSD_xres, MDFNST_UINT, "800" },
  { "lynx.yres", CSD_yres, MDFNST_UINT, "600" },
  { "lynx.xscale", CSD_xscale, MDFNST_FLOAT, "4" },
  { "lynx.yscale", CSD_yscale, MDFNST_FLOAT, "4" },
  { "lynx.xscalefs", CSD_xscalefs, MDFNST_FLOAT, "4" },
  { "lynx.yscalefs", CSD_yscalefs, MDFNST_FLOAT, "4" },
  { "lynx.scanlines", CSD_scanlines, MDFNST_UINT, "0" },
  { "lynx.stretch", CSD_stretch, MDFNST_BOOL, "0" },
  { "lynx.videoip", CSD_videoip, MDFNST_BOOL, "0" },
  { "lynx.special", CSD_special, MDFNST_STRING, "none", NULL, NULL, MDFND_ValidateSpecialScalerSetting },

  { "pce.xres", CSD_xres, MDFNST_UINT, "1024" },
  { "pce.yres", CSD_yres, MDFNST_UINT, "768" },
  { "pce.xscale", CSD_xscale, MDFNST_FLOAT, "3" },
  { "pce.yscale", CSD_yscale, MDFNST_FLOAT, "3" },
  { "pce.xscalefs", CSD_xscalefs, MDFNST_FLOAT, "3" },
  { "pce.yscalefs", CSD_yscalefs, MDFNST_FLOAT, "3" },
  { "pce.scanlines", CSD_scanlines, MDFNST_UINT, "0" },
  { "pce.stretch", CSD_stretch, MDFNST_BOOL, "0" },
  { "pce.videoip", CSD_videoip, MDFNST_BOOL, "1" },
  { "pce.special", CSD_special, MDFNST_STRING, "none", NULL, NULL, MDFND_ValidateSpecialScalerSetting },

  { "fs", "Enable fullscreen mode.", MDFNST_BOOL, "0", },
  { "vdriver", "Select video driver.  0 = OpenGL, 1 = SDL surface blits", MDFNST_UINT, "0", "0", "1" },
  { "doublebuf", "Enable video double buffering.", MDFNST_BOOL, "1" },
  { "ffspeed", "Fast-forwarding speed multiplier.", MDFNST_UINT, "4", "1", "15" },
  { "fftoggle", "Treat the fast-forward button as a toggle.", MDFNST_BOOL, "0" },
  { "ffnosound", "Silence sound output when fast-forwarding.", MDFNST_BOOL, "0" },
  { "autofirefreq", "Auto-fire frequency.", MDFNST_UINT, "3", "0", "1000" },
  { "nes.fcexp", "Select Famicom expansion-port device.", MDFNST_STRING, "none", NULL, NULL, MDFND_ValidateFamicomExpansionPort },
  { "nes.input1", "Select input device for input port 1.", MDFNST_STRING, "gamepad", NULL, NULL, MDFND_ValidateNESInputPort },
  { "nes.input2", "Select input device for input port 2.", MDFNST_STRING, "gamepad", NULL, NULL , MDFND_ValidateNESInputPort },
  { "nothrottle", "Disable speed throttling when sound is disabled.", MDFNST_BOOL, "0"},
  { "autosave", "Automatically save and load save states when a game is closed or loaded, respectively.", MDFNST_BOOL, "0"},
  { "sounddriver", "Select sound driver.", MDFNST_STRING, "default", NULL, NULL },
  { "sounddevice", "Select sound output device.", MDFNST_STRING, "default", NULL, NULL },
  { "soundvol", "Sound volume level, in percent.", MDFNST_UINT, "100", "0", "150" },
  { "sound", "Enable sound emulation.", MDFNST_BOOL, "1" },
  { "soundbufsize", "Specifies the desired size of the sound buffer, in milliseconds.", MDFNST_UINT, 
   #ifdef WIN32
   "52"
   #else
   "32"
   #endif
   ,"1", "1000" },
  { "soundrate", "Specifies the sound playback rate, in frames per second(\"Hz\").", MDFNST_UINT, "44100", "8192", "48000"},
  //{ "soundrate", "Specifies the sound playback rate, in frames per second(\"Hz\").", MDFNST_UINT, "48000", "8192", "48000"},
  { NULL }
};


static SDL_Thread *GameThread;
static uint32 *VTBuffer[2] = { NULL, NULL };
static MDFN_Rect *VTLineWidths[2] = { NULL, NULL };
static uint32 *VTOSDBuffer[2] = { NULL, NULL };

static volatile int VTBackBuffer = 0;
static SDL_mutex *VTMutex = NULL, *EVMutex = NULL;
static volatile uint32 *VTReady;
static volatile MDFN_Rect *VTLWReady;
static volatile uint32 *VTOSDReady;
static volatile MDFN_Rect VTDisplayRect;

//static
 char *soundrecfn=0;	/* File name of sound recording. */

static int inited=0;
static char *DrBaseDirectory;

static void DriverKill(void);
int gametype;

MDFNGI *CurGame=NULL;

static void ParseGI(MDFNGI *gi)
{
 InitOtherInput(gi);
 gametype=gi->nes.type;
}

void xbox_errormessage( const char *s) ;

void MDFND_PrintError(const char *s)
{
#ifdef _XBOX
	xbox_errormessage( s ) ;
#else
 if(SDL_ThreadID() != MainThreadID)
 {
  SDL_Event evt;

  evt.user.type = SDL_USEREVENT;
  evt.user.code = CEVT_PRINTERROR;
  evt.user.data1 = strdup(s);
  SDL_PushEvent(&evt);
 }
 else
 {
  puts(s);
 }
#endif
}

void MDFND_Message(const char *s)
{
 if(SDL_ThreadID() != MainThreadID)
 {
  SDL_Event evt;

  evt.user.type = SDL_USEREVENT;
  evt.user.code = CEVT_PRINTMESSAGE;
  evt.user.data1 = strdup(s);
  SDL_PushEvent(&evt);
 }
 else
  fputs(s,stdout);
}

static CFGSTRUCT fceuconfig[]={
	ADDCFGSTRUCT(InputConfig),
	ENDCFGSTRUCT
};

static void SaveConfig(void)
{	
        std::string tdir;

        tdir = std::string(DrBaseDirectory);
        tdir += PSS;
        tdir += "inputmap.cfg";

        SaveMDFNConfig(tdir.c_str(), fceuconfig);
}

int oldjoyhash_kludge = 0;	// Pre 0.4.1 joystick hashes were calculated incorrectly
				// This kludge will allow old hashes to be imported.
static void LoadConfig(void)
{
	std::string tdir;

        tdir = std::string(DrBaseDirectory);
        tdir += PSS;
        tdir += "inputmap.cfg";

        if(!LoadMDFNConfig(tdir.c_str(),fceuconfig))
	{
         tdir = std::string(DrBaseDirectory);
         tdir += PSS;
         tdir += "mednafen03x.cfg";

         if(LoadMDFNConfig(tdir.c_str(),fceuconfig))
          oldjoyhash_kludge = 1;
	}
	InputUserActiveFix();
}

static void CreateDirs(void)
{
 char *subs[6]={"mcs","mcm","snaps","gameinfo","sav","cheats"};
 char tdir[2048];
 int x;

 #ifdef WIN32
 mkdir(DrBaseDirectory);
 for(x=0;x<6;x++)
 {
  sprintf(tdir,"%s"PSS"%s",DrBaseDirectory,subs[x]);
  mkdir(tdir);
 }
 #else
 mkdir(DrBaseDirectory,S_IRWXU);
 for(x=0;x<6;x++)
 {
  sprintf(tdir,"%s"PSS"%s",DrBaseDirectory,subs[x]);
  mkdir(tdir,S_IRWXU);
 }
 #endif
}

#ifndef WIN32
static void SetSignals(void (*t)(int))
{
  int sigs[10]={SIGINT,SIGTERM,SIGHUP,SIGPIPE,SIGSEGV,SIGFPE,SIGALRM,SIGABRT,SIGUSR1,SIGUSR2};
  int x;
  for(x=0;x<10;x++)
   signal(sigs[x],t);
}

static void CloseStuff(int signum)
{
	//printf("%d\n",SDL_ThreadID());
	//DriverKill();
        printf(_("\nSignal %d has been caught and dealt with...\n"),signum);
        switch(signum)
        {
         case SIGINT:printf(_("How DARE you interrupt me!\n"));break;
         case SIGTERM:printf(_("MUST TERMINATE ALL HUMANS\n"));break;
         case SIGHUP:printf(_("Reach out and hang-up on someone.\n"));break;
         case SIGPIPE:printf(_("The pipe has broken!  Better watch out for floods...\n"));break;
         case SIGSEGV:printf(_("Iyeeeeeeeee!!!  A segmentation fault has occurred.  Have a fluffy day.\n"));break;
	 /* So much SIGBUS evil. */
	 #ifdef SIGBUS
	 #if(SIGBUS!=SIGSEGV)
         case SIGBUS:printf(_("I told you to be nice to the driver.\n"));break;
	 #endif
	 #endif
         case SIGFPE:printf(_("Those darn floating points.  Ne'er know when they'll bite!\n"));break;
         case SIGALRM:printf(_("Don't throw your clock at the meowing cats!\n"));break;
         case SIGABRT:printf(_("Abort, Retry, Ignore, Fail?\n"));break;
         case SIGUSR1:
         case SIGUSR2:printf(_("Killing your processes is not nice.\n"));break;
        }
        exit(1);
}
#endif

static ARGPSTRUCT *MDFN_Internal_Args = NULL;

static int HokeyPokeyFallDown(char *name, char *value)
{
 if(!MDFNI_SetSetting(name + 1, value))
  return(0);
 return(1);
}

static void DeleteInternalArgs(void)
{
 if(!MDFN_Internal_Args) return;
 ARGPSTRUCT *argptr = MDFN_Internal_Args;

 do
 {
  free(argptr->name);
  argptr++;
 } while(argptr->name || argptr->var || argptr->subs);
 free(MDFN_Internal_Args);
 MDFN_Internal_Args = NULL;
}

static void MakeMednafenArgsStruct(void)
{
 const std::vector <MDFNCS> *settings;

 settings = MDFNI_GetSettings();

 MDFN_Internal_Args = (ARGPSTRUCT *)malloc(sizeof(ARGPSTRUCT) * (1 + settings->size()));

 unsigned int x;

 for(x = 0; x < settings->size(); x++)
 {
  MDFN_Internal_Args[x].name = (char *)malloc(strlen( (*settings)[x].name) + 1 + 1);
  MDFN_Internal_Args[x].name[0] = '-';
  strcpy(MDFN_Internal_Args[x].name + 1, (*settings)[x].name);
  MDFN_Internal_Args[x].description = (*settings)[x].desc->description;
  MDFN_Internal_Args[x].var = NULL;
  MDFN_Internal_Args[x].subs = (void *)HokeyPokeyFallDown;
  MDFN_Internal_Args[x].substype = 0x2000;
 }
 MDFN_Internal_Args[x].name = NULL;
 MDFN_Internal_Args[x].var = NULL;
 MDFN_Internal_Args[x].subs = NULL;
}

static int netconnect = 0;
static char * loadcd = NULL;
static int DoArgs(int argc, char *argv[], char **filename)
{
        ARGPSTRUCT MDFNArgs[]={
	 {"-loadcd", "Load and boot a CD for the specified system.", 0, &loadcd, 0x4001},
         {"-inputcfg", "Configure mapping of physical device inputs to specified virtual device.", 0, (void *)MDFND_InputCfg,0x2000},
         {"-ckconfig", "Configure mapping of physical buttons to specified command.", 0, (void *)MDFND_CKConfig, 0x2000},
	 {"-soundrecord", "Record sound output to the specified filename in the MS WAV format.", 0,&soundrecfn,0x4001},
         {0,NULL, (int *)MDFN_Internal_Args, 0, 0},

         #ifdef NETWORK
	 {"-connect", "Connect to the remote server and start network play.", &netconnect, 0, 0 },
         #endif
	 {0,0,0,0}
        };

	const char *usage_string = _("Usage: %s [OPTION]... [FILE]\n");
	if(argc <= 1)
	{
	 printf(_("No command-line arguments specified.\n\n"));
	 printf(usage_string, argv[0]);
	 printf(_("\tPlease refer to the documentation for option parameters and usage.\n\n"));
	 return(0);
	}
	else if(argc == 2 && (!strcasecmp(argv[1], "-help") || !strcasecmp(argv[1], "--help")))
	{
         printf(usage_string, argv[0]);
	 ShowArgumentsHelp(MDFNArgs);
	 return(0);
	}
	else
	{
	 if(!ParseArguments(argc - 1, &argv[1], MDFNArgs, filename))
	  return(0);

	 if(*filename == NULL && loadcd == NULL)
	 {
	  puts(_("No game filename specified!"));
	  return(0);
	 }
	}
	return(1);
}

static volatile int NeedVideoChange = 0;
int GameLoop(void *arg);
static volatile int GameThreadRun = 0;
void MDFND_Update(uint32 *XBuf, float *Buffer, int Count);

bool sound_active;	// true if sound is enabled and initialized

int LoadGame(const char *path)
{
	MDFNGI *tmp;

	CloseGame();

	pending_save_state = 0;
	pending_save_movie = 0;
	pending_snapshot = 0;

	if(loadcd)
	{
	 if(!(tmp = MDFNI_LoadCD(loadcd, path)))
		return(0);
	}
	else
	{
         if(!(tmp=MDFNI_LoadGame(path)))
	  return 0;
	}
	CurGame=tmp;
        ParseGI(tmp);
        RefreshThrottleFPS(1);
        SDL_mutexP(VTMutex);
        NeedVideoChange = -1;
        SDL_mutexV(VTMutex);
        if(SDL_ThreadID() != MainThreadID)
          while(NeedVideoChange)
	  {
           SDL_Delay(1);
	  }
	sound_active = 0;

	if(MDFN_GetSettingB("sound"))
	 sound_active = InitSound(tmp);

        if(MDFN_GetSettingB("autosave"))
	 MDFNI_LoadState(NULL, "ncq");

	if(netconnect)
	 MDFND_NetworkConnect();
	GameThreadRun = 1;
	GameThread = SDL_CreateThread(GameLoop, NULL);

	ffnosound = MDFN_GetSettingB("ffnosound");
	return 1;
}

/* Closes a game and frees memory. */
int CloseGame(void)
{
	if(!CurGame) return(0);

	GameThreadRun = 0;

	SDL_WaitThread(GameThread, NULL);
	if(MDFN_GetSettingB("autosave"))
	 MDFNI_SaveState(NULL, "ncq", NULL, NULL);

	MDFNI_CloseGame();
	DriverKill();

	CurGame = 0;

	if(soundrecfn)
         MDFNI_EndWaveRecord();

	InputUserActiveFix();
	return(1);
}

static void GameThread_HandleEvents(void);
static volatile int NeedExitNow = 0;
int CurGameSpeed = 1;

void MainRequestExit(void)
{
 NeedExitNow = 1;
}


static int ThrottleCheckFS(void);

static bool InFrameAdvance = 0;
static bool NeedFrameAdvance = 0;

void DoRunNormal(void)
{
 InFrameAdvance = 0;
}

void DoFrameAdvance(void)
{
 InFrameAdvance = 1;
 NeedFrameAdvance = 1;
}


int GameLoop(void *arg)
{
	while(GameThreadRun)
	{
         float *sound;
         int32 ssize;
         static int fskip=0;
        
	 /* If we requested a new video mode, wait until it's set before calling the emulation code again.
	 */
	 while(NeedVideoChange) 
	 { 
	  if(!GameThreadRun) return(1);	// Might happen if video initialization failed
	  SDL_Delay(1);
	  }
         do
         {
	  if(InFrameAdvance && !NeedFrameAdvance)
	  {
	   SDL_Delay(10);
	  }
	 } while(InFrameAdvance && !NeedFrameAdvance);

         //fskip = ThrottleCheckFS();
	 if(pending_snapshot || pending_save_state || pending_save_movie || NeedFrameAdvance)
	  fskip = 0;
 	 NeedFrameAdvance = 0;
         if(NoWaiting) fskip = 1;

	 VTLineWidths[VTBackBuffer][0].w = ~0;

	 int ThisBackBuffer = VTBackBuffer;

	 fskip = sounddanger && ( throttle == 0 ) ;

#if MEDNA_TYPE != MEDNA_PCE
	 
         MDFNI_Emulate((uint32*)xbox_getvidbuffer(), (MDFN_Rect *)VTLineWidths[VTBackBuffer], &sound, &ssize, fskip, CurGameSpeed);
#else
         MDFNI_Emulate((uint32 *)VTBuffer[VTBackBuffer], (MDFN_Rect *)VTLineWidths[VTBackBuffer], &sound, &ssize, fskip, CurGameSpeed);
#endif
	 do
	 {
		 if ( xbox_check_events() )
		 {
			 GameThreadRun = 0 ;
			 NeedExitNow = 1 ;
			 break ;
		 }

          GameThread_HandleEvents();
	  VTBackBuffer = ThisBackBuffer;
          MDFND_Update(fskip ? NULL : (uint32 *)VTBuffer[ThisBackBuffer], sound, ssize);
	  if(InFrameAdvance && !NeedFrameAdvance)
	  {
	   for(int x = 0; x < CurGame->soundchan * ssize; x++)
	    sound[x] = 0;
	  }
		if(VTReady)
		{
		BlitScreen((uint32 *)VTReady, (MDFN_Rect *)&VTDisplayRect, (MDFN_Rect*)VTLWReady, (uint32 *)VTOSDReady);
		if(VTOSDReady)
		memset((void *)VTOSDReady, 0, 256 * 224 * sizeof(uint32));
		VTReady = NULL;
		} 
		else
		{
			VTReady = NULL ;
		}
	 } while(InFrameAdvance && !NeedFrameAdvance && GameThreadRun);
	}
	return(1);
}   

char *GetBaseDirectory(void)
{
 char *ol;
 char *ret;
#ifdef _XBOX
	return xbox_savedir() ;
#else
 ol=getenv("HOME");

 if(ol)
 {
  ret=(char *)malloc(strlen(ol)+1+strlen("/.mednafen"));
  strcpy(ret,ol);
  strcat(ret,"/.mednafen");
 }
 else
 {
  #ifdef WIN32
  char *sa;

  ret=(char *)malloc(MAX_PATH+1);
  GetModuleFileName(NULL,ret,MAX_PATH+1);

  sa=strrchr(ret,'\\');
  if(sa)
   *sa = 0;
  #else
  ret=(char *)malloc(1);
  ret[0]=0;
  #endif
  //printf("%s\n",ret);
 }
 return(ret);
#endif
}

static volatile int (*EventHook)(const SDL_Event *event) = 0;

static volatile SDL_Event gtevents[16384];
static volatile int gte_read = 0;
static volatile int gte_write = 0;


#ifdef NETWORK
extern int MDFNDnetplay;
#endif


/* This function may also be called by the main thread if a game is not loaded. */
static void GameThread_HandleEvents(void)
{
 SDL_mutexP(EVMutex);
 while(gte_read != gte_write)
 {
  volatile SDL_Event *event = &gtevents[gte_read];

  if(EventHook)
   EventHook((const SDL_Event *)event);

  switch(event->type)
  {
   case SDL_USEREVENT:
	switch(event->user.code)
	{
	 case CEVT_NETPLAYTEXT: if(CurGame && MDFNDnetplay) { MDFNI_NetplayText((uint8 *)event->user.data1); } free(event->user.data1); break;
	 case CEVT_NETPLAYCONNECT: if(CurGame) { if(*(int *)event->user.data1) MDFND_NetworkConnect(); else MDFND_NetworkClose(); free(event->user.data1); } break;
	}
  }
  gte_read = (gte_read + 1) & 16383;
 }
 SDL_mutexV(EVMutex);
}

void SDL_MDFN_ShowCursor(int toggle)
{
 int *toog = (int *)malloc(sizeof(int));
 *toog = toggle;

 SDL_Event evt;
 evt.user.type = SDL_USEREVENT;
 evt.user.code = CEVT_SHOWCURSOR;
 evt.user.data1 = toog;
 SDL_PushEvent(&evt);

}

void PumpWrap(void)
{
 SDL_Event event;

 SDL_mutexP(EVMutex);
 while(SDL_PollEvent(&event))
 {
  NetplayEventHook(&event);
  /* This is a very ugly hack for some joystick hats that don't behave very well. */
  if(event.type == SDL_JOYHATMOTION)
  {
   SDL_Event ne[64];
   int count;
   //printf("Cheep: %d\n", event.jhat.value);
   if((count = SDL_PeepEvents(ne, 64, SDL_PEEKEVENT, SDL_EVENTMASK(SDL_JOYHATMOTION))) >= 1)
   {
    int x;
    int docon = 0;

    for(x=0;x<count;x++)
     if(event.jhat.which == ne[x].jhat.which)
      docon = 1;
    if(docon) continue;
   }
  } // && event.jhat.
  /* Handle the event, and THEN hand it over to the GUI. Order is important due to global variable mayhem(CEVT_TOGGLEFS. */
  switch(event.type)
  {
   case SDL_VIDEORESIZE: VideoResize(event.resize.w, event.resize.h); break;
   case SDL_VIDEOEXPOSE: break;
   case SDL_QUIT: NeedExitNow = 1;break;
   case SDL_USEREVENT:
		switch(event.user.code)
		{
		 case CEVT_NETPLAYTOGGLEVIEW: Netplay_ToggleTextView(); break;
		 case CEVT_TOGGLEFS: NeedVideoChange = 1; break;
		 case CEVT_VIDEOSYNC: NeedVideoChange = -1; break;
		 case CEVT_PRINTERROR: MDFND_PrintError((char *)event.user.data1); free(event.user.data1); break;
		 case CEVT_PRINTMESSAGE: MDFND_Message((char *)event.user.data1); free(event.user.data1); break;
		 case CEVT_DISPLAYNETPLAYTEXT: NetplayText_InMainThread((uint8 *)event.user.data1); free(event.user.data1); break;
		 case CEVT_SHOWCURSOR: SDL_ShowCursor(*(int *)event.user.data1); free(event.user.data1); break;
		}
		//break; // Make sure SDL_USEREVENT events propagate to the game thread.
   default: memcpy((void *)&gtevents[gte_write], &event, sizeof(SDL_Event)); gte_write = (gte_write + 1) & 16383; break;
  }
 }
 SDL_mutexV(EVMutex);

 if(!CurGame)
  GameThread_HandleEvents();
}

void MainSetEventHook(int (*eh)(const SDL_Event *event))
{
 EventHook = (volatile int (*)(const SDL_Event *))eh;
}

static volatile int JoyModeChange = 0;

void SetJoyReadMode(int mode)	// 0 for events, 1 for manual function calling to update.
{
 SDL_mutexP(VTMutex);
 JoyModeChange = mode | 0x8;
 SDL_mutexV(VTMutex);

 /* Only block if we're calling this from within the game loop(it is also called from within LoadGame(), called in the main loop). */
 if(SDL_ThreadID() != MainThreadID) //if(GameThread && (SDL_ThreadID() != SDL_GetThreadID(GameThread))) - oops, race condition with the setting of GameThread, possibly...
  while(JoyModeChange)
   SDL_Delay(1);
}


static uint64 tfreq;
static uint64 desiredfps;

void RefreshThrottleFPS(int multiplier)
{
        desiredfps=((uint64)CurGame->fps * multiplier)>>8;
	tfreq=10000000;
        tfreq<<=16;    /* Adjustment for fps */
        CurGameSpeed = multiplier;
}


// Code for calculating the current FPS solely for display to the user
/*
static SDL_TimerID FPSTimer;
static volatile uint32 FPSCurrent;
static volatile uint32 FrameCounter = 0;
static uint32 FPSLastFrameCount;
static uint32 FPSLastTime;

static Uint32 FPSTimerCallback(Uint32 interval, void *param)
{
 uint32 CurTime = SDL_GetTicks();

 if(CurTime - FPSLastTime)
  FPSCurrent = (uint32)((double)(FrameCounter - FPSLastFrameCount) / (CurTime - FPSLastTime) * 1000 * 65536);

 FPSLastFrameCount = FrameCounter;
 FPSLastTime = CurTime;

 printf("%f\n", (double)FPSCurrent / 65536);

 return(interval);
}
*/

void xbox_setgamesettings() ;
extern int g_lynx_rotation ;

extern uint32 uppow2(uint32);
int mednafen_main(char *filename, unsigned int doloadcd )
{
	int ret;
	char *needie = NULL;
	DrBaseDirectory=GetBaseDirectory();

	#ifdef ENABLE_NLS
	setlocale(LC_ALL, "");
	#ifdef WIN32
	puts(DrBaseDirectory);
        bindtextdomain(PACKAGE, DrBaseDirectory);
        bind_textdomain_codeset(PACKAGE, "UTF-8");
        textdomain(PACKAGE);
	#else
	bindtextdomain(PACKAGE, LOCALEDIR);
	bind_textdomain_codeset(PACKAGE, "UTF-8");
	textdomain(PACKAGE);
	#endif
	#endif

	if(SDL_Init(SDL_INIT_VIDEO/* | SDL_INIT_TIMER*/)) /* SDL_INIT_VIDEO Needed for (joystick config) event processing? */
	{
	 printf("Could not initialize SDL: %s.\n", SDL_GetError());
	 MDFNI_Kill();
	 return(-1);
	}

	MainThreadID = SDL_ThreadID();
        if(!(ret=MDFNI_Initialize(DrBaseDirectory, DriverSettings)))
         return(-1);

        SDL_EnableUNICODE(1);

        #ifndef WIN32
        SetSignals(CloseStuff);
        #endif

	//CreateDirs();
	MakeInputConfigStruct();
	MakeMednafenArgsStruct();
	LoadConfig();

	xbox_setgamesettings() ;

/*
  { "nes.nofs", "Disabled four-score emulation.", MDFNST_BOOL, "0" },
  { "nes.no8lim", "No 8-sprites-per-scanline limit option.", MDFNST_BOOL, "0" },
  { "nes.fnscan", "Scan filename for (U),(J),(E),etc. strings to en/dis-able PAL emulation.", MDFNST_BOOL, "1" },
  { "nes.pal", "Enable PAL(50Hz) NES emulation.", MDFNST_BOOL, "0" },
  { "nes.gg", "Enable Game Genie emulation.", MDFNST_BOOL, "0" },
  { "nes.ggrom", "Path to Game Genie ROM image.", MDFNST_STRING, "" },
  { "nes.clipsides", "Clip left+right 8 pixel columns.", MDFNST_BOOL, "0" },
  { "nes.slstart", "First rendered scanline in NTSC mode.", MDFNST_UINT, "8" },
  { "nes.slend", "Last rendered scanlines in NTSC mode.", MDFNST_UINT, "231" },
  { "nes.slstartp", "First rendered scanline in PAL mode.", MDFNST_UINT, "0" },
  { "nes.slendp", "Last rendered scanlines in PAL mode.", MDFNST_UINT, "239" },
  { "nes.cpalette", "Filename of custom NES palette.", MDFNST_STRING, "" },
  { "nes.ntsccol", "Enable automatic generation and use of an NTSC NES' colors.", MDFNST_BOOL, "0" },
  { "nes.ntsctint", "NTSC NES color emulation mode \"Tint\" value.", MDFNST_UINT, "56", "0", "128" },
  { "nes.ntschue", "NTSC NES color emulation mode \"Hue\" value.", MDFNST_UINT, "72", "0", "128" },

  { "pce.forcesgx", "Force SuperGrafx emulation.", MDFNST_BOOL, "0" },
  { "pce.ocmultiplier", "CPU overclock multiplier.", MDFNST_UINT, "1", "1", "50"},
  { "pce.nospritelimit", "No 16-sprites-per-scanline limit option.", MDFNST_BOOL, "0" },
  { "pce.cdbios", "Path to the CD BIOS", MDFNST_STRING, "pce.cdbios PATH NOT SET" },
  
  */

	//  { "nes.fcexp", "Select Famicom expansion-port device.", MDFNST_STRING, "none", NULL, NULL, MDFND_ValidateFamicomExpansionPort },
	//static const char *fccortab[11]={"none","arkanoid","shadow","4player","fkb","hypershot",
      //                  "mahjong","quizking","ftrainera","ftrainerb","oekakids"};

  //{ "nes.input1", "Select input device for input port 1.", MDFNST_STRING, "gamepad", NULL, NULL, MDFND_ValidateNESInputPort },
  //{ "nes.input2", "Select input device for input port 2.", MDFNST_STRING, "gamepad", NULL, NULL , MDFND_ValidateNESInputPort },
  //static char *cortab[6]={"none","gamepad","zapper","powerpada","powerpadb","arkanoid"};


		needie = filename ;
	//if(!DoArgs(argc,argv, &needie))
	//{
	 //MDFNI_Kill();
	 //DeleteInternalArgs();
	 //return(-1);
	//}

	/* Now the fun begins! */
	/* Run the video and event pumping in the main thread, and create a 
	   secondary thread to run the game in(and do sound output, since we use
	   separate sound code which should be thread safe(?)).
	*/

	//InitVideo(NULL);

	VTMutex = SDL_CreateMutex();
	EVMutex = SDL_CreateMutex();

	VTReady = NULL;

	NeedVideoChange = -1;

	InitJoysticks();

	NeedExitNow = 0;

	if ( doloadcd )
	{
		loadcd = PCE_STR ;
	}
	else
	{
		loadcd = NULL ;
	}
	//needie = NULL ;

        if(LoadGame(needie))
        {
	 // None of our systems have a height about 256, but we really should
	 // add hints in the MDFNGameInfo struct about the max dimensions of the screen
	 // buffer.
			if ( VTBuffer[0] == NULL )
				VTBuffer[0] = (uint32 *)malloc(CurGame->pitch * 256 * sizeof(uint32));
			if ( VTBuffer[1] == NULL )
		         VTBuffer[1] = (uint32 *)malloc(CurGame->pitch * 256 * sizeof(uint32));
			if ( VTLineWidths[0] == NULL )
				VTLineWidths[0] = (MDFN_Rect *)calloc(256, sizeof(MDFN_Rect));
			if ( VTLineWidths[1] == NULL )
				VTLineWidths[1] = (MDFN_Rect *)calloc(256, sizeof(MDFN_Rect));
			//if ( VTOSDBuffer[0] == NULL )
				//VTOSDBuffer[0] = (uint32 *)calloc(256 * 240, sizeof(uint32));
			//if ( VTOSDBuffer[1] == NULL )
				//VTOSDBuffer[1] = (uint32 *)calloc(256 * 240, sizeof(uint32));
         NeedVideoChange = -1;

		xbox_set_RAM_location() ;
		switch( g_lynx_rotation )
		{
			case 1  : CurGame->rotated = 1 ; break ; 
			case 2  : CurGame->rotated = -1 ; break ; 
			case 0  : 
			default : CurGame->rotated = 0 ; break ; 
		}
		
		
		}
	else
	 NeedExitNow = 1;

	
	//FPSTimer = SDL_AddTimer(500, FPSTimerCallback, NULL);

	while(!NeedExitNow)
	{
	 SDL_mutexP(VTMutex);	/* Lock mutex */

	 if(JoyModeChange)
	 {
	  int t = JoyModeChange & 1;

          PumpWrap(); // I love undefined API behavior, don't you?  SDL_JoystickEventState() seems
		      // to clear the event buffer.
	  if(t) SDL_JoystickEventState(SDL_IGNORE);
	  else SDL_JoystickEventState(SDL_ENABLE);

	  JoyModeChange = 0;
	 }

	 if(NeedVideoChange)
	 {
	  KillVideo(0);

	  memset(VTBuffer[0], 0, 256 * 256 * sizeof(uint32));
	  memset(VTBuffer[1], 0, 256 * 256 * sizeof(uint32));

	  if(NeedVideoChange == -1)
	  {
	   if(!InitVideo(CurGame))
	   {
	    NeedExitNow = 1;
	    break;
	   }
	  }
	  else
	  {
	   MDFNI_SetSetting("fs", !MDFN_GetSettingB("fs"));

	   if(!InitVideo(CurGame))
	   {
            MDFNI_SetSetting("fs", !MDFN_GetSettingB("fs"));
	    InitVideo(CurGame);
	   }
	  }
	  NeedVideoChange = 0;
	 }

	 if(VTReady)
	 {
	  BlitScreen((uint32 *)VTReady, (MDFN_Rect *)&VTDisplayRect, (MDFN_Rect*)VTLWReady, (uint32 *)VTOSDReady);
	  if(VTOSDReady)
	   memset((void *)VTOSDReady, 0, 256 * 224 * sizeof(uint32));
	  VTReady = NULL;
	 } 
	 PumpWrap();
         SDL_mutexV(VTMutex);   /* Unlock mutex */
         SDL_Delay(1);

	GameLoop(NULL);

	}

	CloseGame();
	SDL_DestroyMutex(VTMutex);
	SDL_DestroyMutex(EVMutex);

	if(VTBuffer[0])
	{
	 free(VTBuffer[0]);
	 VTBuffer[0] = NULL;
	}

	if(VTBuffer[1])
	{
	 free(VTBuffer[1]);
	 VTBuffer[1] = NULL;
	}

	if ( VTLineWidths[0] )
	{
		free( VTLineWidths[0] ) ;
		VTLineWidths[0] = NULL ;
	}
	if ( VTLineWidths[1] )
	{
		free( VTLineWidths[1] ) ;
		VTLineWidths[1] = NULL ;
	}

	#ifndef WIN32
	SetSignals(SIG_IGN);
	#endif

	SaveConfig();
        MDFNI_Kill();

	SDL_Quit();

	DeleteInternalArgs();

        return(0);
}

static void DriverKill(void)
{
 if(inited&2)
  KillJoysticks();
 KillSound();
 inited=0;
}

static uint32 last_btime = 0;
static uint64 ttime,ltime=0;
static int skipcount = 0;

// Throttle and check for frame skip
static int ThrottleCheckFS(void)
{
 int needskip = 0;
 bool nothrottle = MDFN_GetSettingB("nothrottle");

 waiter:

 ttime=SDL_GetTicks();
 ttime*=10000;

 if((ttime - ltime) < (tfreq / desiredfps ))
 {
  if(!sound_active && !NoWaiting && !nothrottle)
  {
   int64 delay;
   delay=(tfreq/desiredfps)-(ttime-ltime);
   if(delay>0)
    SDL_Delay(delay/10000);
   goto waiter;
  }
 }

 extern int MDFNDnetplay;
 if(!MDFNDnetplay)
  if(((ttime-ltime) >= (1.5*tfreq/desiredfps)))
  {
   if(skipcount < 4 || (CurGameSpeed > 1 && skipcount < CurGameSpeed))     // Only skip four frames in a row at maximum.
   {
    skipcount ++;
    needskip = 1;
   } else skipcount = 0;
   if(!sound_active) // && (ttime-ltime) >= ((uint64)3.0*tfreq/desiredfps))    // Only adjust base time if sound is disabled.
    ltime=ttime;
  }
  else
   ltime+=tfreq/desiredfps;

 return(needskip);
}

void MD_SaveState( char *fname )
{
	MDFNI_SaveState(fname, NULL, NULL, NULL);
}
void MD_LoadState( char *fname )
{
	MDFNI_LoadState(fname, NULL);
}

void MDFND_Update(uint32 *XBuf, float *Buffer, int Count)
{
 //FrameCounter++;
 if(Count)
 {
  if(ffnosound && CurGameSpeed > 1)
  {
   for(int x = 0; x < Count * CurGame->soundchan; x++)
    Buffer[x] = 0;
  }
  int32 max = GetWriteSound();
  if(Count > max)
  {
   if(NoWaiting)
    Count = max;
  }
  if(Count >= (max * 0.95))
  {
   ltime = ttime;		// Resynchronize
  }
  WriteSound(Buffer, Count);

  #ifdef NETWORK
  extern int MDFNDnetplay;
  if(MDFNDnetplay && GetWriteSound() >= Count * 1.00) // Cheap code to fix sound buffer underruns due to accumulation of timer error during netplay.
  {
   float zbuf[128 * 2];
   for(int x = 0; x < 128 * 2; x++) zbuf[x] = 0;
   int t = GetWriteSound();
   t /= CurGame->soundchan;
   while(t > 0) 
   {
    WriteSound(zbuf, (t > 128 ? 128 : t));
    t -= 128;
   }
   ltime = ttime;
   //puts("Ack");
  }
  #endif

 }
 MDFND_UpdateInput();

 bool osd_drawn = 0;
 if(XBuf && CurGame)
 {
  if(pending_snapshot)
   MDFNI_SaveSnapshot();
  if(pending_save_state)
   MDFNI_SaveState(NULL, NULL, XBuf, (MDFN_Rect *)VTLineWidths[VTBackBuffer]);
  if(pending_save_movie)
   MDFNI_SaveMovie(NULL, XBuf, (MDFN_Rect *)VTLineWidths[VTBackBuffer]);
  pending_save_movie = pending_snapshot = pending_save_state = 0;
  MDFN_Rect toorect;
  toorect.x = 0;
  toorect.y = 0;
  toorect.w = 256;
  toorect.h = 224;
  //osd_drawn = MDFNI_DrawOverlay(XBuf, (uint32 *)VTOSDBuffer[VTBackBuffer], &toorect);
  osd_drawn = 0 ;
  if(Netplay_GetTextView()) osd_drawn = 1;
 }
 if(XBuf)
 {
  /* If it's been >= 100ms since the last blit, assume that the blit
     thread is being time-slice starved, and let it run.  This is especially necessary
     for fast-forwarding to respond well(since keyboard updates are
     handled in the main thread) on slower systems or when using a higher fast-forwarding speed ratio.
  */
  if((last_btime + 100) < SDL_GetTicks())
  {
   //puts("Eep");
   while(VTReady) SDL_Delay(1);
  }
  if(!VTReady)
  {
   skipcount = 0;
   memcpy((void *)&VTDisplayRect, &CurGame->DisplayRect, sizeof(MDFN_Rect));
   VTOSDReady = osd_drawn ? VTOSDBuffer[VTBackBuffer] : NULL;
   VTLWReady = VTLineWidths[VTBackBuffer];
   VTReady = VTBuffer[VTBackBuffer];
   VTBackBuffer ^= 1;
   last_btime = SDL_GetTicks();
  }
 }
}


uint32 MDFND_GetTime(void)
{
 return(SDL_GetTicks());
}
