let self = this;
let lastUpdate = (new Date()).getTime();
let reloadConfig = false;
function addEl(type, clazz, parent, text) {
    let el = document.createElement(type);
    if (clazz) {
        if (!(clazz instanceof Array)) {
            clazz = clazz.split(/  */);
        }
        clazz.forEach(function (ce) {
            el.classList.add(ce);
        });
    }
    if (text) el.textContent = text;
    if (parent) parent.appendChild(el);
    return el;
}
function forEl(query, callback,base) {
    if (! base) base=document;
    let all = base.querySelectorAll(query);
    for (let i = 0; i < all.length; i++) {
        callback(all[i]);
    }
}
function alertRestart() {
    reloadConfig = true;
    alert("Board reset triggered, reconnect WLAN if necessary");
}
function getJson(url) {
    return fetch(url)
        .then(function (r) { return r.json() });
}
function reset() {
    fetch('/api/reset');
    alertRestart();
}
function update() {
    let now = (new Date()).getTime();
    let ce = document.getElementById('connected');
    if (ce) {
        if ((lastUpdate + 3000) > now) {
            ce.classList.add('ok');
        }
        else {
            ce.classList.remove('ok');
        }
    }
    getJson('/api/status')
        .then(function (jsonData) {
            for (let k in jsonData) {
                if (typeof (jsonData[k]) === 'object') {
                    for (let sk in jsonData[k]) {
                        let key = k + "." + sk;
                        if (typeof (jsonData[k][sk]) === 'object') {
                            //msg details
                            updateMsgDetails(key, jsonData[k][sk]);
                        }
                        else {
                            let el = document.getElementById(key);
                            if (el) el.textContent = jsonData[k][sk];
                        }
                    }
                }
                else {
                    let el = document.getElementById(k);
                    if (el) el.textContent = jsonData[k];
                }
            }
            lastUpdate = (new Date()).getTime();
            if (reloadConfig) {
                reloadConfig = false;
                resetForm();
            }
        })
}
function resetForm(ev) {
    getJson("/api/config")
        .then(function (jsonData) {
            for (let k in jsonData) {
                let el = document.querySelector("[name='" + k + "']");
                if (el) {
                    let v = jsonData[k];
                    el.value = v;
                    el.setAttribute('data-loaded', v);
                    let changeEvent = new Event('change');
                    el.dispatchEvent(changeEvent);
                }
            }
        });
}
function checkMaxClients(v) {
    let parsed = parseInt(v);
    if (isNaN(parsed)) return "not a valid number";
    if (parsed < 0) return "must be >= 0";
    if (parsed > 10) return "max is 10";
}
function checkSystemName(v) {
    //2...32 characters for ssid
    let allowed = v.replace(/[^a-zA-Z0-9]*/g, '');
    if (allowed != v) return "contains invalid characters, only a-z, A-Z, 0-9";
    if (v.length < 2 || v.length > 32) return "invalid length (2...32)";
}
function checkApPass(v) {
    //min 8 characters
    if (v.length < 8) {
        return "password must be at least 8 characters";
    }
}
function changeConfig() {
    let url = "/api/setConfig?";
    let values = document.querySelectorAll('.configForm select , .configForm input');
    for (let i = 0; i < values.length; i++) {
        let v = values[i];
        let name = v.getAttribute('name');
        if (!name) continue;
        if (name.indexOf("_") >= 0) continue;
        let check = v.getAttribute('data-check');
        if (check) {
            if (typeof (self[check]) === 'function') {
                let res = self[check](v.value);
                if (res) {
                    let value = v.value;
                    if (v.type === 'password') value = "******";
                    alert("invalid config for " + v.getAttribute('name') + "(" + value + "):\n" + res);
                    return;
                }
            }
        }
        url += name + "=" + encodeURIComponent(v.value) + "&";
    }
    getJson(url)
        .then(function (status) {
            if (status.status == 'OK') {
                alertRestart();
            }
            else {
                alert("unable to set config: " + status.status);
            }
        })
}
function factoryReset() {
    if (!confirm("Really delete all configuration?\n" +
        "This will reset all your Wifi settings and disconnect you.")) {
        return;
    }
    getJson("/api/resetConfig")
        .then(function (status) {
            alertRestart();
        })
}
function createCounterDisplay(parent,label,key,isEven){
    let clazz="row icon-row counter-row";
    if (isEven) clazz+=" even";
    let row=addEl('div',clazz,parent);
    let icon=addEl('span','icon icon-more',row);
    addEl('span','label',row,label);
    let value=addEl('span','value',row,'---');
    value.setAttribute('id',key+".sumOk");
    let display=addEl('div',clazz+" msgDetails hidden",parent);
    display.setAttribute('id',key+".ok");
    row.addEventListener('click',function(ev){
        let rs=display.classList.toggle('hidden');
        if (rs){
            icon.classList.add('icon-more');
            icon.classList.remove('icon-less');
        }
        else{
            icon.classList.remove('icon-more');
            icon.classList.add('icon-less');
        }
    });
}

