#include <windows.h>
#include <SDL.h>

#include "shared.h"
#include "sms_ntsc.h"
#include "md_ntsc.h"

#define SOUND_FREQUENCY 48000
#define SOUND_SAMPLES_SIZE 2048

int timer_count = 0;
int old_timer_count = 0;
int paused = 0;
int frame_count = 0;
int frameticker = 0;
int joynum = 0;

int update_input(void);
uint8 *keystate;
uint8 buf[STATE_SIZE];

uint8 log_error   = 0;
uint8 debug_on    = 0;
uint8 turbo_mode  = 1;
uint8 use_sound   = 0;
uint8 fullscreen  = 0;

/* sound */

struct {
  char* current_pos;
  char* buffer;
  int current_emulated_samples;
} sdl_sound;

static void sdl_sound_callback(void *userdata, Uint8 *stream, int len)
{
  if(sdl_sound.current_emulated_samples < len) {
    memset(stream, 0, len);
  }
  else {
    memcpy(stream, sdl_sound.buffer, len);
    /* loop to compensate desync */
    do {
      sdl_sound.current_emulated_samples -= len;
    } while(sdl_sound.current_emulated_samples > 2 * len);
    memcpy(sdl_sound.buffer,
           sdl_sound.current_pos - sdl_sound.current_emulated_samples,
           sdl_sound.current_emulated_samples);
    sdl_sound.current_pos = sdl_sound.buffer + sdl_sound.current_emulated_samples;
  }
}

static int sdl_sound_init()
{
  int n;
  SDL_AudioSpec as_desired, as_obtained;
  
  if(SDL_Init(SDL_INIT_AUDIO) < 0) {
    printf("ERROR: %s.\n", SDL_GetError());
    return 0;
  }

  as_desired.freq = SOUND_FREQUENCY;
  as_desired.format = AUDIO_S16LSB;
  as_desired.channels = 2;
  as_desired.samples = SOUND_SAMPLES_SIZE;
  as_desired.callback = sdl_sound_callback;

  if(SDL_OpenAudio(&as_desired, &as_obtained) == -1) {
    printf("ERROR: can't open audio: %s.\n", SDL_GetError());
    return 0;
  }

  if(as_desired.samples != as_obtained.samples) {
    printf("ERROR: soundcard driver does not accept specified samples size.\n");
    return 0;
  }

  sdl_sound.current_emulated_samples = 0;
  n = SOUND_SAMPLES_SIZE * 2 * sizeof(short) * 11;
  sdl_sound.buffer = (char*)malloc(n);
  if(!sdl_sound.buffer) {
    printf("ERROR: can't allocate memory for sound.\n");
    return 0;
  }
  memset(sdl_sound.buffer, 0, n);
  sdl_sound.current_pos = sdl_sound.buffer;
  return 1;
}

static void sdl_sound_update()
{
  int i;
  short* p;

  SDL_LockAudio();
  p = (short*)sdl_sound.current_pos;
  for(i = 0; i < snd.buffer_size; ++i) {
      *p = snd.buffer[0][i];
      ++p;
      *p = snd.buffer[1][i];
      ++p;
  }
  sdl_sound.current_pos = (char*)p;
  sdl_sound.current_emulated_samples += snd.buffer_size * 2 * sizeof(short);
  SDL_UnlockAudio();
}

static void sdl_sound_close()
{
  SDL_PauseAudio(1);
  SDL_CloseAudio();
  free(sdl_sound.buffer);
}

static SDL_Rect rect;
static SDL_Surface* screen;
static SDL_Surface* surface;
static unsigned char* output_pixels; /* 16-bit RGB */
static long output_pitch;

Uint32 fps_callback(Uint32 interval)
{
  if(paused) return 1000/vdp_rate;
  timer_count++;
  frameticker ++;
  if(timer_count % vdp_rate == 0)
  {
    int fps = frame_count + 1;
    char caption[100];
    char region[10];
    if (region_code == REGION_USA) sprintf(region,"USA");
    else if (region_code == REGION_EUROPE) sprintf(region,"EUR");
    else sprintf(region,"JAP");
    sprintf(caption, "Genesis Plus/SDL - %s (%s) - %d fps - xoffset = %d", rominfo.international, region, fps,input.x_offset);
    SDL_WM_SetCaption(caption, NULL);
    frame_count = 0;
    
  }
  return 1000/vdp_rate;
}

