/* FCE Ultra - NES/Famicom 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 "common.h"

// I like hacks.
#define uint8 __UNO492032
#include <winsock.h>
#include <ddraw.h>
#undef LPCWAVEFORMATEX
#include <dsound.h>
#include <dinput.h>
#include <dir.h>
#include <commctrl.h>
#include <shlobj.h>     // For directories configuration dialog.
#undef uint8

#include "input.h"
#include "netplay.h"
#include "joystick.h"
#include "keyboard.h"
#include "cheat.h"
#include "debug.h"

#define VNSCLIP  ((eoptions&EO_CLIPSIDES)?8:0)
#define VNSWID   ((eoptions&EO_CLIPSIDES)?240:256)

uint8 *xbsave=NULL;
int eoptions=EO_BGRUN | EO_FORCEISCALE;

void ResetVideo(void);
void ShowCursorAbs(int w);
void HideFWindow(int h);
void FixWXY(int pref);
int SetMainWindowStuff(void);
int GetClientAbsRect(LPRECT lpRect);
void UpdateFCEUWindow(void);


HWND hAppWnd=0;
HINSTANCE fceu_hInstance;

HRESULT  ddrval;

FCEUGI *GI=0;

// cheats, misc, nonvol, states, snaps, base
static char *DOvers[6]={0,0,0,0,0,0};
static char *defaultds[5]={"cheats","gameinfo","sav","fcs","snaps"};

static char TempArray[2048];
static char BaseDirectory[2048];

void SetDirs(void)
{
 int x;
 static int jlist[5]=
  {FCEUIOD_CHEATS,FCEUIOD_MISC,FCEUIOD_NV,FCEUIOD_STATE,FCEUIOD_SNAPS};

 FCEUI_SetSnapName(eoptions&EO_SNAPNAME);

 for(x=0;x<5;x++)
  FCEUI_SetDirOverride(jlist[x], DOvers[x]);  
 if(DOvers[5])
  FCEUI_SetBaseDirectory(DOvers[5]);
 else
  FCEUI_SetBaseDirectory(BaseDirectory);
}
/* Remove empty, unused directories. */
void RemoveDirs(void)
{
 int x;

 for(x=0;x<5;x++)
  if(!DOvers[x])
  {
   sprintf(TempArray,"%s\\%s",DOvers[5]?DOvers[5]:BaseDirectory,defaultds[x]);
   RemoveDirectory(TempArray);
  }
}

void CreateDirs(void)
{
 int x;

 for(x=0;x<5;x++)
  if(!DOvers[x])
  {
   sprintf(TempArray,"%s\\%s",DOvers[5]?DOvers[5]:BaseDirectory,defaultds[x]);
   CreateDirectory(TempArray,0);
  }
}

static char *gfsdir=0;
void GetBaseDirectory(void)
{
 int x;
 BaseDirectory[0]=0;
 GetModuleFileName(0,(LPTSTR)BaseDirectory,2047);

 for(x=strlen(BaseDirectory);x>=0;x--)
 {
  if(BaseDirectory[x]=='\\' || BaseDirectory[x]=='/')
   {BaseDirectory[x]=0;break;}
 }
}

static int exiting=0;
static volatile int moocow = 0;
int BlockingCheck(void)
{
  MSG msg;
  moocow = 1;
  while( PeekMessage( &msg, 0, 0, 0, PM_NOREMOVE ) ) {
     if( GetMessage( &msg, 0,  0, 0)>0 )
     {
     TranslateMessage(&msg);
     DispatchMessage(&msg);
     }
   }
 moocow = 0;
 if(exiting) return(0);
 return(1);
}

/* Some timing-related variables. */
static int maxconbskip = 9;             /* Maximum consecutive blit skips. */
static int ffbskip = 9;              /* Blit skips per blit when FF-ing */

static int fullscreen=0;
static int soundflush=0;
static int genie=0;
static int palyo=0;
static int windowedfailed;
static double saspectw=1, saspecth=1;
static double winsizemulx=1, winsizemuly=1;
static int winwidth,winheight;
static int ismaximized = 0;

static volatile int nofocus=0;
static volatile int userpause=0;

#define SO_FORCE8BIT  1
#define SO_SECONDARY  2
#define SO_GFOCUS     4
#define SO_D16VOL     8

#define GOO_DISABLESS   1       /* Disable screen saver when game is loaded. */
#define GOO_CONFIRMEXIT 2       /* Confirmation before exiting. */
#define GOO_POWERRESET  4       /* Confirm on power/reset. */

static uint32 goptions = GOO_DISABLESS;