function updateMsgDetails(key, details) {
    forEl('.msgDetails', function (frame) {
        if (frame.getAttribute('id') !== key) return;
        for (let k in details) {
            let el = frame.querySelector("[data-id=\"" + k + "\"] ");
            if (!el) {
                el = addEl('div', 'row', frame);
                let cv = addEl('span', 'label', el, k);
                cv = addEl('span', 'value', el, details[k]);
                cv.setAttribute('data-id', k);
            }
            else {
                el.textContent = details[k];
            }
        }
        forEl('.value',function(el){
            let k=el.getAttribute('data-id');
            if (k && ! details[k]){
                el.parentElement.remove();
            }
        },frame);
    });
}
let counters={
    count2Kin: 'NMEA2000 in',
    count2Kout: 'NMEA2000 out',
    countTCPin: 'TCP in',
    countTCPout: 'TCP out',
    countUSBin: 'USB in',
    countUSBout: 'USB out',
    countSerialIn: 'Serial in',
    countSerialOut: 'Serial out'
}
function showOverlay(text, isHtml) {
    let el = document.getElementById('overlayContent');
    if (isHtml) {
        el.innerHTML = text;
        el.classList.remove("text");
    }
    else {
        el.textContent = text;
        el.classList.add("text");
    }
    let container = document.getElementById('overlayContainer');
    container.classList.remove('hidden');
}
function hideOverlay() {
    let container = document.getElementById('overlayContainer');
    container.classList.add('hidden');
}
function checkChange(el, row) {
    let loaded = el.getAttribute('data-loaded');
    if (loaded !== undefined) {
        if (loaded != el.value) {
            row.classList.add('changed');
        }
        else {
            row.classList.remove("changed");
        }
    }
}
let configDefinitions;
let xdrConfig;
function createInput(configItem, frame,clazz) {
    let el;
    if (configItem.type === 'boolean' || configItem.type === 'list') {
        el=addEl('select',clazz,frame);
        el.setAttribute('name', configItem.name)
        let slist = [];
        if (configItem.list) {
            configItem.list.forEach(function (v) {
                if (v instanceof Object){
                    slist.push({l:v.l,v:v.v});
                }
                else{
                    slist.push({ l: v, v: v });
                }
            })
        }
        else {
            slist.push({ l: 'on', v: 'true' })
            slist.push({ l: 'off', v: 'false' })
        }
        slist.forEach(function (sitem) {
            let sitemEl = addEl('option','',el,sitem.l);
            sitemEl.setAttribute('value', sitem.v);
        })
        return el;
    }
    if (configItem.type === 'filter') {
        return createFilterInput(configItem,frame,clazz);    
    }
    if (configItem.type === 'xdr'){
        return createXdrInput(configItem,frame,clazz);
    }
    el = addEl('input',clazz,frame);
    el.setAttribute('name', configItem.name)
    if (configItem.type === 'password') {
        el.setAttribute('type', 'password');
        let vis = addEl('span', 'icon-eye icon', frame);
        vis.addEventListener('click', function (v) {
            if (vis.classList.toggle('active')) {
                el.setAttribute('type', 'text');
            }
            else {
                el.setAttribute('type', 'password');
            }
        });
    }
    else if (configItem.type === 'number') {
        el.setAttribute('type', 'number');
    }
    else {
        el.setAttribute('type', 'text');
    }
    return el;
}

