function RegisterError(message) {
    this.name = 'Register Error';
    this.message = message;
}
function JumpError(message) {
    this.name = 'Jump Error';
    this.message = message;
}
function MipsError(message) {
    this.name = 'Mips Error';
    this.message = message;
}

/**
 * Mips emulator constructor
 * @param  {Object} mipsArgs Arguments to construct the mips emulater.
 * @param mipsArgs.startingCode Set the default code for this emulator to run.
 * @param mipsArgs.debug If debug is set to true, the console will print debug statements
 * @return {mipsEmulator}
 * @member mipsEmulator
 */
function mipsEmulator(mipsArgs){
    mipsArgs = mipsArgs || {};
    mipsArgs = _.defaults(mipsArgs, {
        startingCode: null,
        debug: false,
        onError: function(message, lineNumber){
            alert(message);
            return false;
        },
        onWarning: function(message, lineNumber){
            alert(message);
        },
        onRegisterChange: function(regName, newValue) {

        },
        onFinish: function(){
            if (debug) console.log("Finished running emulation, resetting $sp to line 1");
        },
        onStackChange: function(){

        },
        onOutput: function(message) {
            console.log(message)
        },
        onInput: function(message) {
            assert(false, "Expecting input, but there is no handler.");
        },
        onConfirm: function(message) {
            assert(false, "Expecting confirmation, but there is no handler.");
        },
        onAlert: function(message) {
            assert(false, "Expecting alert to be displayed, but there is no handler.");
        },
        baseStackAddress: undefined
    });
    var debug = mipsArgs.debug;
    //////////////////////////////////
    // Private Variables / Setup
    //////////////////////////////////

   var stack = new Stack({onChange: mipsArgs.onStackChange, baseAddress: mipsArgs.baseStackAddress});

    /**
     * Hash table of registers
     * @property registers
     * @private
     * @member mipsEmulator
     * @type {Object}
     */
    var registers = {};
    /**
     * Array of all registers
     * @property allRegs
     * @private
     * @member mipsEmulator
     * @type {Array}
     */
    var allRegs = [
        '$zero', '$at', '$v0', '$v1', '$a0', '$a1', '$a2', '$a3',
        '$t0',   '$t1', '$t2', '$t3', '$t4', '$t5', '$t6', '$t7',
        '$s0',   '$s1', '$s2', '$s3', '$s4', '$s5', '$s6', '$s7',
        '$t8',   '$t9', '$k0', '$k1', '$gp', '$sp', '$fp', '$ra'
    ];

    var preservedRegs = [ // these are the registers that are preserved across function calls
        '$zero',
        '$s0', '$s1', '$s2', '$s3', '$s4', '$s5', '$s6', '$s7',
        '$gp', '$sp', '$fp', '$ra',
        // TODO: these shouldn't be here but are required right now because of the check of writable (e.g. when setting garbage data)
        '$at', '$k0', '$k1'
    ];

    /**
     * Array of read only registers
     * @property readonlyRegs
     * @private
     * @member mipsEmulator
     * @type {Array}
     */
    var readonlyRegs = [
        '$zero', '$at',
        '$k0', '$k1',
        '$gp', '$ra'
    ];
    // The intial line where we start the emulation.
    /**
     * The current line the mips emulator is looking at.
     * @property currentLine
     * @private
     * @member mipsEmulator
     * @type {Number}
     */
    var currentLine = 1;
    // populate registers with all the read and write registers and set their inital values to random
    for (var i = 0; i < allRegs.length; i++) {
        registers[allRegs[i]] = createRegister(allRegs[i], i);
    };
    registers.$zero.val = 0;
    registers.$sp.val = stack.pointerToBottomOfStack();
    registers.$fp.val = stack.pointerToBottomOfStack();

    // Object that will contain analyzed code information
    /**
     * @class mipsCode
     * @private
     * @member mipsEmulator
     * Object that keeps the code to be executed
     * @type {Object}
     */
    var mipsCode = {
        /**
         * Array of lines that can be exectued
         * @property code
         * @member mipsCode
         * @type {Array}
         */
        code:[null], // Initialize with null in the 0 place, to make line numbers line up.
        /**
         * Hashtable of labels pointing to lines of code
         * @property labels
         * @member mipsCode
         * @type {Object}
         */
        labels: {}
    };
    // Public methods
    /**
     * @class mipsEmulator
     * Mips Emulation engine.
     */
    var ME = {
        FINISHED_EMULATION: 'FINISHED_EMULATION',
        BYTES_PER_REGISTER: 4,
        BITS_PER_REGISTER: 32,
        running: false,
        stack: stack,
        /**
         * Returns a specified registers value
         * @member mipsEmulator
         * @param  {String} reg
         * @return {Number}
         */
        getRegisterVal: function(reg) {
            if(!reg)throw new Error("Register must be non empty");
            if(reg.charAt(0) != '$') reg = '$'+reg;
            var regval = MIPS.unsignedNumberToSignedNumber(this.getRegister(reg).val, this.BITS_PER_REGISTER);
            if(debug) console.log("Getting signed register value: " + regval );
            return regval;
        },
        /**
         * Returns a specified registers unsigned value
         * @member mipsEmulator
         * @param  {String} reg
         * @return {Number}
         */
        getRegisterUnsignedVal: function(reg) {
            if(reg.charAt(0) != '$') reg = '$'+reg;
            var regval = MIPS.signedNumberToUnsignedNumber(this.getRegisterVal(reg), this.BITS_PER_REGISTER);
            if(debug) console.log("Getting unsigned register value: " + regval);
            return regval;
        },
        /**
         * Returns a specified register
         * @member mipsEmulator
         * @param  {String} reg
         * @return {Object} register object.
         */
        getRegister: function(reg) {
            if(reg.charAt(0) != '$') reg = '$'+reg;
            if(!this.isValidRegister(reg)) throw new RegisterError('Non existant register: {0}'.format(reg));
            return registers[reg];
        },
        /**
         * Checks if a register exists
         * @member mipsEmulator
         * @param  {String}  reg Name of a register ex: '$ra'
         * @return {Boolean}
         */
        isValidRegister: function(reg) {
            return typeof registers[reg] !== "undefined";
        },
        /**
         * checks if a register is writable
         * @member mipsEmulator
         * @param  {String}  reg Register name
         * @return {Boolean}
         */
        isValidWritableRegister: function(reg) {
            return this.isValidRegister(reg) && this.getRegister(reg).writable === true;
        },
        /**
         * Set a register value, and call onChange function for that register
         * @member mipsEmulator
         * @param {String} reg
         * @param {Number} value
         */
        setRegisterVal: function(reg, value, enableCallback) {
            if(debug) console.log("Setting register " + reg + " to " + value);
            if(reg.charAt(0) != '$') reg = '$'+reg; // TODO: I think we should enforce that the reg always has the $ symbol
            var minRegisterValue = MIPS.minSignedValue(this.BITS_PER_REGISTER);
            var maxRegisterValue = MIPS.maxUnsignedValue(this.BITS_PER_REGISTER);
            if (value < minRegisterValue || maxRegisterValue < value) {
                throw new RegisterError('Value out of range: {0}. Must be between {1} and {2}.'.format(value, minRegisterValue, maxRegisterValue));
            }

            enableCallback = enableCallback || true;
            assert(reg[0] === '$');


            var register = registers[reg];
            if(!register) return error("Line " + currentLine + " register: '" + reg + "' does not exist", currentLine);

            if (!register.writable) {
                throw new RegisterError('Register "{0}" is readonly.'.format(reg));
            }

            if(register.onChange && enableCallback) {
                register.onChange();
            }
            register.val = value;
            if(mipsArgs.onRegisterChange && enableCallback) {
                mipsArgs.onRegisterChange(reg, value);
            }
            if(debug) console.log("----> New value: "+ ME.getRegister(reg));
        },
        /**
         * Set an Onchange function for a register
         * @member mipsEmulator
         * @param  {String} reg
         * @param  {Function} func
         * @return {null}
         */
        onChange: function(reg, func){
            registers[reg].onChange = func;
        },
        /**
         * Set which line to run next.
         * @member mipsEmulator
         * @param {Number} lineNo
         * @return {Number} Returns the number the line was set too.
         */
        setLine: function(lineNo){
            var line = mipsCode.code[lineNo];
            if(debug) console.log("setting line: "+ lineNo + " - " + JSON.stringify(line));
            if(!line) return false;
            currentLine = lineNo;
            if(line.ignore) incrementLine();
            return currentLine;
        },
        /**
         * Checks if a string is a valid mips line
         * @member mipsEmulator
         * @param  {String}  line
         * @return {Boolean}
         */
        isValidLine: function(line){
            return !(new mipsLine(line).error);
        },
        /**
         * Resets mips labes, code, and stack
         * @member mipsEmulator
         * @return {null}
         */
        reset: function() {
            mipsCode.labels = {};
            mipsCode.code = [null];
            stack.reset();
            registers.$sp.val = stack.pointerToBottomOfStack();
        },
        /**
         * Set the debug option for the mips_emulator
         * @member mipsEmulator
         * @param {Boolean} dbg
         */
        setDebug: function(dbg){
            debug = dbg;
        },
        /**
         * Set code to be emulated
         * @member mipsEmulator
         * @param {String} mc
         */
        setCode: function(mc){
            ME.reset();
            if(debug) console.log("Analyzing...");

            $.each(mc.split('\n'), function(index, val){
                var line = new mipsLine(val, mipsCode.code.length);
                line.lineNo = mipsCode.code.length; // save the line number
                // if(debug) console.log(JSON.stringify(line));
                mipsCode.code.push(line);
            });
            if(mipsCode.code[currentLine] && mipsCode.code[currentLine].ignore){
                incrementLine();
                if(debug) console.log("First line is to be ignored, first line set to: " + currentLine);
            }
        },
        /**
         * Run an individual line
         * @member mipsEmulator
         * @param  {String} inputLine
         * @return {null}
         */
        runLine: function(inputLine) {
            var line = new mipsLine(inputLine);
            // This refers to the private method, private method should probably be renamed.
            runLine(line);
        },
        /**
         * Run an array of lines
         * @member mipsEmulator
         * @param  {Array} lines Array of mips code, one line per index
         * @return {null}
         */
        runLines: function(lines) {
            // lines is an array of strings
            lines = lines.join('\n');
            this.setCode(lines);
            this.setLine(1);
            this.run();
        },
        /**
         * runs the current set of code in the mips emulator non-stop;
         * @member mipsEmulator
         * @return {null}
         */
        run: function() {
            // run the current set of instructions which were set via setCode
            assert(mipsCode.code !== null, 'Must have already set the code to run.');
            this.running = true;
            while (this.running) {
                this.step();
            }
        },
        /**
         * execute the line PC is pointing to.
         * @member mipsEmulator
         * @return {Object}
         * returns object.lineRan which is the line that was just run
         * and object.nextLine which is the line that is about to be run.
         */
        step: function(){
            if(debug) console.log("Running line: " + currentLine + " - " + JSON.stringify(mipsCode.code[currentLine]));
            // check if we are finished with the emulation
            if(currentLine > mipsCode.code.length - 1) return finishEmulation();
            if(!mipsCode.code[currentLine]) throw new MipsError("Line " + currentLine + " does not exist");
            if(mipsCode.code[currentLine].error) throw new MipsError(mipsCode.code[currentLine].error);
            if(mipsCode.code[currentLine].ignore) incrementLine();
            // we need to check again, because the remainder of the lines could have been comments or blank.


            var ret = {
                lineRan: Number(currentLine)
            };
            runLine(mipsCode.code[currentLine]);
            ret.nextLine = currentLine;
            if(currentLine > mipsCode.code.length - 1) finishEmulation();
            return ret;
        },
        /**
         * Returns the current line number (the next to be run)
         * @member mipsEmulator
         * @return {Number}
         */
        getLineNumber: function(){
            return currentLine;
        },
        /**
         * Increments the line to be executed until it finds a valid line.
         * @member mipsEmulator
         * @return {null}
         */
        incerementPC: function() {
            incrementLine();
        },
        /**
         * Jump to a specified label
         * @member mipsEmulator
         * @param  {String} label The label to jump too
         * @return {Number} The line number you jumped too.
         */
        goToLabel: function(label){
            var line = mipsCode.labels[label];
            if(debug) console.log("Getting label: "+ label + " - " +JSON.stringify(line) );
            if(line){
                ME.setLine(line.lineNo);
                return currentLine; // TODO: probably don't need a return value here, instead, listen for an onChangeLineNumber handler
            } else {
                throw new JumpError('Unknown label: {0}'.format(label));
            }
        },
        onSetOverflowFlag: function() {}, // e.g. for 8 bit registers signed, 127 + 1 causes an overflow, since we can't store 128, so it wraps around to -128.
        onSetCarryFlag: function() {}, // e.g. for 8 bit registers unsigned, 255 + 1 causes a carry flag, since we can't store 256, so it wraps around to 0.
        setUnpreservedRegsToGarbage: function() {
            for (var i = 0; i < allRegs.length; i++) {
                var register = this.getRegister(allRegs[i]);
                if (register.preserved) {
                    continue;
                }

                this.setRegisterVal(register.regName, getGarbageRegisterData());
            };
        },
        output: function(string) {
            mipsArgs.onOutput(string);
        },
        getInput: function(message) {
            return mipsArgs.onInput(message);
        },
        confirm: function(message) {
            return mipsArgs.onConfirm(message);
        },
        alert: function(message) {
            mipsArgs.onAlert(message);
        }
    };


    ////////////////////////////////////////////////
    // Private Methods
    ////////////////////////////////////////////////
    function finishEmulation(){
        ME.running = false;
        mipsArgs.onFinish();
        if(debug) console.log("Emulation finished. Returning to line: " + ME.setLine(1));
        ME.setLine(1);
        return ME.FINISHED_EMULATION;
    };

    /**
     * Increments the current line to the next line which is not ignored.
     * @return {null}
     */
    function incrementLine(){
        currentLine++;
        while(mipsCode.code[currentLine]
                && currentLine <= mipsCode.code.length
                && mipsCode.code[currentLine].ignore != false
        ){
            if(debug) console.log("ignoring line: " + currentLine);
            currentLine++;
        }
    }
    /**
     * Run an individual line
     * @member mipsEmulator
     * @private
     * @return {null}
     */
    function runLine(line) {
        if(debug) console.log("running line: " + line.lineNo);
        if (line.error) {
            throw new MipsError('Error on line: {0}'.format(line.error));
            // TODO: get rid of the other error handler
        }
        if (!line || line.ignore || line.error) {
            if(!line) error("Line is null");
            else error(line.error, currentLine); // returns error if there is one or null if not.
            incrementLine();
            return false;
        }
        // we can assume that we parsed successfully at this point.
        instructionExecutor.runInstruction(line.instruction, line.args);
    };

    function getGarbageRegisterData() {
        return Math.floor((Math.random()*1000));
    }
    /**
     * Create a default register
     * @member mipsEmulator
     * @private
     * @param  {Object} reg
     * @return {register}
     */
    function createRegister(regName, regNumber){
        /**
         * @class register
         * contains register information.
         */
        return {
            /**
             * registers value
             * @property
             * @type {Number}
             */
            val: getGarbageRegisterData(),
            /**
             * Function that is called when this register is changed.
             * @type {Function}
             */
            onChange: null,
            /**
             * Wether or not this register is writable (false if this register is read only)
             * @type {Boolean}
             */
            writable: readonlyRegs.indexOf(regName) === -1,
            /**
             * This registers name
             * @type {String}
             */
            regName: regName,
            regNumber: regNumber,
            preserved: preservedRegs.indexOf(regName) > -1
        };
    };
    // these will be called after the parse method has been called
    // the goal is to make these methods look as close to the MIPS cheat sheet as possible.


    var instructionExecutor = mipsInstructionExecutor(ME);

    /**
     * If the user defined an anError message, use that, if not, alert the message
     * @param  {String} message
     * @param  {Number} lineNo
     * @return {null}
     */
    function error(message, lineNo){
        if(debug) console.error("Error being sent");
        if(debug) console.error("--->" + message);
        lineNo = lineNo || currentLine;
        mipsArgs.onError(message, lineNo);
    }

    /**
     * Turns a string into a mips line object which contains a mips line of code and metadata needed to run it
     * @member mipsEmulator
     * @private
     * @param  {String} line
     * @return {Object}
     */
    function mipsLine(line, lineNo){
        lineNo = lineNo || null

        // Object that will save information about a line of code.
        /**
         * @class line
         * Contains information about a single line of mips code
         * @member mipsEmulator
         * @private
         */
        var LINE = {
            /**
             * Arguments for this line of code ex: [$t0, $s0, $zero]
             * @property
             * @type {Array}
             */
            args: [],
            /**
             * The lines instruction ex: ADD
             * @type {String}
             */
            instruction: null,
            /**
             * flag to indicate weather this line should be ignored (not run).
             * @type {Boolean}
             */
            ignore: true,
            /**
             * The comment (if any) that this line of code contained
             * @type {String}
             */
            comment: '',
            /**
             * The label for this line of code
             * @type {String}
             */
            label: '',
            /**
             * Error when running this line of code (if any)
             * @type {String}
             */
            error: null,
            lineNo: lineNo,
            text: line
        };


        //console.log("--> "+val);
        var regex = /^\s*(?:(\w+)\s*:\s*)?(?:(\w+)(?:\s+([^#]+))?)?(?:#\s*(.*))?$/;
        var ar = line.match(regex);
        // when matched the array contains the following
        // ----> [0] The entire line
        // ----> [1] The label without the ':'
        // ----> [2] The instruction (e.g. 'ADD', 'LW', etc.)
        // ----> [3] The arguments (e.g. '$rd, $rs, $rt'), this should be trimmed
        // ----> [4] The comment without the '#', this should be trimmed
        // if ar is null, that means the regex didn't match

        if(ar){
            // if we have a label, save it to the hashtable and save it to line
            if(ar[1] && ar[1].length > 0){
                LINE.label = ar[1];
                mipsCode.labels[LINE.label] = LINE;
                // TODO: mipsCode.labels[ar[1]] = line;
            }

            // If we got variables back
            if(ar[3]){
                // Split the args by `,`
                LINE.args = ar[3].split(',');

                // Trim the varaibles
                for(var i = 0; i < LINE.args.length; i++){
                   LINE.args[i] = $.trim(LINE.args[i]);
                }
            }

            // The instruction for this code;
            LINE.instruction = $.trim(ar[2]).toUpperCase();

            // If the line has an instruction, we should not ignore it. otherwise it may be a comment or blank
            if(LINE.instruction && LINE.instruction.length > 0) LINE.ignore = false;

            // The comment, obviously
            LINE.comment = $.trim(ar[4]);

            // parse the instruction now, so it can be executed whenever we want
            if (LINE.instruction) {
                // an instruction was found, so try to parse it
                var error = {};
                if (!instructionExecutor.parseInstruction(LINE.instruction, LINE.args, null, error)) {
                    LINE.error = error.message;
                }
            };

        // In the else case, the regex didn't match, possible error?
        } else {
            // TODO: check for special cases
            if(debug) console.log("----> No matches");
            LINE.error = "Error parsing line: "+ (lineNo+1);
        }
        if(debug) console.log("Finished parsing line: " + JSON.stringify(LINE));

        return LINE;
    }

    // Set the starting code if there was any.
    if(mipsArgs.startingCode) ME.setCode(mipsArgs.startingCode);
    return ME;
}