#include <SPI.h>
#include <GD.h>

struct dirent {
  char name[8];
  char ext[3];
  byte attribute;
  byte reserved[10];
  uint16_t time;
  uint16_t date;
  uint16_t cluster;
  uint32_t size;
};

#include "videocopy.h"

class sdcard {
  public:
  void sel() { digitalWrite(pin, LOW); }
  void desel() { digitalWrite(pin, HIGH); }

  void cmd(byte cmd, uint32_t lba = 0) {
    sel();
    SPI.transfer(0x40 | cmd);
    SPI.transfer(0xff & (lba >> 24));
    SPI.transfer(0xff & (lba >> 16));
    SPI.transfer(0xff & (lba >> 8));
    SPI.transfer(0xff & (lba));
    SPI.transfer(0x95);
  }

  byte R1() {   // read response R1
    byte r;
    while ((r = SPI.transfer(0xff)) & 0x80)
      ;
    SPI.transfer(0xff);   // trailing byte
    desel();
    return r;
  }

  byte sdR3(uint32_t &ocr) {  // read response R3
    uint32_t r;
    while ((r = SPI.transfer(0xff)) & 0x80)
      ;
    for (byte i = 4; i; i--)
      ocr = (ocr << 8) | SPI.transfer(0xff);
    SPI.transfer(0xff);   // trailing byte

    desel();
    return r;
  }

  void appcmd(byte cc, uint32_t lba = 0) {
    cmd(55); R1();
    cmd(cc, lba);
  }

  void begin(byte p) {
    pin = p;

    pinMode(pin, OUTPUT);
    desel();
    SPI.begin();

    sel();
    delay(100);
    desel();
    delay(100);

    // attempt reset
    do {       // reset, enter idle
      cmd(0);
    } while (R1() != 1);
    // reset OK

    do {
      delay(1);
      appcmd(41); // card init
    } while (R1() & 1);

    o_partition = 512L * rd2(0x1be + 0x8);

    sectors_per_cluster = rd(o_partition + 0xd);
    reserved_sectors = rd2(o_partition + 0xe);
    max_root_dir_entries = rd2(o_partition + 0x11);
    sectors_per_fat = rd2(o_partition + 0x16);
    cluster_size = 512L * sectors_per_cluster;

    o_fat = o_partition + 512L * reserved_sectors;
    o_root = o_fat + (2 * 512L * sectors_per_fat);
    // data area starts with cluster 2, so offset it here
    o_data = o_root + (max_root_dir_entries * 32L) - (2 * cluster_size); 
  }
  void rdn(byte *d, uint32_t off, uint16_t n) {
    cmd(17, off & ~511L); R1();
    sel();
    while (SPI.transfer(0xff) != 0xfe)
      ;
    int i;
    for (i = 0; i < (off & 511); i++)
      SPI.transfer(0xff);
    while (n--)
      *d++ = SPI.transfer(0xff);
    for (i += n; i < 512; i++)
      SPI.transfer(0xff);
    desel();
  }

  uint32_t rd4(uint32_t off) {
    uint32_t r;
    rdn((byte*)&r, off, sizeof(r));
    return r;
  }

  uint16_t rd2(uint32_t off) {
    uint16_t r;
    rdn((byte*)&r, off, sizeof(r));
    return r;
  }

  byte rd(uint32_t off) {
    byte r;
    rdn((byte*)&r, off, sizeof(r));
    return r;
  }
  byte pin;

  uint16_t sectors_per_cluster;
  uint16_t reserved_sectors;
  uint16_t max_root_dir_entries;
  uint16_t sectors_per_fat;
  uint16_t cluster_size;

  // These are all linear addresses, hence the o_ prefix
  uint32_t o_partition;
  uint32_t o_fat;
  uint32_t o_root;
  uint32_t o_data;
};

sdcard SD;

class Reader {
public:
  void begin(dirent &de) {
    cluster = de.cluster;
    sector = 0;
    offset = 0;
    remaining = de.size;
  }
  void skipsector() {
    if (sector == SD.sectors_per_cluster) {
      sector = 0;
      cluster = SD.rd2(SD.o_fat + 2 * cluster);
    }
    sector++;
    offset += 512;
  }
  void readsector() {
    if (sector == SD.sectors_per_cluster) {
      sector = 0;
      cluster = SD.rd2(SD.o_fat + 2 * cluster);
    }
    uint32_t off = SD.o_data + ((long)SD.cluster_size * cluster) + (512L * sector);
    SD.cmd(17, off & ~511L); SD.R1();
    SD.sel();
    while (SPI.transfer(0xff) != 0xfe)
      ;
    sector++;
    offset += 512;
  }
  void readsector(byte *dst) {
    readsector();
    for (int i = 0; i < 512; i++) {
      dst[i] = SPI.transfer(0xff);
    }
    SD.desel();
  }
  uint16_t cluster;
  uint32_t offset;
  uint32_t remaining;
  byte sector;
};

#define SOUNDBUFFER 0x3f00

// at 8KHz the 256 byte audio buffer is enough for 32ms
// so need to call feedaudio quicker than 32ms to avoid
// running dry

// Read 512 byte sectors from 'a' into 'audiobuf',
// and copy bytes from audiobuf into Gameduino's
// audio sample FIFO.

