/*
 * Copyright (C) 2011-2013 by James Bowman <jamesb@excamera.com>
 * Gameduino library for arduino.
 *
 */

#ifndef _GD_H_INCLUDED
#define _GD_H_INCLUDED

// define SS_PIN before including "GD.h" to override this
#ifndef SS_PIN
#define SS_PIN 9
#endif

#ifdef BOARD_maple

#include "wirish.h"

typedef const unsigned char prog_uchar;
#define Serial SerialUSB
#define pgm_read_byte_near(x) (*(prog_uchar*)x)
#define pgm_read_byte(x) (*(prog_uchar*)x)
#define PROGMEM const

extern HardwareSPI SPI;

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#endif

struct sprplot
{
  char x, y;
  byte image, palette;
};

class GDClass {
public:
  static void begin();
  static void end();
  static void __start(unsigned int addr);
  static void __wstart(unsigned int addr);
  static void __end(void);
  static byte rd(unsigned int addr);
  static void wr(unsigned int addr, byte v);
  static unsigned int rd16(unsigned int addr);
  static void wr16(unsigned int addr, unsigned int v);
  static void fill(int addr, byte v, unsigned int count);
  static void copy(unsigned int addr, prog_uchar *src, int count);
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  static void copy(unsigned int addr, uint_farptr_t src, int count);
  static void microcode(uint_farptr_t src, int count);
  static void uncompress(unsigned int addr, uint_farptr_t src);
#endif

  static void setpal(int pal, unsigned int rgb);
  static void sprite(int spr, int x, int y, byte image, byte palette, byte rot = 0, byte jk = 0);
  static void sprite2x2(int spr, int x, int y, byte image, byte palette, byte rot = 0, byte jk = 0);
  static void waitvblank();
  static void microcode(prog_uchar *src, int count);
  static void uncompress(unsigned int addr, prog_uchar *src);

  static void voice(int v, byte wave, unsigned int freq, byte lamp, byte ramp);
  static void ascii();
  static void putstr(int x, int y, const char *s);

  static void screenshot(unsigned int frame);

  void __wstartspr(unsigned int spr = 0);
  void xsprite(int ox, int oy, char x, char y, byte image, byte palette, byte rot = 0, byte jk = 0);
  void xhide();
  void plots(int ox, int oy, sprplot *psp, byte count, byte rot, byte jk);

  byte spr;   // Current sprite, incremented by xsprite/xhide above
};

#define GD_HAS_PLOTS    1     // have the 'GD.plots' method

extern GDClass GD;

#define RGB(r,g,b) ((((r) >> 3) << 10) | (((g) >> 3) << 5) | ((b) >> 3))
#define TRANSPARENT (1 << 15) // transparent for chars and sprites

#define RAM_PIC     0x0000    // Screen Picture, 64 x 64 = 4096 bytes
#define RAM_CHR     0x1000    // Screen Characters, 256 x 16 = 4096 bytes
#define RAM_PAL     0x2000    // Screen Character Palette, 256 x 8 = 2048 bytes

#define IDENT         0x2800
#define REV           0x2801
#define FRAME         0x2802
#define VBLANK        0x2803
#define SCROLL_X      0x2804
#define SCROLL_Y      0x2806
#define JK_MODE       0x2808
#define J1_RESET      0x2809
#define SPR_DISABLE   0x280a
#define SPR_PAGE      0x280b
#define IOMODE        0x280c

#define BG_COLOR      0x280e
#define SAMPLE_L      0x2810
#define SAMPLE_R      0x2812

#define MODULATOR     0x2814

#define SCREENSHOT_Y  0x281e

