/* A program that will simulate an IBCM program, and it has a number
 * of command line switches that control its execution.  This program
 * is useful for automating the execution of a number of IBCM programs
 * without having to load each one into the web interface.  It will
 * also print out traces of the entire program execution, if desired.
 * Run with the `-help` flag to see the full list of options.
 */

#ifndef IS_LITTLE_ENDIAN
#ifndef IS_BIG_ENDIAN
#error Must define IS_LITTLE_ENDIAN or IS_BIG_ENDIAN via the `-D` flag to the compiler
#define IS_LITTLE_ENDIAN
#endif
#endif

#include <iostream>
#include <fstream>
#include <string>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <stdint.h>

using namespace std;

int compState = 0;
int simState = 0;
int printState = 0;
int printStats = 0;
int dumpState = 0;
int verbose = 0;
unsigned int numinst = 0;
int printTicks = 0;
unsigned int maxticks = 0;
string outputFile = "ibcm.bin";
string inputFile;
uint16_t mem[4096];

void printHelp(char *name);
void checkEndian(void);
void compileCode (string infilename, string outfilename);
void simulateIBCM ();
void readBinaryIBCMFile (string infilename);
void dumpIBCMFile ();
char* decodeIBCMInstruction(uint16_t inst);

int main(int argc, char *argv[]) {
    int i = 1;
    checkEndian();
    assert (sizeof(short) == 2);
    ios::sync_with_stdio(); // Synchronize C++ and C I/O subsystems!
    while (i < argc) {
        if (strcmp(argv[i], "-comp")==0) {
            compState=1;
            inputFile=argv[i+1]; // TODO: check that this doesn't go past the array bounds
        } else if (strcmp(argv[i], "-sim")==0) {
            simState=1;
            inputFile=argv[i+1]; // TODO: check that this doesn't go past the array bounds
        } else if (strcmp(argv[i], "-dump")==0) {
            dumpState=1;
            inputFile=argv[i+1]; // TODO: check that this doesn't go past the array bounds
        } else if (strcmp(argv[i], "-o")==0) {
            outputFile=argv[i+1]; // TODO: check that this doesn't go past the array bounds
            i++;
        } else if (strcmp(argv[i], "-verbose")==0) {
            verbose++;
        } else if (strcmp(argv[i], "-help")==0) {
            printHelp(argv[0]);
        } else if (strcmp(argv[i], "-print")==0) {
            printState = 1;
        } else if (strcmp(argv[i], "-stats")==0) {
            printStats = 1;
        } else if (strcmp(argv[i], "-maxticks")==0) {
            maxticks = atoi(argv[++i]); // TODO: check the format of the int
        }
        i++;
    }
    //Check for proper combination of params
    if (simState==0 && compState==0 && inputFile=="") {
        //cerr << "No option specified..." << endl;
        printHelp(argv[0]);
        return 1;
    }
    if ( compState )
        compileCode (inputFile, outputFile);
    if ( simState ) {
        readBinaryIBCMFile (inputFile);
        if ( printState ) {
            dumpIBCMFile ();
            cout << endl << endl;
        }
        simulateIBCM();
        if ( printStats )
            cout << "Total of " << numinst << " instructions executed." << endl;
        if ( printState ) {
            cout << endl << endl;
            dumpIBCMFile ();
        }
    }
    if ( dumpState ) {
        readBinaryIBCMFile (inputFile);
        dumpIBCMFile ();
    }
    return 0;
}

void printHelp(char *name) {
    static bool helpPrinted; // Static values are initialized to 0 or 0-equivalent
    if(helpPrinted) return;

    helpPrinted = true;

    cout << "Usage: " << name << " [option] ..." << endl;
    cout << "Options:" << endl;
    cout << "\t[-comp <inputfile>]\tSignals the program to compile the IBCM file speicfied by <inputfile>" << endl;
    cout << "\t[-sim <inputfile>]\tSignals the program to simulate the IBCM program specified by <inputfile>" << endl;
    cout << "\t[-o <outfile>]\t\tSpecify the name of the outfile produced by compiling <inputfile>" << endl
         << "\t\t\t\tThe defaut value for <outfile> is \"ibcm.bin.\"" << endl;
    cout << "\t[-verbose]\t\tPrints useful debugging information while compiling and emulating" << endl
         << "\t\t\t\the specified IBCM program.";
    cout << " Specify twice for more output." << endl;
    cout << "\t[-dump <inputfile>]\tOutputs a binary IBCM file in text format.";
    cout << " Also specify -verbose for decompilation." << endl;
    cout << "\t[-print]\t\tPrints a listing of the program before and after the simulation." << endl;
    cout << "\t[-maxticks <n>]\t\tSet the maximum number of ticks." << endl;
    cout << "\t[-stats]\t\tPrints stats of the executed program, including the number of ticks." << endl;
    cout << "\t[-help]\t\t\tPrints this help message." << endl;
}