function updateSelectList(item,slist){
    item.innerHTML='';
    slist.forEach(function (sitem) {
        let sitemEl = addEl('option','',item,sitem.l);
        sitemEl.setAttribute('value', sitem.v);
    })
}
function getXdrCategories(){
    let rt=[];
    for (let c in xdrConfig){
        if (xdrConfig[c].enabled !== false){
            rt.push({l:c,v:xdrConfig[c].id});
        }
    }
    return rt;
}
function getXdrSelectors(category){
    category=parseInt(category);
    for (let c in xdrConfig){
        let base=xdrConfig[c];
        if (parseInt(base.id) == category){
            return base.selector || [];
        }
    }
    return [];
}
function getXdrFields(category){
    category=parseInt(category);
    for (let c in xdrConfig){
        let base=xdrConfig[c];
        if (parseInt(base.id) == category){
            return base.fields || [];
        }
    }
    return [];
}

function createXdrLine(parent,label){
    let d=addEl('div','xdrline',parent);
    addEl('span','xdrlabel',d,label);
    return d;
}
function showHideXdr(el,show,useParent){
    if (useParent) el=el.parentElement;
    if (show) el.classList.remove('xdrunused');
    else el.classList.add('xdrunused');
}

function createXdrInput(configItem,frame){
    let el = addEl('div','xdrinput',frame);
    let d=createXdrLine(el,'Direction');
    let direction=createInput({
        type:'list',
        name: configItem.name+"_dir",
        list: [
            //GwXDRMappingDef::Direction
            {l:'off',v:0},
            {l:'bidir',v:1},
            {l:'to2K',v:2},
            {l:'from2K',v:3}
        ]
    },d,'xdrdir');
    d=createXdrLine(el,'Category');
    let category=createInput({
        type: 'list',
        name: configItem.name+"_cat",
        list:getXdrCategories()
    },d,'xdrcat');
    d=createXdrLine(el,'Source');
    let selector=createInput({
        type: 'list',
        name: configItem.name+"_sel",
        list:[]
    },d,'xdrsel');
    d=createXdrLine(el,'Field');
    let field=createInput({
        type:'list',
        name: configItem.name+'_field',
        list: []
    },d,'xdrfield');
    d=createXdrLine(el,'Instance');
    let imode=createInput({
        type:'list',
        name: configItem.name+"_imode",
        list:[
            //GwXDRMappingDef::InstanceMode
            {l:'single',v:0},
            {l:'ignore',v:1},
            {l:'auto',v:2}
        ]
    },d,'xdrimode');
    let instance=createInput({
        type:'number',
        name: configItem.name+"_instance",
    },d,'xdrinstance');
    d=createXdrLine(el,'Transducer');
    let xdrName=createInput({
        type:'text',
        name: configItem.name+"_xdr"
    },d,'xdrname');
    let data = addEl('input',undefined,el);
    data.setAttribute('type', 'hidden');
    data.setAttribute('name', configItem.name);
    let changeFunction = function () {
        let parts=data.value.split(',');
        direction.value=parts[1] || 0;
        category.value=parts[0] || 0;
        let selectors=getXdrSelectors(category.value);
        updateSelectList(selector,selectors);
        showHideXdr(selector,selectors.length>0);
        let fields=getXdrFields(category.value);
        updateSelectList(field,fields);
        showHideXdr(field,fields.length>0);
        selector.value=parts[2]||0;
        field.value=parts[3]||0;
        imode.value=parts[4]||0;
        instance.value=parts[5]||0;
        showHideXdr(instance,imode.value == 0);
        xdrName.value=parts[6]||'';
    }
    let updateFunction = function () {
        let txt=category.value+","+direction.value+","+
            selector.value+","+field.value+","+imode.value;
        let instanceValue=parseInt(instance.value||0);
        if (isNaN(instanceValue)) instanceValue=0;
        if (instanceValue<0) instanceValue=0;
        if (instanceValue>255) instanceValue=255;
        txt+=","+instanceValue;
        let xdr=xdrName.value.replace(/[^a-zA-Z0-9]/g,'');
        xdr=xdr.substr(0,12);
        txt+=","+xdr;    
        data.value=txt;
        let ev=new Event('change');
        data.dispatchEvent(ev);
    }
    category.addEventListener('change',updateFunction);
    direction.addEventListener('change',updateFunction);
    selector.addEventListener('change',updateFunction);
    field.addEventListener('change',updateFunction);
    imode.addEventListener('change',updateFunction);
    instance.addEventListener('change',updateFunction);
    xdrName.addEventListener('change',updateFunction);
    data.addEventListener('change',changeFunction);
    return data;
}

