#define __SEQUENCE_C
#include "common.h"
#include <PR/sched.h>
#include "ks_nes.h"
#include "audio/emuaudio.h"
#define _128
#define _64
#define _32
#define _16

#include "letters_img.h"

extern u8 _nes_donkeySegmentRomStart[], _nes_donkeySegmentRomEnd[];
extern u8 _nes_donkeyjrSegmentRomStart[], _nes_donkeyjrSegmentRomEnd[];
extern u8 _nes_balloonSegmentRomStart[], _nes_balloonSegmentRomEnd[];
extern u8 _nes_marioSegmentRomStart[], _nes_marioSegmentRomEnd[];

// $B%a%K%e!<%;%l%/%H$9$k%2!<%`%?%$%H%k(B
typedef struct game_menu_t {
  char *name;
  u8 *begin;
  u8 *end;
} game_menu_t;

game_menu_t game_menu[] = {
  {"Donkey Kong", _nes_donkeySegmentRomStart, _nes_donkeySegmentRomEnd},
  {"Donkey Kong JR", _nes_donkeyjrSegmentRomStart, _nes_donkeyjrSegmentRomEnd},
  {"Balloon Fight", _nes_balloonSegmentRomStart, _nes_balloonSegmentRomEnd},
  {"Mario Bros.", _nes_marioSegmentRomStart, _nes_marioSegmentRomEnd},
  {NULL, NULL, NULL}
};

/* $B%P%k!<%s%U%!%$%H$G!"5/F0;~!"Gr?'$N%U%i%C%7%s%0$,H/@8$9$k$N$G(B
 * $B5/F0D>8e(B3$B%U%l!<%`$r6/@)%V%i%s%-%s%0$K$9$k!#(B
 */
int force_blank_count;
int menu_page;
char game_name_buf[64];
char menu_string_buf[64];

void my_strcpy(char *dst, char *src)
{
  while (*src) {
    *dst++ = *src++;
  }
  *dst = '\0';
}


void my_strcat(char *dst, char *src)
{
  while (*dst) {
    dst++;
  }
  while (*src) {
    *dst++ = *src++;
  }
  *dst = '\0';
}


// 0x60 (ASCII 0x20-0x7f) chars, 8 dots * 16 lines
u16 letters_u16[0x60 * 8 * 16];

extern GAME_NES game_nes, game_nes_audio;
u8 *nesrom_ptr;

PUBLIC void *sq_menu(int initial, void *self);
PUBLIC void *sq_game_select(int initial, void *self);

