// VisualBoyAdvance - Nintendo Gameboy/GameboyAdvance (TM) emulator.
// Copyright (C) 1999-2003 Forgotten
// Copyright (C) 2004 Forgotten and the VBA development team

// 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, 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 <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "global.h"
#include "psp_kbd.h"

#include "AutoBuild.h"

#include "SDL.h"
#include "GBA.h"
#include "agbprint.h"
#include "Flash.h"
#include "Port.h"
#include "RTC.h"
#include "Sound.h"
#include "Text.h"
#include "unzip.h"
#include "Util.h"
#include "gb/GB.h"
#include "gb/gbGlobals.h"

#include "global.h"
#include <pspaudiolib.h>
#include "psp_sdl.h"
#include "psp_kbd.h"

#ifndef WIN32
# include <unistd.h>
# define GETCWD getcwd
#else // WIN32
# include <direct.h>
# define GETCWD _getcwd
#endif // WIN32

# include "psp_sdl.h"

#ifdef MMX
extern "C" bool cpu_mmx;
#endif
extern bool soundEcho;
extern bool soundLowPass;
extern bool soundReverse;
extern int Init_2xSaI(u32);
extern void _2xSaI(u8*,u32,u8*,u8*,u32,int,int);
extern void _2xSaI32(u8*,u32,u8*,u8*,u32,int,int);  
extern void Super2xSaI(u8*,u32,u8*,u8*,u32,int,int);
extern void Super2xSaI32(u8*,u32,u8*,u8*,u32,int,int);
extern void SuperEagle(u8*,u32,u8*,u8*,u32,int,int);
extern void SuperEagle32(u8*,u32,u8*,u8*,u32,int,int);  
extern void Pixelate(u8*,u32,u8*,u8*,u32,int,int);
extern void Pixelate32(u8*,u32,u8*,u8*,u32,int,int);
extern void MotionBlur(u8*,u32,u8*,u8*,u32,int,int);
extern void MotionBlur32(u8*,u32,u8*,u8*,u32,int,int);
extern void AdMame2x(u8*,u32,u8*,u8*,u32,int,int);
extern void AdMame2x32(u8*,u32,u8*,u8*,u32,int,int);
extern void Simple2x(u8*,u32,u8*,u8*,u32,int,int);
extern void Simple2x32(u8*,u32,u8*,u8*,u32,int,int);
extern void Bilinear(u8*,u32,u8*,u8*,u32,int,int);
extern void Bilinear32(u8*,u32,u8*,u8*,u32,int,int);
extern void BilinearPlus(u8*,u32,u8*,u8*,u32,int,int);
extern void BilinearPlus32(u8*,u32,u8*,u8*,u32,int,int);
extern void Scanlines(u8*,u32,u8*,u8*,u32,int,int);
extern void Scanlines32(u8*,u32,u8*,u8*,u32,int,int);
extern void ScanlinesTV(u8*,u32,u8*,u8*,u32,int,int);
extern void ScanlinesTV32(u8*,u32,u8*,u8*,u32,int,int);
extern void hq2x(u8*,u32,u8*,u8*,u32,int,int);
extern void hq2x32(u8*,u32,u8*,u8*,u32,int,int);
extern void lq2x(u8*,u32,u8*,u8*,u32,int,int);
extern void lq2x32(u8*,u32,u8*,u8*,u32,int,int);

extern void SmartIB(u8*,u32,int,int);
extern void SmartIB32(u8*,u32,int,int);
extern void MotionBlurIB(u8*,u32,int,int);
extern void MotionBlurIB32(u8*,u32,int,int);

void Init_Overlay(SDL_Surface *surface, int overlaytype);
void Quit_Overlay(void);
static inline void Draw_Overlay(SDL_Surface *surface, int size);

extern void remoteInit();
extern void remoteCleanUp();
extern void remoteStubMain();
extern void remoteStubSignal(int,int);
extern void remoteOutput(char *, u32);
extern void remoteSetProtocol(int);
extern void remoteSetPort(int);
extern void debuggerOutput(char *, u32);

extern void CPUUpdateRenderBuffers(bool);

struct EmulatedSystem emulator = {
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  false,
  0
};

SDL_Surface *surface = NULL;
SDL_Overlay *overlay = NULL;
SDL_Rect overlay_rect;

int systemSpeed = 0;
int systemRedShift = 0;
int systemBlueShift = 0;
int systemGreenShift = 0;
int systemDebug = 0;
int systemVerbose = 0;
int systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;

int srcPitch = 0;
int srcWidth = 0;
int srcHeight = 0;
int destWidth = 0;
int destHeight = 0;

int sensorX = 2047;
int sensorY = 2047;

u8 *delta = NULL;

int sdlPrintUsage = 0;
int disableMMX = 0;

int cartridgeType = 3;
int captureFormat = 0;

int pauseWhenInactive = 0;
int active = 1;
int emulating = 0;
int RGB_LOW_BITS_MASK=0x821;
u16 systemColorMap16[0x10000];
u16 systemGbPalette[24];
int ifbType = 0;
char filename[2048];
char ipsname[2048];
char biosFileName[2048];
char captureDir[2048];
char saveDir[2048];
char batteryDir[2048];

static char *rewindMemory = NULL;
static int rewindPos = 0;
static int rewindTopPos = 0;
static int rewindCounter = 0;
static int rewindCount = 0;
static bool rewindSaveNeeded = false;
static int rewindTimer = 0;

