var apikey = null;
var apiversion = 'vnd.ddel.v1';
var isFocused = false;
var groups = {};
var sensors = {};
var PAGE_STATE_INIT = 0;
var PAGE_STATE_UPDATE = 1;
var PAGE_STATE_SAVE = 2;
var PAGE_STATE_IDLE = 3;
var STD_LUX_LOW = 400;
var STD_LUX_HIGH = 900;

var pageState = PAGE_STATE_INIT;

/**
 * Init touch and small displays.
 */
function daylightControlInit() {
    pageState = PAGE_STATE_INIT;
    curEdit.light = {};
    curEdit.light.state = {};
    curEdit.light.state.bri = "-";

    // check if this is mobile or desktop
    // and load custom js and css files
    if (Modernizr.touch) {
        loadjscssfile("mobile.css", "css");
        isDesktop = false;
    }
    else {
        isDesktop = true;
        loadjscssfile("desktop.css", "css");
    }

    if (Modernizr.touch) {
        clickevent = "touchend";
        pointerStart = "touchstart";
        pointerEnd = "touchend";
        pointerMove = "touchmove";
    }
}

/**
 * Init the base branding of the interface.
 */
function initBranding() {
    var customBrand = false;

    var navName = "Wireless Light";

    if (customBrand) {
        navName = "CUSTOM <span style=\"position: relative; display: inline-block; bottom: 8px;\">&reg;</span>";
        var brand = '<span class="custom-brand">' + navName + '</span>';
        $(".navbar-inner .brand").html(brand);
    }
}

