var Socket = function() {
    this.host = void null;
    this.port = void null;
    this.socket = void null;
    this.onOpenCallback = void null;
};

Socket.prototype.setHost = function(host) {
    this.host = host;
};

Socket.prototype.setPort = function(port) {
    this.port = port;
};

Socket.prototype.setOnOpen = function(callback) {
    this.onOpenCallback = callback;
};

Socket.prototype.isOpen = function() {
    return this.socket && this.socket.readyState === WebSocket.OPEN;
};

Socket.prototype.establishConnection = function() {
    if (this.socket) {
        if (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING) {
            return true;
        }

        if (this.socket.readyState === WebSocket.CLOSING) {
            return false;
        }
    }

    if (!this.host || !this.port) {
        console.warn('Socket host/port is not configured');
        return false;
    }

    try {
        this.socket = new WebSocket('ws://' + this.host + ':' + this.port);
    } catch (error) {
        console.error('Socket connection failed to initialize: ' + error);
        this.socket = void null;
        return false;
    }

    var self = this;

    this.socket.onopen = function() {
        if (typeof self.onOpenCallback === 'function') {
            self.onOpenCallback();
        }
    };

    this.socket.onclose = function(event) {
        console.log('Socket closed: code=' + event.code + ' reason=' + event.reason);
    };
    this.socket.onerror = function() {
        console.error('Socket error');
    };
    this.socket.onmessage = function() {
        //console.log('Socket message received');
    };

    return true;
};

Socket.prototype.sendData = function(data) {
    if (!this.socket) {
        console.warn('DMX packet dropped: socket is not connected');
        return false;
    }

    if (this.socket.readyState !== WebSocket.OPEN) {
        console.warn('DMX packet dropped: socket state is ' + this.socket.readyState);
        return false;
    }

    try {
        this.socket.send(data);
        return true;
    } catch (error) {
        console.error('Socket send failed: ' + error);
        return false;
    }
};

Socket.prototype.closeConnection = function() {
    if (!this.socket) {
        return;
    }

    if (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING) {
        this.socket.close();
    }

    this.socket = void null;
};

Socket.prototype.delete = function() {
    this.closeConnection();
};

var DmxLightManager = function(lightCount) {
    this.sock = new Socket();
    this.lightCount = 0;
    this.headerSize = 10;
    this.mockColor = void null;
    this.initialWarmupSent = false;

    this.dataPacket = void null;
}

DmxLightManager.prototype.deinit = function() {
    this.sock.closeConnection();
    this.sock.delete();
}

DmxLightManager.prototype.isInitialized = function() {
    return this.lightCount > 0;
}

DmxLightManager.prototype.init = function(lightCount) {
    this.initialWarmupSent = false;

    var self = this;
    this.sock.setOnOpen(function() {
        self.sendInitialWarmup();
    });

    if (!this.sock.establishConnection()) {
        console.warn("could not connect to DMX lights!");
    }

    this.lightCount = lightCount;

    // Protocol: https://github.com/Instanssi/effectserver
    this.dataPacket = new Uint8Array(this.headerSize + 6 * this.lightCount);
    this.dataPacket[ 0] = 1; // spec version
    this.dataPacket[ 1] = 0; // nick tag start
    this.dataPacket[ 2] = 'J'.charCodeAt(0);
    this.dataPacket[ 3] = 'M'.charCodeAt(0);
    this.dataPacket[ 4] = 'L'.charCodeAt(0);
    this.dataPacket[ 5] = '2'.charCodeAt(0);
    this.dataPacket[ 6] = '0'.charCodeAt(0);
    this.dataPacket[ 7] = '2'.charCodeAt(0);
    this.dataPacket[ 8] = '6'.charCodeAt(0);
    this.dataPacket[ 9] = 0;  // nick tag end

    for (var i = 0; i < this.lightCount; i++) {
        var light = this.headerSize + 6 * i;
        this.dataPacket[light + 0] = 1; // type = light
        this.dataPacket[light + 1] = i; // light id
        this.dataPacket[light + 2] = 0; // light type (0 = RGB)
        this.dataPacket[light + 3] = 0x00; // Red
        this.dataPacket[light + 4] = 0x00; // Green
        this.dataPacket[light + 5] = 0x00; // Blue
    }

    if (this.sock.isOpen()) {
        this.sendInitialWarmup();
    }
}

DmxLightManager.prototype.sendInitialWarmup = function() {
    if (this.initialWarmupSent) {
        return;
    }

    console.log("DMX connection established, sending initial warmup packets");

    this.initialWarmupSent = true;

    // just ensure that there will be black in the begin
    for(var i = 0; i < 3; i++) {
        this.setColor(0x00, 0x00, 0x00, 0, this.lightCount);
        this.sendData();
    }
}

DmxLightManager.prototype.setColor = function(r,g,b,lightIdStart,lightIdEnd,clearColor) {
    if (lightIdStart === void null) {
        lightIdStart = 0;
    }
    if (lightIdEnd === void null) {
        lightIdEnd = this.lightCount;
    } else if (lightIdEnd > this.lightCount) {
        lightIdEnd = this.lightCount;
    }

    for (var i = 0; i < this.lightCount; i++) {
        var light = this.headerSize + 6 * i;
        if (i >= lightIdStart && i < lightIdEnd) {
            this.dataPacket[light + 3] = r; // Red
            this.dataPacket[light + 4] = g; // Green
            this.dataPacket[light + 5] = b; // Blue
        }
        
        if (clearColor) {
            this.dataPacket[light + 3] = clearColor[0]; // Red
            this.dataPacket[light + 4] = clearColor[1]; // Green
            this.dataPacket[light + 5] = clearColor[2]; // Blue
        }
    }
}