#define REWIND_SIZE 400000

#define _stricmp strcasecmp

bool sdlButtons[4][12] = {
  { false, false, false, false, false, false, 
    false, false, false, false, false, false },
  { false, false, false, false, false, false,
    false, false, false, false, false, false },
  { false, false, false, false, false, false,
    false, false, false, false, false, false },
  { false, false, false, false, false, false,
    false, false, false, false, false, false }
};

bool sdlMotionButtons[4] = { false, false, false, false };

int sdlNumDevices = 0;
SDL_Joystick **sdlDevices = NULL;

bool wasPaused = false;

int frameskipadjust = 0;
int throttle = 0;
u32 throttleLastTime = 0;

int renderedFrames = 0;
int showRenderedFrames = 0;
# if 0 //LUDO: 
int showSpeed = 1;
# else
int showSpeed = 0;
# endif
int showSpeedTransparent = 1;

bool disableStatusMessages = false;
bool paused = false;
bool pauseNextFrame = false;
bool debugger = false;
int fullscreen = 1;
bool systemSoundOn = true;
int yuvType = 0;
bool removeIntros = false;
int sdlFlashSize = 0;
int sdlAutoIPS = 1;
int sdlRtcEnable = 0;
int sdlAgbPrint = 0;

int sdlDefaultJoypad = 0;

extern void debuggerSignal(int,int);

int  mouseCounter = 0;
int autoFire = 0;
bool autoFireToggle = false;

bool screenMessage = false;
char screenMessageBuffer[21];
u32  screenMessageTime = 0;

SDL_cond *cond = NULL;
SDL_mutex *mutex = NULL;
u8 sdlBuffer[4096];
int sdlSoundLen = 0;

char *arg0;

u16 joypad[4][12] = {
  { SDLK_LEFT,  SDLK_RIGHT,
    SDLK_UP,    SDLK_DOWN,
    SDLK_z,     SDLK_x,
    SDLK_RETURN,SDLK_BACKSPACE,
    SDLK_a,     SDLK_s,
    SDLK_SPACE, SDLK_F12
  },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};

u16 defaultJoypad[12] = {
  SDLK_LEFT,  SDLK_RIGHT,
  SDLK_UP,    SDLK_DOWN,
  SDLK_z,     SDLK_x,
  SDLK_RETURN,SDLK_BACKSPACE,
  SDLK_a,     SDLK_s,
  SDLK_SPACE, SDLK_F12
};

u16 motion[4] = {
  SDLK_KP4, SDLK_KP6, SDLK_KP8, SDLK_KP2
};

u16 defaultMotion[4] = {
  SDLK_KP4, SDLK_KP6, SDLK_KP8, SDLK_KP2
};

extern bool CPUIsGBAImage(char *);
extern bool gbIsGameboyRom(char *);

static inline void sdlStretch16x1(u8 *src, u8 *dest)
{
  u16 *s = (u16 *)src;
  u16 *d = (u16 *)dest;
  for(int i = 0; i < srcWidth; i++)
    *d++ = *s++;
}

#ifdef __MSC__
#define stat _stat
#define S_IFDIR _S_IFDIR
#endif

void sdlCheckDirectory(char *dir)
{
  struct stat buf;

  int len = strlen(dir);

  char *p = dir + len - 1;

  if(*p == '/' ||
     *p == '\\')
    *p = 0;
  
  if(stat(dir, &buf) == 0) {
    if(!(buf.st_mode & S_IFDIR)) {
      fprintf(stderr, "Error: %s is not a directory\n", dir);
      dir[0] = 0;
    }
  } else {
    fprintf(stderr, "Error: %s does not exist\n", dir);
    dir[0] = 0;
  }
}

char *sdlGetFilename(char *name)
{
  static char filebuffer[2048];

  int len = strlen(name);
  
  char *p = name + len - 1;
  
  while(true) {
    if(*p == '/' ||
       *p == '\\') {
      p++;
      break;
    }
    len--;
    p--;
    if(len == 0)
      break;
  }
  
  if(len == 0)
    strcpy(filebuffer, name);
  else
    strcpy(filebuffer, p);
  return filebuffer;
}