function buildPage() {

    $("#curGroup").empty()

    if (groups.length === 0) {
    } else {
        for (group in groups) {
            var g = groups[group];
            var opt = $('<option></option>').attr("value", g.id).text(g.name);
            $("#curGroup").append(opt);
        }
    }



    $("#curSensor").empty();

    if (sensors.length === 0) {
    } else {
        for (sensor in sensors) {
            var s = sensors[sensor];
            var opt = $('<option></option>').attr("value", s.id).text(s.name + ' / ' + s.modelid);
            $("#curSensor").append(opt);
        }
    }


    var txt = "";
    let containerId = 0;

    for (group in groups) {
        g = groups[group];

        if (!g.rules || !g.sensor)
            continue;

        const panelId = 'panel' + containerId;
        containerId++;

        let utc = g.sensor.state.lastupdated + 'z';
        var dupt = new Date(utc);
        var timestamp = dupt.toLocaleTimeString();

        txt += `<div id="${panelId}" class="container row rule-panel location">`;
        txt += '<h3><span class="visible-indicator"><img src="img/minus.png"></span>' +  g.name + ' <small>' + g.sensor.name + ' (' + g.sensor.state.lux + 'Lux - ' +  timestamp + ')</small></h3>';

        var sortedRules = [];

        for (rule in g.rules) {
            sortedRules[sortedRules.length] = g.rules[rule];
        }

        sortedRules.sort(function(a,b) {
            var briA = a.actions[0].body.bri;
            var briB = b.actions[0].body.bri;
            return briA < briB;
        });

        let timeSpan = null;

        for (rule in sortedRules) {
            var r = sortedRules[rule];
            var onValue = true;
            var bri = 100;
            var transitiontime = 100;

            if (r.actions[0].body.on !== undefined) {
                onValue = r.actions[0].body.on;
            }

            if (onValue) {
                if (r.actions[0].body.bri !== undefined) {
                    bri = Math.floor((r.actions[0].body.bri / 255) * 100);
                }
            } else {
                bri = 0;
            }

            if (r.actions[0].body.transitiontime !== undefined) {
                transitiontime = r.actions[0].body.transitiontime / 10;
            }

            var grids = g.id + '-' + r.id;
            txt += '<div class="rule-panel-item"> <a class="close pull-right" href="#" onclick="deleteRule(' + r.id + '); return false;">&times;</a>';
            // basic rule settings
            txt += '<div class="span4"><table class="table table-condensed">';
            txt += '   <tr><td>Rule Name</td><td><input type="text" id="rule-name' + grids + '" class="input-medium" placeholder="Name" value="' + r.name +'"></td></tr>';
            txt += '   <tr><td>Brightness</td><td><div class="input-append"><input type="number" id="rule-bri' + grids + '" value="' + bri + '" class="input-small"><span class="add-on">%</span></div></td></tr>';
            txt += '   <tr><td>Transitiontime</td><td><div class="input-append"><input type="number" id="rule-time' + grids + '" class="input-small" value="' + transitiontime + '"><span class="add-on">s</span></div></td></tr>';
            if (r.lasttriggered !== "none") {
                dupt = new Date(r.lasttriggered);
                timestamp = dupt.toLocaleTimeString();
                txt += '   <tr><td>Last triggered</td><td>' + timestamp + '</td></tr>';
            }
            txt += '</table></div>';

            // rule conditions
            txt += '<div class="span5">'; // rule conditions
            txt += '<table class="table"><tr><th>Lux Conditions</th><th>Value</th><th></tr></tr>';

            for (condition in r.conditions) {
                var c = r.conditions[condition];

                if (!timeSpan && c.operator === 'in' && c.address === '/config/localtime') {
                    timeSpan = c.value;
                }

                if (['lt', 'gt'].indexOf(c.operator) === -1)
                    continue;

                var grcids = g.id + '-' + r.id + '-' + condition;
                var sel = "";
                var opts = { "lt": { "name": "lower than", "sel": "" }, "gt": { "name": "greater than", "sel": "" }};

                if (c.operator in opts) {
                    opts[c.operator].sel = "selected";
                }

                txt += '<tr><td><select id="rule-cond-op' + grcids + '" class="input-medium">';
                for (opt in opts) {
                    txt += '<option value="' + opt +'" ' + opts[opt].sel + '>' + opts[opt].name + '</option>';
                }
                txt += '</select></td>';
                txt += '<td> <div class="input-append"> <input id="rule-cond-val' + grcids + '" type="number" value="' + c.value + '" class="input-small"> <span class="add-on">Lux</span></div></td>';
                txt += '<td>';
                txt += '</td></tr>';
            }

            txt += '</table>';

            txt += '<div class="clearfix"></div>';
            txt += '</div>'; // rule conditions
            txt += '<div class="clearfix"></div>';

            txt += '</div>'; // end rule panel item
        } // end rules


        let startTime = '00:00';
        let endTime = '00:00';
        if (timeSpan) {
            timeSpan = timeSpan.split('/');
            if (timeSpan.length === 3) {
                startTime = timeSpan[1].slice(1, 6);
                endTime = timeSpan[2].slice(1, 6);
            }
        }

        txt += `<div class="start-end-time">Active from <input type="time" value="${startTime}" required> till <input type="time" value="${endTime}" required> </div>`;

        txt += '<div class="btn-toolbar pull-right">';
        txt += '  <span class="btn " onclick="createSoloRule(' + g.id + '); return false;">Add Point</span>';
        txt += '  <span class="btn " onclick="createRangeRule(' + g.id + '); return false;">Add Range</span>';
        txt += `  <span class="btn btn-primary" onclick="storeGroupRules('${g.id}', '${panelId}'); return false;">Apply</span>`;
        txt += '</div>';
        txt += '</div>'; // end rule panel
    } // end groups


    $("#rule-container").html(txt);

//    // handler when user selects a group from the list
//    $("#curGroup").change(function() {
//        if ($("#curGroup option:selected").val() === "none") {
//          curEdit.group = undefined;
//        } else {
//          curEdit.group = {};
//          curEdit.group.id = $("#curGroup option:selected").val();
//        }
//    });

//    $('input').change(function() {
//        if (this.id === 'moSensor' || this.id === 'briSensor') {
//            checkSensorCheckedState();
//        }
//    });
}

function request(method, url, ms) {
    return new Promise(function (resolve, reject) {
        var xhr = new XMLHttpRequest();

        xhr.open(method, url);
        xhr.responseType = 'json';
        xhr.timeout = ms;
        xhr.onload = function(e) {
            if (xhr.readyState === 4 && xhr.status === 200) {
                resolve(xhr);
            } else {
                reject();
            }
        };
        xhr.ontimeout = reject;
        xhr.onerror = function(e) {
            if (xhr.readyState === 4 && xhr.status === 0) {
                reject('avail');
            } else {
                reject();
            }
        };
        xhr.send();
    });
}

