var express = require('express');
var http = require('http')
var io = require('socket.io');
var net = require('net');

var app_debugging = express();
var http_debugging = http.Server(app_debugging)
var io_debugging = io(http_debugging);
var app_vis = express();
var http_vis = http.Server(app_vis)
var io_vis = io(http_vis);

var queue = []

// serve GET requests of client for debugging interface
app_debugging.get('*', function(req, res) {
    if ( req.url == '/' ) {
        res.sendFile(__dirname + '/debugging_environment.html');
    } else if ( req.url.match(/\.js$/)
                || req.url.match(/\.html$/)
                || req.url.match(/\.css$/)
                || req.url.match(/\.png$/)
               ) {
        res.sendFile(__dirname + req.url);
    }
});

// user connects on the debugging interface: queue all incoming messages and tag them as debug messages
io_debugging.on('connection', function(socket){
    console.log('A user connected!');
    
    socket.on('message', function(msg) {
        queue.push('dbg_' + msg);
    })
});

http_debugging.listen(9595, function() {
    console.log('Listening for user on port 9595');
});

// serve GET requests of client for visualization interface
app_vis.get('*', function(req, res) {
    if ( req.url == '/' ) {
        res.sendFile('/visualization_server.html', {root: '../'});
    } else if ( req.url.match(/\.js$/)
                || req.url.match(/\.html$/)
                || req.url.match(/\.css$/)
                || req.url.match(/\.png$/)
               ) {
        res.sendFile(req.url, {root: '../'});
    }
});

// user connects on the visualization interface: queue all incoming messages and tag them as visualization messages
io_vis.on('connection', function(socket){
    console.log('A user connected!');
    
    socket.on('message', function(msg) {
        queue.push('vis_' + msg);
    })
});

http_vis.listen(9696, function() {
    console.log('Listening for user on port 9696');
});

String.prototype.endsWith = function(suffix) {
    return this.indexOf(suffix, this.length - suffix.length) !== -1;
};

/*
This is the server that ties debugging UI - visualization UI - debuggable simulator together.
It waits for the simulation client to connect on port 9999 and coordinates all message traffic between the three components.
*/
net.createServer(function(sock) {
    console.log('CONNECTED: ' + sock.remoteAddress + ':' + sock.remotePort);
    
    buffer = '';
    
    // Protocol: each message ends with a newline. Buffers messages until the buffer ends with a new line, in case a message is only partially sent.
    sock.on('data', function(data) {       
        buffer += String(data);
        if (buffer.endsWith('\n')) {
            String(buffer).split('\n').forEach(function(entry) {
                if (entry != '') {
                    try {
                        msg = eval(entry); // TODO: probably not a very good idea, msg = JSON.parse(entry); is better but doesn't work with Infinity
                    } catch (err) {
                        throw err;
                    }
                    if (msg[0] == "VIS") {
                        // visualization messages are sent to the visualization interface
                        io_vis.emit('msg', JSON.stringify(msg.slice(1)));
                    } else if (msg[0] == "DBG") {
                        // debugging messages (state updates, for example) are sent to the debugging interface
                        io_debugging.emit('msg', JSON.stringify(msg.slice(1)));
                    }
                }
            })
            buffer = '';
        }
        
    });
    
    sock.on('close', function(data) {
        console.log('CLOSED: ' + sock.remoteAddress + ' ' + sock.remotePort);
    });
    
    sock.on('error', function(error) {
        console.log('ERROR: ' + error);
    });
    
    /*
    Every 5 milliseconds, check whether any message was sent by the user from the debugging interface and/or the visualization interface. If so, pass it on.
    In case of a message from the debugging interface: send it to the simulator, although if it's a reset, also forward to the visualization.
    In case of a message from the visualization interface: only send it to the debugger when it's an interrupt. Send it to the debugging interface if it's a message wanting to select an entity.
    */
    setInterval(function() {
        while (queue.length) {
            var msg = queue.pop();
            var msg_head = msg.substring(0, 4)
            if (msg_head == "dbg_") {
                var parsed_msg = eval('(' + msg.substring(4) + ')');
                // forward selected messages to visualization
                if (parsed_msg["parameters"].length && parsed_msg["parameters"][0] == "reset") {
                    io_vis.emit('msg', msg);
                }
                // forward to simulator
                sock.write(msg + '\n');
            } else if (msg_head == "vis_") {
                if (msg.substring(4).lastIndexOf('INTERRUPT', 0) == 0) {
                    // forward to simulator
                    sock.write(msg + '\n');
                } else {
                    var parsed_msg = eval('(' + msg.substring(4) + ')');
                    // forward selected messages to debugging interface
                    if (parsed_msg["name"] == "select_instance") {
                        io_debugging.emit('msg', msg);
                    }
                }
            }
        }
    }, 5)
    
}).listen(9999, '127.0.0.1');

console.log('Listening for simulator on localhost:9999');