#include <u.h>
#include <libc.h>
#include <thread.h>
#include <draw.h>
#include <mouse.h>
#include <keyboard.h>
#include "../eui.h"
#include "dat.h"
#include "fns.h"

char *bindir = "/sys/lib/c64";
Image *red;
Rectangle progr;
u8int *rom;
int nrom;
u16int joys;
uchar *tape, tapever, tapeplay;
ulong tapelen;
int joymode;

void
progress(int a, int b)
{
       static int cur;
       int w;

       extern Image *bg;
       if(b == 0 || a == 0){
               if(cur != 0){
                       draw(screen, progr, bg, nil, ZP);
                       cur = 0;
               }
               return;
       }
       w = a * Dx(progr) / b;
       if(cur == w)
               return;
       draw(screen, Rect(progr.min.x, progr.min.y, progr.min.x + w, progr.max.y), red, nil, ZP);
       cur = w;
}

static void
loadsys(char *name, u8int *p, int n)
{
       static char buf[256];
       int fd;

       snprint(buf, sizeof(buf), "%s/%s", bindir, name);
       fd = open(buf, OREAD);
       if(fd < 0)
               sysfatal("open: %r");
       if(readn(fd, p, n) < n)
               sysfatal("readn: %r");
       close(fd);
}

static void
loadrom(char *name)
{
       int fd;

       fd = open(name, OREAD);
       if(fd < 0)
               sysfatal("open: %r");
       nrom = seek(fd, 0, 2);
       if(nrom > 4096)
               sysfatal("large ROM not supported");
       if((nrom & nrom-1) != 0)
               sysfatal("non-power-of-two ROM size");
       rom = malloc(nrom);
       if(rom == nil)
               sysfatal("malloc: %r");
       pread(fd, rom, nrom, 0);
       close(fd);
}

static void
loadcart(char *name)
{
       int fd;
       u16int t, l;
       u8int buf[80];

       fd = open(name, OREAD);
       if(fd < 0)
               sysfatal("open: %r");
       read(fd, buf, 80);
       if(memcmp(buf, "C64 CARTRIDGE   ", 16) != 0)
               sysfatal("not a c64 cartridge");
       t = buf[0x16] << 8 | buf[0x17];
       if(t != 0)
               sysfatal("unsupported type %d", t);
       if(buf[0x18] == 0) pla &= ~EXROM;
       if(buf[0x19] == 0) pla &= ~GAME;
       t = buf[0x4c] << 8 | buf[0x4d];
       if(t < 0x8000 || t >= 0xc000 && t < 0xe000)
               sysfatal("odd starting address %x", t);
       if(t >= 0xe000)
               t -= 0x4000;
       t -= 0x8000;
       l = buf[0x4e] << 8 | buf[0x4f];
       if(l + t > 16384)
               sysfatal("cart too large");
       read(fd, cart + t, l);
       close(fd);
}

static void
loadtape(char *name)
{
       int fd;
       uchar buf[20];

       fd = open(name, OREAD);
       if(fd < 0)
               sysfatal("open: %r");
       read(fd, buf, 20);
       if(memcmp(buf, "C64-TAPE-RAW", 12) != 0)
               sysfatal("not a c64 raw tape");
       tapever = buf[12];
       if(tapever > 1)
               sysfatal("unsupported tape version %d", tapever);
       tapelen = buf[16] | buf[17] << 8 | buf[18] << 16 | buf[19] << 24;
       tape = malloc(tapelen);
       readn(fd, tape, tapelen);
       close(fd);
}

