#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

#ifndef _MSC_VER
#define stderr _stderr
#endif

#define INTERP_ASSERT(cond) __interp_assert(cond,__FILE__,__LINE__)


#define MAX_MEMORY 100
#define MAX_CODE 100
#define MAX_CPU 14
#define MAGIC_NUMBER 0xCAFABABE

#define IPUSH 0
#define APOP 1
#define APUSH 2
#define POP 3
#define ADD 4
#define SUB 5
#define MUL 6
#define DIV 7
#define JMP 8
#define JZ 9
#define PRINT 10
#define CMP 11
#define HALT 12
#define NOP 13

#ifdef _MSC_VER
typedef long long value_t;
#else
typedef long value_t;
#endif

typedef struct _Code {
   unsigned char opcode;
   value_t arg1;
} Code;

typedef void (*pf_t)(Code* x);

void __interp_assert(long cond, const char* file, int line);
void dbg_print(const char* message);
void my_push(value_t x);
value_t my_pop();
void add_code(size_t opcode, value_t arg1);
void check_file(FILE* fin);
void check_magic(unsigned int magic, FILE* fin);
void check_code_size(size_t i, FILE* fin);
void read_code(FILE* fin);
void ipush(Code* x);
void apop(Code* x);
void apush(Code* x);
void pop(Code* x);
void add(Code* x);
void sub(Code* x);
void mul(Code* x);
void div0(Code* x);
void jmp(Code* x);
void jz(Code* x);
void print(Code* x);
void cmp(Code* x);
void halt(Code* x);
void nop(Code* x);
void init();
void scan(int argc, char** argv);
void load();
void run();
void fin();

value_t memory[MAX_MEMORY];
size_t stk = 0;
Code code[MAX_CODE];
size_t code_ptr = 0;
const pf_t CPU[MAX_CPU] = {
   ipush,
   apop,
   apush,
   pop,
   add,
   sub,
   mul,
   div0,
   jmp,
   jz,
   print,
   cmp,
   halt,
   nop,
};
const size_t size_array[MAX_CPU] = {
   1,
   1,
   1,
   0,
   0,
   0,
   0,
   0,
   1,
   1,
   0,
   0,
   0,
   0,
};
int halt_flag = 0;
size_t pc = 0;
int debug = 0;
const char* file_name = "65000.bin";

void
__interp_assert(long cond, const char* file, int lineno)
{
   if(!cond) { 
      fprintf(stderr, "Sorry, interp assert failed (%s:%d)...\n",  __FILE__, __LINE__); 
      exit(EXIT_FAILURE); 
   }
}

void 
dbg_print(const char* message)
{
   if(debug) {
      printf(message);
   }
}

void
my_push(value_t x)
{
   INTERP_ASSERT(stk < MAX_MEMORY);
   memory[stk] = x;
   stk++;
}

value_t
my_pop()
{
   value_t x;
   INTERP_ASSERT(stk != 0);
   stk--;
   x = memory[stk];
   return x;
}

void
ipush(Code* x)
{
   dbg_print("ipush\n");
   my_push(x->arg1);
   pc++;
}

void
apop(Code* x)
{
   value_t a;
   dbg_print("apop\n");
   a = my_pop();
   memory[x->arg1] = a;
   pc++;
}

void
apush(Code* x)
{
   dbg_print("apush\n");
   my_push(memory[x->arg1]);
   pc++;
}


void
pop(Code* x)
{
   dbg_print("pop\n");
   my_pop();
   pc++;
}

void
add(Code* x)
{
   value_t a,b,c;
   dbg_print("add\n");
   b = my_pop();
   a = my_pop();
   c = a + b;
   my_push(c);
   pc++;
}

void 
sub(Code* x)
{
   value_t a,b,c;
   dbg_print("sub\n");
   b = my_pop();
   a = my_pop();
   c = a - b;
   my_push(c);
   pc++;
}

