#include "fce.h"
#include "cpu.h"
#include "memory.h"
#include "ppu.h"
#include "psg.h"
#include <klib.h>
#include <amdev.h>

static int frame_cnt;
bool candraw() { return frame_cnt % 3 == 0; }

static uint32_t canvas[W * H];

void draw(int x, int y, int idx) {
  if (x >= 0 && x < W && y >= 0 && y < H && candraw()) {
    canvas[y * W + x] = palette[idx];
  }
}

typedef struct {
  char signature[4];
  byte prg_block_count;
  byte chr_block_count;
  word rom_type;
  byte reserved[8];
} ines_header;

static byte *buf;
static ines_header *fce_rom_header;

byte *romread(int size) {
  byte *ret = buf;
  buf += size;
  return ret;
}

int fce_load_rom(char *rom) {
  buf = (byte*)rom;
  fce_rom_header = (ines_header*)romread(sizeof(ines_header));

  if (memcmp(fce_rom_header->signature, "NES\x1A", 4)) {
    return -1;
  }

  mmc_id = ((fce_rom_header->rom_type & 0xF0) >> 4);

  int prg_size = fce_rom_header->prg_block_count * 0x4000;

  byte *blk = romread(prg_size);

  if (mmc_id == 0 || mmc_id == 3) {
    // if there is only one PRG block, we must repeat it twice
    if (fce_rom_header->prg_block_count == 1) {
      mmc_copy(0x8000, blk, 0x4000);
      mmc_copy(0xC000, blk, 0x4000);
    }
    else {
      mmc_copy(0x8000, blk, 0x8000);
    }
  }
  else {
    return -1;
  }

  // Copying CHR pages into MMC and PPU
  int i;
  for (i = 0; i < fce_rom_header->chr_block_count; i++) {
    byte *blk = romread(0x2000);
    mmc_append_chr_rom_page(blk);

    if (i == 0) {
      ppu_copy(0x0000, blk, 0x2000);
    }
  }

  return 0;
}

void fce_init() {
  cpu_init();
  ppu_init();
  ppu_set_mirroring(fce_rom_header->rom_type & 1);
  cpu_reset();
}

static unsigned long gtime;

void wait_for_frame() {
  unsigned long cur = uptime();
  while (cur - gtime < 1000 / FPS) {
    cur = uptime();
  }
  gtime = cur;
}

// FCE Lifecycle

void fce_run() {
  gtime = uptime();
  int nr_draw = 0;
  uint32_t last = gtime;
  while(1) {
    wait_for_frame();
    int scanlines = 262;

    while (scanlines-- > 0) {
      ppu_cycle();
      psg_detect_key();
    }

    nr_draw ++;
    if (uptime() - last > 1000) {
      last = uptime();
      printf("FPS = %d\n", nr_draw);
      nr_draw = 0;
    }
  }
}

void fce_update_screen() {
  frame_cnt ++;
  if (!candraw()) return;

  int idx = ppu_ram_read(0x3F00);
  uint32_t bgc = palette[idx];

  int w = screen_width();
  int h = screen_height();

  int xpad = (w - W) / 2;
  int ypad = (h - H) / 2;
  assert(xpad >= 0 && ypad >= 0);

  for (int x = 0; x < W; x ++) {
    for (int y = 0; y < H; y ++) {
      draw_rect(&canvas[y * W + x], xpad + x, ypad + y, 1, 1);
    }
  }

  draw_sync();

  for (int i = 0; i < W * H; i ++) canvas[i] = bgc;
}

extern unsigned char rom_mario_nes[];

int main(const char *rom_name) {
  _ioe_init();

  printf("LiteNES can only run Super Mario\n");

  fce_load_rom((void *)rom_mario_nes);
  fce_init();
  printf("Initialization finish!\n");
  fce_run();
  return 1;
}