function checkSchedulesWeekdays(xhr, gid) {
    return new Promise((resolve, reject) => {
        if (xhr.status !== 200 || typeof(xhr.response) !== 'object')
            return reject(new Error('failed to query schedules'));

        for (id in xhr.response) {
            const s = xhr.response[id];
            if (!s.command || !s.command.address || !s.command.body || !s.time || s.time.indexOf('W') !== 0)
                continue;
            const a = s.command.address.split('/').slice(3);
            if (a[0] !== 'groups' || a[2] !== 'action' || gid !== a[1])
                continue;
            if (s.command.body.on === false)
                return resolve(s.time.split('/')[0]); // resolve weekdays W128
        }

        // if no schedule was found enable all days of the week
        return resolve('W127');
    });
}

/**
* GET /api/<apikey>/sensors
*/
function getSensors() {
    $.ajax({
        url: 'api/' + apikey + '/sensors',
        dataType: 'json',
        type: 'GET',
        cache: false,
        contentType: 'application/json; charset=utf-8',
        headers: { 'Accept': apiversion },
        success: function(json, status, xhr) {
            for (key in json){
                var s = json[key];
                if (s.type.indexOf("ZHALight") !== -1 && s.modelid !== "") {
                    if (s.state !== undefined && s.uniqueid !== undefined) {
                        s.id = key;
                        sensors[key] = s;
                    }
                }
            }

            getGroups();
        },
        error: function(jqXHR, textStatus, errorThrown) {
            switch (jqXHR.status) {
            case 403:
                window.location.assign("/pwa/login.html");
                break;

            default:
                setTimeout(getSensors, 3000);
                showAlert('alert-error', '<b>Error!</b> Lost connection, retry in one second ...');
                break;
            }
        },
        timeout: 10000
    });
}

/**
* GET /api/<apikey>/rules
*/
function getRules() {

    $.ajax({
        url: 'api/' + apikey + '/rules',
        dataType: 'json',
        type: 'GET',
        cache: false,
        contentType: 'application/json; charset=utf-8',
        headers: { 'Accept': apiversion },
        success: function(json, status, xhr) {
            for (key in json) {

                var g = undefined;
                var r = json[key];

                // filter
                // check actions
                if  (r.actions.length === 1 && r.actions[0].method === "PUT") {
                    var a = r.actions[0]
                    var gaddr = a.address.match(/groups\/(\d+)\/action/);

					if (gaddr === null) {
						continue;
					}

					if (gaddr.length !== 2) {
                        break;
                    }

                    if (gaddr[1] in groups) {
                        g = groups[gaddr[1]];
                    }
                }

                if (g === undefined) {
                    // group not found
                    continue;
                }

                if (!g.rules) {
                    g.rules = {}; // attach object if not already there
                }

                // check confitions
                if  (!(r.conditions.length >= 1 && r.conditions.length <= 4)) {
                    continue;
                }

                let ok = false;
                let foundLux = false;

                for (let i = 0; i < r.conditions.length; i++) {
                    const c = r.conditions[i];
                    const addr = c.address.split('/').slice(1);

                    if (addr[3] === 'lux') {
                        foundLux = true;
                    } else if (c.address === '/config/localtime' && c.operator === 'in') {
                        continue;
                    } else if (addr[3] !== 'lastupdated') {
                        ok = false;
                        break;
                    }

                    if (addr.length !== 4) {
                        ok = false;
                        break;
                    }

                    if (g.sensor === undefined) {
                        // search attached sensor
                        for (sensor in sensors) {
                            if (sensors[sensor].id === addr[1]) {
                                g.sensor = sensors[sensor];
                            }
                        }
                    }

                    if (!g.sensor) { // sensor not found
                        ok = false;
                        break;
                    }

                    if (g.sensor.id !== addr[1]) { // sensor other than in this this rule
                        ok = false;
                        break;
                    }

                    ok = true;
                }

                if (ok && foundLux) {
                    r.id = key;
                    g.rules[r.id] = r;
                }
            }

            buildPage();
        },
        error: function(jqXHR, textStatus, errorThrown) {
            switch (jqXHR.status) {
            case 403:
                window.location.assign("/pwa/login.html");
                break;

            default:
                setTimeout(getRules, 3000);
                showAlert('alert-error', '<b>Error!</b> Lost connection, retry in one second ...');
                break;
            }
        },
        timeout: 10000
    });
}