FILE *sdlFindFile(const char *name)
{
  char buffer[4096];
  char path[2048];

#ifdef WIN32
#define PATH_SEP ";"
#define FILE_SEP '\\'
#define EXE_NAME "VisualBoyAdvance-SDL.exe"
#else // ! WIN32
#define PATH_SEP ":"
#define FILE_SEP '/'
#define EXE_NAME "VisualBoyAdvance"
#endif // ! WIN32

  GETCWD(buffer, 2048);
  
  FILE *f = fopen(name, "r");
  if(f != NULL) {
    return f;
  }

  char *home = getenv("HOME");

  if(home != NULL) {
    sprintf(path, "%s%c%s", home, FILE_SEP, name);
    f = fopen(path, "r");
    if(f != NULL)
      return f;
  }

#ifdef WIN32
  home = getenv("USERPROFILE");
  if(home != NULL) {
    sprintf(path, "%s%c%s", home, FILE_SEP, name);
    f = fopen(path, "r");
    if(f != NULL)
      return f;
  }
#else // ! WIN32
    sprintf(path, "%s%c%s", SYSCONFDIR, FILE_SEP, name);
    f = fopen(path, "r");
    if(f != NULL)
      return f;
#endif // ! WIN32

  if(!strchr(arg0, '/') &&
     !strchr(arg0, '\\')) {
    char *path = getenv("PATH");

    if(path != NULL) {
      strncpy(buffer, path, 4096);
      buffer[4095] = 0;
      char *tok = strtok(buffer, PATH_SEP);
      
      while(tok) {
        sprintf(path, "%s%c%s", tok, FILE_SEP, EXE_NAME);
        f = fopen(path, "r");
        if(f != NULL) {
          char path2[2048];
          fclose(f);
          sprintf(path2, "%s%c%s", tok, FILE_SEP, name);
          f = fopen(path2, "r");
          if(f != NULL) {
            return f;
          }
        }
        tok = strtok(NULL, PATH_SEP);
      }
    }
  } else {
    // executable is relative to some directory
    strcpy(buffer, arg0);
    char *p = strrchr(buffer, FILE_SEP);
    if(p) {
      *p = 0;
      sprintf(path, "%s%c%s", buffer, FILE_SEP, name);
      f = fopen(path, "r");
      if(f != NULL)
        return f;
    }
  }
  return NULL;
}

static void sdlApplyPerImagePreferences()
{
  FILE *f = sdlFindFile("vba-over.ini");
  if(!f) {
    return;
  }

  char buffer[7];
  buffer[0] = '[';
  buffer[1] = rom[0xac];
  buffer[2] = rom[0xad];
  buffer[3] = rom[0xae];
  buffer[4] = rom[0xaf];
  buffer[5] = ']';
  buffer[6] = 0;

  char readBuffer[2048];

  bool found = false;
  
  while(1) {
    char *s = fgets(readBuffer, 2048, f);

    if(s == NULL)
      break;

    char *p  = strchr(s, ';');
    
    if(p)
      *p = 0;
    
    char *token = strtok(s, " \t\n\r=");

    if(!token)
      continue;
    if(strlen(token) == 0)
      continue;

    if(!strcmp(token, buffer)) {
      found = true;
      break;
    }
  }

  if(found) {
    while(1) {
      char *s = fgets(readBuffer, 2048, f);

      if(s == NULL)
        break;

      char *p = strchr(s, ';');
      if(p)
        *p = 0;

      char *token = strtok(s, " \t\n\r=");
      if(!token)
        continue;
      if(strlen(token) == 0)
        continue;

      if(token[0] == '[') // starting another image settings
        break;
      char *value = strtok(NULL, "\t\n\r=");
      if(value == NULL)
        continue;
      
      if(!strcmp(token, "rtcEnabled"))
        rtcEnable(atoi(value) == 0 ? false : true);
      else if(!strcmp(token, "flashSize")) {
        int size = atoi(value);
        if(size == 0x10000 || size == 0x20000)
          flashSetSize(size);
      } else if(!strcmp(token, "saveType")) {
        int save = atoi(value);
        if(save >= 0 && save <= 5)
          cpuSaveType = save;
      }
    }
  }
  fclose(f);
}

static int sdlCalculateShift(u32 mask)
{
  int m = 0;
  
  while(mask) {
    m++;
    mask >>= 1;
  }

  return m-5;
}

static int sdlCalculateMaskWidth(u32 mask)
{
  int m = 0;
  int mask2 = mask;

  while(mask2) {
    m++;
    mask2 >>= 1;
  }

  int m2 = 0;
  mask2 = mask;
  while(!(mask2 & 1)) {
    m2++;
    mask2 >>= 1;
  }

  return m - m2;
}

# if 0
void sdlWriteState(int num)
{
  char stateName[2048];

  if(saveDir[0])
    sprintf(stateName, "%s/%s%d.sgm", saveDir, sdlGetFilename(filename),
            num+1);
  else
    sprintf(stateName,"%s%d.sgm", filename, num+1);
  if(emulator.emuWriteState)
    emulator.emuWriteState(stateName);
  sprintf(stateName, "Wrote state %d", num+1);
  systemScreenMessage(stateName);
}

void sdlReadState(int num)
{
  char stateName[2048];

  if(saveDir[0])
    sprintf(stateName, "%s/%s%d.sgm", saveDir, sdlGetFilename(filename),
            num+1);
  else
    sprintf(stateName,"%s%d.sgm", filename, num+1);

  if(emulator.emuReadState)
    emulator.emuReadState(stateName);

  sprintf(stateName, "Loaded state %d", num+1);
  systemScreenMessage(stateName);
}
# endif

void sdlWriteBattery()
{
  char buffer[1048];

  if(batteryDir[0])
    sprintf(buffer, "%s/%s.sav", batteryDir, sdlGetFilename(filename));
  else  
    sprintf(buffer, "%s.sav", filename);

  emulator.emuWriteBattery(buffer);

  systemScreenMessage("Wrote battery");
}

void sdlReadBattery()
{
  char buffer[1048];
  
  if(batteryDir[0])
    sprintf(buffer, "%s/%s.sav", batteryDir, sdlGetFilename(filename));
  else 
    sprintf(buffer, "%s.sav", filename);
  
  bool res = false;

  res = emulator.emuReadBattery(buffer);

  if(res)
    systemScreenMessage("Loaded battery");
}