static int soundrate=44100;
static int soundbuftime=50;
static int soundoptions=SO_SECONDARY|SO_GFOCUS;
static int soundvolume=100;
static int soundquality=0;

static unsigned int srendline,erendline;
static unsigned int srendlinen=8;
static unsigned int erendlinen=231;
static unsigned int srendlinep=0;
static unsigned int erendlinep=239;


static unsigned int totallines;

static void FixFL(void)
{
 FCEUI_GetCurrentVidSystem(&srendline,&erendline);
 totallines=erendline-srendline+1;
}

static void UpdateRendBounds(void)
{ 
 FCEUI_SetRenderedLines(srendlinen,erendlinen,srendlinep,erendlinep);
 FixFL(); 
}

static uint8 cpalette[192];
static int vmod = 0;
static int soundo=1;
static int ntsccol=0,ntsctint,ntschue;

void FCEUD_PrintError(char *s)
{
 AddLogText(s,1);
 if(fullscreen) ShowCursorAbs(1);
 MessageBox(0,s,"FCE Ultra Error",MB_ICONERROR|MB_OK|MB_SETFOREGROUND|MB_TOPMOST);
 if(fullscreen)ShowCursorAbs(0);
}

void ShowAboutBox(void)
{
 sprintf(TempArray,"FCE Ultra "FCEU_VERSION"\n\nhttp://fceultra.sourceforge.net\n\n"__TIME__"\n"__DATE__"\n""gcc "__VERSION__);
 MessageBox(hAppWnd,TempArray,"About FCE Ultra",MB_OK);
}


void DoFCEUExit(void)
{
 /* Wolfenstein 3D had cute exit messages. */
 char *emsg[4]={"Are you sure you want to leave?  I'll become lonely!",
                "If you exit, I'll... EAT YOUR MOUSE.",
                "You can never really exit, you know.",
                "E-x-i-t?"
               };

 if(exiting)    /* Eh, oops.  I'll need to try to fix this later. */
  return;

 StopSound();
 if(goptions & GOO_CONFIRMEXIT)
  if(IDYES != MessageBox(hAppWnd,emsg[rand()&3],"Exit FCE Ultra?",MB_ICONQUESTION|MB_YESNO))
   return;

 exiting=1;
 if(GI)
 {
  FCEUI_CloseGame();
  GI=0;
 }
}

void DoPriority(void)
{
 if(eoptions&EO_HIGHPRIO)
 {
  if(!SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_HIGHEST))
  {
   AddLogText("Error setting thread priority to THREAD_PRIORITY_HIGHEST.",1);
  }
 }
 else
  if(!SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_NORMAL))
  {
   AddLogText("Error setting thread priority to THREAD_PRIORITY_NORMAL.",1);
  }
}

static int changerecursive=0;

#include "throttle.c"

#include "sound.c"
#include "video.c"
#include "window.c"
#include "config.c"
#include "args.c"

int DriverInitialize(void)
{
  if(soundo)
   soundo=InitSound();

  SetVideoMode(fullscreen);
  InitInputStuff();             /* Initialize DInput interfaces. */
  return 1;
}

static void DriverKill(void)
{ 
 sprintf(TempArray,"%s/fceu98.cfg",BaseDirectory);
 SaveConfig(TempArray);
 DestroyInput();
 ResetVideo();
 if(soundo) TrashSound();
 CloseWave();
 ByebyeWindow();
}

void FCEUD_Update(uint8 *XBuf, int32 *Buffer, int Count);

