var vm = require('vm');
var events = require('events');


var SandboxApi = function () {
    // API for use by applications
    var self = this;
    self.id = 0;
    self.waiting_requests = [];

    self.next_id = function () {
        self.id += 1;
        return self.id;
    }

    self.populate_command = function (command, msg) {
        msg.cmd = command;
        msg.reply = false;
        msg.cmd_id = self.next_id();
        return msg;
    }

    self.request = function (command, msg, callback) {
        // * callback is optional and is called once a reply to
        //   the request is received.
        // * requests created are only sent once the handler
        //   completes.
        // * callbacks can generate more requests.
        self.populate_command(command, msg);
        if (callback) {
            msg._callback = callback;
        }
        self.waiting_requests.push(msg);
    }

    self.pop_requests = function () {
        var requests = self.waiting_requests.splice(0,
            self.waiting_requests.length);
        return requests;
    }

    self.log_info = function (msg, callback) {
        self.request('log.info', {'msg': msg}, callback);
    }

    self.done = function () {
        self.request('log.info', {'_last': true, 'msg': "Done."});
    }

    // handlers:
    // * on_unknown_command is the default message handler
    // * other handlers are looked up based on the command name
    self.on_unknown_command = function(command) {}
}

var SandboxRunner = function (api) {
    // Runner for a sandboxed app
    var self = this;

    self.api = api;
    self.emitter = new events.EventEmitter();
    self.chunk = "";
    self.pending_requests = {};
    self.loaded = false;

    self.emitter.on('command', function (command) {
        var handler_name = "on_" + command.cmd.replace('.', '_').replace('-', '_');
        var handler = api[handler_name];
        if (!handler) {
            handler = api.on_unknown_command;
        }
        if (handler) {
            handler.call(self.api, command);
            self.process_requests(api.pop_requests());
        }
    });

    self.emitter.on('reply', function (reply) {
        var handler = self.pending_requests[reply.msg_id];
        if (handler && handler.callback) {
            handler.callback.call(self.api, reply);
            self.process_requests(api.pop_requests());
        }
    });

    self.emitter.on('exit', function () {
        process.exit(0);
    });

    self.load_code = function (command) {
        self.log("Loading sandboxed code ...");
        var ctxt;
        var loaded_module = vm.createScript(command['javascript']);
        if (command['app_context']) {
            eval("ctxt = " + command['app_context'] + ";");
        } else {
            ctxt = {};
        }
        ctxt.api = self.api;
        loaded_module.runInNewContext(ctxt);
        self.loaded = true;
        // process any requests created when the app module was loaded.
        self.process_requests(api.pop_requests());
    }

    self.process_requests = function (requests) {
        requests.forEach(function (msg) {
            var last = msg._last;
            delete msg._last;
            var callback = msg._callback;
            delete msg._callback;
            self.send_command(msg);
            if (last) {
                self.emitter.emit('exit');
            }
            if (callback) {
                self.pending_requests[msg.msg_id] = {'callback': callback};
            }
        });
    }

    self.send_command = function (cmd) {
        process.stdout.write(JSON.stringify(cmd));
        process.stdout.write("\n");
    }

    self.log = function(msg) {
        var cmd = self.api.populate_command("log.info", {"msg": msg});
        self.send_command(cmd);
    }

    self.data_from_stdin = function (data) {
        parts = data.split("\n");
        parts[0] = self.chunk + parts[0];
        for (i = 0; i < parts.length - 1; i++) {
            if (!parts[i]) {
                continue;
            }
            msg = JSON.parse(parts[i]);
            if (!self.loaded) {
                if (msg.cmd == 'initialize') {
                    self.load_code(msg);
                }
            }
            else if (!msg.reply) {
                self.emitter.emit('command', msg);
            }
            else {
                self.emitter.emit('reply', msg);
            }
        }
        self.chunk = parts[parts.length - 1];
    }

    self.run = function () {
        process.stdin.resume();
        process.stdin.setEncoding('ascii');
        process.stdin.on('data', function(data) {
            self.data_from_stdin(data); });
    }
};


var api = new SandboxApi();
var runner = new SandboxRunner(api);

runner.run();
runner.log("Starting sandbox ...");

@@ -265,16 +265,27 @@
  self.id
+.toString()
 ;%0A    %7D%0A
@@ -2228,19 +2228,19 @@
 s%5Breply.
-msg
+cmd
 _id%5D;%0A  
@@ -3457,19 +3457,19 @@
 sts%5Bmsg.
-msg
+cmd
 _id%5D = %7B
@@ -3849,24 +3849,28 @@
 ) %7B%0A        
+var 
 parts = data
@@ -4055,16 +4055,20 @@
         
+var 
 msg = JS

Fix sandboxer.js bugs and oddities: 1) Fix references to cmd_id (there were two places it was refered to as msg_id), 2) Make some global variables local, 3) Call .toString() on autogenerated cmd_id's so they're not numbers. (thanks @smn for reviewing)