void display_output( void )
{
  SDL_Rect dest;
  dest.w=rect.w;
  dest.h=rect.h;
  dest.x=(640-rect.w)/2;
  dest.y=(480-rect.h)/2;
  if ( SDL_BlitSurface( surface, &rect, screen, &dest ) < 0 || SDL_Flip( screen ) < 0 )
    MessageBox(NULL, "SDL blit failed", "Error", 0);
}

md_ntsc_t md_ntsc;
sms_ntsc_t sms_ntsc;

int main (int argc, char **argv)
{
  int running = 1;
  int sym;
  md_ntsc_setup_t md_setup;
  sms_ntsc_setup_t sms_setup;

  error_init();

  /* Print help if no game specified */
  if(argc < 2)
  {
    char caption[256];
    sprintf(caption, "Genesis Plus\\SDL by Charles MacDonald\nWWW: http://cgfm2.emuviews.com\nusage: %s gamename\n", argv[0]);
    MessageBox(NULL, caption, "Information", 0);
    exit(1);
  }

  /* set default config */
  set_config_defaults();

  /* Load game */
  cart_rom = malloc(10*1024*1024);
  memset(cart_rom, 0, 10*1024*1024);
  if(!load_rom(argv[1]))
  {
    char caption[256];
    sprintf(caption, "Error loading file `%s'.", argv[1]);
    MessageBox(NULL, caption, "Error", 0);
    exit(1);
  }

  /* initialize SDL */
  if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0)
  {
    char caption[256];
    sprintf(caption, "SDL initialization failed");
    MessageBox(NULL, caption, "Error", 0);
    exit(1);
  }
  SDL_WM_SetCaption("Genesis Plus/SDL", NULL);
  SDL_ShowCursor(0);

  screen = SDL_SetVideoMode(640, 480, 16,  fullscreen ? (SDL_HWSURFACE|SDL_FULLSCREEN): (SDL_HWSURFACE));
  surface  = SDL_CreateRGBSurface(SDL_HWSURFACE, 720, 576, 16, 0, 0, 0, 0);
  if (!screen || !surface)
  {
    MessageBox(NULL, "Video initialization failed", "Error", 0);
    exit(1);
  }

  /* initialize Genesis display */
  memset(&bitmap, 0, sizeof(t_bitmap));
  bitmap.width  = 720;
  bitmap.height = 576;
  bitmap.depth  = 16;
  bitmap.granularity = 2;
  bitmap.pitch = (bitmap.width * bitmap.granularity);
  bitmap.data   = surface->pixels;
  bitmap.viewport.w = 256;
  bitmap.viewport.h = 224;
  bitmap.viewport.x = 0;
  bitmap.viewport.y = 0;

  /* load BIOS */
  memset(bios_rom, 0, sizeof(bios_rom));
  FILE *f = fopen("./BIOS.bin", "rb");
  if (f!=NULL)
  {
    fread(&bios_rom, 0x800,1,f);
    fclose(f);
    int i;
    for(i = 0; i < 0x800; i += 2)
    {
      uint8 temp = bios_rom[i];
      bios_rom[i] = bios_rom[i+1];
      bios_rom[i+1] = temp;
    }
    config.bios_enabled |= 2;
  }

  /* initialize emulation */
  system_init();
  audio_init(SOUND_FREQUENCY);

  /* initialize SDL audio */
  if (use_sound) use_sound = sdl_sound_init();

  /* load SRAM */
  f = fopen("./game.srm", "rb");
  if (f!=NULL)
  {
    fread(sram.sram,0x10000,1, f);
    fclose(f);
  }

  /* reset emulation */
  system_reset();

  /* start emulation loop */
  SDL_SetTimer(1000/vdp_rate, fps_callback);
  if (use_sound) SDL_PauseAudio(0);

  while(running)
  {
    SDL_Event event;
    if (SDL_PollEvent(&event)) 
    {
      switch(event.type) 
      {
        case SDL_QUIT: /* Windows was closed */
          running = 0;
          break;

        case SDL_KEYDOWN:   /* user options */
          sym = event.key.keysym.sym;

          if(sym == SDLK_TAB)    system_reset();
          else if (sym == SDLK_RETURN)
          {
            fullscreen ^=1;
            screen = SDL_SetVideoMode(640, 480, 16,  fullscreen ? (SDL_HWSURFACE|SDL_FULLSCREEN): (SDL_HWSURFACE));

          }
          else if(sym == SDLK_F3) config.render ^=1;
          else if(sym == SDLK_F4)
          {
            SDL_FillRect( screen, 0, 0 );
            config.ntsc ++;
            if (config.ntsc > 3) config.ntsc = 0;
            bitmap.viewport.changed = 1;
          }
          else if(sym == SDLK_F5)     log_error ^=1;
          else if(sym == SDLK_F6)
          {
            turbo_mode ^=1;
            frameticker = 0;
          }
          else if(sym == SDLK_F7)
          {
            f = fopen("game.gpz","r+b");
            if (f)
            {
              fread(&buf, STATE_SIZE, 1, f);
              state_load(buf);
              fclose(f);
            }
          }
          else if(sym == SDLK_F8)
          {
            f = fopen("game.gpz","w+b");
            if (f)
            {
              state_save(buf);
              fwrite(&buf, STATE_SIZE, 1, f);
              fclose(f);
            }
          }
          else if(sym == SDLK_F9)     
          {
            vdp_pal ^= 1;

            /* reinitialize timings */
            system_init ();
            unsigned char *temp = malloc(YM2612GetContextSize());
            if (temp) memcpy(temp, YM2612GetContextPtr(), YM2612GetContextSize());
            audio_init(48000);
            if (temp)
            {
              YM2612Restore(temp);
              free(temp);
            }

            /* reinitialize HVC tables */
            vctab = (vdp_pal) ? ((reg[1] & 8) ? vc_pal_240 : vc_pal_224) : vc_ntsc_224;
            hctab = (reg[12] & 1) ? cycle2hc40 : cycle2hc32;

            /* reinitialize overscan area */
            bitmap.viewport.x = config.overscan ? 14 : 0;
            bitmap.viewport.y = config.overscan ? (((reg[1] & 8) ? 0 : 8) + (vdp_pal ? 24 : 0)) : 0;
          }
          else if(sym == SDLK_F10) set_softreset();
          else if(sym == SDLK_F11)
          {
            joynum ++;
            if (joynum > MAX_DEVICES - 1) joynum = 0;
            while (input.dev[joynum] == NO_DEVICE)
            {
              joynum ++;
              if (joynum > MAX_DEVICES - 1) joynum = 0;
            }
          }
          else if(sym == SDLK_ESCAPE) running = 0;
          else if(sym == SDLK_F12)
          {
            config.overscan ^= 1;
            bitmap.viewport.x = config.overscan ? ((reg[12] & 1) ? 16 : 12) : 0;
            bitmap.viewport.y = config.overscan ? (((reg[1] & 8) ? 0 : 8) + (vdp_pal ? 24 : 0)) : 0;
            bitmap.viewport.changed = 1;
          }
          break;

        default:
          break;
      }
    }


    if(!paused)
    {
      if (frameticker > 1)
      {
        /* Frame skipping */
        frameticker--;
        system_frame (1);
      }
      else
      {
        /* Delay */
        while (!frameticker && !turbo_mode) SDL_Delay(0);

        system_frame (0);
        frame_count++;
      }

      frameticker--;

      /* Sound Update */
      audio_update(snd.buffer_size);
      if (use_sound) sdl_sound_update();

      /* Video update */
      if(bitmap.viewport.changed)
      {
        bitmap.viewport.changed = 0;
        rect.w = bitmap.viewport.w+2*bitmap.viewport.x;
        rect.h = bitmap.viewport.h+2*bitmap.viewport.y;
        if (config.render && (interlaced || config.ntsc))  rect.h *= 2;
        if (config.ntsc) rect.w = (reg[12]&1) ? MD_NTSC_OUT_WIDTH(rect.w) : SMS_NTSC_OUT_WIDTH(rect.w);

        /* init NTSC filter */
        if (config.ntsc == 1)
        {
          sms_setup = sms_ntsc_composite;
          md_setup  = md_ntsc_composite;
          sms_ntsc_init( &sms_ntsc, &sms_setup );
          md_ntsc_init( &md_ntsc, &md_setup );
        }
        else if (config.ntsc == 2)
        {
          sms_setup = sms_ntsc_svideo;
          md_setup  = md_ntsc_svideo;
          sms_ntsc_init( &sms_ntsc, &sms_setup );
          md_ntsc_init( &md_ntsc, &md_setup );
        }
        else if (config.ntsc == 3)
        {
          sms_setup = sms_ntsc_rgb;
          md_setup  = md_ntsc_rgb;
          sms_ntsc_init( &sms_ntsc, &sms_setup );
          md_ntsc_init( &md_ntsc, &md_setup );
        }
      }

      display_output();
    }
  }

  /* save SRAM */
  f = fopen("./game.srm", "wb");
  if (f!=NULL)
  {
    fwrite(&sram.sram,0x10000,1, f);
    fclose(f);
  }

  if (use_sound) sdl_sound_close();
  SDL_FreeSurface(surface);
  SDL_FreeSurface(screen);
  SDL_Quit();
  system_shutdown();
  error_shutdown();
  free(cart_rom);

  return 0;
}