void make_texture_image(void)
{
  static u16 table[] = {
    // ASCII, byte offset, top blank lines, data lines
#define GROUPA 1,10,
    'a', 0x020, GROUPA
    'b', 0x024, GROUPA
    'c', 0x028, GROUPA
    'd', 0x02c, GROUPA
    'e', 0x030, GROUPA
    'f', 0x034, GROUPA
    'h', 0x038, GROUPA
    'i', 0x03c, GROUPA

#define GROUPB 4,7,
    'u', 0x200, GROUPB
    'v', 0x204, GROUPB
    's', 0x208, GROUPB
    'n', 0x20c, GROUPB
    'o', 0x210, GROUPB
    'r', 0x214, GROUPB
    'm', 0x218, GROUPB
    '.', 0x21c, GROUPB

#define GROUPC GROUPA
    'k', 0x320, GROUPC
    'l', 0x324, GROUPC
    'x', 0x328, GROUPC
    'z', 0x32c, GROUPC
    '4', 0x330, GROUPC
    '3', 0x334, GROUPC
    '2', 0x338, GROUPC
    '1', 0x33c, GROUPC

#define GROUPD GROUPC
    '5', 0x4a0, GROUPD
    '6', 0x4a4, GROUPD
    '7', 0x4a8, GROUPD
    '8', 0x4ac, GROUPD
    '9', 0x4b0, GROUPD
    '0', 0x4b4, GROUPD
    '\`', 0x4b8, GROUPD
    '!', 0x4bc, GROUPD

#define GROUPE 1,14,
    'g', 0x620, GROUPE
    'j', 0x624, GROUPE
    'p', 0x628, GROUPE
    'q', 0x62c, GROUPE
    'y', 0x630, GROUPE
    't', 0x634, GROUPE
    'w', 0x638, GROUPE
    ',', 0x63c, GROUPE

#define GROUPF GROUPC
    'E', 0x820, GROUPF
    'F', 0x824, GROUPF
    'G', 0x828, GROUPF
    'H', 0x82c, GROUPF
    'K', 0x830, GROUPF
    'L', 0x834, GROUPF
    'N', 0x838, GROUPF
    'O', 0x83c, GROUPF

#define GROUPG GROUPF
    'P', 0x9a0, GROUPG
    'R', 0x9a4, GROUPG
    'S', 0x9a8, GROUPG
    'U', 0x9ac, GROUPG
    'V', 0x9b0, GROUPG
    'X', 0x9b4, GROUPG
    'Z', 0x9b8, GROUPG
    '@', 0x9bc, GROUPG

#define GROUPH GROUPF
    'J', 0xb20, GROUPH
    'M', 0xb24, GROUPH
    ':', 0xb28, GROUPH
    'W', 0xb2c, GROUPH
    'I', 0xb30, GROUPH
    'Y', 0xb34, GROUPH
    'A', 0xb38, GROUPH
    'B', 0xb3c, GROUPH

#define GROUPI GROUPF
    'C', 0xca0, GROUPI
    'D', 0xca4, GROUPI
    '\'', 0xca8, GROUPI
    'T', 0xcac, GROUPI
    '\"', 0xcb0, GROUPI
    '+', 0xcb4, GROUPI
    '-', 0xcb8, GROUPI
    '=', 0xcbc, GROUPI

#define GROUPJ 1,14,
    'Q', 0xe20, GROUPJ
    '_', 0xe24, GROUPJ
    '/', 0xe28, GROUPJ
    '?', 0xe2c, GROUPJ
    '(', 0xe30, GROUPJ
    ')', 0xe34, GROUPJ
    '[', 0xe38, GROUPJ
    ']', 0xe3c, GROUPJ
      0 // end
  };
  int ch;
  int search_ch;
  int x, y;
  int bit;

  for (ch = 0x21; ch < 0x7f; ch++) {
    search_ch = 0;
    while (table[search_ch * 4 + 0]) {
      if (ch == table[search_ch * 4 + 0]) {
        for (y = 0; y < table[search_ch * 4 + 3]; y++) {
          for (x = 0; x < 8; x += 2) {
            bit = letters_img[table[search_ch * 4 + 1] + y * 0x20 + (x >> 1)];
            letters_u16[((ch - 0x20) * 16 + y + table[search_ch * 4 + 2]) * 8 + x + 0]
              = ((bit & 0xf0) ? 0xffff : 0);
            letters_u16[((ch - 0x20) * 16 + y + table[search_ch * 4 + 2]) * 8 + x + 1]
              = ((bit & 0x0f) ? 0xffff : 0);
          }
        }
        break;
      }
      search_ch++;
    }
  }
}

void put_letter(u16 *cfbp, u32 ch)
{
  u16 *imgp;
  int x, y;
  u8 img;

  ch = (ch - 0x20) * 8 * 16;
  for (y = 0; y < 16; y++) {
    bcopy(&letters_u16[ch], &cfbp[y * SCREEN_WD], 8 * sizeof(u16));
    ch += 8;
  }
}

void print_string(u16 *cfbp, char *s)
{
  int i;

  while (*s) {
    put_letter(cfbp, *s++);
    cfbp += 8;
  }
}

