#define CODE_ROM
#define ISV64
#define __MAIN_C
#include "common.h"
#include <PR/sched.h>
#include "ks_nes.h"


GAME_NES game_nes, game_nes_audio;

PUBLIC int emulator_running;
PUBLIC OSTask my_task[MY_TASK_MAX];
PUBLIC u32 ks_memblock_begin, ks_memblock_end;
/* $B%+%i!<%U%l!<%`%P%C%U%!(B */
PUBLIC u16 *cfbp; /* $B<!$KI=<($7$?$$(B $B%U%l!<%`%P%C%U%!%]%$%s%?(B */
PUBLIC u16 *cfb_oldp; /* cfb_oldp $B$,(B retrace $B;~$K(B osViSwapBuffer $B$5$l$k(B */
PUBLIC u16 *menu_cfbp;
PUBLIC u16 *menu_cfb_oldp;
PUBLIC vu32 frame_count;
PUBLIC task_request_object task_request[MY_TASK_MAX];
static RSPTask *rsp_taskp;
/* boot_stack $B$O(B PUBLIC $B$H$$$&$o$1$G$O$J$$$,(B static $B$G$"$C$F$O(B $B$J$i$J$$(B */
u64 boot_stack[BOOT_STACKSIZE / sizeof(u64)];
#ifdef PTN64
static OSThread rmonThread;
static u64      rmonStack[RMON_STACKSIZE/sizeof(u64)];
#endif
static OSThread audio_thread;
static u64      audio_thread_stack[AUDIO_STACKSIZE / sizeof(u64)];
PUBLIC OSThread main_thread;
static u64      main_thread_stack[MAIN_STACKSIZE / sizeof(u64)];
static OSThread tman_thread;
static u64      tman_thread_stack[TMAN_STACKSIZE / sizeof(u64)];
static OSThread idle_thread;
static u64      idle_thread_stack[IDLE_STACKSIZE / sizeof(u64)];
static OSMesgQueue pi_msgq;
static OSMesg      pi_msgbuf[PI_MSGS_MAX];
PUBLIC OSMesgQueue dma_msgq;
static OSMesg dma_msgbuf[DMA_MSGS_MAX];
PUBLIC OSMesgQueue audio_timing_msgq;
static OSMesg audio_timing_msgbuf[AUDIO_TIMING_MSGS_MAX];
static OSMesgQueue main_timing_msgq;
static OSMesg main_timing_msgbuf[MAIN_TIMING_MSGS_MAX];
static OSMesgQueue taskman_msgq;
static OSMesg taskman_msgbuf[TASKMAN_MSGS_MAX];
PUBLIC OSIoMesg dma_io_msgbuf[DMA_IO_MSGS_MAX];
#define NUM_LEO_MESGS 8
static OSMesg LeoMessages[NUM_LEO_MESGS];
static OSPiHandle *cart_epi_handle;
static OSPiHandle *disk_epi_handle;
/* $B%3%s%H%m!<%i(B $B4X78(B */
PUBLIC OSMesgQueue pad_msgq;
static OSMesg pad_msgbuf[PAD_MSGS_MAX];
static OSContStatus pad_status[MAXCONTROLLERS];
PUBLIC OSContPad os_pad_data[MAXCONTROLLERS];
/* npad: $BM-8z$J(B $B%3%s%H%m!<%i$N(B $B?t(B */
PUBLIC u32 npad;
PUBLIC pad_object pad[MAXCONTROLLERS];
PUBLIC u8 pad_index[MAXCONTROLLERS];
/* sequence: $B%7!<%1%s%9(B $B4X?t%]%$%s%?(B */
PUBLIC sq sequencep;
PUBLIC u32 blanking_flag; /* retrace $B;~$K(B $B$3$NCM$r(B osViBlack $B0z?t$H$9$k(B */

/* $B%a%b%j%V%m%C%/$rF@$k!#(B64 byte alignment $B$5$l$k!#(B
 * $B=i4|2=$OM?$($F$bNI$$%V%m%C%/A4BN$N3+;O(B(+0)$B!"=*N;%"%I%l%9(B(+1)$B$r(B
 * ks_memblock_begin $B$H(B ks_memblock_end $B$K(B $B$=$l$>$l%;%C%H$9$k!#(B
 * ks_memblock_begin $B$O(B $B%a%b%j$rM?$($k$[$I(B ks_memblock_end $B$K6a$E$$$F$$$/!#(B
 * garbage collection $B$O$7$J$$!#%V%m%C%/$K%X%C%@$O0l@ZIU$+$J$$!#(B
 */