#define MOD_KEYS    (KMOD_CTRL|KMOD_SHIFT|KMOD_ALT|KMOD_META)
#define MOD_NOCTRL  (KMOD_SHIFT|KMOD_ALT|KMOD_META)
#define MOD_NOALT   (KMOD_CTRL|KMOD_SHIFT|KMOD_META)
#define MOD_NOSHIFT (KMOD_CTRL|KMOD_ALT|KMOD_META)

int
gba_load_rom_filename(char *filename)
{
  //LUDO:
  char *szFile = strdup(filename);

  utilGetBaseName(szFile, filename);
  char *p = strrchr(filename, '.');

  if(p) *p = 0;

  if(ipsname[0] == 0)
    sprintf(ipsname, "%s.ips", filename);
  
  bool failed = false;

  enum IMAGE_TYPE type = utilFindType(szFile);

  if(type == IMAGE_UNKNOWN) {
    systemMessage(0, "Unknown file type %s", szFile);
    exit(-1);
  }
  cartridgeType = (int)type;
  
  if(type == IMAGE_GB) {
    failed = !gbLoadRom(szFile);
    if(!failed) {
      cartridgeType = 1;
      emulator = GBSystem;
      if(sdlAutoIPS) {
        int size = gbRomSize;
        utilApplyIPS(ipsname, &gbRom, &size);
        if(size != gbRomSize) {
          extern bool gbUpdateSizes();
          gbUpdateSizes();
          gbReset();
        }
      }
    }
  } else if(type == IMAGE_GBA) {
    int size = CPULoadRom(szFile);
    failed = (size == 0);
    if(!failed) {
      //        if(cpuEnhancedDetection && cpuSaveType == 0) {
      //          utilGBAFindSave(rom, size);
      //        }

      sdlApplyPerImagePreferences();
      
      cartridgeType = 0;
      emulator = GBASystem;

      CPUInit(biosFileName, useBios);

      CPUReset();
      if(sdlAutoIPS) {
        int size = 0x2000000;
        utilApplyIPS(ipsname, &rom, &size);
        if(size != 0x2000000) {
          CPUReset();
        }
      }
    }
  }
  
  if(failed) {
    systemMessage(0, "Failed to load file %s", szFile);
    free(szFile);
    return -1;
  }

  free(szFile);

  sdlReadBattery();
  
  if(cartridgeType == 0) {
    srcWidth = 240;
    srcHeight = 160;
  } else if (cartridgeType == 1) {
    if(gbBorderOn) {
      srcWidth = 256;
      srcHeight = 224;
      gbBorderLineSkip = 256;
      gbBorderColumnSkip = 48;
      gbBorderRowSkip = 40;
    } else {      
      srcWidth = 160;
      srcHeight = 144;
      gbBorderLineSkip = 160;
      gbBorderColumnSkip = 0;
      gbBorderRowSkip = 0;
    }
  } else {
    srcWidth = 320;
    srcHeight = 240;
  }
  
  destWidth  = srcWidth;
  destHeight = srcHeight;

  return 0;
}

extern void psp_sdl_sound_init(void);

int 
SDL_main(int argc, char **argv)
{
  arg0 = argv[0];
  
  captureDir[0] = 0;
  saveDir[0] = 0;
  batteryDir[0] = 0;
  ipsname[0] = 0;
  
  gbBorderOn = 0;

  parseDebug = true;

  sdlPrintUsage = 0;
  
  gba_initialize_config();

#ifdef MMX
  if(disableMMX) cpu_mmx = 0;
#endif

  if(rewindTimer) rewindMemory = (char *)malloc(8*REWIND_SIZE);

  if(sdlFlashSize == 0) flashSetSize(0x10000);
  else                  flashSetSize(0x20000);

  rtcEnable(sdlRtcEnable ? true : false);
  agbPrintEnable(sdlAgbPrint ? true : false);
  
  for(int i = 0; i < 24;) {
    systemGbPalette[i++] = (0x1f) | (0x1f << 5) | (0x1f << 10);
    systemGbPalette[i++] = (0x15) | (0x15 << 5) | (0x15 << 10);
    systemGbPalette[i++] = (0x0c) | (0x0c << 5) | (0x0c << 10);
    systemGbPalette[i++] = 0;
  }

  systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;

  if (gba_load_rom_filename("default.gba")) {
    fprintf(stderr,"failed to load default rom\n");
    exit(-1);
  }

  psp_sdl_init();
  surface = back_surface;

  psp_sdl_sound_init();

  systemRedShift   = sdlCalculateShift(surface->format->Rmask);
  systemGreenShift = sdlCalculateShift(surface->format->Gmask);
  systemBlueShift  = sdlCalculateShift(surface->format->Bmask);
  
  if(sdlCalculateMaskWidth(surface->format->Gmask) == 6) {
    Init_2xSaI(565);
    RGB_LOW_BITS_MASK = 0x821;
  } else {
    Init_2xSaI(555);
    RGB_LOW_BITS_MASK = 0x421;      
  }
  if(cartridgeType == 2) {
    for(int i = 0; i < 0x10000; i++) {
      systemColorMap16[i] = (((i >> 1) & 0x1f) << systemBlueShift) |
        (((i & 0x7c0) >> 6) << systemGreenShift) |
        (((i & 0xf800) >> 11) << systemRedShift);  
    }      
  } else {
    for(int i = 0; i < 0x10000; i++) {
      systemColorMap16[i] = ((i & 0x1f) << systemRedShift) |
        (((i & 0x3e0) >> 5) << systemGreenShift) |
        (((i & 0x7c00) >> 10) << systemBlueShift);  
    }
  }
  srcPitch = srcWidth * 2+4;

  if(delta == NULL) {
    delta = (u8*)malloc(322*242*4);
    memset(delta, 255, 322*242*4);
  }
  
  emulating = 1;
  renderedFrames = 0;

  if(!soundOffFlag) soundInit();

  while (emulating) {
    if(!paused && active) {
      emulator.emuMain(emulator.emuCount);
      if(rewindSaveNeeded && rewindMemory && emulator.emuWriteMemState) {
        rewindCount++;
        if(rewindCount > 8)
          rewindCount = 8;
        if(emulator.emuWriteMemState &&
           emulator.emuWriteMemState(&rewindMemory[rewindPos*REWIND_SIZE], 
                                     REWIND_SIZE)) {
          rewindPos = (rewindPos + 1) & 7;
          if(rewindCount == 8)
            rewindTopPos = (rewindTopPos + 1) & 7;
        }
      }

      rewindSaveNeeded = false;
      
    }

    psp_update_keys();
  }
  
  emulating = 0;
  remoteCleanUp();
  soundShutdown();

  if(gbRom != NULL || rom != NULL) {
    sdlWriteBattery();
    emulator.emuCleanUp();
  }

  if(delta) {
    free(delta);
    delta = NULL;
  }
  
  SDL_Quit();
  return 0;
}