/**
* GET /api/<apikey>/groups
*/
function getGroups() {
    $.ajax({
        url: 'api/' + apikey + '/groups',
        dataType: 'json',
        type: 'GET',
        cache: false,
        contentType: 'application/json; charset=utf-8',
        headers: { 'Accept': apiversion },
        success: function(json, status, xhr) {
            for (key in json){
                if (json[key].type !== 'LightGroup' && json[key].type !== 'Luminaire')
                    continue;
                var group = {};
                group.id = key;
                group.name = json[key].name;
                groups[key] = group;
            }

            getRules();
        },
        error: function(jqXHR, textStatus, errorThrown) {
            switch (jqXHR.status) {
            case 403:
                window.location.assign("/pwa/login.html");
                break;

            default:
                setTimeout(getGroups, 3000);
                showAlert('alert-error', '<b>Error!</b> Lost connection, retry in one second ...');
                break;
            }
        },
        timeout: 10000
    });
}

function updateRules(group, ruleList, panelId, weekDays) {
    if (ruleList.length === 0)
        return;

    const panel = document.querySelector(`#${panelId}`);


    const startTimeInput = panel.querySelector('input[type="time"]:nth-child(1)');
    const endTimeInput = panel.querySelector('input[type="time"]:nth-child(2)');

    const startTime = startTimeInput.value;
    const endTime = endTimeInput.value;

    //var r = ruleList[0];
    const r = JSON.parse(JSON.stringify(ruleList[0])) // deep copy
    var ids = group.id + '-' + r.id;

    // extract values
    r.periodic = 30;
    r.name = $('#rule-name' + ids).val();
    r.actions[0].body.bri = parseInt($('#rule-bri' + ids).val());
    if (r.actions[0].body.bri > 0) {
        r.actions[0].body.bri = Math.floor(r.actions[0].body.bri / 100 * 255);
    }
    r.actions[0].body.transitiontime = parseInt($('#rule-time' + ids).val()) * 10;
    r.actions[0].body.on = r.actions[0].body.bri > 0 ? true : false;

    //let foundDx = false;
    let addr = null;

    r.conditions = r.conditions.filter((c) => c.operator !== 'dx' && c.operator !== 'in');

    for (const condition in r.conditions) {
        const grcids = group.id + '-' + r.id + '-' + condition;
        const c = r.conditions[condition];

        //if (c.operator === 'dx') {
        //    foundDx = true;
        //    continue;
        //}

        c.operator = $('#rule-cond-op' + grcids).val();
        c.value = parseInt($('#rule-cond-val' + grcids).val());

        if (!addr)
            addr = c.address.split('/').slice(1);
    }

    if (!addr) {
        console.log('missing valid conditions');
        return;
    }


    addr[3] = 'lastupdated';
    const dx = {
        address: '/' + addr.join('/'),
        operator: 'dx'
    };
    r.conditions.push(dx);


    if (startTime === '00:00' && endTime === '00:00') {
        // no time span configured

    } else {
        const timeSpan = `${weekDays}/T${startTime}:00/T${endTime}:00`;

        const ts = {
            address: '/config/localtime',
            operator: 'in',
            value: timeSpan
        };

        r.conditions.push(ts);
    }

    const out = {
        "name": r.name,
        "actions": r.actions,
        "conditions": r.conditions,
        "periodic": r.periodic,
        "status": r.status
    };

    $.ajax({
        url: 'api/' + apikey + '/rules/' + r.id,
        dataType: 'json',
        type: 'PUT',
        cache: false,
        contentType: 'application/json; charset=utf-8',
        headers: { 'Accept': apiversion },
        data: JSON.stringify(out),
        success: function(json) {
            if (ruleList.length > 1)
                updateRules(group, ruleList.slice(1), panelId, weekDays);
        },
        error: function(jqXHR, textStatus, errorThrown) {
            console.log("textStatus:", textStatus + ' ' + errorThrown);
            console.log(jqXHR.responseText);
            console.log("failed to update rule" + r.id + " " + JSON.stringify(out));
        },
        timeout: 10000,
    });
}

