// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// % COMMUNICATION

var timeoutID; // We need this ID to reset the timer every 30 seconds

// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// Server Sent Event


function buildUpdateConnection() {
    // Establishes server-sent-event-connection, which is used for all sorts of
    // updates and exists as long as the client is running.
    // Executed at program start (see also SEAWebClientMain.js).

    var path = "http://" + hostPort + "/update?" + window.clientTags;
    if (debugCommunication) {
        console.log("%cto server (SSE): " + path,
                "color:white;background:lightblue");
    }

    try {
        var src = new EventSource(path);
    } catch (e) {
        console.log(e)
        alertify.prompt("NETWORK ERROR",
                        "Failed to establish connection to data-server at the given address!"
                                + "Try to enter HOST and PORT of the data-server manually!",
                        hostPort, function(evt, value) {
                            hostPort = value;
                            buildUpdateConnection();
                        }, function() {
                        })
    }

    src.onmessage = function(e) {
        var message = JSON.parse(e.data);
        if (message) {
            handleUpdateMessage(src, message);
        }
    };

    src.onerror = function(e) {
        console.log(e);
        console.log('EVTSRC error')
        alertify.prompt("NETWORK ERROR",
                        "Failed to establish connection to data-server at the given address!"
                                + "Try to enter HOST and PORT of the data-server manually!",
                        hostPort, function(evt, value) {
                            hostPort = value;
                            buildUpdateConnection();
                        }, function() {
                        })
        src.close();
    };
}

function handleUpdateMessage(src, message) {
    // Handles incoming SSE-messages depending on type of message.
    if (debugCommunication > 1) {
        console.log("%cfrom server (SSE): " + message.type,
                "color:white;background:lightgray", message);
    }
    // console.log(message.type, "at", new Date())
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // <---------------------------------------------------------------------------------BUG!!!
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    resetTimer(src);
    switch (message.type) {
    // id-message: Confirms establishment of SSE-connection and determines
    // specific ID of the client
    case "id":
        clientID = message.id;
        if ("device" in message) {
            if (message.device == "_inst_select") {
                clientTitle = "select instrument";
                console.log('IDselect')
                pushInitCommand("getblock?path=_inst_select&", "instrument selection");
                sizeChange();
            } else {
                clientTitle = message.instrument + " " + message.device;
                // console.log('loadBlocks', message);
                loadFirstBlocks();
            }
            document.title = clientTitle;
        } else {
            document.title = clientTitle + " " + message.title;
        }
        var header = document.getElementById("header");
        header.style.width = 'auto';
        let instrument = document.getElementById("instrument");
        let device = document.getElementById("device");
        instrument.style.width = 'auto'
        device.style.width = 'auto'
        instrument.innerHTML = message.instrument
        device.innerHTML = message.device
        // console.log('ID', initCommands);
        nextInitCommand();
        break;
    // console-update-message: Confirms a command.
    case "command":
        var histories = document.getElementsByClassName("history");
        for (var i = 0; i < histories.length; i++) {
            var line = document.createElement('div');
            line.innerHTML = htmlEscape(message.line);
            line.style.color = "cornflowerblue";
            if (message.origin == "self") {
                line.style.fontWeight = "bold";
            }
            histories[i].appendChild(document.createElement('br'));
            histories[i].appendChild(line);
            histories[i].scrollTop = histories[i].scrollHeight;
        }
        var cmd = htmlEscape(message.line);
        if (commandHistory.indexOf(cmd) === -1) {
            commandHistory.unshift(cmd);
        }
        break;
    // console-update-message: Confirms execution of command.
    case "reply":
        var histories = document.getElementsByClassName("history");
        for (var i = 0; i < histories.length; i++) {
            var line = document.createElement('div');
            line.innerHTML = htmlEscape(message.line);
            line.style.color = "black";
            if (message.origin == "self") {
                line.style.fontWeight = "bold";
            } else if (message.origin == "async") {
                line.style.color = "green";
                if (!showAsync) continue;
            }
            histories[i].appendChild(line);
            histories[i].scrollTop = histories[i].scrollHeight;
        }
        break;
    // graph-message: Evokes redraw of graphics.
    case "graph":
        alert('obsolete code "graph" called')
        console.log("graph");
        createCharts2(message.graph);
        break;
    // redraw.message: Communicates changes requiring reset of a whole group
    case "redraw": // SHOULD BE TESTED
        // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        // SPECIAL CASE: is message.title = "main possible? It should evoke
        // redraw of client

        // special meaning: s < 0: replace slides in all swiper instances
        reqJSON(-1, "http://" + hostPort + "/getblock?path=" + message.path
                + "&id=" + clientID, successHandler, errorHandler);

        break;
    // graph-update-message:
    case "graph-update":
        //if (getUpdatesGraphics) {
            //timeServer = message.time;
        updateCharts2(message.graph);
        //}
        break;
    // update-message: Communicates change of values.
    case "update":
        if (debugCommunication > 1) {
            console.log(message);
        }
        handleUpdate(message, src);
        break;
    }
}

