import {HacknetNode}                    from "./HacknetNode.js";
import {NetscriptFunctions}             from "./NetscriptFunctions.js";
import {NetscriptPort}                  from "./NetscriptPort.js";

/* Environment
 * 	NetScript program environment
 */
function Environment(workerScript,parent) {
    if (parent){
        //Create a copy of parent's variables
        //this.vars = parent.vars;
        this.vars = Object.assign({}, parent.vars);
    } else {
        this.vars = NetscriptFunctions(workerScript);
    }
    this.parent = parent;
	this.stopFlag = false;
}
Environment.prototype = {
	//Create a "subscope", which is a new new "sub-environment"
	//The subscope is linked to this through its parent variable
    extend: function() {
        return new Environment(null, this);
    },

	//Finds the scope where the variable with the given name is defined
    lookup: function(name) {
        var scope = this;
        while (scope) {
            if (Object.prototype.hasOwnProperty.call(scope.vars, name)) {
                return scope;
            }
            scope = scope.parent;
        }
        return null;
    },

	//Get the current value of a variable
    get: function(name) {
        if (name in this.vars) {
            return this.vars[name];
        }
        throw new Error("Undefined variable " + name);
    },

	//Sets the value of a variable in any scope
    set: function(name, value) {
        var scope = this.lookup(name);

        //If scope has a value, then this variable is already set in a higher scope, so
        //set is there. Otherwise, create a new variable in the local scope
        if (scope !== null) {
            return scope.vars[name] = value;
        } else {
            return this.vars[name] = value;
        }
    },

    setArrayElement: function(name, idx, value) {
        if (!(idx instanceof Array)) {
            throw new Error("idx parameter is not an Array");
        }
        var scope = this.lookup(name);
        if (!scope && this.parent) {
            throw new Error("Undefined variable " + name);
        }
        var arr = (scope || this).vars[name];
        if (!(arr.constructor === Array || arr instanceof Array)) {
            throw new Error("Variable is not an array: " + name);
        }
        var res = arr;
        for (var iterator = 0; iterator < idx.length-1; ++iterator) {
            var i = idx[iterator];
            if (!(res instanceof Array) || i >= res.length) {
                throw new Error("Out-of-bounds array access");
            }
            res = res[i];
        }

        //Cant assign to ports or HacknetNodes
        if (res[idx[idx.length-1]] instanceof HacknetNode) {
            throw new Error("Cannot assign a Hacknet Node handle to a new value");
        }
        if (res[idx[idx.length-1]] instanceof NetscriptPort) {
            throw new Error("Cannot assign a Netscript Port handle to a new value");
        }
        return res[idx[idx.length-1]] = value;
    },

	//Creates (or overwrites) a variable in the current scope
    def: function(name, value) {
        return this.vars[name] = value;
    }
};

export {Environment};