PUBLIC void *sq_main(int initial, void *self)
{
  extern void ScanController(void);
  extern void ReadController(void);
  extern void ks_nes_reset(void *game_p);
  int i;
  int j;

  if (force_blank_count) {
    force_blank_count--;
  }

  ScanController();
  ReadController();

  for (i = 0; i < MAXCONTROLLERS; i++) {
    game_nes.n64_buttons[i] = pad[i].button;
    /* R $B$G$b%;%l%/%H%\%?%s$H$_$J$9(B */
    if (pad[i].button & CONT_R) {
      game_nes.n64_buttons[i] |= CONT_Z;
    }
  }
  game_nes.g.exec(&game_nes);

  /* $BA02s(B task $B$rH/9T$7$?$N$J$i(B $B$=$N40N;(B Mesg $B$rBT$A!"(Bswapbuffer$B!#(B*/

  /* local settings
   * task request $B=hM}(B ... $B$3$C$A$G$O(B $B%j%"%k%?%$%`$G%?%9%/H/9T$,$G$-$J$$$s$@$J!#(B
   */
  task_request[MY_TASK_RFC].taskp = &game_nes.os_sc_task.list;
  while (task_request[MY_TASK_RFC].status) {
    /* wait
     * $B$3$l$rF~$l$J$$$H!"A02s$N(B task $B$,(B $B=hM}Mn$A$G$^$@<B9T$5$l$F$$$J$$;~(B
     * handshake $B$G;_$^$C$F$7$^$&!#(B
     */
  }
  osWritebackDCacheAll();


#if 0
  // RESET NES ?
  if (pad[0].trig & CONT_L) {
    extern void Sound_Write(u16 reg, u8 value, u16 time); /*** Write Register (4000-4015) ***/
    Sound_Write(0x4015, 0, 0);
    ks_nes_reset(NULL);
  } else
#endif

  // Menu ?
  if (force_blank_count == 0 && (pad[0].trig & CONT_L)) {
    emulator_running = 0; /* $B%a%$%s%9%l%C%I$N(B $B%3%s%H%m!<%i%"%/%;%9$r5v2D(B */
    return sq_menu;
  }
  task_request[MY_TASK_RFC].status = MY_TASK_STATUS_REQUESTED;


  osViBlack(force_blank_count ? TRUE : blanking_flag);
  osViSetXScale(0.888);
  osViSwapBuffer(game_nes.cfbp[game_nes.cfb_no]);

  return self;
}

PUBLIC void *sq_menu(int initial, void *self)
{
  static int flag;
  static int selbuf;
  u16 *cfbp;
  int i;
  int j;
  int x, y;

  if (initial) {
    for (i = 2; i < 4; i++) {
      bcopy(game_nes.cfbp[game_nes.cfb_no], game_nes.cfbp[i], SCREEN_WD * SCREEN_HT_FPAL * sizeof(u16));
    }
    selbuf = 2;
    flag = 0;
    return self;
  }

  selbuf ^= 1;
  cfbp = game_nes.cfbp[selbuf];
  for (y = 50; y < 200; y++) {
    for (x = 50; x < 200; x++) {
      cfbp[y * SCREEN_WD + x] = 0;
    }
  }
  print_string(&cfbp[100 * SCREEN_WD + 60], "MENU");
  print_string(&cfbp[160 * SCREEN_WD + 60], "L or B: Continue");
  print_string(&cfbp[180 * SCREEN_WD + 60], "R: Quit");

  if (flag == 0) {
    if (pad[0].button == 0) {
      // $B0lC6%\%?%s$,N%$5$l$k$^$GA`:n$r<u$1IU$1$J$$(B
      flag = 1;
    }
  }

  if (flag && (pad[0].trig & (CONT_L | CONT_B))) {
    task_request[MY_TASK_RFC].status = MY_TASK_STATUS_REQUESTED;
    emulator_running = 1; /* $B%a%$%s%9%l%C%I$O(B $B%3%s%H%m!<%i$N%"%/%;%96X;_(B */
    return sq_main;
  }

  if (flag && (pad[0].trig & CONT_R)) {
    return sq_game_select;
  }

  if (flag && (pad[0].trig & CONT_RIGHT)) {
    if (menu_page < 9) {
      menu_page++;
    }
  } else if (flag && (pad[0].trig & CONT_LEFT)) {
    if (menu_page > 0) {
      menu_page--;
    }
  }

  my_strcpy(menu_string_buf, "menu_page");
  menu_string_buf[9] = ' ';
  menu_string_buf[10] = menu_page + '0';
  menu_string_buf[11] = '\0';
  print_string(&cfbp[120 * SCREEN_WD + 60], menu_string_buf);

  osWritebackDCacheAll();
  osViSwapBuffer(game_nes.cfbp[selbuf]);
  return self;
}