function createFilterInput(configItem, frame) {
    let el = addEl('div','filter',frame);
    let ais = createInput({
        type: 'list',
        name: configItem.name + "_ais",
        list: ['aison', 'aisoff']
    }, el);
    let mode = createInput({
        type: 'list',
        name: configItem.name + "_mode",
        list: ['whitelist', 'blacklist']
    }, el);
    let sentences = createInput({
        type: 'text',
        name: configItem.name + "_sentences",
    }, el);
    let data = addEl('input',undefined,el);
    data.setAttribute('type', 'hidden');
    let changeFunction = function () {
        let cv = data.value || "";
        let parts = cv.split(":");
        ais.value = (parts[0] == '0') ? "aisoff" : "aison";
        mode.value = (parts[1] == '0') ? "whitelist" : "blacklist";
        sentences.value = parts[2] || "";
    }
    let updateFunction = function () {
        let nv = (ais.value == 'aison') ? "1" : "0";
        nv += ":";
        nv += (mode.value == 'blacklist') ? "1" : "0";
        nv += ":";
        nv += sentences.value;
        data.value = nv;
        let chev = new Event('change');
        data.dispatchEvent(chev);
    }
    mode.addEventListener('change', updateFunction);
    ais.addEventListener("change", updateFunction);
    sentences.addEventListener("change", updateFunction);
    data.addEventListener('change', function (ev) {
        changeFunction();
    });
    data.setAttribute('name', configItem.name);
    return data;
}