function htmlEscape(str) {
    str = "" + str;
    if (!str) return "";
    return str.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g,
                '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

function resetTimer(src) {
    // Executed every time a heartbeat-message is obtained.
    // If no heartbeat-messages are obtained for a certain amount of time,
    // an error-message is thrown.

    clearTimeout(timeoutID);
    timeoutID = setTimeout(function(src) {
        console.log("timeout");
        alertify.error("connection lost");
        if (src) {
            src.close();
        }
    }, 60000);
}

function handleUpdate(message, src) {
    // Handles changes of parameter-values
    for (var i = 0; i < message.updates.length; i++) {
        let component = message.updates[i];
        
        // Check for status updates
        if (component.name.split(":")[1] == 'status') {
            updateStatus(component);
        }
        // Check for target updates in the module block
        if (component.name.split(":")[1] == 'target') {
            updateTarget(component);
        }

        updateValue(component);
    }
}

function updateTarget(component) {
    let matches = document.getElementsByName(component.name);
    let elem = matches[0]; // Should be the only match
    elem.value = component.value;

    let row = elem.closest('div');
    row.style.backgroundColor = "white";

    let oldValue = ('oldValue' in elem) ? elem.oldValue : elem.value;
    if (component.value != elem.value && parseFloat(component.value) != parseFloat(elem.value) && component.value != oldValue) {
        if (elem == document.activeElement || oldValue != elem.value) {
            row.style.backgroundColor = "orange";
        } else {
            elem.value = component.value;
        }
    }
    elem.actualValue = component.value;
    resizeTextfield(elem);
}

function updateStatus(component) {
    let matches = document.getElementsByName(component.name);
    let status_icon = matches[0];
    let row = status_icon.closest(".row");
    let right = row.lastChild;
    let statusCode = component.statuscode;
    
    // Update status info, visible when mouse cursor is hovering over status icon
    let status_info = document.getElementsByName(component.name.split(":")[0] + '-info')[0];
    if(status_info) {
        status_info.innerHTML = component.formatted;
    }

    status_icon.classList.remove('status-icon-disabled');
    status_icon.classList.remove('status-icon-idle');
    status_icon.classList.remove('status-icon-warn');
    status_icon.classList.remove('status-icon-busy');
    status_icon.classList.remove('status-icon-error');
    row.classList.remove('row-disabled');
    right.classList.remove = 'col-right-disabled';

    switch (statusCode) {
        case 0:
            status_icon.classList.add('status-icon-disabled');
            row.classList.add('row-disabled');
            right.classList.add = 'col-right-disabled';
            break;
        case 1:
            status_icon.classList.add('status-icon-idle');
            break;
        case 2:
            status_icon.classList.add('status-icon-warn');
            break;
        case 3:
            status_icon.classList.add('status-icon-busy');
            break;
        case 4:
            status_icon.classList.add('status-icon-error');
            break;
    }

}

function updateValue(component) {

    let matches = document.getElementsByName(component.name);

    for (var j = 0; j < matches.length; j++) {
        let elem = matches[j];
        let type = elem.__ctype__; // -> Show Dom-Properties
        if (type == "rdonly" || type == "none") {
            let text = htmlEscape(component.formatted);
            if (text) {
                elem.innerHTML = text;
                // elem.innerHTML = ' ein etwas zu langer, nein viel zu langer Text...';
            }
        } else if (type == "input") {
            let row = elem.closest('div');
            row.style.backgroundColor = "white";

            let oldValue = ('oldValue' in elem) ? elem.oldValue : elem.value;
            if (component.value != elem.value && parseFloat(component.value) != parseFloat(elem.value) && component.value != oldValue) {
                if (elem == document.activeElement || oldValue != elem.value) {
                    row.style.backgroundColor = "orange";
                } else {
                    elem.value = component.value;
                }
            }
            elem.actualValue = component.value;
            resizeTextfield(elem);
        } else if (type == "checkbox") {
            let row = elem.closest('div');
            row.style.backgroundColor = "white";
            
            console.log('receive: ', component.value);
            if (component.value == 'False' || component.value == 'false' || component.value == 0) {
                elem.checked = false;
            } else {
                elem.checked = true;
            }
        } else if (type == "enum") {
            elem.style.display = "block";
            let row = elem.closest('div');
            row.style.backgroundColor = "white";
        
            let options = elem.childNodes;
            for (var j = 0; j < options.length; j++) {
                if (options[j].label == component.value) {
                    elem.value = j + 1;
                }
            }
        } else if (type == "pushbutton") {
            console.log('update pushbutton');
            console.log('component.name:', component.name);
            console.log('element', elem);
        }
    }
}

// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// XMLHttpRequest

function reqJSON(s, url, successHandler, errorHandler) {
    var xhr = typeof XMLHttpRequest != 'undefined' ? new XMLHttpRequest()
            : new ActiveXObject('Microsoft.XMLHTTP');
    if (debugCommunication) {
        console.log("%cto server (reqJSON): %s",
                "color:white;background:darkgreen", url);
    }
    xhr.open('get', url, true);
    xhr.onreadystatechange = function() {
        // console.log(xhr)
        var status;
        var data;
        // https://xhr.spec.whatwg.org/#dom-xmlhttprequest-readystate
        if (xhr.readyState == 4) { // `DONE`
            status = xhr.status;
            if (status == 200) {
                data = JSON.parse(xhr.responseText);
                successHandler && successHandler(s, data);
            } else {
                errorHandler && errorHandler(status);
            }
        }
    };
    xhr.send();
}

function reqJSONPOST(s, url, parameters, successHandler, errorHandler) {
    var xhr = typeof XMLHttpRequest != 'undefined' ? new XMLHttpRequest()
            : new ActiveXObject('Microsoft.XMLHTTP');
    if (debugCommunication) {
        console.log("%cto server (reqJSONPOST): %s",
                "color:white;background:lightgreen", url);
    }
    xhr.open('post', url, true);
    xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xhr.onreadystatechange = function() {
        // console.log(xhr)
        var status;
        var data;
        // https://xhr.spec.whatwg.org/#dom-xmlhttprequest-readystate
        if (xhr.readyState == 4) { // `DONE`
            status = xhr.status;
            if (status == 200) {
                data = JSON.parse(xhr.responseText);
                successHandler && successHandler(s, data);
            } else {
                errorHandler && errorHandler(status);
            }
        }
    };
    xhr.send(parameters);
}

function successHandler(s, message) {
    // Handles incoming XMLHttp-messages depending on type of message.
    if (debugCommunication) {
        console.log("%cfrom server (reqJSON): " + message.type,
                "color:white;background:dimgray", message);
    }
    switch (message.type) {
    // Response to a "getblock"-server-request.
    case "draw":
        if (message.path == "main") {
            // Happens only initially or at device change.
            appendToGridElement(1, message.title, "main", createContent(message));
            // appendToGridElement(2, "", "parameters", createContent({components:[]}));
        } else {
            // In the module-block a parameter was selected
            showParams = true;
            // -> write parameter-block to grid-element2
            isl = appendToGridElement(2, message.title, 'parameters', createContent(message));
            adjustGrid();
            if (nColumns == 1 || nColumns == 2 || nColumns == 3) {
                document.getElementById('close-cross').innerHTML = '<img class = "icon-close" src="res/icon_close.png">';
            }
        }
        nextInitCommand();
        // Request for updates.
        if (getUpdates) {
            reqJSON(s, "http://" + hostPort + "/updateblock?path="
                    + message.path + "&id=" + clientID, successHandler,
                    errorHandler);
        }
        break;
    // Response to a "update-block"-server-request.
    case "accept-block":
        break;
    // Response to a "console"-server-request.
    case "accept-console":
        // draw console only to the last grid-element
        appendToGridElement(3, "console", "console",createContentConsole(3));
        nextInitCommand();
        // send empty command in order to trigger getting history
        reqJSON(0, "http://" + hostPort + "/sendcommand?command=&id=" + clientID, successHandler,
                errorHandler);
        break;
    // Response to a "gettime"-server-request.
    case "time":
        timeRange = message.time;
        /*createGraphics();
        // By default mostleft swiper-instance shows graphics.

        // Update time-selection. (see also SEAWebClientGraphics.js)
        var select = document.getElementsByClassName("select-time")[0];
        begin = timeRange[0] - timeRange[1];
        select.value = begin;
        // Server-request for variable-list.*/
        // console.log('TIME', timeRange)
        reqJSONPOST(0, "http://" + hostPort + "/getvars",
                "time=" + timeRange[0] + ',' + timeRange[1]
                + "&userconfiguration=" + JSON.stringify(getFormattedUserConfigurationFromLocalStorage())
                + "&id=" + clientID, successHandler, errorHandler);
        break;
    // Response to a "getvars"-server-request.
    case "var_list":
        //blocks = message.blocks;
        /*var varlist = [];
        for (var i = 0; i < blocks.length; i++) {
            for (var j = 0; j < blocks[i].curves.length; j++) {
                varlist.push(blocks[i].curves[j].name);
            }
        }
        // Update graphics
        if (varlist.length > 0) {
            reqJSON(0, "http://" + hostPort + "/graph?time=" + timeRange
                    + "&variables=" + varlist + "&id=" + clientID,
                    successHandler, errorHandler);
        } else {
            nextInitCommand();
        }*/
        // graphs.receivedVars(message.blocks);
        document.getElementById("device").innerHTML = message.device
        graphs.initGraphs(message.blocks);
        nextInitCommand();
        break;
    // Response to a "graph"-server-request.
    case "graph-draw":
        // obsolete?
        if (debugCommunication) {
            console.log("graph-draw", message);
        }
        alert('obsolete code graph-draw called')
        createCharts2(message.graph);
        nextInitCommand();
        // Request for updates.
        reqJSON(s, "http://" + hostPort + "/updategraph?id=" + clientID,
                successHandler, errorHandler);
        break;
    // Response to a "updategraph"-server-request.
    case "accept-graph":
        break;
    case "error":
        console.log("%cError-Message received!", "color:white;background:red");
        console.log(message);
        break;
    default:
        break;
    }
}

function errorHandler(status) {
    if (debugCommunication) {
        console.log("error", status);
    }
}