void 
systemMessage(int num, const char *msg, ...)
{
  char buffer[1024];
  va_list valist;
  
  va_start(valist, msg);
  vsprintf(buffer, msg, valist);
  
  fprintf(stderr, "%s\n", buffer);
  va_end(valist);
}

static
void Display_normal()
{
  int destPitch = surface->pitch;
  u8 *src = (u8*)pix;
  u8 *dest = (u8*)surface->pixels;
  int i;
  src += srcPitch;
  int delta_y = ((272 - srcHeight) / 2);
  int delta_x = ((480 - srcWidth) / 2);
  dest += (delta_y * destPitch) + (delta_x * 2);
  for(i = 0; i < srcHeight; i++) {
    u16 *s = (u16*)src;
    u16 *d = (u16*)dest;
    for(int i = 0; i < srcWidth; i++) {
      *d++ = *s++;
    }
    src += srcPitch;
    dest += destPitch;
  }
}

# if 0
static
void Display_fit_width()
{
  int destPitch = surface->pitch;
  u8 *src = (u8*)pix;
  u8 *dest = (u8*)surface->pixels;
  int i;
  int delta_y = ((272 - srcHeight) / 2);
  dest += (delta_y * destPitch);
  src  += srcPitch;
  for(i = 0; i < srcHeight; i++) {
    u16 *s = (u16*)src;
    u16 *d = (u16*)dest;
    for(int i = 0; i < 240; i++) {
      *d++ = *s;
      *d++ = *s++;
    }
    src  += srcPitch;
    dest += destPitch;
  }
}
# endif
static
void Display_fit()
{
  int destPitch = surface->pitch;
  u8 *src  = (u8*)pix;
  u8 *dest = (u8*)surface->pixels;
  src += srcPitch;
  int y;
  for(y = 0; y < 271; y++) {
    int  y_s = (y * srcHeight) /  272;
    u16 *s = (u16*)(src + (y_s * srcPitch));
    u16 *d = (u16*)dest;
    for(int i = 0; i < 240; i++) {
      *d++ = *s;
      *d++ = *s++;
    }
    dest += destPitch;
  }
}

static
void Display_speed()
{
  renderedFrames++;

  if(showSpeed && fullscreen) {
    char buffer[50];
    if(showSpeed == 1) {
      sprintf(buffer, "%d%%", systemSpeed);
    }
    if(showSpeedTransparent)
      drawTextTransp((u8*)surface->pixels,
                     surface->pitch,
                     10,
                     surface->h-20,
                     buffer);
    else
      drawText((u8*)surface->pixels,
               surface->pitch,
               10,
               surface->h-20,
               buffer);        
  }
}

void systemDrawScreen()
{
  if(screenMessage) {
    if(cartridgeType == 1 && gbBorderOn) {
      gbSgbRenderBorder();
    }
    if(((systemGetClock() - screenMessageTime) < 3000) &&
       !disableStatusMessages) {
      drawText(pix, srcPitch, 10, srcHeight - 20,
               screenMessageBuffer); 
    } else {
      screenMessage = false;
    }
  }

  if (GBA.gba_render_mode == GBA_RENDER_NORMAL   ) Display_normal();
  else
  if (GBA.gba_render_mode == GBA_RENDER_FIT      ) Display_fit();

  Display_speed();

  psp_sdl_flip();
  
  if (psp_screenshot_mode) {
    psp_screenshot_mode--;
    if (psp_screenshot_mode <= 0) {
      psp_sdl_save_screenshot();
      psp_screenshot_mode = 0;
    }
  }
}

bool systemReadJoypads()
{
  return true;
}

void
gba_sdl_key_press(int key_id, int key_press)
{
  sdlButtons[0][key_id] = (key_press != 0);
}