#define PALETTE16A 0x2840   // 16-color palette RAM A, 32 bytes
#define PALETTE16B 0x2860   // 16-color palette RAM B, 32 bytes
#define PALETTE4A  0x2880   // 4-color palette RAM A, 8 bytes
#define PALETTE4B  0x2888   // 4-color palette RAM A, 8 bytes
#define COMM       0x2890   // Communication buffer
#define COLLISION  0x2900   // Collision detection RAM, 256 bytes
#define VOICES     0x2a00   // Voice controls
#define J1_CODE    0x2b00   // J1 coprocessor microcode RAM
#define SCREENSHOT 0x2c00   // screenshot line RAM

#define RAM_SPR     0x3000    // Sprite Control, 512 x 4 = 2048 bytes
#define RAM_SPRPAL  0x3800    // Sprite Palettes, 4 x 256 = 2048 bytes
#define RAM_SPRIMG  0x4000    // Sprite Image, 64 x 256 = 16384 bytes

#ifndef GET_FAR_ADDRESS // at some point this will become official... https://savannah.nongnu.org/patch/?6352
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
#define GET_FAR_ADDRESS(var)                          \
({                                                    \
    uint_farptr_t tmp;                                \
                                                      \
    __asm__ __volatile__(                             \
                                                      \
            "ldi    %A0, lo8(%1)"           "\n\t"    \
            "ldi    %B0, hi8(%1)"           "\n\t"    \
            "ldi    %C0, hh8(%1)"           "\n\t"    \
            "clr    %D0"                    "\n\t"    \
        :                                             \
            "=d" (tmp)                                \
        :                                             \
            "p"  (&(var))                             \
    );                                                \
    tmp;                                              \
}) 
#else
#define GET_FAR_ADDRESS(var) (var)
#endif
#endif

// simple utilities for accessing the asset library in a filesystem-like
// way
// Details of the flash chip:
// http://www.atmel.com/dyn/resources/prod_documents/doc3638.pdf

const int FLASHSEL = 2; // flash SPI select pin
class Asset {

  private:

    uint32_t addr;    // pointer into flash memory
    uint16_t remain;  // number of remaing unread bytes

    byte find_name(const char *name) {
      // addr points at a directory, scan for name, if found set addr
      // to the entry and return 1, otherwise return 0.
      while (true) {
        static struct {
          char name[12];
          uint16_t length;
          uint32_t addr;
        } de;
        read(&de, sizeof(de));
        if (de.name[0] == 0)
          return 0;   // end of dir, no match found
        if (strcmp(de.name, name) == 0) {
          remain = de.length;
          addr = de.addr;
          return 1;
        }
      }
    }

  public:

    int open(const char *d, ...) {
      va_list ap;
      va_start(ap, d);
      addr = 512L * 640;
      remain = 1024;
      pinMode(FLASHSEL, OUTPUT);
      digitalWrite(FLASHSEL, HIGH);
      do {
        if (!find_name(d))
          return 0;
        d = va_arg(ap, const char *);
      } while (d != NULL);
      return 1;
    }
    int read(void *dst, uint16_t n) {
      GD.wr(IOMODE, 'F');
      digitalWrite(FLASHSEL, LOW);
      SPI.transfer(0x03);
      SPI.transfer((byte)(addr >> 16));
      SPI.transfer((byte)(addr >> 8));
      SPI.transfer((byte)(addr >> 0));
      uint16_t actual = min(n, remain);   // actual bytes read
      byte *bdst = (byte*)dst;
      for (uint16_t a = actual; a; a--) {
        byte b = SPI.transfer(0);
        *bdst++ = b;
        addr++;
        if ((511 & (uint16_t)addr) == 264)
          addr = addr - 264 + 512;
      }
      remain -= actual;
      digitalWrite(FLASHSEL, HIGH);
      GD.wr(IOMODE, 0);
      return actual;
    }
    void load(uint16_t dst) {
      while (remain) {
        byte buf[16];
        uint16_t n = min(remain, sizeof(buf));
        read(buf, n);
        GD.__wstart(dst);
        for (byte i = 0; i < n; i++)
          SPI.transfer(buf[i]);
        GD.__end();
        dst += n;
      }
    }
    uint16_t available() {
      return remain;
    }
};

#endif

