/// <reference path="../lib1/innovaphone.lib1.js" />
/// <reference path="../ui1.lib/innovaphone.ui1.lib.js" />

innovaphone = innovaphone || {};
innovaphone.ui1 = innovaphone.ui1 || {};

innovaphone.ui1.TableConfig = innovaphone.ui1.TableConfig || function (mediaOrContainerQueryConditions, cl,
    styleTable, styleThead, styleTh, styleTr, styleTbody, styleTd,
    styleMediaTable, styleMediaThead, styleMediaTh, styleMediaTr, styleMediaTbody, styleMediaTd) {

    function checkClass(className) {
        return (className ? " " + className : "");
    }

    this.mediaOrContainerQueryConditions = mediaOrContainerQueryConditions || "@media (max-width:800px)";
    this.styleTable = styleTable || "";
    this.styleThead = styleThead || "";
    this.styleTh = styleTh || "";
    this.styleTr = styleTr || "";
    this.styleTbody = styleTbody || "";
    this.styleTd = styleTd || "";
    this.styleMediaTable = styleMediaTable || "";
    this.styleMediaThead = styleMediaThead || "";
    this.styleMediaTh = styleMediaTh || "";
    this.styleMediaTr = styleMediaTr || "";
    this.styleMediaTbody = styleMediaTbody || "";
    this.styleMediaTd = styleMediaTd || "";
    this.cl = checkClass(cl);
};

innovaphone.ui1.TableCss = innovaphone.ui1.TableCss || {};