void checkEndian(void) {
    static int firsttime = 1;
    if (firsttime) {
        union {
            char charword[4];
            unsigned int intword;
        } check;
        check.charword[0] = 1;
        check.charword[1] = 2;
        check.charword[2] = 3;
        check.charword[3] = 4;
#ifdef IS_BIG_ENDIAN
        if (check.intword != 0x01020304) {  /* big */
            cerr << "ERROR: Host machine is not Big Endian.\nExiting." << endl;
            exit(205);
        }
#else
#ifdef IS_LITTLE_ENDIAN
        if (check.intword != 0x04030201) {  /* little */
            cerr << "ERROR: Host machine is not Little Endian.\nExiting." << endl;
            exit(206);
        }
#else
#error ERROR: must define either IS_LITTLE_ENDIAN or IS_BIG_ENDIAN
#endif // IS_LITTLE_ENDIAN
#endif // IS_BIG_ENDIAN
        firsttime = 0;
    }
}

bool ishexdigit (int c) {
    if ( isdigit(c) )
        return true;
    if ( (c >= 'a') && (c <= 'f') )
        return true;
    if ( (c >= 'A') && (c <= 'F') )
        return true;
    return false;
}

void dumpIBCMFile () {
    // find last command to not print
    int end;
    for ( end = 4095; end >= 0; end-- ) {
        if ( mem[end] != 0 )
            break;
    }
    for ( int i = 0; i <= end; i++ ) {
        printf ("%.4x\tpc = %.4x\t%s\n", mem[i], i,
                decodeIBCMInstruction(mem[i]));
    }
}


void compileCode (string infilename, string outfilename) {
    string line;
    int linenum = -1, outputlines = 0;
    // open input file
    ifstream infile(infilename.c_str());
    if ( !infile.is_open() ) {
        cerr << "Error: unable to open input file." << endl;
        exit(200);
    }
    // open output file
    ofstream outfile(outfilename.c_str());
    if ( !outfile.is_open() ) {
        cerr << "Error: unable to open output file." << endl;
        exit(201);
    }
    // read input file, write to output file
    while (!infile.eof()) {
        linenum++;
        getline (infile,line);
        // skip blank lines
        if ( line.size() == 0 )
            continue;
        if ( (line[0] == '/') && (line[1] == '/') )
            continue;
        // sanity check
        if ( !ishexdigit(line[0]) || !ishexdigit(line[1]) ||
                !ishexdigit(line[2]) || !ishexdigit(line[3]) ) {
            cerr << "Error on line " << linenum << ": invalid hex digits" << endl;
        }
        line[4] = 0;
        int hex;
        sscanf (line.c_str(), "%x", &hex);
#ifdef IS_LITTLE_ENDIAN
        char g = hex % 256;
        outfile.write(&g,1);
        g = hex / 256;
        outfile.write(&g,1);
#else
#error Not yet working for non-little-endian machines
#endif
        //cout << line[0] << line[1] << line[2] << line[3] << endl;
        outputlines++;
    }
    infile.close();
    // fill remaining space with 0's
    for (; outputlines < 4096; outputlines++ ) {
        char x = 0;
        //cout << "0000" << endl;
        outfile.write (&x,1);
        outfile.write (&x,1);
    }
    outfile.close();
}

void readBinaryIBCMFile (string infilename) {
    int cmdnum = 0;
    // open input file
    ifstream infile(infilename.c_str());
    if ( !infile.is_open() ) {
        cerr << "Error: unable to open input file." << endl;
        exit(202);
    }
    // load up file into array
    for ( int i = 0; i < 4096; i++ )
        mem[i] = 0;
    char buf[2];
    while (!infile.eof()) {
        infile.read(buf,2);
#ifdef IS_LITTLE_ENDIAN
        mem[cmdnum] = (buf[1] & 0x000000ff) * 256 + (buf[0] & 0x000000ff);
#else
#error Not yet working for non-little-endian machines
#endif
        cmdnum++;
    }
    //for ( int i = 0; i < 4096; i++ ) printf ("%.4x\n", mem[i] & 0x0000ffff);
}