int main(int argc,char *argv[])
{
  char *t;

  if(timeBeginPeriod(1)!=TIMERR_NOERROR)
  {
   AddLogText("Error setting timer granularity to 1ms.",1);
  }

  if(!FCEUI_Initialize())
   goto doexito;

  srand(GetTickCount());        // rand() is used for some GUI sillyness.

  fceu_hInstance=GetModuleHandle(0);

  GetBaseDirectory();

  sprintf(TempArray,"%s\\fceu98.cfg",BaseDirectory);
  LoadConfig(TempArray);

  t=ParseArgies(argc,argv);
  /* Bleh, need to find a better place for this. */
  {
        palyo&=1;
        FCEUI_SetVidSystem(palyo);
        genie&=1;
        FCEUI_SetGameGenie(genie);
        fullscreen&=1;
        soundo&=1;
        FCEUI_SetSoundVolume(soundvolume);
        FCEUI_SetSoundQuality(soundquality);

  }
  ParseGIInput(NULL);      /* Since a game doesn't have to be
                     loaded before the GUI can be used, make
                     sure the temporary input type variables
                     are set.
                  */

  CreateDirs();
  SetDirs();

  DoVideoConfigFix();
  DoTimingConfigFix();

  if(eoptions&EO_CPALETTE)
   FCEUI_SetPaletteArray(cpalette);

  if(!t) fullscreen=0;

  CreateMainWindow();

  if(!InitDInput())
   goto doexito;

  if(!DriverInitialize())
   goto doexito;
 
  InitSpeedThrottle();
  UpdateMenu();

  if(t)
   ALoad(t);
  else if(eoptions&EO_FOAFTERSTART)
   LoadNewGamey(hAppWnd, 0);

  doloopy:
  UpdateFCEUWindow();  
  if(GI)
  {
   while(GI)
   {
         uint8 *gfx;
         int32 *sound;
         int32 ssize;

         FCEUI_Emulate(&gfx, &sound, &ssize, 0);
         xbsave = gfx;
         FCEUD_Update(gfx, sound, ssize);
   }
   xbsave = NULL;
   RedrawWindow(hAppWnd,0,0,RDW_ERASE|RDW_INVALIDATE);
   StopSound();
  }
  Sleep(50);
  if(!exiting)
   goto doloopy;

  doexito:
  DriverKill();
  timeEndPeriod(1);
  FCEUI_Kill();
  return(0);
}


void FCEUD_Update(uint8 *XBuf, int32 *Buffer, int Count)
{
 static int skipcount = 0;
 int maxskip = maxconbskip;

 if(NoWaiting & 1)
  maxskip = ffbskip;

 if(Count)
 {
  int32 can=GetWriteSound();
  static int uflow=0;
  int32 tmpcan;
  extern int FCEUDnetplay;

  if(can >= GetMaxSound()) uflow=1;	/* Go into massive underflow mode. */

  if(can > Count) can=Count;
  else uflow=0;

  FCEUD_WriteSoundData(Buffer,can);

  tmpcan = GetWriteSound();
  if(((tmpcan < Count*0.90) && !uflow) || (skipcount >= maxskip))
  {
   if(XBuf && (!NoWaiting || skipcount >= maxskip))
   {
    skipcount = 0;
    FCEUD_BlitScreen(XBuf);
   }
   else
   {
    skipcount++;
    //FCEU_printf("Skipped0");
   }
   Buffer+=can;
   Count-=can;
   if(Count)
   {
    if(NoWaiting)
    {
     can=GetWriteSound(); 
     if(Count>can) Count=can;
    }
    FCEUD_WriteSoundData(Buffer,Count);
   }
  }
  else
  {
   skipcount++;
   //FCEU_printf("Skipped");
  #ifdef NETWORK
   if(!NoWaiting && FCEUDnetplay && (uflow || tmpcan >= (Count * 0.90)))
   {
    if(Count > tmpcan) Count=tmpcan;
    while(tmpcan > 0)
    {
     //printf("Overwrite: %d\n", (Count <= tmpcan)?Count : tmpcan);
     FCEUD_WriteSoundData(Buffer, (Count <= tmpcan)?Count : tmpcan);
     tmpcan -= Count;
    }
   }
  #endif
  }
 }
 else
 {
  /* This complex statement deserves some explanation.
     Make sure this special speed throttling hasn't been disabled by the user
     first. Second, we don't want to throttle the speed if the fast-forward
     button is pressed down(or during certain network play conditions).
       
     Now, if we're at this point, we'll throttle speed if sound is disabled.
     Otherwise, it gets a bit more complicated.  We'll throttle speed if focus
     to FCE Ultra has been lost and we're writing to the primary sound buffer
     because our sound code won't block.  Blocking does seem to work when
     writing to a secondary buffer, so we won't throttle when a secondary
     buffer is used.
  */
  int skipthis = 0;

  if(!(eoptions&EO_NOTHROTTLE))
   if(!NoWaiting)
    if(!soundo || (soundo && nofocus && !(soundoptions&SO_SECONDARY)) )
        skipthis = SpeedThrottle();

  if(XBuf)
  {
   if((!skipthis && !NoWaiting) || (skipcount >= maxskip))
   {
    FCEUD_BlitScreen(XBuf);
    skipcount = 0;
   }
   else
   {
    skipcount++;
   }
  }
 }
 UpdateFCEUWindow();
 FCEUD_UpdateInput();
}

/*
void FCEUD_Update(uint8 *XBuf, int32 *Buffer, int Count)
{
 FCEUD_BlitScreen(XBuf);
 if(Count && soundo)
  FCEUD_WriteSoundData(Buffer,Count);
 FCEUD_UpdateInput();
}
*/

FILE *FCEUD_UTF8fopen(const char *n, const char *m)
{
  return(fopen(n,m));
}