/**
 * Stores all rules of a group/sensor combo.
 *
 * @param {array} ruleList - list of rules to create
 */
function createRules(ruleList) {

    if (ruleList.length === 0) {
        getRules();
        return;
    }

    var r = ruleList[0];

    console.log("create rule " + r.name);

    $.ajax({
        url: 'api/' + apikey + '/rules/',
        dataType: 'json',
        type: 'POST',
        cache: false,
        contentType: 'application/json; charset=utf-8',
        headers: { 'Accept': apiversion },
        data: JSON.stringify(r),
        success: function(json) {
            createRules(ruleList.slice(1));
        },
        error: function(jqXHR, textStatus, errorThrown) {
            console.log("textStatus:", textStatus + ' ' + errorThrown);
            console.log(jqXHR.responseText);
            console.log("failed to create rule " + r.name);
        },
        timeout: 10000,
    });

}

/**
 * Stores all rules of a group/sensor combo.
 *
 * @param {string} gid - the group id
 * @param {string} panelId - id of the html panel
 */
function storeGroupRules(gid, panelId) {


    if (gid in groups) {
        var g = groups[gid];
        var rules = []
        for (var r in g.rules) {
            rules[rules.length] = g.rules[r];
        }

        request('GET', `api/${apikey}/schedules`, 5000)
        .then((xhr) => checkSchedulesWeekdays(xhr, gid))
        .then((weekDays) => updateRules(g, rules.slice(0), panelId, weekDays));
    } else {
        console.log("can't save group " + gid + ", not found");
    }
}

/**
 * Creates a rule with one condition.
 *
 * @param {string} gid - the group id
 */
function createSoloRule(gid) {
    if (!(gid in groups)) {
        console.log("can't create rule for group " + gid + ", not found");
        return;
    }

    var g = groups[gid];

    if (!g.sensor) {
        console.log("can't create rule for group " + gid + ", no sensor");
        return;
    }

    var rulesList = [ {
                    "name": "__delight <New>",
                    "periodic": 30000,
                    "status": "enabled",
                    "actions": [ {
                            "address": "/groups/" + g.id + "/action",
                            "method": "PUT",
                            "body": { "on": false, "bri": 0, "transitiontime": 60 }
                        }],
                    "conditions": [ {
                            "address": "/sensors/" + g.sensor.id + "/state/lux",
                            "operator": "gt",
                            "value": STD_LUX_HIGH
                        },
                        {
                            "address": "/sensors/" + g.sensor.id + "/state/lastupdated",
                            "operator": "dx"
                        }],
                } ];

    createRules(rulesList);
}

/**
 * Creates a rule with two conditions (range).
 *
 * @param {string} gid - the group id
 */
function createRangeRule(gid) {
    if (!(gid in groups)) {
        console.log("can't create rule for group " + gid + ", not found");
        return;
    }

    var g = groups[gid];

    if (!g.sensor) {
        console.log("can't create rule for group " + gid + ", no sensor");
        return;
    }

    var rulesList = [ {
                         "name": "__delight <New Range>",
                         "periodic": 30000,
                         "status": "enabled",
                         "actions": [ {
                                 "address": "/groups/" + g.id + "/action",
                                 "method": "PUT",
                                 "body": { "on": true, "bri": 254, "transitiontime": 60 }
                             }],
                         "conditions": [ {
                                 "address": "/sensors/" + g.sensor.id + "/state/lux",
                                 "operator": "gt",
                                 "value": STD_LUX_LOW
                             },
                             {
                                 "address": "/sensors/" + g.sensor.id + "/state/lux",
                                 "operator": "lt",
                                 "value": STD_LUX_HIGH
                             },
                             {
                                 "address": "/sensors/" + g.sensor.id + "/state/lastupdated",
                                 "operator": "dx"
                             }],
                     } ];

    createRules(rulesList);
}