DmxLightManager.prototype.sendMockColorData = function() {
    this.mockColor = [];
    for (var i = 0; i < this.lightCount; i++) {
        var light = this.headerSize + 6 * i;
        var r = this.dataPacket[light + 3] / 255.0;
        var g = this.dataPacket[light + 4] / 255.0;
        var b = this.dataPacket[light + 5] / 255.0;
        this.mockColor.push([r, g, b]);
    }
}

DmxLightManager.prototype.sendData = function() {
    //console.warn("PRKL! " + JSON.stringify(this.dataPacket,null,2));
    // NOTE: Sending all light data every time (mitigating possible UDP issues, like missing packets)
    
    if ((new DemoEngine.LoadingBar()).isLoading() === true) {
        //return;
    }

    // Drop packet if connection is not open; caller keeps rendering flow alive
    if (this.sock.isOpen()) {
        if (!this.sock.sendData(this.dataPacket)) {
            console.warn("DMX data packet dropped");
        }
    }

    this.sendMockColorData();
}

window.DmxLightManager = DmxLightManager;

function createDmxLightManager() {
    // do not reinit if host and port matches
    if (window.dmxLightManager && window.dmxLightManager.sock.isOpen() && window.dmxLightManager.sock.host === document.getElementById('dmxHostInput').value && window.dmxLightManager.sock.port === parseInt(document.getElementById('dmxPortInput').value)) {
        console.log("DMX light initiated, not updating");
        return;
    }

    if (window.dmxLightManager && window.dmxLightManager.isInitialized()) {
        window.dmxLightManager.deinit();
    }

    // in case menu did not exist
    let dmxLightManager = new DmxLightManager();
    dmxLightManager.sock.setHost(document.getElementById('dmxHostInput').value);
    dmxLightManager.sock.setPort(parseInt(document.getElementById('dmxPortInput').value));
    dmxLightManager.init(parseInt(document.getElementById('dmxLightCountInput').value));
    window.dmxLightManager = dmxLightManager;
}

const menuControlDiv = getDemoMenuControlDomElement();
if (menuControlDiv) {
    // actual DMX host: valot.instanssi (used to be valot.party at some point)
    // DMX host text field, default 127.0.0.1
    // add label for DMX host input
    // css 50% width for label, 50% for input
    const dmxHostContainer = document.createElement('div');
    dmxHostContainer.style.display = 'flex';
    dmxHostContainer.style.marginBottom = '5px';
    menuControlDiv.appendChild(dmxHostContainer);

    const dmxHostLabel = document.createElement('label');
    dmxHostLabel.htmlFor = 'dmxHostInput';
    dmxHostLabel.textContent = 'DMX Host: ';
    dmxHostContainer.appendChild(dmxHostLabel);
    const dmxHostInput = document.createElement('input');
    dmxHostInput.id = 'dmxHostInput';
    dmxHostInput.type = 'text';

    const settings = new Settings();

    dmxHostInput.value = settings.engine.preload ? 'valot.instanssi' : '127.0.0.1';
    dmxHostContainer.appendChild(dmxHostInput);

    // DMX port text field, default 9910
    const dmxPortContainer = document.createElement('div');
    dmxPortContainer.style.display = 'flex';
    dmxPortContainer.style.marginBottom = '5px';
    menuControlDiv.appendChild(dmxPortContainer);

    const dmxPortLabel = document.createElement('label');
    dmxPortLabel.htmlFor = 'dmxPortInput';
    dmxPortLabel.textContent = 'DMX Port: ';
    dmxPortContainer.appendChild(dmxPortLabel);   
    const dmxPortInput = document.createElement('input');
    dmxPortInput.id = 'dmxPortInput';
    dmxPortInput.type = 'text';
    dmxPortInput.value = '9910';
    dmxPortContainer.appendChild(dmxPortInput);
    
    // DMX light count text field, default 24
    const dmxLightCountLabel = document.createElement('label');
    dmxLightCountLabel.htmlFor = 'dmxLightCountInput';
    dmxLightCountLabel.textContent = 'DMX Light Count: ';
    menuControlDiv.appendChild(dmxLightCountLabel);
    const dmxLightCountInput = document.createElement('input');
    dmxLightCountInput.id = 'dmxLightCountInput';
    dmxLightCountInput.type = 'text';
    dmxLightCountInput.value = '24';
    menuControlDiv.appendChild(dmxLightCountInput);

    createDmxLightManager();
    
    // DMX light test button, random RGB values to all lights

    const dmxTestButton = document.createElement('button');
    dmxTestButton.style.display = 'block';
    dmxTestButton.textContent = 'Test DMX Lights';
    menuControlDiv.appendChild(dmxTestButton);
    dmxTestButton.onclick = () => {
        createDmxLightManager();
        setTimeout(() => {
            console.log('Testing DMX lights with random colors');
            if (window.dmxLightManager) {
                for (let i = 0; i < window.dmxLightManager.lightCount; i++) {
                    const r = Math.random() >= 0.5 ? Math.floor(Math.random() * 0xFF) : 0x00;
                    const g = Math.random() >= 0.5 ? Math.floor(Math.random() * 0xFF) : 0x00;
                    const b = Math.random() >= 0.5 ? Math.floor(Math.random() * 0xFF) : 0x00;
                    window.dmxLightManager.setColor(r, g, b, i, i + 1);
                }
                window.dmxLightManager.sendData();
            }
        }, 500);
    };
} else {
    loggerWarning('DMX Light Manager menu controls not found, DMX light control will not work');
}
