#include "language.h"
#include <dirent.h>

#define PC memory.sys_start[0]
#define SP memory.sys_start[1]
#define RSP memory.sys_start[2]
#define JUMP_OFF memory.sys_start[3]

int stack_frames[256];
int stack_frame_sizes[256];
int stack_frames_index = 0;
//this stores free regions as start, end, size
int heap_list[2048][3];
int heap_length = 1;

double total_time = 0.0;
int number_of_steps = 0;

struct Memory {
   short *memory;
   short *sys_start;
   short *rom_start;
   short *stack_start;
   short *return_stack_start;
   short *heap_start;
};

int find_available_heap(int size)
{
   for(int x = 0; x < 2048; x++)
   {
       if( heap_list[x][2] > size )
       {
           int found = heap_list[x][0];
           heap_list[x][0] = heap_list[x][0] + size;
           heap_list[x][2] = heap_list[x][2] - size;
           return found;
       }
   }
}

void free_heap(int start, int size)
{
   heap_list[++heap_length][0] = start;
   heap_list[heap_length][1] = start + size;
   heap_list[heap_length][2] = size;
   for(int x = 0; x < heap_length - 1; x++)
   {
       if((heap_list[x][0] == (heap_list[heap_length][1])))
       {
           heap_list[x][0] -= size;
           heap_list[x][2] += size;
           heap_length--;
       }
       else if(heap_list[x][1] == (heap_list[heap_length][0]))
       {
           heap_list[x][1] += size;
           heap_list[x][2] += size;
           heap_length--;
       }
   }
}

int POP_S(int amount, struct Memory* memory)
{
   int data = memory->stack_start[memory->sys_start[1]-1];
   memory->sys_start[1] -= amount;
   if(memory->sys_start[1] < 0) memory->sys_start[1] = 0;
   return data;
}

void Q_POP_S(int amount, struct Memory* memory)
{
   memory->sys_start[1] -= amount;
   if(memory->sys_start[1] < 0) memory->sys_start[1] = 0;
}

void out_char(int output_format, int output_value)
{
   if(output_format == 'I') printf("%i",output_value);
   if(output_format == 'C') printf("%c",output_value);
   if(output_format == 'X') printf("%x",output_value);
   if(output_format == 'B'){
       while (output_value){
           if (output_value & 1) printf("1");
           else printf("0");
           output_value >>= 1;
       }
   }
}

