module.exports = function(RED) {
    "use strict";
    var path = require("path");
    var ws = require("ws");
    var url = require("url");
    var colours = require('./colours');

    // Xaccel.x,y,z,gyro.x,y,z,orientation.roll,pitch,yaw,compass
    var HF_RE = /^X(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+),(.+)$/;
    //  Ytemperature,humidity,pressure
    var LF_RE = /^Y(.+),(.+),(.+)$/;
    //  K[U|L|R|D|E][0|1|2] - joystick event:  direction,state
    var KEY_RE = /^K(.)(.)$/;
    var KEY_MAP = {
        "U":"UP",
        "D":"DOWN",
        "L":"LEFT",
        "R":"RIGHT",
        "E":"ENTER"
    };

    var currentFlipH = false;
    var currentFlipV = false;
    var currentRotation = "R0";
    var currentDisplay = [];
    var HAT = (function() {
        var hatWS = null;
        var wsServerListener;
        var wsConnections = {};
        var currentEnvironment = {temperature: 20, humidity: 80, pressure: 1000};
        var hat = null;
        var onclose = null;
        var users = [];
        var motionUsers = 0;
        var envUsers = 0;
        var reconnectTimer = null;
        var envTimer = null;

        var connect = function() {
            if (!hatWS) {
                currentFlipH = false;
                currentFlipV = false;
                currentRotation = "R0";
                currentDisplay = [];
                for (var y=0; y<8; y++) {
                    currentDisplay.push([]);
                    for (var x=0; x<8; x++) {
                        currentDisplay[y].push('0,0,0');
                    }
                }
                var wsPath = RED.settings.httpNodeRoot || "/";
                wsPath = wsPath + (wsPath.slice(-1) == "/" ? "":"/") + "sensehat-simulator/ws"

                var storeListener = function(event,listener) {
                    if (event == "error" || event == "upgrade" || event == "listening") {
                        wsServerListeners[event] = listener;
                    }
                }
                // Create a WebSocket Server
                hatWS = new ws.Server({noServer: true});
                hatWS.on('error', function(err) {

                })
                hatWS.on('connection', function(socket) {
                    socket.on('error', function(err) {
                        delete wsConnections[id];
                    });
                    var id = (1+Math.random()*4294967295).toString(16);
                    wsConnections[id] = socket;
                    socket.send("Y"+currentEnvironment.temperature+","+currentEnvironment.humidity+","+currentEnvironment.pressure);
                    socket.send(currentRotation);
                    if (currentFlipV) {
                        socket.send("FV");
                    }
                    if (currentFlipH) {
                        socket.send("FH");
                    }
                    var cmd = "";
                    for (var y=0; y<8; y++) {
                        for (var x=0; x<8; x++) {
                            cmd += ","+x+","+y+","+currentDisplay[y][x];
                        }
                    }
                    socket.send("P"+cmd.substring(1));


                    socket.on('close',function() {
                        delete wsConnections[id];
                    });
                    socket.on('message',function(data,flags) {
                        var m;
                        var msg;
                        if ((m = LF_RE.exec(data)) !== null) {
                            currentEnvironment = {temperature: Number(m[1]), humidity: Number(m[2]), pressure: Number(m[3])};
                            msg = "Y"+currentEnvironment.temperature+","+currentEnvironment.humidity+","+currentEnvironment.pressure;
                            for (var id in wsConnections) {
                                if (wsConnections.hasOwnProperty(id)) {
                                    var client = wsConnections[id];
                                    if (client !== socket) {
                                        client.send(msg);
                                    }
                                }
                            }
                        } else if ((m = KEY_RE.exec(data)) !== null) {
                            msg = {
                                topic: "joystick",
                                payload: {key: KEY_MAP[m[1]], state: Number(m[2])}
                            }
                            for (var j=0; j<users.length; j++) {
                                var node = users[j];
                                if (node.stick) {
                                    node.send(RED.util.cloneMessage(msg));
                                }
                            }
                        }

                    });

                });

                wsServerListener = function upgrade(request, socket, head) {
                    const pathname = url.parse(request.url).pathname;
                    if (pathname === wsPath) {
                        hatWS.handleUpgrade(request, socket, head, function done(ws) {
                            hatWS.emit('connection', ws, request);
                        });
                    }
                    // Don't destroy the socket as other listeners may want to handle the
                    // event.
                };
                RED.server.on('upgrade', wsServerListener)

            }
        }

        var disconnect = function(done) {
            if (hatWS !== null) {
                RED.server.removeListener('upgrade', wsServerListener)
                wsConnections = {};
                hatWS.close();
                hatWS = null;
            }
            done();
        }


        return {
            open: function(node) {
                if (!hatWS) {
                    connect();
                }
                if (!reconnectTimer) {
                    node.status({fill:"green",shape:"dot",text:"node-red:common.status.connected"});
                }

                if (node.motion) {
                    motionUsers++;
                }
                if (node.env) {
                    envUsers++;
                }
                users.push(node);
                if (envUsers === 1) {
                    envTimer = setInterval(function() {
                        var msg = {
                            topic: "environment",
                            payload: {temperature: currentEnvironment.temperature, humidity: currentEnvironment.humidity, pressure: currentEnvironment.pressure}
                        };
                        for (var j=0; j<users.length; j++) {
                            var node = users[j];
                            if (node.env) {
                                node.send(RED.util.cloneMessage(msg));
                            }
                        }
                    },1000);
                }


            },
            close: function(node,done) {
                if (node.motion) {
                    motionUsers--;
                }
                if (node.env) {
                    envUsers--;
                }
                if (envUsers === 0) {
                    clearTimeout(envTimer);
                }
                users.splice(users.indexOf(node),1);
                if (users.length === 0) {
                    disconnect(done);
                } else {
                    done();
                }
            },
            send: function(msg) {
                for (var id in wsConnections) {
                    if (wsConnections.hasOwnProperty(id)) {
                        var client = wsConnections[id];
                        client.send(msg);
                    }
                }
            }
        }
    })();


    function SenseHatInNode(n) {
        RED.nodes.createNode(this,n);
        this.motion = n.motion;
        this.env = n.env;
        this.stick = n.stick;
        var node = this;
        node.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"});
        HAT.open(this);

        node.on("close", function(done) {
            HAT.close(this,done);
        });
    }
    RED.nodes.registerType("rpi-sensehatsim in",SenseHatInNode);

    function SenseHatOutNode(n) {
        RED.nodes.createNode(this,n);
        var node = this;
        node.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"});

        HAT.open(this);

        node.on("close", function(done) {
            HAT.close(this,done);
        });
        var handleTextMessage = function(line,msg) {
            var textCol = colours.getRGB(msg.color||msg.colour)||"255,255,255";
            var backCol = colours.getRGB(msg.background)||"0,0,0";
            var speed = null;
            if (!isNaN(msg.speed)) {
                speed = msg.speed;
            }
            var command = "T";
            if (textCol) {
                command += textCol;
                if (backCol) {
                    command += ","+backCol;
                }
            }

            if (speed) {
                var s = parseInt(speed);
                if (s >= 1 && s <= 5) {
                    s = 0.1 + (3-s)*0.03;
                }
                command = command + ((command.length === 1)?"":",") + s;
            }
            command += ":" + line;
            return command;
        }

        node.on("input",function(msg) {
            var command;
            var parts;
            var col;
            if (typeof msg.payload === 'number') {
                HAT.send(handleTextMessage(""+msg.payload,msg));
            } else if (typeof msg.payload === 'string') {
                var lines = msg.payload.split("\n");
                lines.forEach(function(line) {
                    command = null;
                    if ( /^(([0-7]|\*|[0-7]-[0-7]),([0-7]|\*|[0-7]-[0-7]),(\d{1,3},\d{1,3},\d{1,3}|#[a-f0-9]{3,6}|[a-z]+))(,([0-7]|\*|[0-7]-[0-7]),([0-7]|\*|[0-7]-[0-7]),(\d{1,3},\d{1,3},\d{1,3}|#[a-f0-9]{3,6}|[a-z]+))*$/i.test(line)) {
                        parts = line.split(",");
                        var expanded = [];
                        var i=0;
                        var j=0;
                        while (i<parts.length) {
                            var x = parts[i++];
                            var y = parts[i++];
                            col = parts[i++];
                            if (/#[a-f0-9]{3,6}|[a-z]/i.test(col)) {
                                col = colours.getRGB(col);
                                if (col === null) {
                                    // invalid colour, go no further
                                    return;
                                }
                            } else {
                                col += ","+parts[i++]+","+parts[i++];
                            }
                            if (x === '*') {
                                x = "0-7";
                            }
                            if (y === '*') {
                                y = "0-7";
                            }
                            var x0,x1;
                            var y0,y1;
                            if (x.indexOf("-") === -1) {
                                x0 = x1 = parseInt(x);
                            } else {
                                var px = x.split("-");
                                x0 = parseInt(px[0]);
                                x1 = parseInt(px[1]);
                                if (x1<x0) {
                                    j = x1;
                                    x1 = x0;
                                    x0 = j;
                                }
                            }
                            if (y.indexOf("-") === -1) {
                                y0 = y1 = parseInt(y);
                            } else {
                                var py = y.split("-");
                                y0 = parseInt(py[0]);
                                y1 = parseInt(py[1]);
                                if (y1<y0) {
                                    j = y1;
                                    y1 = y0;
                                    y0 = j;
                                }
                            }
                            x = x0;
                            while (x<=x1) {
                                y = y0;
                                while (y<=y1) {
                                    expanded.push([x,y,col]);
                                    y++;
                                }
                                x++;
                            }
                        }
                        if (expanded.length > 0) {
                            var pixels = {};
                            var rules = [];
                            for (i=expanded.length-1; i>=0; i--) {
                                var rule = expanded[i];
                                if (!pixels[rule[0]+","+rule[1]]) {
                                    rules.unshift(rule.join(","));
                                    currentDisplay[Number(rule[1])][Number(rule[0])] = rule[2];
                                    pixels[rule[0]+","+rule[1]] = true;
                                }
                            }
                            if (rules.length > 0) {
                                command = "P"+rules.join(",");
                            }
                        }
                    }


                    if (!command) {
                        if (/^R(0|90|180|270)$/i.test(line)) {
                            command = line.toUpperCase();
                            currentRotation = command;
                        } else if (/^F(H|V)$/i.test(line)) {
                            command = line.toUpperCase();
                            if (command === 'FH') {
                                currentFlipH = !currentFlipH;
                            } else {
                                currentFlipV = !currentFlipV;
                            }
                        } else if (/^D(0|1)$/i.test(line)) {
                            command = line.toUpperCase();
                        } else {
                            command = handleTextMessage(line,msg);
                        }
                    }
                    if (command) {
                        //console.log(command);
                        HAT.send(command);
                    }
                });
            }
        });
    }
    RED.nodes.registerType("rpi-sensehatsim out",SenseHatOutNode);

    RED.httpAdmin.get("/sensehat-simulator", RED.auth.needsPermission('sensehat-simulator.read'), function(req,res) {
        res.sendFile(path.join(__dirname,"public","index.html"));
    });
}