PUBLIC u8 *ks_get_memblock(u32 size, char *purpose)
{
  u32 tmp;

  if (purpose == NULL) {
    purpose = "?";
  }

  tmp = ALIGN64B(ks_memblock_begin);
  ks_memblock_begin = tmp + size;
  if (ks_memblock_begin >= ks_memblock_end) {
    osSyncPrintf("ks_get_memblock: failed to get 0x%x bytes as %s, 0x%x bytes free\n",
		 size, purpose, ks_memblock_end - tmp);
    while (TRUE)
      ;
  }
  osSyncPrintf("ks_get_memblock: from 0x%x, 0x%x bytes as %s\n",
	       tmp, size, purpose);
  return (u8 *)tmp;
}
  
/* $B3F<o%0%m!<%P%kJQ?t$N(B $B=i4|2=(B */
static void init_globals(void)
{
  int i;
  extern u8 _nes_codeSegmentEnd[];

  /* $B%a%b%j%V%m%C%/(B begin, end */
  ks_memblock_begin = (u32)_nes_codeSegmentEnd;
  ks_memblock_end = 0x80400000;

  blanking_flag = TRUE;
  cfb_oldp = (u16 *)ks_get_memblock(SCREEN_WD * SCREEN_HT_FPAL * SCREEN_BPP / 8, "CFB0");
  bzero(cfb_oldp, SCREEN_WD * SCREEN_HT_FPAL * SCREEN_BPP / 8);
  cfbp = (u16 *)ks_get_memblock(SCREEN_WD * SCREEN_HT_FPAL * SCREEN_BPP / 8, "CFB1");
  bzero(cfbp, SCREEN_WD * SCREEN_HT_FPAL * SCREEN_BPP / 8);
  menu_cfb_oldp = (u16 *)ks_get_memblock(SCREEN_WD * SCREEN_HT_FPAL * SCREEN_BPP / 8, "MENU_CFB0");
  bzero(menu_cfb_oldp, SCREEN_WD * SCREEN_HT_FPAL * SCREEN_BPP / 8);
  menu_cfbp = (u16 *)ks_get_memblock(SCREEN_WD * SCREEN_HT_FPAL * SCREEN_BPP / 8, "MENU_CFB1");
  bzero(menu_cfbp, SCREEN_WD * SCREEN_HT_FPAL * SCREEN_BPP / 8);
  frame_count = 0;
  
  rsp_taskp = NULL;

  return;
}

PUBLIC void rom_load(u8 *src, u8 *dst, u32 length)
{
  if(MQ_IS_FULL(&dma_msgq)) {
    osRecvMesg(&dma_msgq, NULL, OS_MESG_BLOCK);
  }
  if (length < 0x2000) {
    osWritebackDCache(dst, length);
    osInvalDCache(dst, length);
  } else {
    osWritebackDCacheAll();
  }
  dma_io_msgbuf[0].hdr.pri = OS_MESG_PRI_NORMAL;
  dma_io_msgbuf[0].hdr.retQueue = &dma_msgq;
  dma_io_msgbuf[0].dramAddr = dst;
  dma_io_msgbuf[0].devAddr = (u32)src;
  dma_io_msgbuf[0].size = length;
  
  osEPiStartDma(cart_epi_handle, &dma_io_msgbuf[0], OS_READ);

  osRecvMesg(&dma_msgq, NULL, OS_MESG_BLOCK);
}

extern void ScanController(void)
{
  osContStartReadData(&pad_msgq);
}  

extern void ReadController(void)
{
  osRecvMesg(&pad_msgq, NULL, OS_MESG_BLOCK);
  osContGetReadData(&os_pad_data[0]);
  if (npad) {
    int j;
    pad_object *padp;
    OSContPad *os_padp;
    
    j = npad - 1;
    padp = &pad[j];
    for (j = npad - 1; j >= 0; j--) {
      os_padp = &os_pad_data[pad_index[j]];
      padp -> trig = (padp -> button ^ os_padp -> button) & os_padp -> button;
      padp -> button = os_padp -> button;
      padp -> stick_x = os_padp -> stick_x;
      padp -> stick_y = os_padp -> stick_y;
      padp--;
    }
  }
}  

/* audioproc: $B%*!<%G%#%*%9%l%C%I!#(B
 * RSP $B$O;H$o$J$$$,(B $B%@%_!<$G(B RSP $B%?%9%/$r5/F0$9$k!#$=$7$F!"%@%_!<(B RSPDONE $B$r(B SendMesg $B$9$k!#(B
 * yield $B$9$kI,MW$,$"$k$H$-$O!D(B taskman $B$,$"$i$+$8$a(B yield $B$7$F$*$/$Y$-!#(B
 */