innovaphone.ui1.Table = innovaphone.ui1.Table || function (cfg, texts, defaultDescending) {
    const ui1TableClass = "innovaphone-ui1-table";

    cfg = cfg || new innovaphone.ui1.TableConfig();

    this.createNode("table", cfg.styleTable, "", ui1TableClass + cfg.cl);

    var that = this,        
        thead = this.add(new innovaphone.ui1.Node("thead", cfg.styleThead, null, ui1TableClass + cfg.cl)),
        theadTr = thead.add(new innovaphone.ui1.Node("tr", cfg.styleTr, null, ui1TableClass + cfg.cl)),
        tbody = this.add(new innovaphone.ui1.Node("tbody", cfg.styleTbody, null, ui1TableClass + cfg.cl)),
        columns = [],
        ascending = (defaultDescending === true ? false : true),
        lastSortColumn = 0,
        rows = {},
        sortedRows = [];

    function getClasses(className) {
        return (className == "" ? ui1TableClass : ui1TableClass + "." + className.trim());
    }

    function init() {
        // create a unique key depending on the used media query and the classes to prevent adding the same CSS multiple times
        var key = cfg.mediaOrContainerQueryConditions + ";" + cfg.cl;
        if (!innovaphone.ui1.TableCss.hasOwnProperty(key)) {
            innovaphone.ui1.TableCss[key] = true;
            var style = document.createElement("style"),
                css = cfg.mediaOrContainerQueryConditions + " {\r\n" +
                    "table." + getClasses(cfg.cl) + " {\r\n" +
                    "    border: 0;\r\n" + cfg.styleMediaTable +
                    "}\r\n" +
                    "thead." + getClasses(cfg.cl) + " {\r\n" +
                    "    display: none;\r\n" + cfg.styleMediaThead +
                    "}\r\n" +
                    "th." + getClasses(cfg.cl) + " {\r\n" +
                    "    " + cfg.styleMediaTh +
                    "}\r\n" +
                    "tr." + getClasses(cfg.cl) + " {\r\n" +
                    "    display: block;\r\n" + cfg.styleMediaTr +
                    "}\r\n" +
                    "td." + getClasses(cfg.cl) + " {\r\n" +
                    "    display: block;\r\n" +
                    "    " + cfg.styleMediaTd +
                    "}\r\n" +
                    "td." + getClasses(cfg.cl) + " {\r\n" +
                    "    " + cfg.styleMediaTbody +
                    "}\r\n" +
                    "td." + getClasses(cfg.cl) + "::before {\r\n" +
                    "    content: attr(data-label); \r\n" +
                    "    float: left; \r\n" +
                    "} \r\n" +
                    "td." + getClasses(cfg.cl) +":last-child {" +
                    "\r\n" +
                    "    border-bottom: 0;\r\n" +
                    "} \r\n" +
                    "}";
            style.setAttribute("type", "text/css");
            style.appendChild(document.createTextNode(css));
            document.head.appendChild(style);
        }
    }

    function addColumn(headerTranslationId, headerText) {
        var th = theadTr.add(new innovaphone.ui1.Node("th", "cursor:pointer;" + cfg.styleTh, headerText, ui1TableClass + cfg.cl));
        if (texts && headerTranslationId) {
            th.addTranslation(texts, headerTranslationId, "innerText");
        }
        columns.push({ text: headerText, tl: headerTranslationId });
        var column = columns.length - 1;
        th.addEvent("click", function () {
            if (lastSortColumn == column) toggleSortOrder();
            else ascending = true;
            sort(column);
        });
        return th;
    }

    function clearColumns() {
        clear();
        columns.length = 0;
        theadTr.clear();
    }

    function addRow(id, data, object, dataSorting) {
        if (rows.hasOwnProperty(id)) return;
        if (data.length != columns.length) {
            console.warn("innovaphone.ui1.table:addRow row ignored as data.length!=columns.length");
            return;
        }
        if (dataSorting && dataSorting.length != columns.length) {
            console.warn("innovaphone.ui1.table:addRow row ignored as dataSorting.length!=columns.length");
            return;
        }
        var row = new innovaphone.ui1.Node("tr", cfg.styleTr, null, ui1TableClass + cfg.cl),
            closestRow = null;
        row.tds = [];
        row.id = id;
        row.object = object;
        for (var i = 0; i < data.length; i++) {
            var td = row.add(new innovaphone.ui1.Node("td", cfg.styleTd, null, ui1TableClass + cfg.cl));
            if (data[i] instanceof innovaphone.ui1.Node) {
                td.add(data[i]);
            }
            else {
                td.addText(data[i]);
            }
            if (columns[i].tl) {
                td.setAttribute("data-label", texts.text(columns[i].tl));
            }
            else {
                td.setAttribute("data-label", columns[i].text);
            }
            if (dataSorting && dataSorting[i]) {
                td.sorting = td.dataSorting = getSortValue(dataSorting[i]);
            }
            else {
                td.sorting = getSortValue(td.container.innerText.toLowerCase());
            }
            row.tds.push(td);
            if (i == lastSortColumn) {
                closestRow = findClosestSortRow(row);
            }
        }
        rows[id] = row;
        if (closestRow) tbody.add(row, closestRow);
        else tbody.add(row);
        return row;
    }

    function getSortValue(value) {
        if (!isNaN(value)) return parseFloat(value);
        return value;
    }

    function findClosestSortRow(row) {
        if (sortedRows.length == 0) {
            sortedRows.push(row);
            return null;
        }

        let left = 0,
            right = sortedRows.length - 1,
            index = -1,
            target = row.tds[lastSortColumn].sorting,
            pushBack = false;
        
        while (left <= right) {
            const mid = Math.floor((left + right) / 2);
            if (sortedRows[mid].tds[lastSortColumn].sorting === target) {
                index = mid;
                break;
            }
            if ((ascending && sortedRows[mid].tds[lastSortColumn].sorting < target) ||
               (!ascending && sortedRows[mid].tds[lastSortColumn].sorting > target)) left = mid + 1;
            else right = mid - 1;
        }
        if (index == -1) {
            if (right < 0) index = 0;
            else if (left >= sortedRows.length) {
                index = sortedRows.length - 1;
                pushBack = true;
            }
            else if ((ascending && Math.abs(sortedRows[right].tds[lastSortColumn].sorting - target) < Math.abs(sortedRows[left].tds[lastSortColumn].sorting - target)) ||
                    (!ascending && Math.abs(sortedRows[right].tds[lastSortColumn].sorting - target) > Math.abs(sortedRows[left].tds[lastSortColumn].sorting - target))) index = right;
            else index = left;
        }
        let ret = null;
        if (pushBack) sortedRows.push(row);
        else {
            ret = sortedRows[index];
            sortedRows.splice(index, 0, row);
        }
        return ret;
    }

    function removeRow(id) {
        if (!rows.hasOwnProperty(id)) return null;

        tbody.rem(rows[id]);
        var row = rows[id];
        delete rows[id];
        for (var i = 0; i < sortedRows.length; i++) {
            if (sortedRows[i].id == id) {
                sortedRows.splice(i, 1);
                break;
            }
        }
        return row;
    }

    function onLanguageChanged() {
        window.setTimeout(function () { // ensure that we run after all other language changing things have been handled
            Object.keys(rows).forEach(function (key, index) {
                for (var j = 0; j < rows[key].tds.length; j++) {
                    if (columns[j].tl) {
                        rows[key].tds[j].setAttribute("data-label", texts.text(columns[j].tl));
                    }
                    if (!columns[j].dataSorting) {
                        td.sorting = getSortValue(td.container.innerText.toLowerCase());                        
                    }
                }
            });
            sort(lastSortColumn);
        }, 0);        
    }

    function getRows() {
        return rows;
    }

    function toggleSortOrder() {
        ascending = !ascending;
    }

    function sort(column) {
        if (column == undefined) column = lastSortColumn;
        sortedRows = Object.values(rows);
        sortedRows.sort(function (a, b) {
            if (ascending) {
                return a.tds[column].sorting > b.tds[column].sorting ? 1 : -1;
            } else {
                return a.tds[column].sorting < b.tds[column].sorting ? 1 : -1;
            }
        });

        for (var i = 0; i < sortedRows.length; i++) {
            tbody.add(sortedRows[i]);
        }        
        lastSortColumn = column;
    }

    function clear() {
        if (texts) {
            texts.clear(tbody);
        }
        tbody.clear();
        rows = {};
        sortedRows.length = 0;
    }

    this.addColumn = addColumn;
    this.addRow = addRow;
    this.removeRow = removeRow;
    this.getRows = getRows;
    this.onLanguageChanged = onLanguageChanged;
    this.toggleSortOrder = toggleSortOrder;
    this.sort = sort;
    this.clear = clear;
    this.clearColumns = clearColumns;

    init();
};
innovaphone.ui1.Table.prototype = innovaphone.ui1.nodePrototype;