PUBLIC void *sq_game_select(int initial, void *self)
{
  extern u8 ksNesVersion[];
  static int flag;
  static int selbuf;
  u16 *cfbp;
  static int game_index;
  int game_max;
  int i;

  if (initial) {
    osViSetMode(&osViModeTable[OS_VI_NTSC_LPN1]);
    osViSetSpecialFeatures(
			   OS_VI_GAMMA_OFF |
			   OS_VI_GAMMA_DITHER_OFF |
			   OS_VI_DIVOT_OFF
			   );
    /* OS bug workaround */
    osViSetSpecialFeatures(OS_VI_DITHER_FILTER_OFF);
    osViSetXScale(0.888);
    flag = 0;
    selbuf = 2;
    for (i = 0; i < 4; i++) {
      bzero(game_nes.cfbp[i], SCREEN_WD * SCREEN_HT_FPAL * sizeof(u16));
    }
    osWritebackDCacheAll();
    osViSwapBuffer(game_nes.cfbp[2]);
    osViBlack(FALSE);
    return self;
  }

  game_max = 0;
  while (game_menu[game_max].name) {
    game_max++;
  }

  selbuf ^= 1;
  cfbp = game_nes.cfbp[selbuf];
  bzero(cfbp, SCREEN_WD * SCREEN_HT_FPAL * sizeof(u16));

  if (flag == 0) {
    if (pad[0].button == 0) {
      // $B0lC6%\%?%s$,N%$5$l$k$^$GA`:n$r<u$1IU$1$J$$(B
      flag = 1;
    }
  }
  if (flag && (pad[0].trig & (CONT_START | CONT_A))) {
    osWritebackDCacheAll();
    osViSwapBuffer(game_nes.cfbp[0]);
    rom_load(game_menu[game_index].begin, nesrom_ptr,
             game_menu[game_index].end - game_menu[game_index].begin);
    game_nes.nesrom = nesrom_ptr;
    game_nes.g.init(&game_nes);

    force_blank_count = 3;
    emulator_running = 1; /* $B%a%$%s%9%l%C%I$O(B $B%3%s%H%m!<%i$N%"%/%;%96X;_(B */
    return sq_main;
  } else {
    print_string(&cfbp[30 * SCREEN_WD + 40], "GAME SELECT");

    // $B%G%P%C%0%P!<%8%g%s$J$i$=$&I=<((B
    print_string(&cfbp[10 * SCREEN_WD + 100], (char *)ksNesVersion);
    
    for (i = 0; i < game_max; i++) {
      if (i == game_index) {
        game_name_buf[0] = '[';
        my_strcpy(&game_name_buf[1], game_menu[i].name);
        my_strcat(&game_name_buf[1], "]");
      } else {
        my_strcpy(game_name_buf, game_menu[i].name);
      }
      print_string(&cfbp[(50 + i * 20) * SCREEN_WD + 50 + ((i != game_index) ? 8 : 0)], game_name_buf);
    }

    if (flag && (pad[0].trig & CONT_UP)) {
      if (game_index) {
        game_index--;
      }
    }
    if (flag && (pad[0].trig & CONT_DOWN)) {
      if (game_index < (game_max - 1)) {
        game_index++;
      }
    }
  }

  osWritebackDCacheAll();
  osViSwapBuffer(game_nes.cfbp[selbuf]);
  return self;
}


PUBLIC void *sq_init(int initial, void *self)
{
  extern void ks_nes_audio_init(GAME_NES *game_p);
  extern void ks_nes_init(GAME_NES *game_p);

  game_nes_audio.g.init = ks_nes_audio_init;
  game_nes.g.init = ks_nes_init;

  game_nes_audio.g.init(&game_nes_audio);

  nesrom_ptr = ks_get_memblock(0x100000, "nes_rom");

  make_texture_image();

  /* local settings */
  blanking_flag = FALSE;
  game_nes.cfbp[0] = cfbp;
  game_nes.cfbp[1] = cfb_oldp;
  game_nes.cfbp[2] = menu_cfbp;
  game_nes.cfbp[3] = menu_cfb_oldp;
  audioproc_enabled = 1;

  return sq_game_select;
}