PUBLIC int audioproc_enabled;
static void audioproc(void *dummyp)
{
  while (TRUE) {
    osRecvMesg(&audio_timing_msgq, NULL, OS_MESG_BLOCK);
    /* dummy RSP task, and dummy RSPDONE */
    osSendMesg(&taskman_msgq, (OSMesg)MSG_RSPDONE, OS_MESG_NOBLOCK);
    if (audioproc_enabled) {
      game_nes_audio.g.exec(&game_nes_audio);
    }
  }
}

/* mainproc: $B%a%$%s%9%l%C%I(B
 * main_timing_msgq $B$N%a%C%;!<%8$rBT$?$J$/$F$b(B
 * $B%3%s%H%m!<%i%G!<%?$N99?7$rBT$C$F$$$k$N$G!"7k6I(B1$B%U%l!<%`(B1$B%k!<%W$N=hM}$H$J$k!#(B
 */
static void mainproc(void *dummyp)
{
  u32 count;
  sq sequencep_next, sequencep_old; /* $BA0$N(B $B%7!<%1%s%9$H(B $BJQ$o$C$?$+$I$&$+(B */
  OSTask *taskp;
  int i;
  
  cart_epi_handle = osCartRomInit();
#ifndef CODE_ROM
  disk_epi_handle = sub_osDriveRomInit();
#endif
  emulator_running = 0;
  sequencep_next = sq_init;


  while (TRUE) {
/*     osSyncPrintf("mainproc: waiting main_timing_msgq\n"); */
    if (MQ_IS_FULL(&main_timing_msgq)) {
/*       osSyncPrintf("main_timing_msgq: MQ_FULL\n"); */
      osRecvMesg(&main_timing_msgq, NULL, OS_MESG_BLOCK);
    }
    osRecvMesg(&main_timing_msgq, NULL, OS_MESG_BLOCK);
/*     osSyncPrintf("  mainproc: got main_timing_msgq\n"); */

    /* emu $B%k!<%A%s<B9TCf$J$i(B $B%Q%C%I%G!<%?$rFI$^$J$$!#(B
     * $B%(%_%e%l!<%?%9%l%C%I$,(B $BI,MW$K1~$8$F(B $BFI$`!#(B
     */
    if ( emulator_running ) {
/*       osSyncPrintf("mainproc: emuproc active at frame 0x%x\n", frame_count); */
    } else {
      osContStartReadData(&pad_msgq);
      /* syncram $B%3%^%s%I:n@.(B */
      osRecvMesg(&pad_msgq, NULL, OS_MESG_BLOCK);
      osContGetReadData(&os_pad_data[0]);
      if (npad) {
	int j;
	pad_object *padp;
	OSContPad *os_padp;
      
	j = npad - 1;
	padp = &pad[j];
	for (j = npad - 1; j >= 0; j--) {
	  os_padp = &os_pad_data[pad_index[j]];
	  padp -> trig = (padp -> button ^ os_padp -> button) & os_padp -> button;
	  padp -> button = os_padp -> button;
	  padp -> stick_x = os_padp -> stick_x;
	  padp -> stick_y = os_padp -> stick_y;
	  padp--;
	}
      }
      /* $B$3$3$G(B syncram/verify $B%3%^%s%IH/9T(B */
    }

    sequencep = sequencep_next;
    sequencep_next = sequencep((u32)sequencep ^ (u32)sequencep_old, sequencep);
    sequencep_old = sequencep;

    {
      u16 *tmp;
      /* $B%U%l!<%`%P%C%U%!%]%$%s%?$N(B swap */
      tmp = cfb_oldp;
      cfb_oldp = cfbp;
      cfbp = tmp;
    }
  }
}