function createConfigDefinitions(parent, capabilities, defs,includeXdr) {
    let category;
    let categoryEl;
    let frame = parent.querySelector('.configFormRows');
    if (!frame) throw Error("no config form");
    frame.innerHTML = '';
    configDefinitions = defs;
    defs.forEach(function (item) {
        if (!item.type) return;
        if ((item.category === 'xdr') !== includeXdr) return; 
        if (item.category != category || !categoryEl) {
            let categoryFrame = addEl('div', 'category', frame);
            let categoryTitle = addEl('div', 'title', categoryFrame);
            let categoryButton = addEl('span', 'icon icon-more', categoryTitle);
            addEl('span', 'label', categoryTitle, item.category);
            categoryEl = addEl('div', 'content', categoryFrame);
            categoryEl.classList.add('hidden');
            let currentEl = categoryEl;
            categoryTitle.addEventListener('click', function (ev) {
                let rs = currentEl.classList.toggle('hidden');
                if (rs) {
                    categoryButton.classList.add('icon-more');
                    categoryButton.classList.remove('icon-less');
                }
                else {
                    categoryButton.classList.remove('icon-more');
                    categoryButton.classList.add('icon-less');
                }
            })
            category = item.category;
        }
        if (item.capabilities !== undefined) {
            for (let capability in item.capabilities) {
                let values = item.capabilities[capability];
                if (!capabilities[capability]) return;
                let found = false;
                values.forEach(function (v) {
                    if (capabilities[capability] == v) found = true;
                });
                if (!found) return;
            }
        }
        let row = addEl('div', 'row', categoryEl);
        let label = item.label || item.name;
        addEl('span', 'label', row, label);
        let valueFrame = addEl('div', 'value', row);
        let valueEl = createInput(item, valueFrame);
        if (!valueEl) return;
        valueEl.setAttribute('data-default', item.default);
        valueEl.addEventListener('change', function (ev) {
            let el = ev.target;
            checkChange(el, row);
        })
        if (item.check) valueEl.setAttribute('data-check', item.check);
        let btContainer = addEl('div', 'buttonContainer', row);
        let bt = addEl('button', 'defaultButton', btContainer, 'X');
        bt.setAttribute('data-default', item.default);
        bt.addEventListener('click', function (ev) {
            valueEl.value = valueEl.getAttribute('data-default');
            let changeEvent = new Event('change');
            valueEl.dispatchEvent(changeEvent);
        })
        bt = addEl('button', 'infoButton', btContainer, '?');
        bt.addEventListener('click', function (ev) {
            showOverlay(item.description);
        });
    })
}
function loadConfigDefinitions() {
    getJson("api/capabilities")
        .then(function (capabilities) {
            getJson("config.json")
                .then(function (defs) {
                    getJson("xdrconfig.json")
                        .then(function(xdr){
                            xdrConfig=xdr;
                            configDefinitions=defs;
                            let normalConfig=document.getElementById('configPage');
                            let xdrParent=document.getElementById('xdrPage');
                            if (normalConfig) createConfigDefinitions(normalConfig,capabilities,defs,false);
                            if (xdrParent) createConfigDefinitions(xdrParent,capabilities,defs,true);
                            resetForm();
                        })
                })
        })
        .catch(function (err) { alert("unable to load config: " + err) })
}
function converterInfo() {
    getJson("api/converterInfo").then(function (json) {
        let text = "<h3>Converted entities</h3>";
        text += "<p><b>NMEA0183 to NMEA2000:</b><br/>";
        text += "   " + (json.nmea0183 || "").replace(/,/g, ", ");
        text += "</p>";
        text += "<p><b>NMEA2000 to NMEA0183:</b><br/>";
        text += "   " + (json.nmea2000 || "").replace(/,/g, ", ");
        text += "</p>";
        showOverlay(text, true);
    });
}
function handleTab(el) {
    let activeName = el.getAttribute('data-page');
    if (!activeName) return;
    let activeTab = document.getElementById(activeName);
    if (!activeTab) return;
    let all = document.querySelectorAll('.tabPage');
    for (let i = 0; i < all.length; i++) {
        all[i].classList.add('hidden');
    }
    let tabs = document.querySelectorAll('.tab');
    for (let i = 0; i < all.length; i++) {
        tabs[i].classList.remove('active');
    }
    el.classList.add('active');
    activeTab.classList.remove('hidden');
}
/**
 *
 * @param {number} coordinate
 * @param axis
 * @returns {string}
 */
 function formatLonLatsDecimal(coordinate,axis){
    coordinate = (coordinate+540)%360 - 180; // normalize for sphere being round

    let abscoordinate = Math.abs(coordinate);
    let coordinatedegrees = Math.floor(abscoordinate);

    let coordinateminutes = (abscoordinate - coordinatedegrees)/(1/60);
    let numdecimal=2;
    //correctly handle the toFixed(x) - will do math rounding
    if (coordinateminutes.toFixed(numdecimal) == 60){
        coordinatedegrees+=1;
        coordinateminutes=0;
    }
    if( coordinatedegrees < 10 ) {
        coordinatedegrees = "0" + coordinatedegrees;
    }
    if (coordinatedegrees < 100 && axis == 'lon'){
        coordinatedegrees = "0" + coordinatedegrees;
    }
    let str = coordinatedegrees + "\u00B0";

    if( coordinateminutes < 10 ) {
        str +="0";
    }
    str += coordinateminutes.toFixed(numdecimal) + "'";
    if (axis == "lon") {
        str += coordinate < 0 ? "W" :"E";
    } else {
        str += coordinate < 0 ? "S" :"N";
    }
    return str;
};
let valueFormatters = {
    formatCourse: {
        f: function (v) {
            let x = parseFloat(v);
            let rt = x * 180.0 / Math.PI;
            if (rt > 360) rt -= 360;
            if (rt < 0) rt += 360;
            return rt.toFixed(0);
        },
        u: '°'
    },
    formatKnots: {
        f: function (v) {
            let x = parseFloat(v);
            x = x * 3600.0 / 1852.0;
            return x.toFixed(2);
        },
        u: 'kn'
    },
    formatWind: {
        f: function (v) {
            let x = parseFloat(v);
            x = x * 180.0 / Math.PI;
            if (x > 180) x = 180 - x;
            return x.toFixed(0);
        },
        u: '°'
    },
    mtr2nm: {
        f: function (v) {
            let x = parseFloat(v);
            x = x / 1852.0;
            return x.toFixed(2);
        },
        u: 'nm'
    },
    kelvinToC: {
        f: function (v) {
            let x = parseFloat(v);
            x = x - 273.15;
            return x.toFixed(0);
        },
        u: '°'
    },
    formatFixed0: {
        f: function (v) {
            let x = parseFloat(v);
            return x.toFixed(0);
        },
        u: ''
    },
    formatDepth: {
        f: function (v) {
            let x = parseFloat(v);
            return x.toFixed(1);
        },
        u: 'm'
    },
    formatLatitude: {
        f: function (v) {
            let x = parseFloat(v);
            if (isNaN(x)) return '-----';
            return formatLonLatsDecimal(x,'lat');
        },
        u: '°'
    },
    formatLongitude: {
        f: function (v) {
            let x = parseFloat(v);
            if (isNaN(x)) return '-----';
            return formatLonLatsDecimal(x,'lon');
        },
        u: ''
    },
    formatRot:{
        f: function(v){
            let x=parseFloat(v);
            if (isNaN(x)) return '---';
            x = x * 180.0 / Math.PI;
            return x.toFixed(2);
        },
        u:'°/s'
    },
    formatXte:{
        f: function(v){
            let x=parseFloat(v);
            if (isNaN(x)) return '---';
            return x.toFixed(0);
        },
        u:'m'
    }
}
function createDashboardItem(name, def, parent) {
    let frame = addEl('div', 'dash', parent);
    let title = addEl('span', 'dashTitle', frame, name);
    let value = addEl('span', 'dashValue', frame);
    value.setAttribute('id', 'data_' + name);
    let fmt=valueFormatters[def.format];
    if (def.format) value.classList.add(def.format);
    let footer = addEl('div','footer',frame);
    let src= addEl('span','source',footer);
    src.setAttribute('id','source_'+name);
    addEl('span','unit',footer,fmt?fmt.u:'');
    return value;
}
function createDashboard() {
    let frame = document.getElementById('dashboardPage');
    if (!frame) return;
    getJson("api/boatData").then(function (json) {
        frame.innerHTML = '';
        for (let n in json) {
            createDashboardItem(n, json[n], frame);
        }
        updateDashboard(json);
    });
}
function sourceName(v){
    if (v == 0) return "N2K";
    if (v == 1) return "USB";
    if (v == 2) return "SER";
    if (v >= 3) return "TCP";
    return "---";
}
function updateDashboard(data) {
    for (let n in data) {
        let de = document.getElementById('data_' + n);
        if (de) {
            if (data[n].valid) {
                let formatter;
                if (data[n].format && data[n].format != "NULL") {
                    let key = data[n].format.replace(/^\&/, '');
                    formatter = valueFormatters[key];
                }
                if (formatter) {
                    de.textContent = formatter.f(data[n].value);
                }
                else {
                    let v = parseFloat(data[n].value);
                    if (!isNaN(v)) {
                        v = v.toFixed(3)
                        de.textContent = v;
                    }
                    else {
                        de.textContent = data[n].value;
                    }
                }
            }
            else de.textContent = "---";
        }
        let src=document.getElementById('source_'+n);
        if (src){
            src.textContent=sourceName(data[n].source);
        }
    }
}