u32 systemReadJoypad(int which)
{
  if(which < 0 || which > 3)
    which = sdlDefaultJoypad;
  
  u32 res = 0;
  
  if(sdlButtons[which][KEY_BUTTON_A])
    res |= 1;
  if(sdlButtons[which][KEY_BUTTON_B])
    res |= 2;
  if(sdlButtons[which][KEY_BUTTON_SELECT])
    res |= 4;
  if(sdlButtons[which][KEY_BUTTON_START])
    res |= 8;
  if(sdlButtons[which][KEY_RIGHT])
    res |= 16;
  if(sdlButtons[which][KEY_LEFT])
    res |= 32;
  if(sdlButtons[which][KEY_UP])
    res |= 64;
  if(sdlButtons[which][KEY_DOWN])
    res |= 128;
  if(sdlButtons[which][KEY_BUTTON_R])
    res |= 256;
  if(sdlButtons[which][KEY_BUTTON_L])
    res |= 512;

  // disallow L+R or U+D of being pressed at the same time
  if((res & 48) == 48)
    res &= ~16;
  if((res & 192) == 192)
    res &= ~128;

  if(sdlButtons[which][KEY_BUTTON_SPEED])
    res |= 1024;
  if(sdlButtons[which][KEY_BUTTON_CAPTURE])
    res |= 2048;

  if(autoFire) {
    res &= (~autoFire);
    if(autoFireToggle)
      res |= autoFire;
    autoFireToggle = !autoFireToggle;
  }
  
  return res;
}

void 
systemShowSpeed(int speed)
{
  systemSpeed = speed;
  showRenderedFrames = renderedFrames;
  renderedFrames = 0;  
}

void system10Frames(int rate)
{
  //LUDO: u32 time = systemGetClock();  
  if(!wasPaused && throttle) {
# if 0 //LUDO:
    if(!speedup) {
      u32 diff = time - throttleLastTime;
      
      int target = (1000000/(rate*throttle));
      int d = (target - diff);
      //LUDO: SDL_delay(d);
    }
    throttleLastTime = systemGetClock();
# endif
  }
  if(rewindMemory) {
    if(++rewindCounter >= rewindTimer) {
      rewindSaveNeeded = true;
      rewindCounter = 0;
    }
  }

  if(systemSaveUpdateCounter) {
    if(--systemSaveUpdateCounter <= SYSTEM_SAVE_NOT_UPDATED) {
      sdlWriteBattery();
      systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;
    }
  }

  wasPaused = false;
}

void systemScreenCapture(int a)
{
  char buffer[2048];

  if(captureFormat) {
    if(captureDir[0])
      sprintf(buffer, "%s/%s%02d.bmp", captureDir, sdlGetFilename(filename), a);
    else
      sprintf(buffer, "%s%02d.bmp", filename, a);

    emulator.emuWriteBMP(buffer);
  } else {
    if(captureDir[0])
      sprintf(buffer, "%s/%s%02d.png", captureDir, sdlGetFilename(filename), a);
    else
      sprintf(buffer, "%s%02d.png", filename, a);
    emulator.emuWritePNG(buffer);
  }

  systemScreenMessage("Screen capture");
}

extern int soundBufferIndex;
unsigned int soundBufferReady=0;

void systemWriteDataToSoundBuffer()
{
	soundBufferReady=soundBufferIndex;
	return;
}

/* This function gets called by pspaudiolib every time the
   audio buffer needs to be filled. The sample format is
   16-bit, stereo. */
void audioCallback(void* buf, unsigned int length,void *userdata) 
{
	if (!systemSoundOn || !soundBufferReady) {
		memset(buf,0, length*4);
		return;
	}
	
	//memcpy(buf,soundFinalWave, length);
	unsigned int l=(length>soundBufferReady)?soundBufferReady:length;
	soundBufferReady = 0;
	memcpy(buf, soundFinalWave, l*4);
}

void wavoutClear()
{
	systemSoundOn=false;
	memset(soundFinalWave, 0, sizeof(soundFinalWave));
}

bool systemSoundInit()
{
	if (systemSoundOn)
		return 1;
	else
		return 0;
}

void systemSoundShutdown()
{
	wavoutClear();
}

void systemSoundPause()
{
	wavoutClear();
}

void systemSoundResume()
{
	systemSoundOn=true;
}

void systemSoundReset()
{
}

void
psp_sdl_sound_init()
{
	pspAudioInit();
	pspAudioSetChannelCallback(0, audioCallback, NULL);
}

u32 systemGetClock()
{
  return SDL_GetTicks();
}

void systemUpdateMotionSensor()
{
  if(sdlMotionButtons[KEY_LEFT]) {
    sensorX += 3;
    if(sensorX > 2197)
      sensorX = 2197;
    if(sensorX < 2047)
      sensorX = 2057;
  } else if(sdlMotionButtons[KEY_RIGHT]) {
    sensorX -= 3;
    if(sensorX < 1897)
      sensorX = 1897;
    if(sensorX > 2047)
      sensorX = 2037;
  } else if(sensorX > 2047) {
    sensorX -= 2;
    if(sensorX < 2047)
      sensorX = 2047;
  } else {
    sensorX += 2;
    if(sensorX > 2047)
      sensorX = 2047;
  }

  if(sdlMotionButtons[KEY_UP]) {
    sensorY += 3;
    if(sensorY > 2197)
      sensorY = 2197;
    if(sensorY < 2047)
      sensorY = 2057;
  } else if(sdlMotionButtons[KEY_DOWN]) {
    sensorY -= 3;
    if(sensorY < 1897)
      sensorY = 1897;
    if(sensorY > 2047)
      sensorY = 2037;
  } else if(sensorY > 2047) {
    sensorY -= 2;
    if(sensorY < 2047)
      sensorY = 2047;
  } else {
    sensorY += 2;
    if(sensorY > 2047)
      sensorY = 2047;
  }    
}