void
mul(Code* x)
{
   value_t a,b,c;
   dbg_print("mul\n");
   b = my_pop();
   a = my_pop();
   c = a * b;
   my_push(c);
   pc++;
}

void
div0(Code* x)
{
   value_t a,b,c;
   dbg_print("div\n");
   b = my_pop();
   a = my_pop();
   c = a / b;
   my_push(c);
   pc++;
}

void
jmp(Code* x)
{
   dbg_print("jmp\n");
   pc = x->arg1;
}

void
cmp(Code* x)
{
   value_t a,b;
   dbg_print("cmp\n");
   a = my_pop();
   b = my_pop();
   if(a == b)
      my_push(0);
   else if(a < b) 
      my_push(-1);
   else
      my_push(1);
   pc++;
}

void
halt(Code* x)
{
   dbg_print("halt\n");
   halt_flag = 1;
   pc++;
}

void
jz(Code* x) 
{
   value_t a;
   dbg_print("jz\n");
   a = my_pop();
   if(a == 0) 
      pc = x->arg1;
   else
      pc++;
}

void 
print(Code* x)
{
   value_t a;
   a = my_pop();
   printf("%ld\n", a);
   pc++;
}

void 
nop(Code* x)
{
   dbg_print("nop\n");
   pc++;
}

void 
check_file(FILE* fin) 
{
   if(fin == NULL) {
      fprintf(stderr, "Sorry, file not found: %s\n", file_name);
      exit(EXIT_FAILURE);
   }
}

void
check_magic(unsigned int magic, FILE* fin)
{
   if(feof(fin) || magic != MAGIC_NUMBER) {
      fclose(fin);
      fprintf(stderr, "Sorry, not an c interp file.\n");
      exit(EXIT_FAILURE);
   }
}

void
check_code_size(size_t i, FILE* fin)
{
   if(i >= MAX_CODE) {
      fclose(fin);
      fprintf(stderr, "Sorry, MAX_CODE=%d reached.\n", i);
      exit(EXIT_FAILURE);
   }
}

void
read_code(FILE* fin)
{
   size_t i = 0;
   while(!feof(fin)) {
      if(fread(&code[i].opcode, sizeof(unsigned char), 1, fin)) {
         if(size_array[code[i].opcode] == 1) {
            if(!fread(&code[i].arg1, sizeof(value_t), 1, fin)) {
               break;
            }
         }
         i++;
      }
      else {
         break;
      }
      check_code_size(i, fin);
   }
   code_ptr = i;
}

void
load()
{
   FILE* fin;
   unsigned int magic;
   dbg_print("load\n");
   fin = fopen(file_name, "rb");
   check_file(fin);
   fread(&magic, sizeof(unsigned int), 1, fin);
   check_magic(magic,fin);
   read_code(fin);
   fclose(fin);
}

void
init()
{
   dbg_print("init\n");
   memset(code, 0, sizeof(Code)*MAX_CODE);
   memset(memory, 0, sizeof(value_t)*MAX_MEMORY);
}

void
fin()
{
   dbg_print("fin\n");
   exit(EXIT_SUCCESS);
}

void
run()
{
   Code* cx;
   clock_t start,end,total;
   dbg_print("run\n");
   pc = 0;
   start = clock();
   while(pc < code_ptr && !halt_flag) {
      cx = &code[pc];
      (CPU[cx->opcode])(cx);
   }
   end = clock();
   total = end - start;
   printf("Total run time was %ld milliseconds...\n", total);
}

void
scan(int argc, char** argv)
{
   int i;
   for(i = 1; i < argc; i++) {
      if(strcmp(argv[i], "-g") == 0) {
         debug = 1;
      }
      else {
         file_name = argv[i];
      }
   }
}

int
main(int argc, char* argv[])
{
   init();
   scan(argc,argv);
   load();
   run();
   fin();
   return 0;
}