static void feedaudio(Reader &a)
{
  static byte writepointer = 0;
  static byte audiobuf[512];
  static int bufi;

  // Figure out the longest continuous write:
  //   do not cross (read pointer - 1); emptiness
  //   do not cross the 256 boundary; maxburst
  //   do not exceed the end of audiobuf; 512-bufi

  byte readpointer = GD.rd(COMM+2) - 1;
  byte emptiness = readpointer - writepointer;
  int maxburst = 256 - writepointer;    // Maximum burst write
  byte canwrite = min(512 - bufi, min(maxburst, emptiness));
  if (canwrite) {
    GD.__wstart(SOUNDBUFFER + writepointer);
    writepointer += canwrite;
    while (canwrite--)
      SPI.transfer(audiobuf[bufi++]);
    GD.__end();
  }
  if (bufi == 512) {
    a.readsector(audiobuf);
    bufi = 0;
  }
}

// A sector is 2.5ms to transfer, so feeding audio every 4 sectors
// is plenty

static void playvideo(Reader &v, Reader &a)
{
  byte sectors = 16;
  byte sec;
  for (sec = 0; sec < sectors; sec++) {
    static byte buf[512];

    if ((sec & 3) == 0)
      feedaudio(a);
    v.readsector(buf);

    if (sec == 0) {
      byte nchr = buf[0];
      byte w = buf[1];
      byte h = buf[2];
      uint16_t payload = 3 + (nchr * 24) + (w * h);
      sectors = (payload + 511) >> 9;

      // Wait for microprogram to signal "ready" on COMM+15
      while (GD.rd(COMM+15) == 0)
        ;
    }
    GD.__wstart(0x4000 + (sec << 9));
    for (int i = 0; i < 512; i++)
      SPI.transfer(buf[i]);
    GD.__end();
  }
  while (sec++ < 16)
    v.skipsector();

  GD.wr(COMM+15, 0);
  GD.wr(J1_RESET, 1);
  GD.wr(J1_RESET, 0);
}

static void readgd(uint16_t dst, Reader &r, int n)
{
  static byte scratch[512];

  while (n > 0) {
    r.readsector(scratch);
    GD.__wstart(dst);
    for (int i = 0; i < min(n, 512); i++)
      SPI.transfer(scratch[i]);
    GD.__end();
    n -= 512;
    dst += 512;
  }
}

static void dos83(byte dst[11], const char *ps)
{
  byte i = 0;
  while (*ps) {
    if (*ps != '.')
      dst[i++] = toupper(*ps);
    else {
      while (i < 8)
        dst[i++] = ' ';
    }
    ps++;
  }
  while (i < 11)
    dst[i++] = ' ';
}

static int openfile(Reader &r, const char *filename)
{
  int i = 0;
  byte dosname[11];
  dirent de;

  dos83(dosname, filename);
  do {
    SD.rdn((byte*)&de, SD.o_root + i * 32, sizeof(de));
    if (0 == memcmp(de.name, dosname, 11)) {
      r.begin(de);
      return 1;
    }
    i++;
  } while (de.name[0]);
  return 0;
}

void setup()
{
  Serial.begin(1000000);
  pinMode(8, OUTPUT);
  digitalWrite(8, HIGH);
  pinMode(9, OUTPUT);
  digitalWrite(9, HIGH);

  delay(900);

  if (1) {
    GD.begin();
    GD.ascii();
  }

  SD.begin(8);

  SPI.setClockDivider(SPI_CLOCK_DIV2);
  SPSR = (1 << SPI2X);

  digitalWrite(3, HIGH);
  Reader slide;
  for (char c = 'A'; ; c++) {
    char filename[20];
    sprintf(filename, "SLIDE-%c.IMG", c);
    if (openfile(slide, filename) == 0)
      break;

    GD.waitvblank();
    GD.wr(SPR_DISABLE, 1);
    GD.fill(RAM_PAL, 0, 2048);

    readgd(RAM_PIC, slide, 8192);
    readgd(RAM_SPRIMG, slide, 16384);
    readgd(RAM_SPR, slide, 1024);
    GD.waitvblank();
    readgd(RAM_PAL, slide, 2048);
    readgd(PALETTE16A, slide, 32);
    GD.wr(SPR_DISABLE, 0);
    while (digitalRead(3) == 0)
      ;
    while (digitalRead(3) == 1)
      ;
  }
  GD.wr(SPR_DISABLE, 1);

  {
    Reader vs;
    Reader as;
    openfile(vs, "output.gvf");
    openfile(as, "output.gaf");

    GD.copy(J1_CODE, videocopy_code, sizeof(videocopy_code));
    GD.fill(RAM_PIC, 0xff, 4096);
    // GD.wr(J1_RESET, 0);
    GD.wr(COMM+15, 1);
    long t0 = millis();
    byte pframes = GD.rd(FRAME);
    for (int i = 0; i < 50000; i++) {
      playvideo(vs, as);
      byte frame = GD.rd(FRAME);
      Serial.print((byte)(frame - pframes), DEC);
      Serial.print(' ');
      pframes = frame;
      Serial.println(1000 * as.offset / (millis() - t0), DEC);
    }
    Serial.println(millis() - t0, DEC);
  }
}

void loop()
{
}