static void
keyproc(void *)
{
       int fd, i, n, setnmi;
       u16int j;
       u64int k;
       static Rune keymap[64] = {
               [0] Kbs, /* Inst/Del */
               [1] '\n', /* Return */
               [2] Kleft, /* ←→ */
               [3] KF|7, /* F7/F8 */
               [4] KF|1, /* F1/F2 */
               [5] KF|3, /* F3/F4 */
               [6] KF|5, /* F5/F6 */
               [7] Kup, /* ↑↓ */
               [8] '3', /* 3 */
               [9] 'w', /* W */
               [10] 'a', /* A */
               [11] '4', /* 4 */
               [12] 'z', /* Z */
               [13] 's', /* S */
               [14] 'e', /* E */
               [15] Kshift, /* LeftShift */
               [16] '5', /* 5 */
               [17] 'r', /* R */
               [18] 'd', /* D */
               [19] '6', /* 6 */
               [20] 'c', /* C */
               [21] 'f', /* F */
               [22] 't', /* T */
               [23] 'x', /* X */
               [24] '7', /* 7 */
               [25] 'y', /* Y */
               [26] 'g', /* G */
               [27] '8', /* 8 */
               [28] 'b', /* B */
               [29] 'h', /* H */
               [30] 'u', /* U */
               [31] 'v', /* V */
               [32] '9', /* 9 */
               [33] 'i', /* I */
               [34] 'j', /* J */
               [35] '0', /* 0 */
               [36] 'm', /* M */
               [37] 'k', /* K */
               [38] 'o', /* O */
               [39] 'n', /* N */
               [40] '\'', /* + */
               [41] 'p', /* P */
               [42] 'l', /* L */
               [43] '-', /* − */
               [44] '.', /* > */
               [45] '\\', /* [ */
               [46] '@', /* @ */
               [47] ',', /* < */
               [48] '[', /* £ */
               [49] '*', /* * */
               [50] ';', /* ] */
               [51] Khome, /* Clr/Home */
               [52] Kalt, /* RightShift */
               [53] '=', /* = */
               [54] ']', /* ↑ */
               [55] '/', /* ? */
               [56] '1', /* 1 */
               [57] Kins, /* ← */
               [58] '\t', /* Ctrl */
               [59] '2', /* 2 */
               [60] ' ', /* Space */
               [61] Kctl, /* Commodore */
               [62] 'q', /* Q */
               [63] Kdel, /* Run/Stop */
       };
       static char buf[256];
       char *s;
       Rune r;

       fd = open("/dev/kbd", OREAD);
       if(fd < 0)
               sysfatal("open: %r");
       for(;;){
               if(buf[0] != 0){
                       n = strlen(buf)+1;
                       memmove(buf, buf+n, sizeof(buf)-n);
               }
               if(buf[0] == 0){
                       n = read(fd, buf, sizeof(buf)-1);
                       if(n <= 0)
                               sysfatal("read /dev/kbd: %r");
                       buf[n-1] = 0;
                       buf[n] = 0;
               }
               if(buf[0] == 'c'){
                       if(utfrune(buf, Kend)){
                               close(fd);
                               threadexitsall(nil);
                       }
                       if(utfrune(buf, KF|12))
                               trace ^= 1;
               }
               if(buf[0] != 'k' && buf[0] != 'K')
                       continue;
               s = buf + 1;
               j = 0;
               k = 0;
               setnmi = 0;
               while(*s != 0){
                       s += chartorune(&r, s);
                       switch(r){
                       case Kend: close(fd); threadexitsall(nil);
                       case Kesc:
                               if(paused)
                                       qunlock(&pauselock);
                               else
                                       qlock(&pauselock);
                               paused = !paused;
                               break;
                       case '`':
                               setnmi = 1;
                               break;
                       case Kleft: if(joymode) j |= 1<<2+5*(joymode-1); break;
                       case Kright: if(joymode) j |= 1<<3+5*(joymode-1); break;
                       case Kup: if(joymode) j |= 1<<0+5*(joymode-1); break;
                       case Kdown: if(joymode) j |= 1<<1+5*(joymode-1); break;
                       case Kctl: if(joymode) j |= 1<<4+5*(joymode-1); break;
                       }
                       for(i = 0; i < 64; i++)
                               if(keymap[i] == r)
                                       k |= 1ULL<<i;
               }
               if(setnmi)
                       nmi |= IRQRESTORE;
               else
                       nmi &= ~IRQRESTORE;
               keys = k;
               joys = j;
       }

}

static void
usage(void)
{
       fprint(2, "usage: %s [-Nap] [-c cart] [-t tape] [-d bindir] [-x scale] rom\n", argv0);
       exits("usage");
}

void
threadmain(int argc, char **argv)
{
       memreset();

       ARGBEGIN {
       case 'c':
               loadcart(EARGF(usage()));
               break;
       case 't':
               loadtape(EARGF(usage()));
               break;
       case 'N':
               region = NTSC0;
               break;
       case 'p':
               region = PAL;
               break;
       case 'd':
               bindir = strdup(EARGF(usage()));
               break;
       case 'x':
               fixscale = strtol(EARGF(usage()), nil, 0);
               break;
       default:
               usage();
       } ARGEND;
       if(argc >= 2)
               usage();
       loadsys("kernal.bin", krom, 8192);
       loadsys("basic.bin", brom, 8192);
       loadsys("crom.bin", crom, 4096);

       vicreset();
       initemu(picw, pich, 4, XRGB32, 1, keyproc);
       red = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0xFF0000FF);

       nmien = IRQRESTORE;
       pc = memread(0xFFFC) | memread(0xFFFD) << 8;
       rP = FLAGI;
       for(;;){
               if(paused){
                               qlock(&pauselock);
                               qunlock(&pauselock);
                       }
               step();
       }
}

static void
menu(void)
{
       enum { JOY, TAPE };
       static char joystr[32] = "joy: none";
       static char tapestr[32] = "tape: play";
       static char *items[] = {
               [JOY] joystr,
               [TAPE] tapestr,
               nil
       };
       static Menu m = {
               items, nil, 0
       };

       extern Mousectl *mc;
       switch(menuhit(3, mc, &m, nil)){
       case JOY:
               joymode = (joymode + 1) % 3;
               if(joymode == 0)
                       strcpy(joystr, "joy: none");
               else
                       sprint(joystr, "joy: %d", joymode);
               break;
       case TAPE:
               tapeplay ^= 1;
               if(tapeplay == 0){
                       strcpy(tapestr, "tape: play");
                       progress(0, 0);
               }else
                       strcpy(tapestr, "tape: stop");
               break;
       }
}

void
flush(void)
{
       extern Mousectl *mc;
       flushmouse(0);
       while(nbrecv(mc->c, &mc->Mouse) > 0)
               if((mc->buttons & 4) != 0)
                       menu();
       flushscreen();
       flushaudio(nil);
}