#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <malloc.h>
#include <sys/stat.h>
#include <arpa/inet.h>

typedef uint32_t word;

word reg[8];  // automatically initalized to 0
word ip;      // automatically initalized to 0
word *zero;

void run (word *prg)
{
  zero = prg;
  for (;;) {
    word w = zero[ip++];

#define A ((w >> 6) & 0x00000007)
#define B ((w >> 3) & 0x00000007)
#define C (w & 0x00000007)
#define array(r) ((r) == 0 ? zero : (word *) (r))

#define A13 ((w >> 25) & 0x00000007)
#define V13 (w & 0x01FFFFFF)

    switch (w >> 28) {
    case 0: // cond move
      if (reg[C] != 0)
        reg[A] = reg[B];
      break;
    case 1: // load
      reg[A] = array(reg[B])[reg[C]];
      break;
    case 2: // store
      array(reg[A])[reg[B]] = reg[C];
      break;
    case 3: // add
      reg[A] = reg[B] + reg[C];
      break;
    case 4: // times
      reg[A] = reg[B] * reg[C];
      break;
    case 5: // div
      reg[A] = reg[B] / reg[C];
      break;
    case 6: // nand
      reg[A] = ~(reg[B] & reg[C]);
      break;
    case 7: // halt
      return;
    case 8: // alloc
      reg[B] = (word) calloc(reg[C], 4);
      break;
    case 9: // free
      free(array(reg[C]));
      break;
    case 10: // output
      putchar(reg[C]);
      fflush(stdout);
      break;
    case 11: // input
      {
        int c = getchar();
        reg[C] = (c == EOF) ? 0xFFFFFFFF : c;        
        break;
      }
    case 12:
      if (reg[B] != 0) {
        free(zero);
        word size = malloc_usable_size(array(reg[B]));
        zero = (word *) malloc(size);
        memcpy(zero, array(reg[B]), size);
      }
      ip = reg[C];
      break;
    case 13:
      reg[A13] = V13;
      break;
    }

  }
}

int main (int argc, char *argv[])
{
  if (argc < 2) {
    fprintf(stderr, "You forgot to give the program name.\n");
    return 1;
  }

  struct stat buf;
  if (stat(argv[1], &buf) != 0) {
    perror("stat");
    return 1;
  }

  word size = buf.st_size;
  word real_size = size;
  if (real_size % 4 != 0)
    real_size = size + (4 - size % 4);
  assert(real_size % 4 == 0);

  word *prg = calloc(real_size, 1);
  if (prg == NULL) {
    fprintf(stderr, "Out of memory.\n");
    return 1;
  }

  FILE *f = fopen(argv[1], "rb");
  if (f == NULL) {
    fprintf(stderr, "There is no program: %s.\n", argv[1]);
    return 1;
  }
  if (fread(prg, 1, size, f) != size) {
    fprintf(stderr, "Error reading the program: %s.\n", argv[1]);
    return 1;
  }
  
#if __BYTE_ORDER != __BIG_ENDIAN
  for (int i = 0; i < size / 4; i++)
    prg[i] = ntohl(prg[i]);
#endif

  fclose(f);
  run(prg);
  return 0;
}
