/***************************************************************
   crc's _ _
        (_) | ___
        | | |/ _ \  a tiny virtual computer
        | | | (_) | 64kw RAM, 32-bit, Dual Stack, MISC
        |_|_|\___/  ilo-sdl-1bpp.c (c) charles childers
                                   (c) arland childers
 **************************************************************/

/* Configuration **********************************************/

#define FB_W 640
#define FB_H 320
#define FW 640
#define FH 320

#define SCALE 1
// #define FULLSCREEN

#define FONT_H 16
#define FONT_W 8
#define PADDING 0     /* set to 1 or 2 for better readability */
                      /* if using a display large enough      */

#define KEY_QUEUE_SIZE 512

#define T ds[sp]    /* Top of Data Stack    */
#define N ds[sp-1]  /* Next on Data Stack   */
#define R as[rp]    /* Top of Address Stack */
#define V void
#define I int
#define C char

/**************************************************************/

#if defined(_WIN32) || defined(_WIN64)
#define WINDOWS
#endif

#include <stdio.h>
#include <stdlib.h>
#include <SDL.h>
#include <fcntl.h>
#include <limits.h>

#ifndef WINDOWS
#include <unistd.h>
#endif

#include "font.h"

/* Global Variables, Data Structures **************************/

V term_putc(I);
V dputc(I);
V cursor() {}

char *rom, *blocks;

/* SDL Globals */
SDL_Window *window;
SDL_Event event;
SDL_Renderer *renderer;
SDL_Texture *texture;

I mx, my, mb;          /* mouse x, y, button              */
I tx, ty;              /* cursor x, y                     */
I current;
I frame[FB_H * FB_W];  /* display memory                  */
I cycles;              /* inst. bundles before event poll */

I ip,        /* instruction pointer             */
  sp,        /* data stack pointer              */
  rp,        /* address stack pointer           */
  ds[33],    /* data stack                      */
  as[257],   /* address stack                   */
  m[65536 + (((FB_H * FB_W)/32) + 64)];  /* memory */

/* the other variables are used by the various
   functions for misc. purposes                   */

I a, b, f, s, d, l;
C i[1];

V push(I v) { ds[sp + 1] = v; sp += 1; }
I pop() { sp -= 1; return ds[sp + 1]; }


/* Keyboard input queue ***************************************/

I keyQueue[KEY_QUEUE_SIZE];
I keyQueueStart = 0;
I keyQueueEnd = 0;
I keyQueueCount = 0;

V enqueue_key(I key) {
  if (keyQueueCount < KEY_QUEUE_SIZE) {
    keyQueue[keyQueueEnd] = key;
    keyQueueEnd = (keyQueueEnd + 1) % KEY_QUEUE_SIZE;
    keyQueueCount++;
    printf("Added %d to queue\n", key);
  }
}

I dequeue_key() {
  if (keyQueueCount > 0) {
    I key = keyQueue[keyQueueStart];
    keyQueueStart = (keyQueueStart + 1) % KEY_QUEUE_SIZE;
    keyQueueCount--;
    printf("Key from queue: %d, %d remaining\n", key, keyQueueCount);
    return key;
  }
  return 0;
}

/* Graphica & SDL Functions ***********************************/

void handle_events() {
  while (SDL_PollEvent(&event)) { process_sdl_event(event, 0); }
}

#define CURRENT_KEY event.key.keysym.sym

V pixel(I x, I y, I c) {
  I index = y * FB_W + x;
  if (c == 0) {
    m[65536 + (index / 32)] &= ~(1 << (31 - index % 32));
  } else {
    m[65536 + (index / 32)] |= 1 << (31 - index % 32);
  }
}

I get_pixel(I x, I y) {
  I index = y * FB_W + x;
  I bit_pos = 31 - index % 32;
  I mask = 1 << bit_pos;
  return (m[65536 + (index / 32)] & mask) >> bit_pos;
}

V g_draw_pixel() {
  I color = pop(); I y = pop(); I x = pop(); pixel(x, y, color);
}

V g_mouse() { push(mx); push(my); push(mb); }

void g_get_pixel() {
  I y = pop(); I x = pop(); I c = get_pixel(x, y);
  push((c != 0) ? 1 : 0); }

V redraw() {
  for (I i = 0; i < (FB_H * FB_W); i++) {
    frame[i] = get_pixel(i, 0) ? -1 : 0;
  }
  SDL_UpdateTexture(texture, NULL, frame, FB_W * sizeof(int));
  SDL_RenderClear(renderer);
  SDL_RenderCopy(renderer, texture, NULL, NULL);
  SDL_RenderPresent(renderer);
}