/**
 * Deletes a rule.
 *
 * @param {string} rid - the rule id
 */
function deleteRule(rid) {

    $.ajax({
        url: 'api/' + apikey + '/rules/' + rid,
        dataType: 'json',
        type: 'DELETE',
        cache: false,
        contentType: 'application/json; charset=utf-8',
        headers: { 'Accept': apiversion },
        success: function(json) {
            console.log("deleted rule " + rid);

            for (var group in groups) {
                var g = groups[group];
                if (g.rules && rid in g.rules) {
                    console.log("delete rule from group");
                    delete g.rules[rid];
                }
            }

            getRules();
        },
        error: function(jqXHR, textStatus, errorThrown) {
            console.log("failed to deleted rule " + rid);
            console.log("textStatus:", textStatus + ' ' + errorThrown);
            console.log(jqXHR.responseText);
            //setTimeout(deleteRule, 5000);
        },
        timeout: 10000,
    });
}

function addRuleSet() {
    var s = sensors[$('#curSensor').val()];
    var g = groups[$('#curGroup').val()];

    if (!g || !s)
        return;

    if (g.sensor) {
        if (g.sensor === s) {
            showAlert('alert-info', '<b>Note!</b> Group <em>' + g.name + '</em> already attached to sensor <em>' + g.sensor.name + '</em>.');
        } else {
            showAlert('alert-error', '<b>Failed!</b> Group <em>' + g.name + '</em> already attached to sensor <em>' + g.sensor.name + '</em>.');
        }
    } else {
        // create standard rules
        g.sensor = s;
        g.rules = {};
        var rulesList = [ {
                        "name": "__delight Full Brightness",
                        "periodic": 30000,
                        "status": "enabled",
                        "actions": [ {
                                "address": "/groups/" + g.id + "/action",
                                "method": "PUT",
                                "body": { "on": true, "bri": 255, "transitiontime": 60 }
                            }],
                        "conditions": [ {
                                "address": "/sensors/" + s.id + "/state/lux",
                                "operator": "lt",
                                "value": STD_LUX_LOW
                            },
                            {
                                "address": "/sensors/" + s.id + "/state/lastupdated",
                                "operator": "dx"
                            }]
                   },
                   {
                       "name": "__delight Mid",
                       "periodic": 30000,
                       "status": "enabled",
                       "actions": [ {
                               "address": "/groups/" + g.id + "/action",
                               "method": "PUT",
                               "body": { "on": true, "bri": 127, "transitiontime": 60 }
                           }],
                       "conditions": [ {
                               "address": "/sensors/" + s.id + "/state/lux",
                               "operator": "gt",
                               "value": STD_LUX_LOW
                           },
                           {
                               "address": "/sensors/" + s.id + "/state/lux",
                               "operator": "lt",
                               "value": STD_LUX_HIGH
                           },
                           {
                               "address": "/sensors/" + s.id + "/state/lastupdated",
                               "operator": "dx"
                           }]
                  },
                  {
                       "name": "__delight Off",
                       "periodic": 30000,
                       "status": "enabled",
                       "actions": [ {
                               "address": "/groups/" + g.id + "/action",
                               "method": "PUT",
                               "body": { "on": false, "bri": 0, "transitiontime": 60 }
                           }],
                       "conditions": [ {
                               "address": "/sensors/" + s.id + "/state/lux",
                               "operator": "gt",
                               "value": STD_LUX_HIGH
                           },
                           {
                                "address": "/sensors/" + s.id + "/state/lastupdated",
                                "operator": "dx"
                           }]
                   }
                ];

        createRules(rulesList);

        showAlert('alert-info', '<b>OK</b> Rule set created.');
    }
}

function showAlert(alert, text, container) {
    if (typeof(container) === 'undefined') {
        container = '#alerts';
    }

    var d = new Date();
    var timestr = d.toUTCString();
    var txt = "";

    txt += '<div class="alert ' + alert + '">';
    txt += text;
    txt += ' <span class="pull-right hidden-phone">';
    txt += timestr;
    txt += '</span></div>';

    setTimeout(function() {
        $(container).html(txt).scrollintoview();
    }, 250);
}