int update_input(void)
{
  keystate = SDL_GetKeyState(NULL);

  while (input.dev[joynum] == NO_DEVICE)
  {
    joynum ++;
    if (joynum > MAX_DEVICES - 1) joynum = 0;
  }

  /* reset input */
  input.pad[joynum] = 0;

  /* keyboard */
  if(keystate[SDLK_UP])     input.pad[joynum] |= INPUT_UP;
  else
  if(keystate[SDLK_DOWN])   input.pad[joynum] |= INPUT_DOWN;
  if(keystate[SDLK_LEFT])   input.pad[joynum] |= INPUT_LEFT;
  else
  if(keystate[SDLK_RIGHT])  input.pad[joynum] |= INPUT_RIGHT;

  if(keystate[SDLK_a])      input.pad[joynum] |= INPUT_A;
  if(keystate[SDLK_s])      input.pad[joynum] |= INPUT_B;
  if(keystate[SDLK_d])      input.pad[joynum] |= INPUT_C;
  if(keystate[SDLK_f])      input.pad[joynum] |= INPUT_START;
  if(keystate[SDLK_z])      input.pad[joynum] |= INPUT_X;
  if(keystate[SDLK_x])      input.pad[joynum] |= INPUT_Y;
  if(keystate[SDLK_c])      input.pad[joynum] |= INPUT_Z;
  if(keystate[SDLK_v])      input.pad[joynum] |= INPUT_MODE;

  if (input.dev[joynum] == DEVICE_LIGHTGUN)
  {
    /* get mouse (absolute values) */
    int x,y;
    int state = SDL_GetMouseState(&x,&y);

    /* Calculate X Y axis values */
    input.analog[joynum - 4][0] = (x * bitmap.viewport.w) / 640;
    input.analog[joynum - 4][1] = (y * bitmap.viewport.h) / 480;

    /* Map mouse buttons to player #1 inputs */
    if(state & SDL_BUTTON_MMASK) input.pad[joynum] |= INPUT_C;
    if(state & SDL_BUTTON_RMASK) input.pad[joynum] |= INPUT_B;
    if(state & SDL_BUTTON_LMASK) input.pad[joynum] |= INPUT_A;
  }
  else if (input.dev[joynum] == DEVICE_MOUSE)
{
    /* get mouse (relative values) */
    int x,y;
    int state = SDL_GetRelativeMouseState(&x,&y);

    /* Sega Mouse range is -256;+256 */
    input.analog[2][0] = x;
    input.analog[2][1] = y;

    /* Vertical movement is upsidedown */
    if (!config.invert_mouse) input.analog[2][1] = 0 - input.analog[2][1];

    /* Map mouse buttons to player #1 inputs */
    if(state & SDL_BUTTON_MMASK) input.pad[joynum] |= INPUT_C;
    if(state & SDL_BUTTON_RMASK) input.pad[joynum] |= INPUT_B;
    if(state & SDL_BUTTON_LMASK) input.pad[joynum] |= INPUT_A;
  }
  else if (system_hw == SYSTEM_PICO)
  {
    /* get mouse (absolute values) */
    int x,y;
    int state = SDL_GetMouseState(&x,&y);

    /* Calculate X Y axis values */
    input.analog[0][0] = 0x3c  + (x * (0x17c-0x03c+1)) / 640;
    input.analog[0][1] = 0x1fc + (y * (0x2f7-0x1fc+1)) / 480;
 
    /* Map mouse buttons to player #1 inputs */
    if(state & SDL_BUTTON_MMASK) pico_current++;
    if(state & SDL_BUTTON_RMASK) input.pad[joynum] |= INPUT_B;
    if(state & SDL_BUTTON_LMASK) input.pad[joynum] |= INPUT_A;
  }

  /* options */
  return (1);
}