/* taskman: $B%?%9%/%^%M!<%8%c(B */
static void taskman(void *dummyp)
{
  u32 task_search_index;
  s32 task_search_index_rdp; /* DP $B$r;H$&(B task index (M_GFXTASK) */
  u32 mesg;
  u32 count_tmp, count_tmp_bak;
  int i;
  
  /* VI $B=i4|2=(B
   * NTSC, Low reso., Anti alias(opp. Point sample), Non-interlaced,
   * 1 = 16 bpp (opp. 2 = 32 bpp) */
  osCreateViManager(OS_PRIORITY_VIMGR);
  osViSetMode(&osViModeNtscLpn1);
  osViBlack(TRUE);
  osViSetSpecialFeatures(
			 OS_VI_GAMMA_OFF |
			 OS_VI_GAMMA_DITHER_OFF |
			 OS_VI_DIVOT_OFF
			 );
  /* OS bug workaround */
  osViSetSpecialFeatures(OS_VI_DITHER_FILTER_OFF);

  /* PI $B=i4|2=(B */
  osCreatePiManager(OS_PRIORITY_PIMGR, &pi_msgq, pi_msgbuf, PI_MSGS_MAX);

  /* $B3F<o(B $B%0%m!<%P%kJQ?t$N(B $B=i4|@_Dj(B */
  init_globals();

#if 0
#ifdef PTN64
  /**** Start rmonThread so you can do printf's ****/
  osCreateThread(&rmonThread, 0, rmonMain, (void *)0,
		 (void *)(rmonStack+(RMON_STACKSIZE/sizeof(u64))),
		 (OSPri) OS_PRIORITY_RMON );
  osStartThread(&rmonThread);
#endif
#endif

  for (i = 0; i != MY_TASK_MAX; i++) {
    task_request[i].status = MY_TASK_STATUS_DONE;
  }
  task_search_index = 0;
  task_search_index_rdp = -1;
  /* initialize up RCP tasks */
  /* dummy audio task */
  
  /* dma, rcp, $B%j%H%l!<%9(B $B%a%C%;!<%8%-%e!<$N@_Dj(B */
  /* $B$=$l$>$l(B $B%-%e!<$O(B 1 $B$D$N$_(B */
  osCreateMesgQueue(&dma_msgq, dma_msgbuf, DMA_MSGS_MAX);
  osCreateMesgQueue(&audio_timing_msgq, audio_timing_msgbuf, AUDIO_TIMING_MSGS_MAX);
  osCreateMesgQueue(&main_timing_msgq, main_timing_msgbuf, MAIN_TIMING_MSGS_MAX);
  osCreateMesgQueue(&taskman_msgq, taskman_msgbuf, TASKMAN_MSGS_MAX);
  osCreateMesgQueue(&pad_msgq, pad_msgbuf, PAD_MSGS_MAX); /* $B%P%C%U%!$O(B $B0l$D(B */

  
  osSetEventMesg(OS_EVENT_SP, &taskman_msgq, (OSMesg)MSG_RSPDONE);
  osSetEventMesg(OS_EVENT_DP, &taskman_msgq, (OSMesg)MSG_RDPDONE);
  osSetEventMesg(OS_EVENT_PRENMI, &taskman_msgq, (OSMesg)MSG_PRENMI);
  osSetEventMesg(OS_EVENT_SI, &pad_msgq, (OSMesg)MSG_PAD);
  osViSetEvent(               &taskman_msgq, (OSMesg)MSG_RETRACE,
			      1); /* 1 = $BKh%U%l!<%`8F$S=P$7(B */

  /* $B3F%9%l%C%I5/F0(B */
  osCreateThread(&audio_thread, TID_AUDIO, audioproc, NULL,
		 &audio_thread_stack[AUDIO_STACKSIZE / sizeof(u64)], PRI_AUDIO);
  osStartThread(&audio_thread);

  osCreateThread(&main_thread, TID_MAIN, mainproc, NULL,
		 &main_thread_stack[MAIN_STACKSIZE / sizeof(u64)], PRI_MAIN);
  osStartThread(&main_thread);

  /* $B%3%s%H%m!<%i=i4|2=(B */
  {
    u8 pattern;
    osContInit(&pad_msgq, &pattern, &pad_status[0]);
/*     osContStartQuery(&pad_msgq); */
/*     osRecvMesg(&pad_msgq, NULL, OS_MESG_BLOCK); */
/*     osContGetQuery(&pad_status[0]); */
    npad = 0;
    for (i = 0; i < MAXCONTROLLERS; i++) {
      pad[i].button =
	pad[i].trig =
	pad[i].stick_x =
	pad[i].stick_y = 0;
      if ((pattern & (1 << i))
	  && !(pad_status[i].errno & CONT_NO_RESPONSE_ERROR)) {
	pad_index[npad] = i;
	npad++;
      }
    }
  }

/*   osSyncPrintf("taskman: created sio, audio, main threads\n"); */
  
  while (TRUE) {
wait_new_msg:
/*     osSyncPrintf("taskman: waiting taskman_msgq\n"); */
    osRecvMesg(&taskman_msgq, (OSMesg)&mesg, OS_MESG_BLOCK);
    count_tmp = osGetCount();
/*     osSyncPrintf("  taskman: got taskman_msgq=%c%c%c%c, count_tmp=0x%x, diff=0x%x\n", */
/* 		 (mesg>>24)&0xff, (mesg>>16)&0xff, (mesg>>8)&0xff, mesg&0xff, count_tmp, count_tmp-count_tmp_bak); */
/*     osSyncPrintf("%c%c%c%c, 0x%x\n",(mesg>>24)&0xff, (mesg>>16)&0xff, (mesg>>8)&0xff, mesg&0xff, count_tmp-count_tmp_bak); */
    count_tmp_bak = count_tmp;
    switch (mesg) {
    case MSG_RETRACE:
      if (emulator_running == 0) {
       	osViSwapBuffer(cfb_oldp);
	osViBlack(blanking_flag);
      }
      frame_count++;
      /* $B%5%&%s%I=hM}$O(B $BKh%U%l!<%`(B $B9T$&(B
       * $B$,!"(Baudioproc < sioproc $B$J$N$G(B SP $B$,(B $BL5BL$K$J$k(B?
       */
      osWritebackDCacheAll();
      osSendMesg(&audio_timing_msgq, (OSMesg)MSG_AUDIO_GO, OS_MESG_NOBLOCK);

      /* main $B%9%l%C%I(B $B8F$S=P$7!#(B*/
      osSendMesg(&main_timing_msgq, (OSMesg)MSG_MAIN_GO, OS_MESG_NOBLOCK);
      break;
    case MSG_RSPDONE:
      /* $B2?$i$+$N(B RSP $B%?%9%/$,(B $B=*$o$C$?!#(B
       * $B$b$7(B yield $B=*N;$J$i(B $B$=$l$J$j$K(B $B=hM}$9$k!#(B
       */
      task_request[task_search_index].status &= ~MY_TASK_STATUS_REQUESTED;
      task_request[task_search_index].rspdone = osGetCount() - task_request[task_search_index].rspdone;
      task_search_index++;
      
      /* search for new task */
      while (task_search_index != MY_TASK_MAX) {
	if (task_request[task_search_index].status) {
	  /* writeback
	   * ...All(); $B$O(B $B=E$$$N$+$J(B? $B$G$b$7$g$&$,$J$$$G$7$g(B?
	   */
	  /* execute */
	  task_request[task_search_index].rspdone = osGetCount();
	  /* needs DP ? */
	  if (task_request[task_search_index].taskp -> t.type == M_GFXTASK) {
	    task_request[task_search_index].rdpdone = task_request[task_search_index].rspdone | 1;
	    task_search_index_rdp = task_search_index;
	  } else {
	    task_search_index_rdp = -1;
	  }
	  osSpTaskStart(task_request[task_search_index].taskp);
	  goto wait_new_msg; /* continue $B$G$O(B $B0lHV6a$/$N(B while $B$K(B $B$J$C$F$7$^$&(B! */
	}
	task_search_index++;
      }
      task_search_index = 0;
      break;
    case MSG_RDPDONE: /* rdp done */
      task_request[task_search_index_rdp].rdpdone = (osGetCount() - task_request[task_search_index_rdp].rdpdone) & 0xfffffffe;
      break;
    case MSG_PRENMI:
/*       sub_LeoReset(); */
      while (osAfterPreNMI()) {
        IO_WRITE(SP_STATUS_REG, SP_CLR_RSPSIGNAL);  /* $B$3$l$rF~$l$J$$$H%j%;%C%H8e(B $BK=Av$9$k$3$H$,$"$k(B */
      }
      while (TRUE); /* for debug */
      break;
    default:
/*       sub_unknown_msgq(mesg); */
      osSyncPrintf("unknown message taskman_msgq = 0x%x\n\n\n\n", mesg);
      for (;;);
      break;
    }
  }
}

/* idle: $B%"%$%I%k%9%l%C%I(B */
static void idle(void *dummyp)
{
  /* $B%?%9%/%^%M!<%8%c(B $B5/F0(B */
  osCreateThread(&tman_thread, TID_TMAN, taskman, NULL,
		 &tman_thread_stack[TMAN_STACKSIZE / sizeof(u64)], PRI_TMAN);
  osStartThread(&tman_thread);

  /* bye. */
  osSetThreadPri(NULL, OS_PRIORITY_IDLE);
  while (TRUE) {
    ;
  }
}

#if 0
#ifdef PTN64
#include "pt.c"
#endif
#endif

/* boot: $B%V!<%H(B */
void boot(void)
{
  osInitialize();

#if 0
#ifdef PTN64
   ptstart();
#endif
#endif
   
  /* $B%"%$%I%k%9%l%C%I$N@8@.!&5/F0(B $B0z?t$O(B NULL */
  osCreateThread(&idle_thread, TID_IDLE, idle, NULL,
		 &idle_thread_stack[IDLE_STACKSIZE / sizeof(u64)], PRI_IDLE);
  osStartThread(&idle_thread);
}