int systemGetSensorX()
{
  return sensorX;
}

int systemGetSensorY()
{
  return sensorY;
}

void systemGbPrint(u8 *data,int pages,int feed,int palette, int contrast)
{
}

void systemScreenMessage(const char *msg)
{
  screenMessage = true;
  screenMessageTime = systemGetClock();
  if(strlen(msg) > 20) {
    strncpy(screenMessageBuffer, msg, 20);
    screenMessageBuffer[20] = 0;
  } else
    strcpy(screenMessageBuffer, msg);  
}

bool systemCanChangeSoundQuality()
{
  return false;
}

bool systemPauseOnFrame()
{
  if(pauseNextFrame) {
    paused = true;
    pauseNextFrame = false;
    return true;
  }
  return false;
}

// Code donated by Niels Wagenaar (BoycottAdvance)

// GBA screensize.
#define GBA_WIDTH   240
#define GBA_HEIGHT  160

void Init_Overlay(SDL_Surface *gbascreen, int overlaytype)
{
  
  overlay = SDL_CreateYUVOverlay( GBA_WIDTH,
                                  GBA_HEIGHT,
                                  overlaytype, gbascreen);
  fprintf(stderr, "Created %dx%dx%d %s %s overlay\n",
          overlay->w,overlay->h,overlay->planes,
          overlay->hw_overlay?"hardware":"software",
          overlay->format==SDL_YV12_OVERLAY?"YV12":
          overlay->format==SDL_IYUV_OVERLAY?"IYUV":
          overlay->format==SDL_YUY2_OVERLAY?"YUY2":
          overlay->format==SDL_UYVY_OVERLAY?"UYVY":
          overlay->format==SDL_YVYU_OVERLAY?"YVYU":
          "Unknown");
}

void Quit_Overlay(void)
{
  
  SDL_FreeYUVOverlay(overlay);
}

/* NOTE: These RGB conversion functions are not intended for speed,
   only as examples.
*/
inline void RGBtoYUV(Uint8 *rgb, int *yuv)
{
  yuv[0] = (int)((0.257 * rgb[0]) + (0.504 * rgb[1]) + (0.098 * rgb[2]) + 16);
  yuv[1] = (int)(128 - (0.148 * rgb[0]) - (0.291 * rgb[1]) + (0.439 * rgb[2]));
  yuv[2] = (int)(128 + (0.439 * rgb[0]) - (0.368 * rgb[1]) - (0.071 * rgb[2]));
}

inline void ConvertRGBtoYV12(SDL_Overlay *o)
{
  int x,y;
  int yuv[3];
  Uint8 *p,*op[3];
  
  SDL_LockYUVOverlay(o);
  
  /* Black initialization */
  /*
    memset(o->pixels[0],0,o->pitches[0]*o->h);
    memset(o->pixels[1],128,o->pitches[1]*((o->h+1)/2));
    memset(o->pixels[2],128,o->pitches[2]*((o->h+1)/2));
  */
  
  /* Convert */
  for(y=0; y<160 && y<o->h; y++) {
    p=(Uint8 *)pix+srcPitch*y;
    op[0]=o->pixels[0]+o->pitches[0]*y;
    op[1]=o->pixels[1]+o->pitches[1]*(y/2);
    op[2]=o->pixels[2]+o->pitches[2]*(y/2);
    for(x=0; x<240 && x<o->w; x++) {
      RGBtoYUV(p,yuv);
      *(op[0]++)=yuv[0];
      if(x%2==0 && y%2==0) {
        *(op[1]++)=yuv[2];
        *(op[2]++)=yuv[1];
      }
      p+=4;//s->format->BytesPerPixel;
    }
  }
  
  SDL_UnlockYUVOverlay(o);
}

inline void ConvertRGBtoIYUV(SDL_Overlay *o)
{
  int x,y;
  int yuv[3];
  Uint8 *p,*op[3];
  
  SDL_LockYUVOverlay(o);
  
  /* Black initialization */
  /*
    memset(o->pixels[0],0,o->pitches[0]*o->h);
    memset(o->pixels[1],128,o->pitches[1]*((o->h+1)/2));
    memset(o->pixels[2],128,o->pitches[2]*((o->h+1)/2));
  */
  
  /* Convert */
  for(y=0; y<160 && y<o->h; y++) {
    p=(Uint8 *)pix+srcPitch*y;
    op[0]=o->pixels[0]+o->pitches[0]*y;
    op[1]=o->pixels[1]+o->pitches[1]*(y/2);
    op[2]=o->pixels[2]+o->pitches[2]*(y/2);
    for(x=0; x<240 && x<o->w; x++) {
      RGBtoYUV(p,yuv);
      *(op[0]++)=yuv[0];
      if(x%2==0 && y%2==0) {
        *(op[1]++)=yuv[1];
        *(op[2]++)=yuv[2];
      }
      p+=4; //s->format->BytesPerPixel;
    }
  }
  
  SDL_UnlockYUVOverlay(o);
}