I process_sdl_event(SDL_Event event, I output) {
  I mod, x, y, pressed;
  C upper[] = {  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                '!', '"', '#', '$', '%', '&', '"', '(', ')', '*',
                '+', '<', '_', '>', '?', ')', '!', '@', '#', '$',
                '%', '^', '&', '*', '(', ':', ':', ':', '+', '>',
                '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
                'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
                'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '{', '|',
                '}', '^', '_', '~', 'A', 'B', 'C', 'D', 'E', 'F',
                'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
                'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
                '{', '|', '}', '~', };
  switch(event.type) {
    case SDL_QUIT:
      exit(0);
      break;
    case SDL_MOUSEMOTION:
      SDL_GetMouseState(&x, &y);
      mx = x / SCALE;
      my = y / SCALE;
      break;
    case SDL_MOUSEBUTTONDOWN:
      SDL_GetMouseState(&x, &y);
      mx = x / SCALE;
      my = y / SCALE;
      mb = 1;
      break;
    case SDL_MOUSEBUTTONUP:
      SDL_GetMouseState(&x, &y);
      mx = x / SCALE;
      my = y / SCALE;
      mb = 0;
      break;
    case SDL_KEYDOWN:
      if (CURRENT_KEY != SDLK_LSHIFT && CURRENT_KEY != SDLK_RSHIFT) {
        current = CURRENT_KEY;
        mod = SDL_GetModState();
        if (mod & KMOD_LSHIFT || mod & KMOD_RSHIFT ) {
          current = upper[current];
        }
      }
      break;
    case SDL_KEYUP:
      if (current == 13) current = 10;
      if (current != 0) {
        enqueue_key(current);
        pressed = current;
        current = 0;
        return pressed;
      }
      break;
    default:
      break;
  }
  return 0;
}

I read_key() {
  I key;
  current = 0;
  while (keyQueueCount == 0) { handle_events(); }
  key = dequeue_key();
  term_putc(key);
  redraw();
  return key;
}

/* a barebones terminal emulator by Arland ********************/

/* variables for Arland's DEC subset                  */
I acc[32], acc_i = 0, on = 0;

V clear() {
  for (I i = 0; i < (FB_H * FB_W); i++) { pixel(i, 0, 0); }
  tx = ty = 0;
  redraw();
}

V scroll () {
  I memsize = (FB_H*FB_W);
  I last_row = FB_W * (FONT_H + PADDING);
  for (I i = 0; i < memsize - last_row; i++) {
    pixel(i, 0, get_pixel(i+last_row, 0));
  }
  for (I i = memsize-last_row; i < memsize; i++) {
    pixel(i, 0, 0);
  }
}

unsigned C reverse(unsigned C b) {
  b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
  b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
  b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
  return b;
}

V handle_special_character(I c) {
  if (c == 8 || c == 127) { /* BACKSPACE/DELETE */
    if (tx - FONT_W >= 0) {
      tx -= FONT_W ;
      dputc(' ');
      tx -= FONT_W;
      redraw();
    }
  } else if (c == 9) { /* TAB */
    I ts = tx / FONT_W;
    for (I i = ts; i < ((ts + 7) / 8) * 8; i++) { dputc(' '); }
  } else if (c == 10 || c == 13) { /* CR or LF */
    ty += FONT_H;
    tx = 0;
    redraw();
  }
}

V handle_regular_character(C *bitmap) {
  I x, y, set;
  for (x = 0; x < FONT_H; x++) {
    for (y = 0; y < FONT_W; y++) {
      set = reverse(bitmap[x]) & 1 << y;
      pixel(tx + y, ty + x, set ? 1 : 0);
    }
  }
  tx += FONT_W;
}

V advance_cursor_and_scroll() {
  if (tx >= FW) { tx = 0; ty += FONT_H; }
  if (ty > (FH - FONT_H)) { ty -= FONT_H; scroll(); }
}

V dputc(I c) {
  C *bitmap = (C *)font + (c * ((FONT_H * FONT_W) / 8));

  handle_special_character(c);

  if (!(c == 8 || c == 127 || c == 9 || c == 10 || c == 13)) {
    handle_regular_character(bitmap);
  }

  advance_cursor_and_scroll();
  cursor();
}

I acc_pop() {
  I k = acc_i;
  if (acc_i > 0) {
    acc_i -= 1;
    if (acc_i == -1) {
      acc_i = 0;
    }
  }
  return acc[k];
}

V acc_reset() {
  for (I x = 0; x < 32; x++) { acc[x] = 0; }
  on = 0;
}

V gt_up(I n)       { ty -= FONT_H; if (ty < 0) ty = 0;  }
V gt_down(I n)     { ty += FONT_H; if (ty > 24 * FONT_H) ty = 24 * FONT_H; }
V gt_left(I n)     { tx -= FONT_W; if (tx < 0) tx = 0; }
V gt_right(I n)    { tx += FONT_W; if (tx > 79 * FONT_W) ty = 79 * FONT_W; }
V gt_move_cursor() {
  if (acc_i >= 1) {
    I a = (acc_pop() - 1) * FONT_W;
    I b = (acc_pop() - 1) * FONT_H;
    ty = b; if (ty < 0) ty = 0; if (ty > FONT_H * 24) ty = FONT_H * 24;
    tx = a; if (tx < 0) tx = 0; if (tx > FONT_W * 79) tx = FONT_W * 79;
    acc_reset();
    return;
  }
  /* home */
  tx = 0; ty = 0;
  acc_reset();
}

V gt_reset()       { acc_reset(); }
V gt_next()        { acc_i += 1; acc[acc_i] = 0; }
V gt_clear() {
  acc_reset();
  for (I x = 0; x < (FW * FH) / 32; x++) frame[x] = 0;
  tx = ty = 0;
  redraw();
}
V gt_set_attr()    { acc_reset(); }

V term_putc(I c) {
  if (c == 27) { on = -2; return; }
  switch (on) {
    case 0:
      dputc(c);
      return;
    case -2:
      if (c == '[') {
        on = -1;
        return;
      }
      on = 0;
      dputc(c);
      return;
  }
  if (c >= '0' && c <= '9') {
    acc[acc_i] *= 10;
    acc[acc_i] += c - 48;
  } else {
    switch (c) {
      case 'A': gt_up(acc_pop());    break;
      case 'B': gt_down(acc_pop());  break;
      case 'C': gt_left(acc_pop());  break;
      case 'D': gt_right(acc_pop()); break;
      case 'm': gt_set_attr();       break;
      case ';': gt_next();           break;
      case 'H': gt_move_cursor();    break;
      case 'J': gt_clear();          break;
      default: on = 0;
               dputc(c);
    }
  }
}

/* the ilo computer **************************************** */

V prepare_vm() {
  for (ip = 0; ip < 32;  ip++) ds[ip] = 0;
  for (ip = 0; ip < 128; ip++) as[ip] = 0;
  ip = sp = rp = 0;
}

V load_image() {
  f = open(rom, O_RDONLY, 0666);
  if (!f) { return; };
  read(f, &m, 65536 * 4);
  close(f);
  ip = sp = rp = 0;
}

V save_image() {
  f = open(rom, O_WRONLY, 0666);
  write(f, &m, 65536 * 4);
  close(f);
}

V block_common() {
  b = pop(); /* block buffer */
  a = pop(); /* block number */
  lseek(f, 4096 * a, SEEK_SET);
}

V read_block() {
  f = open(blocks, O_RDONLY, 0666);
  block_common();
  read(f, m + b, 4096);
  close(f);
}

V write_block() {
  f = open(blocks, O_WRONLY, 0666);
  block_common();
  write(f, m + b, 4096);
  close(f);
}

V save_ip() { rp += 1; R = ip; }
V symmetric() { if (b >= 0 && N < 0) { T += 1; N -= b; } }

V li() { ip += 1; push(m[ip]); }
V du() { push(T); }
V dr() { ds[sp] = 0; sp -= 1; }
V sw() { a = T; T = N; N = a; }
V pu() { rp += 1; R = pop(); }
V po() { push(R); rp -= 1; }
V ju() { ip = pop() - 1; }
V ca() { save_ip(); ip = pop() - 1; }
V cc() { a = pop(); if (pop()) { save_ip(); ip = a - 1; } }
V cj() { a = pop(); if (pop()) { ip = a - 1; } }
V re() { ip = R; rp -= 1; }
V eq() { N = (N == T) ? -1 : 0; sp -= 1; }
V ne() { N = (N != T) ? -1 : 0; sp -= 1; }
V lt() { N = (N <  T) ? -1 : 0; sp -= 1; }
V gt() { N = (N >  T) ? -1 : 0; sp -= 1; }
V fe() { T = m[T]; }
V st() { m[T] = N; sp -= 2; }
V ad() { N += T; sp -= 1; }
V su() { N -= T; sp -= 1; }
V mu() { N *= T; sp -= 1; }
V di() { a = T; b = N; T = b / a; N = b % a; symmetric(); }
V an() { N = T & N; sp -= 1; }
V or() { N = T | N; sp -= 1; }
V xo() { N = T ^ N; sp -= 1; }
V sl() { N = N << T; sp -= 1; }
V sr() { N = N >> T; sp -= 1; }
V cp() { size_t l = pop() * 4;
         void *d = m + pop();
         void *s = m + pop();
         push((memcmp(s, d, l) == 0) ? -1 : 0);
}
V cy() { size_t l = pop() * 4;
         void *d = m + pop();
         void *s = m + pop();
         memmove(d, s, l);
}
V ioa() { i[0] = (char)pop(); write(1, &i, 1); }
V iob() { read(0, &i, 1); push(i[0]); }
V ioc() { read_block(); }
V iod() { write_block(); }
V ioe() { save_image(); }
V iof() { load_image(); ip = -1; }
V iog() { ip = 65536; }
V ioh() { push(sp); push(rp); }
V io() {
  switch (pop()) {
    case 0:   term_putc(pop()); redraw(); break;
    case 1:   push(read_key()); break;
    case 2:   read_block(); break;
    case 3:   write_block(); break;
    case 4:   save_image(); break;
    case 5:   load_image(); ip = -1; break;
    case 6:   exit(0); break;
    case 7:   push(sp); push(rp); break;
    case 33:  g_draw_pixel(); break;
    case 34:  g_get_pixel(); break;
    case 35:  g_mouse(); break;
    default:  break;
  }
}

/* Using a switch here instead of a jump table to avoid   */
/* some issues w/relocation stuff when building w/o libc  */

V process(I o) {
  switch (o) {
    case  0:       break;   case  1: li(); break;
    case  2: du(); break;   case  3: dr(); break;
    case  4: sw(); break;   case  5: pu(); break;
    case  6: po(); break;   case  7: ju(); break;
    case  8: ca(); break;   case  9: cc(); break;
    case 10: cj(); break;   case 11: re(); break;
    case 12: eq(); break;   case 13: ne(); break;
    case 14: lt(); break;   case 15: gt(); break;
    case 16: fe(); break;   case 17: st(); break;
    case 18: ad(); break;   case 19: su(); break;
    case 20: mu(); break;   case 21: di(); break;
    case 22: an(); break;   case 23: or(); break;
    case 24: xo(); break;   case 25: sl(); break;
    case 26: sr(); break;   case 27: cp(); break;
    case 28: cy(); break;   case 29: io(); break;
    default: break;
  }
}

I execute(I address) {
  I o, x;
  ip = address;
  while (ip < 65536) {
    handle_events();
    for (I i = 0; i < cycles; i++) {
      o = m[ip];
      x =  o        & 0xFF; if (x) process(x);
      x = (o >>  8) & 0xFF; if (x) process(x);
      x = (o >> 16) & 0xFF; if (x) process(x);
      x = (o >> 24) & 0xFF; if (x) process(x);
      ip++;
    }
    redraw();
  }
}

/**************************************************************/

V initialize_display() {
  if (SDL_Init(SDL_INIT_VIDEO) < 0) {
    printf("SDL could not initialize! %s\n", SDL_GetError());
    return;
  }
  window = NULL;
  window = SDL_CreateWindow("ilo",
            SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
            FB_W * SCALE, FB_H * SCALE,
            SDL_WINDOW_SHOWN);
  renderer =  SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
  texture = SDL_CreateTexture(renderer,
                              SDL_PIXELFORMAT_ARGB8888,
                              SDL_TEXTUREACCESS_STATIC,
                              FB_W, FB_H);
  SDL_RenderSetScale(renderer, SCALE, SCALE);
  SDL_RenderSetLogicalSize(renderer, FB_W, FB_H);
#ifdef FULLSCREEN
  SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
#endif
  cycles = 25000;
  clear();
}

V cleanup() {
  SDL_DestroyWindow(window);
  SDL_Quit();
}

I main (I argc, C* argv[]) {
  blocks = "ilo.blocks";
  rom = (argc > 1) ? argv[1] : "ilo.rom";
  printf("rom:%s  blocks:%s\n", rom, blocks);
  load_image();
  initialize_display();
  execute(0);
  cleanup();
  return 0;
}