void main(int argc, char *argv[])
{
   struct Memory memory;

   memory.memory = malloc(256*256*sizeof(short));
   memory.sys_start = &memory.memory[0];
   memory.rom_start = &memory.memory[255];
   memory.stack_start = &memory.memory[256*5-1];
   memory.heap_start = &memory.memory[256*6-1];
   memory.return_stack_start = &memory.memory[256*6-1];
   heap_list[0][0] = 256*7-1;
   heap_list[0][1] = 256*256;
   heap_list[0][2] = 256*256 - 256*7-1;
   stack_frames[0] = RSP + 256*6-1;

   PC = 255;
   JUMP_OFF = 0;

   int requested_number_of_steps = 100000000;

   if( argc > 2) requested_number_of_steps = atoi(argv[2]);
   if( argc > 3) memory.sys_start[4] = (short) atoi(argv[3]);
   if( argc > 4) memory.sys_start[5] = (short) atoi(argv[4]);

   FILE *fp = fopen(argv[1], "r");
   if (fp == NULL) printf("ROM could not be loaded\n");

   fread(memory.rom_start, sizeof(short)*256*4, 1, fp);
   fclose(fp);

   int running = 1;
   while(running){
       int redraw = 0;
       short datum = memory.memory[PC];

       memcpy(memory.stack_start+SP, memory.memory+PC, sizeof(short));

       int address;
       int output_length, output_address, output_mode, output_format, output_value;
       int compare_mode;
       short  load_value;
       switch( datum ){
           case LIT: PC++;memory.stack_start[SP] = memory.memory[PC];SP++;break;
           case DUP: memory.stack_start[SP] = memory.stack_start[SP - 1];SP++; break;
           case POP: Q_POP_S(1, &memory); break;
           case FHA:
               output_length = POP_S(1, &memory);
               output_address = POP_S(1, &memory);
               free_heap(output_address, output_length);
               break;
           case NFH:
               output_length = POP_S(1, &memory);
               output_address = find_available_heap(output_length);
               memory.stack_start[SP] = output_address;
               SP++;
               break;
           case STA: address = POP_S(1, &memory);memory.memory[address] = POP_S(1, &memory); break;
           case LFA: address = POP_S(1, &memory);memory.stack_start[SP] = memory.memory[address];SP++;
break;
           case JMP: PC = POP_S(1, &memory) - 1 + JUMP_OFF; break;
           case JMR:
               memory.return_stack_start[++RSP] = PC;
               PC = POP_S(1, &memory) - 1 + JUMP_OFF;
               stack_frames_index++;
               stack_frame_sizes[stack_frames_index] = 1;
               stack_frames[stack_frames_index] = RSP + 256*6-1;
               break;
           case JCC:
               RSP -= stack_frame_sizes[stack_frames_index]-1;
               PC = memory.return_stack_start[RSP--];
               stack_frames_index--;
               break;
           case JEQ:
               if( memory.stack_start[SP-2] == memory.stack_start[SP-3]){
                   PC = POP_S(3, &memory) - 1 + JUMP_OFF;
               }
               else Q_POP_S(3, &memory);
               break;
           case JNE:
               if( memory.stack_start[SP-2] != memory.stack_start[SP-3]){
                   PC = POP_S(3, &memory) - 1 + JUMP_OFF;
               }
               else Q_POP_S(3, &memory);
               break;
           case JER:
               if( memory.stack_start[SP-2] == memory.stack_start[SP-3]){
                   memory.return_stack_start[++RSP] = PC;
                   PC = POP_S(3, &memory) - 1 + JUMP_OFF;
                   stack_frames_index++;
                   stack_frame_sizes[stack_frames_index] = 1;
                   stack_frames[stack_frames_index] = RSP + 256*6-1;
               }
               else Q_POP_S(3, &memory);
               break;
           case JNR:
               if( memory.stack_start[SP-2] != memory.stack_start[SP-3]){
                   memory.return_stack_start[++RSP] = PC;
                   PC = POP_S(3, &memory) - 1 + JUMP_OFF;
                   stack_frames_index++;
                   stack_frame_sizes[stack_frames_index] = 1;
                   stack_frames[stack_frames_index] = RSP + 256*6-1;
               }
               else Q_POP_S(3, &memory);
               break;
           case SWP: memory.stack_start[SP] = memory.stack_start[SP-2];memory.stack_start[SP-2] =
memory.stack_
start[SP-1];memory.stack_start[SP-1] = memory.stack_start[SP]; break;
           case OVR: memory.stack_start[SP] = memory.stack_start[SP-2];SP++; break;
           //case DIV: memory.stack_start[SP-2] = floor(memory.stack_start[SP-2] /
memory.stack_start[SP-1]);Q_
POP_S(1, &memory); break;
           case MUL: memory.stack_start[SP-2] = memory.stack_start[SP-2] *
memory.stack_start[SP-1];Q_POP_S(1,
&memory); break;
           case SUB: memory.stack_start[SP-2] = memory.stack_start[SP-2] -
memory.stack_start[SP-1];Q_POP_S(1,
&memory); break;
           case ADD: memory.stack_start[SP-2] = memory.stack_start[SP-2] +
memory.stack_start[SP-1];Q_POP_S(1,
&memory); break;
           case LSL: memory.stack_start[SP-1] = (memory.stack_start[SP-1] << 1) |
(memory.stack_start[SP-1] >>
(15)) & (0x7F >> (15)); break;
           case AND:
               memory.stack_start[SP-2] = memory.stack_start[SP-2] & memory.stack_start[SP-1];
               Q_POP_S(1, &memory);
               break;
           case LOR: memory.stack_start[SP-2] = memory.stack_start[SP-2] |
memory.stack_start[SP-1];Q_POP_S(1,
&memory); break;
           case CMP:
               compare_mode = memory.stack_start[SP-1];
               switch (compare_mode){
                   case 0: memory.stack_start[SP-3] = memory.stack_start[SP-3] <
memory.stack_start[SP-2];Q_POP
_S(2, &memory); break;
                   case 1: memory.stack_start[SP-3] = memory.stack_start[SP-3] >
memory.stack_start[SP-2];Q_POP
_S(2, &memory); break;
                   case 2: memory.stack_start[SP-3] = memory.stack_start[SP-3] <=
memory.stack_start[SP-2];Q_PO
P_S(2, &memory); break;
                   case 3: memory.stack_start[SP-3] = memory.stack_start[SP-3] >=
memory.stack_start[SP-2];Q_PO
P_S(2, &memory); break;
                   case 4: memory.stack_start[SP-3] = memory.stack_start[SP-3] ==
memory.stack_start[SP-2];Q_PO
P_S(2, &memory); break;
                   case 5: memory.stack_start[SP-3] = memory.stack_start[SP-3] !=
memory.stack_start[SP-2];Q_PO
P_S(2, &memory); break;
                   case 6: memory.stack_start[SP-2] = memory.stack_start[SP-2] > 0;Q_POP_S(1, &memory);
break;
                   case 7: memory.stack_start[SP-2] = memory.stack_start[SP-2] < 0;Q_POP_S(1, &memory);
break;
               }
               break;
           case OUT:
               output_format = POP_S(1, &memory);
               output_length = POP_S(1, &memory);
               output_mode = POP_S(1, &memory);
               if(output_mode == 0) output_address = POP_S(1, &memory);
               if(output_length == ((short)555)) output_length = 60000;
               for(int x=0; x < output_length; x++){
                   if(output_mode == 0){
                       output_value = memory.memory[output_address+x];
                       out_char(output_format,output_value);
                   }
                   else{
                       memory.memory[5000-x] = memory.stack_start[SP-1];
                       output_value = POP_S(1, &memory);
                   }
                   if((output_value == 0) && (output_length == 60000)){
                       output_length = x;
                       break;
                   }
               }
               if(output_mode == 1){
                   for(int x=1; x <= output_length; x++){
                       out_char(output_format, memory.memory[5000-output_length+x]);
                   }
               }
               break;
           case LFR:
               load_value = stack_frames[stack_frames_index]+POP_S(1, &memory);
               memory.stack_start[SP++] = load_value;
               break;
           case PRS:
               memory.return_stack_start[++RSP] = POP_S(1, &memory);
               stack_frame_sizes[stack_frames_index]++;
               break;
           case STV:
               output_length = POP_S(1, &memory);
               output_address = POP_S(1, &memory);
               for(int x=1; x < output_length; x++){
                   memory.memory[output_address+output_length-x-1] = POP_S(1, &memory);
               }
               memory.memory[output_address+output_length-1] = POP_S(1, &memory);
               break;
           case STZ:
               output_length = POP_S(1, &memory);
               output_address = POP_S(1, &memory);
               for(int x=0; x < output_length; x++){
                   memory.memory[output_address+x] = 0;
               }
               break;
           case RIN:
               output_length = POP_S(1, &memory);
               output_address = POP_S(1, &memory);
               char format[24];
               sprintf(format, " %%n%%%i[^\n]%%n", output_length);
               int n1, n2;
               char test[255];
               scanf(format, &n1, &test, &n2);
               memory.memory[16] = (short)n2-n1;
               for(int x=0; x < n2-n1; x++){
                   memory.memory[output_address+x] = (short)test[x];
               }
               break;
           case END: running = 0; break;
           default: break;
       }
       if( memory.sys_start[48] > 0 ){
           char path[255];
           short path_address = memory.sys_start[49];
           short path_len = memory.sys_start[50];
           short result_address = memory.sys_start[51];
           short request_options = memory.sys_start[52];
           if((request_options & ((short)-32768)) == ((short)-32768));//local path
           for(int p = 0; p < path_len; p++){
               printf("%c",memory.memory[path_address+p]);
               path[p] = memory.memory[path_address+p];
           }
           printf("\n");
           path[path_len] = '\0';

           short last_len = 0;
           if((request_options & ((short)2048)) == ((short)2048)){
               //list
               struct dirent *directory;
               DIR *dr = opendir(path);
               if (dr == NULL){
                   memory.sys_start[48] = 0;
                   printf("Could not open current directory\n");
                   break;
               }
               while ((directory = readdir(dr)) != NULL){

                   short p[255];
                   for(int i=0;i<strlen(directory->d_name);i++){
                       p[i] = directory->d_name[i];
                   }
                   p[strlen(directory->d_name)] = 10;
                   memcpy(&memory.memory[result_address + last_len], p, 2*strlen(directory->d_name)+1);
                   last_len += strlen(directory->d_name)+1;
               }
               memory.memory[result_address + last_len] = 3;
               memory.sys_start[53] = last_len;
               closedir(dr);
           }
           if((request_options & ((short)16384)) == ((short)16384)){
               //read
               FILE *file_fp = fopen(path, "r");
               if (file_fp == NULL) printf("ROM could not be loaded\n");
               fread(&memory.memory[result_address], sizeof(short)*256*4, 1, file_fp);
               memory.sys_start[53] = 256;
               fclose(file_fp);
           }
           if((request_options & ((short)8192)) == ((short)8192));//write
           if((request_options & ((short)4096)) == ((short)4096));//execute
           memory.sys_start[48] = 0;
       }

       PC++;
       number_of_steps++;

       if (number_of_steps >= requested_number_of_steps){
           int scanned = 0;
           while(scanned != 1){
               scanf("%d", &scanned);
               if(scanned == 2){
                   printf("SYS:\n");
                   for(int x = 0; x < 255*6; x++){
                       if(x%30==0) printf("\n");
                       printf(" %04x", memory.sys_start[x]);
                   }
                   printf("\n");
               }
           }
       }
   }
}