union ibcm_instruction {
    uint16_t buf;
#ifdef IS_BIG_ENDIAN //The IBCM is big endian
    struct {
        unsigned char high, low;
    } bytes;
    struct {
        unsigned int op:4, unused:12;
    } halt;
    struct {
        unsigned int op:4, ioopt:2, unused:10;
    } io;
    struct {
        unsigned int op:4, shiftop:2, unused:5, shiftcount:5;
    } shifts;
    struct {
        unsigned int op:4, address:12;
    } others;
#else
#ifdef IS_LITTLE_ENDIAN //The IBCM is little endian
    struct {
        unsigned char low, high;
    } bytes;
    struct {
        unsigned int unused:12, op:4;
    } halt;
    struct {
        unsigned int unused:10, ioopt:2, op:4;
    } io;
    struct {
        unsigned int shiftcount:5, unused:5, shiftop:2, op:4;
    } shifts;
    struct {
        unsigned int address:12, op:4;
    } others;
#else
#error Must define BIG_ENDIAN or LITTLE_ENDIAN
#endif // LITTLE_ENDIAN
#endif // BIG_ENDIAN
} ir, ir2;


char* decodeIBCMInstruction(uint16_t inst) {
    static string instnames[] = {"halt", "I/O", "shifts", "load", "store", "add", "sub", "and",
                                 "or", "xor", "not", "nop", "jmp", "jmpe", "jmpl", "brl"
                                };
    static char buf[256], buf2[8];
    ir2.buf = inst;
    int op = ir2.others.op;
    buf[0] = 0;
    if ( (op <= 2) || (op == 10) || (op == 11) ) // halt, not, nop, I/O, and shift
        strcpy (buf, instnames[op].c_str());
    else if ( op >= 3 ) {
        strcpy (buf, instnames[ir2.others.op].c_str());
        strcat (buf, "\t");
        sprintf (buf2, "%.3x", ir2.others.address);
        strcat (buf, buf2);
    }
    return buf;
}