window.setInterval(update, 1000);
window.setInterval(function () {
    let dp = document.getElementById('dashboardPage');
    if (dp.classList.contains('hidden')) return;
    getJson('api/boatData').then(function (data) {
        updateDashboard(data);
    });
}, 1000);
window.addEventListener('load', function () {
    let buttons = document.querySelectorAll('button');
    for (let i = 0; i < buttons.length; i++) {
        let be = buttons[i];
        be.onclick = window[be.id]; //assume a function with the button id
    }
    forEl('.showMsgDetails', function (cd) {
        cd.addEventListener('change', function (ev) {
            let key = ev.target.getAttribute('data-key');
            if (!key) return;
            let el = document.getElementById(key);
            if (!el) return;
            if (ev.target.checked) el.classList.remove('hidden');
            else (el.classList).add('hidden');
        });
    });
    let tabs = document.querySelectorAll('.tab');
    for (let i = 0; i < tabs.length; i++) {
        tabs[i].addEventListener('click', function (ev) {
            handleTab(ev.target);
        });
    }
    loadConfigDefinitions();
    createDashboard();
    let statusPage=document.getElementById('statusPageContent');
    if (statusPage){
        let even=true;
        for (let c in counters){
            createCounterDisplay(statusPage,counters[c],c,even);
            even=!even;
        }
    }
});