inline void ConvertRGBtoUYVY(SDL_Overlay *o)
{
  int x,y;
  int yuv[3];
  Uint8 *p,*op;
  
  SDL_LockYUVOverlay(o);
  
  for(y=0; y<160 && y<o->h; y++) {
    p=(Uint8 *)pix+srcPitch*y;
    op=o->pixels[0]+o->pitches[0]*y;
    for(x=0; x<240 && x<o->w; x++) {
      RGBtoYUV(p,yuv);
      if(x%2==0) {
        *(op++)=yuv[1];
        *(op++)=yuv[0];
        *(op++)=yuv[2];
      } else
        *(op++)=yuv[0];
      
      p+=4; //s->format->BytesPerPixel;
    }
  }
  
  SDL_UnlockYUVOverlay(o);
}

inline void ConvertRGBtoYVYU(SDL_Overlay *o)
{
  int x,y;
  int yuv[3];
  Uint8 *p,*op;
  
  SDL_LockYUVOverlay(o);
  
  for(y=0; y<160 && y<o->h; y++) {
    p=(Uint8 *)pix+srcPitch*y;
    op=o->pixels[0]+o->pitches[0]*y;
    for(x=0; x<240 && x<o->w; x++) {
      RGBtoYUV(p,yuv);
      if(x%2==0) {
        *(op++)=yuv[0];
        *(op++)=yuv[2];
        op[1]=yuv[1];
      } else {
        *op=yuv[0];
        op+=2;
      }
      
      p+=4; //s->format->BytesPerPixel;
    }
  }
  
  SDL_UnlockYUVOverlay(o);
}

inline void ConvertRGBtoYUY2(SDL_Overlay *o)
{
  int x,y;
  int yuv[3];
  Uint8 *p,*op;
  
  SDL_LockYUVOverlay(o);
  
  for(y=0; y<160 && y<o->h; y++) {
    p=(Uint8 *)pix+srcPitch*y;
    op=o->pixels[0]+o->pitches[0]*y;
    for(x=0; x<240 && x<o->w; x++) {
      RGBtoYUV(p,yuv);
      if(x%2==0) {
        *(op++)=yuv[0];
        *(op++)=yuv[1];
        op[1]=yuv[2];
      } else {
        *op=yuv[0];
        op+=2;
      }
      
      p+=4; //s->format->BytesPerPixel;
    }
  }
  
  SDL_UnlockYUVOverlay(o);
}

inline void Convert32bit(SDL_Surface *display)
{
  switch(overlay->format) {
  case SDL_YV12_OVERLAY:
    ConvertRGBtoYV12(overlay);
    break;
  case SDL_UYVY_OVERLAY:
    ConvertRGBtoUYVY(overlay);
    break;
  case SDL_YVYU_OVERLAY:
    ConvertRGBtoYVYU(overlay);
    break;
  case SDL_YUY2_OVERLAY:
    ConvertRGBtoYUY2(overlay);
    break;
  case SDL_IYUV_OVERLAY:
    ConvertRGBtoIYUV(overlay);
    break;
  default:
    fprintf(stderr, "cannot convert RGB picture to obtained YUV format!\n");
    exit(1);
    break;
  }
  
}


static inline void Draw_Overlay(SDL_Surface *display, int size)
{
  SDL_LockYUVOverlay(overlay);
  
  Convert32bit(display);
  
  overlay_rect.x = 0;
  overlay_rect.y = 0;
  overlay_rect.w = GBA_WIDTH  * size;
  overlay_rect.h = GBA_HEIGHT * size;

  SDL_DisplayYUVOverlay(overlay, &overlay_rect);
  SDL_UnlockYUVOverlay(overlay);
}

void systemGbBorderOn()
{
  srcWidth = 256;
  srcHeight = 224;
  gbBorderLineSkip = 256;
  gbBorderColumnSkip = 48;
  gbBorderRowSkip = 40;

  destWidth = srcWidth;
  destHeight = srcHeight;
  
  surface = SDL_SetVideoMode(destWidth, destHeight, 16,
                             SDL_ANYFORMAT|SDL_HWSURFACE|SDL_DOUBLEBUF|
                             (fullscreen ? SDL_FULLSCREEN : 0));  

  if(sdlCalculateMaskWidth(surface->format->Gmask) == 6) {
    Init_2xSaI(565);
    RGB_LOW_BITS_MASK = 0x821;
  } else {
    Init_2xSaI(555);
    RGB_LOW_BITS_MASK = 0x421;      
  }
  if(cartridgeType == 2) {
    for(int i = 0; i < 0x10000; i++) {
      systemColorMap16[i] = (((i >> 1) & 0x1f) << systemBlueShift) |
        (((i & 0x7c0) >> 6) << systemGreenShift) |
        (((i & 0xf800) >> 11) << systemRedShift);  
    }      
  } else {
    for(int i = 0; i < 0x10000; i++) {
      systemColorMap16[i] = ((i & 0x1f) << systemRedShift) |
        (((i & 0x3e0) >> 5) << systemGreenShift) |
        (((i & 0x7c00) >> 10) << systemBlueShift);  
    }
  }
  srcPitch = srcWidth * 2+4;
  
}

void emulator_reset(void) {
  emulator.emuReset();
}