void simulateIBCM() {
    int acc = 0, pc = 0, incpc;
    string buf;
    while (1) {
        acc &= 0x0000ffff;
        incpc = 1;
        ir.buf = mem[pc];
        if ( verbose >= 2 )
            printf ("\npc = %.3x: ir = %.4x, ibcm = '%s'\n", pc, ir.buf, decodeIBCMInstruction(ir.buf));
        numinst++;
        switch (ir.others.op) {
            case 0: // halt
                if ( verbose >= 1 ) printf ("pc = %.3x: halt\n", pc);
                return;
                break;
            case 1: // I/O
                switch ( ir.io.ioopt ) {
                    case 0: // read hex
                        if ( verbose >= 1 ) printf ("pc = %.3x: I/O: read hex\n", pc);
                        printf ("Enter hex input: ");
                        fflush (stdout);
                        cin >> buf;
                        sscanf (buf.c_str(), "%x", &acc);
                        acc &= 0x0000ffff;
                        printf ("\n");
                        break;
                    case 1: // read ascii
                        if ( verbose >= 1 ) printf ("pc = %.3x: I/O: read ascii\n", pc);
                        printf ("Enter ascii input: ");
                        fflush (stdout);
                        cin >> buf;
                        acc = buf[0];
                        acc &= 0x0000ffff;
                        printf ("\n");
                        break;
                    case 2: // write hex
                        if ( verbose >= 1 ) printf ("pc = %.3x: I/O: write hex\n", pc);
                        if ( verbose >= 1 ) printf ("Hex output: ");
                        printf ("%.4x\n", acc);
                        break;
                    case 3: // write ascii
                        if ( verbose >= 1 ) printf ("pc = %.3x: I/O: write ascii\n", pc);
                        if ( verbose >= 1 ) printf ("Ascii output: ");
                        printf ("%c\n", acc & 0xff);
                        break;
                }
                break;
            case 2: // shifts
                switch ( ir.shifts.shiftop ) {
                    case 0: // shift left
                        acc = (acc << ir.shifts.shiftcount) & 0xffff;
                        acc &= 0x0000ffff;
                        if ( verbose >= 1 ) printf ("pc = %.3x: shift left by %d; result is %.4x\n", pc, ir.shifts.shiftop, acc);
                        break;
                    case 1: // shift right
                        acc = (acc >> ir.shifts.shiftcount) & (0xffff >> ir.shifts.shiftcount);
                        acc &= 0x0000ffff;
                        if ( verbose >= 1 ) printf ("pc = %.3x: shift right by %d; result is %.4x\n", pc, ir.shifts.shiftop, acc);
                        break;
                    case 2: // rotate left
                        acc = ((acc << ir.shifts.shiftcount) & 0xffff) |
                              ((acc >> (16-ir.shifts.shiftcount)) & (0xffff >> (16-ir.shifts.shiftcount)));
                        acc &= 0x0000ffff;
                        if ( verbose >= 1 ) printf ("pc = %.3x: rotate left by %d; result is %.4x\n", pc, ir.shifts.shiftop, acc);
                        break;
                    case 3: // rotate right
                        acc = ((acc >> ir.shifts.shiftcount) & (0xffff >> ir.shifts.shiftcount)) |
                              ((acc << (16-ir.shifts.shiftcount)) & 0xffff);
                        acc &= 0x0000ffff;
                        if ( verbose >= 1 ) printf ("pc = %.3x: rotate right by %d; result is %.4x\n", pc, ir.shifts.shiftop, acc);
                        break;
                }
                break;
            case 3: // load
                acc = mem[ir.others.address];
                acc &= 0x0000ffff;
                if ( verbose >= 1 ) printf ("pc = %.3x: load result (@ %.3x) is %.4x\n", pc, ir.others.address, acc);
                break;
            case 4: // store
                acc &= 0x0000ffff;
                mem[ir.others.address] = acc;
                if ( verbose >= 1 ) printf ("pc = %.3x: store result (@ %.3x) is %.4x\n", pc, ir.others.address, mem[ir.others.address]);
                break;
            case 5: // add
                acc += mem[ir.others.address];
                acc &= 0x0000ffff;
                if ( verbose >= 1 ) printf ("pc = %.3x: add result is %.4x\n", pc, acc);
                break;
            case 6: // sub
                acc -= mem[ir.others.address];
                acc &= 0x0000ffff;
                if ( verbose >= 1 ) printf ("pc = %.3x: sub result is %.4x\n", pc, acc);
                break;
            case 7: // and
                acc &= mem[ir.others.address];
                acc &= 0x0000ffff;
                if ( verbose >= 1 ) printf ("pc = %.3x: and result is %.4x\n", pc, acc);
                break;
            case 8: // or
                acc |= mem[ir.others.address];
                acc &= 0x0000ffff;
                if ( verbose >= 1 ) printf ("pc = %.3x: or result is %.4x\n", pc, acc);
                break;
            case 9: // xor
                acc ^= mem[ir.others.address];
                acc &= 0x0000ffff;
                if ( verbose >= 1 ) printf ("pc = %.3x: xor result is %.4x\n", pc, acc);
                break;
            case 10: // not
                acc = ~acc;
                acc &= 0x0000ffff;
                if ( verbose >= 1 ) printf ("pc = %.3x: not result is %.4x\n", pc, acc);
                break;
            case 11: // nop
                // do nothing
                if ( verbose >= 1 ) printf ("pc = %.3x:  nop\n", pc);
                break;
            case 12: // jmp
                if ( verbose >= 1 ) printf ("pc = %.3x: jmp to %.4x\n", pc, ir.others.address);
                pc = ir.others.address;
                incpc = 0;
                break;
            case 13: // jmpe
                if ( verbose >= 1 ) printf ("pc = %.3x: jmpe to %.4x\n", pc, ir.others.address);
                if ( (acc & 0x0000ffff) == 0 ) {
                    pc = ir.others.address;
                    incpc = 0;
                }
                break;
            case 14: // jmpl
                if ( verbose >= 1 ) printf ("pc = %.3x: jmpl to %.3x\n", pc, ir.others.address);
                if ( (acc & 0x00008000) != 0 ) {
                    pc = ir.others.address;
                    incpc = 0;
                }
                break;
            case 15: // brl
                acc = pc+1;
                acc &= 0x0000ffff;
                if ( verbose >= 1 ) printf ("pc = %.3x: brl from %.3x to %.4x\n", pc, acc, ir.others.address);
                pc = ir.others.address;
                incpc = 0;
                break;
        }
        if ( incpc )
            pc++;
        if ( maxticks && (numinst >= maxticks) ) {
            cerr << "Simulation reached the maximum number of " << maxticks << " instructions to execute." << endl;
            cout << "Simulation reached the maximum number of " << maxticks << " instructions to execute." << endl;
            break;
        }
    }
}