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

var innovaphone = innovaphone || {};
innovaphone.ui1 = innovaphone.ui1 || {};
innovaphone.ui1.Editor = innovaphone.ui1.Editor || function (style, scrollWidth, scrollColor, scrollStyle, onCtrlEnter, onEsc, onChanged, onFocus, onEmphasis, onUndo, onRedo, cl) {
    this.createNode("div", style + "; overflow-x:hidden; overflow-y:auto", null, cl);
    var that = this;
    var content = this.add(new innovaphone.ui1.Div(scrollStyle));
    this.content = content;

    content.container.contentEditable = true;
    content.container.style.outlineStyle = "none";
    if (onFocus) {
        content.addEvent("focus", function () { onFocus(true) });
        content.addEvent("blur", function () { onFocus(false) });
    }

    content.addEvent('keydown', onKeyDown);
    content.addEvent('keyup', onKeyUp);
    content.addEvent('mouseup', onMouseUp);
    content.addEvent('paste', onPaste);
    content.addEvent('input', function (e) { if (onChanged) onChanged(0, getSelection(), e) });
    if (onEmphasis) document.addEventListener('selectionchange', onSelectionChange);

    // event handler

    function onKeyDown(e) {
        switch (e.keyCode) {
            case 13:
                if (onCtrlEnter) {
                    if (onCtrlEnter(e.ctrlKey, empty(content.container))) {
                        e.preventDefault();
                        return;
                    }
                }
                if (e.shiftKey) {
                    e.preventDefault();
                    return;
                }
                break;
            case 27:
                if (onEsc) {
                    onEsc();
                }
                return;
            case 89:
                if (e.ctrlKey && onRedo) {
                    e.preventDefault();
                    onRedo();
                    return;
                }
                break;
            case 90:
                if (e.ctrlKey && onUndo) {
                    e.preventDefault();
                    onUndo();
                    return;
                }
                break;
            case 78:
                if (e.ctrlKey && e.shiftKey) {
                    e.preventDefault();
                    normalize(content.container);
                    content.container.normalize();
                    return;
                }
                break;
            case 9:
                if (e.shiftKey) unIndentSel();
                else createList();
                e.preventDefault();
                break;
        }
        if (onChanged) onChanged(e.keyCode, getSelection(), e);
    }

    function onKeyUp(e) {
    }

    function onMouseUp(e) {
    }

    function onPaste(e) {
        if (e.clipboardData.getData('text/html')) {
            e.preventDefault();
            e.stopPropagation();
            var sel = getSelection();
            var start = sel.anchorNode;
            var startOfs = sel.anchorOffset;
            var end = sel.focusNode;
            var endOfs = sel.focusOffset;
            var p = sel.anchorNode.compareDocumentPosition(sel.focusNode);
            if (p == 2 || (p == 0 && startOfs > endOfs)) [start, startOfs, end, endOfs] = [end, endOfs, start, startOfs];
            if (start.nodeName == "BR") start = start.parentNode;

            var insert = document.createElement("span");
            insert.innerHTML = (start.nodeType == 3 ? start.textContent.slice(0, startOfs) : "") + cleanupHTML(e.clipboardData.getData('text/html')).trim();
            var rest = document.createElement("span");
            if (end.nodeType == 3) rest.appendChild(document.createTextNode(end.textContent.slice(endOfs)));
            var cursor = lastText(insert);
            insert.appendChild(rest);

            if (start === content.container) {
                start.insertBefore(insert, start.firstChild);
            }
            else {
                if (start.nodeType == 3) {
                    start.parentNode.insertBefore(insert, start);
                    start.remove();
                }
                else {
                    start.insertBefore(insert, start.firstChild);
                }
            }
            sel.deleteFromDocument();
            normalize(insert.parentNode);
            setCursor(cursor, true);
        }
    }

    function onSelectionChange() {
        var sel = getSelection();
        if (!content.container.contains(sel.anchorNode) || !content.container.contains(sel.focusNode)) return;

        var b, i, u, s, ul, ol, dl, h1, h2, h3, h4, h5, h6;
        traverseSel(function (n) {
            var e;
            if (n.nodeType != 3 || !n.nodeValue.length) return;
            if (exists(content.container, n, "B")) b = b == undefined ? true : b; else b = false;
            if (exists(content.container, n, "I")) i = i == undefined ? true : i; else i = false;
            if (exists(content.container, n, "U")) u = u == undefined ? true : u; else u = false;
            if (exists(content.container, n, "STRIKE")) s = s == undefined ? true : s; else s = false;

            e = exists(content.container, n, "UL");
            if (ul === undefined) ul = e; else if (ul !== e) ul = null;
            e = exists(content.container, n, "OL");
            if (ol === undefined) ol = e; else if (ol !== e) ol = null;
            e = exists(content.container, n, "DL");
            if (dl === undefined) dl = e; else if (dl !== e) dl = null;

            if (exists(content.container, n, "H1")) h1 = h1 == undefined ? true : h1; else h1 = false;
            if (exists(content.container, n, "H2")) h2 = h2 == undefined ? true : h2; else h2 = false;
            if (exists(content.container, n, "H3")) h3 = h3 == undefined ? true : h3; else h3 = false;
            if (exists(content.container, n, "H4")) h4 = h4 == undefined ? true : h4; else h4 = false;
            if (exists(content.container, n, "H5")) h5 = h5 == undefined ? true : h5; else h5 = false;
            if (exists(content.container, n, "H6")) h6 = h6 == undefined ? true : h6; else h6 = false;
        });
        if (!(h1 || h2 || h3 || h4 || h5 || h6)) {
            b = b |= document.queryCommandState("bold");
            i = i |= document.queryCommandState("italic");
            u = u |= document.queryCommandState("underline");
            s = s |= document.queryCommandState("strikeThrough");
        }
        onEmphasis(b, i, u, s, ul, ol, dl, h1, h2, h3, h4, h5, h6);

        function exists(r, n, t) {
            if (!r.contains(n)) return null;
            if (n.nodeType == 1 && n.tagName == t) return n;
            if (n === r) return null;
            return exists(r, n.parentNode, t);
        }
    }

    // helper functions

    // traverese thru all nodes of the current selection
    // callback is done on going up and going to next sibling, so no callbacks on the "way down" to last node
    function traverseSel(onnode) {
        var sel = getSelection();
        var start = sel.anchorNode;
        var startOfs = sel.anchorOffset;
        var end = sel.focusNode;
        var endOfs = sel.focusOffset;
        var p = sel.anchorNode.compareDocumentPosition(sel.focusNode);
        if (p == 2) [start, startOfs, end, endOfs] = [end, endOfs, start, startOfs];
        else if (p == 0 && startOfs > endOfs) [startOfs, endOfs] = [endOfs, startOfs];
        node(start);

        function node(i) {
            var n;
            if (i.firstChild) {
                n = i.firstChild;
            }
            else {
                n = i;
                while (n != end && !n.nextSibling) n = n.parentNode;
                if (n == end) n = null
                else n = n.nextSibling;
            }
            onnode(i, i === start ? startOfs : 0, i === end ? endOfs : undefined);
            if (n) node(n);
        }
    }

    function lastText(node) {
        if (node.nodeType == 3) {
            return node;
        }
        else {
            return node.lastChild ? lastText(node.lastChild) : node;
        }
    }

    // returns element which was created for after the split element
    // split is done on the level of the first non-text node (text, B, I, U) or the node type provided as tag
    function split(r, ofs, tag) {
        var n;
        while (isTextNode(r)) {
            if (r.nodeType == 3) {
                n = document.createTextNode(r.nodeValue.slice(ofs));
                r.nodeValue = r.nodeValue.slice(0, ofs);
                r.parentNode.insertBefore(n, r.nextSibling);
            }
            else {
                var i = n;
                n = r.parentNode.insertBefore(document.createElement(r.tagName), r.nextSibling);
                while (i) {
                    let t = i.nextSibling;
                    n.appendChild(r.removeChild(i));
                    i = t;
                }
                if (r.tagName == tag) break;
            }
            r = r.parentNode;
        }
        return n;
    }

    // use normalize to cleanup an HTML
    function cleanupHTML(text) {
        const parser = new DOMParser();
        const html = parser.parseFromString(text, 'text/html');
        normalize(html.body);
        html.normalize();
        return html.body.innerHTML.replace(/<br>/ig, "<br/>");
    }

    // remove whitespace at begining and end of text
    function trimHTML(text) {
        const parser = new DOMParser();
        const body = parser.parseFromString(text, 'text/html')?.body;
        if (!body) return text;
        while (body.firstChild && empty(body.firstChild)) body.removeChild(body.firstChild);
        while (body.lastChild && empty(body.lastChild)) body.removeChild(body.lastChild);
        return body.innerHTML.replace(/<br>/ig, "<br/>");
    }

    // removes all but the supported nodes and all attributes except href on A
    function normalize(node) {
        for (var n = node.firstChild; n;) {
            var x = n.nextSibling;
            if (n.nodeType != 1 && n.nodeType != 3) {
                node.removeChild(n);
            }
            else if (n.nodeType == 3) {
                let t = n.textContent;
                if (t.length > 1) {
                    let z = t.indexOf("\u200B");
                    if (z > -1) {
                        if (z > 0) n = n.splitText(z);
                        n.splitText(1);
                        n.remove();
                    }
                }
            }
            else if (['B', 'STRONG', 'I', 'U', 'P', 'UL', 'OL', 'DL', 'LI', 'DT', 'DD', 'DIV', 'TABLE', 'TR', 'TD'].indexOf(n.nodeName) != -1) {
                while (n.attributes.length) n.attributes.removeNamedItem(n.attributes.item(0).name);
                normalize(n);
            }
            else if (['CODE', 'PRE', 'SPAN', 'TBODY'].indexOf(n.nodeName) != -1) {
                let b = n.nextSibling;
                if (n.firstChild) x = node.insertBefore(n.removeChild(n.firstChild), b);
                while (n.firstChild) node.insertBefore(n.removeChild(n.firstChild), b);
                node.removeChild(n);
            }
            else if (n.nodeName == 'BR') {
                if (n.previousSibling && !n.nextSibling) n.remove();
            }
            else if (['A'].indexOf(n.nodeName) != -1) {
                let href = n.attributes.getNamedItem("href");
                while (n.attributes.length) n.attributes.removeNamedItem(n.attributes.item(0).name);
                if (href) n.setAttribute("href", href.value);
                n.setAttribute("target", "_blank");
                normalize(n);
            }
            n = x;
        }
    }

    // creates a selection at given position and collapses it to set cursor anywhere
    function setCursor(node, end) {
        if (!node) {
            node = content.container;
            if (end) while (node.lastChild) node = node.lastChild;
            else while (node.firstChild) node = node.firstChild;
        }
        var r = document.createRange();
        for (var e = node; e.lastChild; e = e.lastChild);
        r.setStart(e, e.nodeType == 3 && end ? e.nodeValue.length : 0);
        r.setEnd(e, e.nodeType == 3 && end ? e.nodeValue.length : 0);
        r.collapse();
        getSelection().removeAllRanges();
        getSelection().addRange(r);
        if (node.nodeType == 1 && node.getBoundingClientRect().bottom > that.container.getBoundingClientRect().bottom) {
            that.container.scrollTop += (node.getBoundingClientRect().bottom - that.container.getBoundingClientRect().bottom)
        }
    }

    function savedCursor(node, ofs) {
        var r = document.createRange();
        r.setStart(node, ofs);
        r.setEnd(node, ofs);
        r.collapse();
        getSelection().removeAllRanges();
        getSelection().addRange(r);
    }

    function isTextNode(node) {
        return node && (node.nodeType == 3 || node.tagName == "B" || node.tagName == "I" || node.tagName == "U" || node.tagName == "STRIKE" || node.tagName == "SPAN" || node.tagName == "BR");
    }

    function isListNode(node) {
        return node && (node.tagName == "UL" || node.tagName == "OL" || node.tagName == "DL");
    }

    function isEmphasis(node) {
        if (node.nodeType == 1 && ["B", "I", "U", "STRIKE"].indexOf(node.tagName) != -1) return true;
        return false;
    }

    function isItemNode(node) {
        return node && (node.tagName == "LI" || node.tagName == "DT" || node.tagName == "DD");
    }

    function itemForList(node) {
        switch (node.tagName) {
            case "UL": return "LI";
            case "OL": return "LI";
            case "DL": return "DD";
        }
        return "P";
    }

    function empty(node, next) {
        if (!node) return true;
        if (node.nodeType == 3 && node.textContent.length) {
            let s = node.textContent;
            if (s.search(/\S/) != -1) return false;
        }
        if (node.firstChild) {
            if (!empty(node.firstChild, true)) return false;
        }
        if (next && node.nextSibling) {
            if (!empty(node.nextSibling, true)) return false;
        }
        return true;
    }

    function copyNode(node, tag) {
        var c = document.createElement(tag);
        while (node.firstChild) c.appendChild(node.removeChild(node.firstChild));
        return c;
    }

    // text manipulation

    function emphasis(tag, off) {
        var sel = getSelection();
        if (!content.container.contains(sel.focusNode)) return;
        if (sel.isCollapsed) {
            if (off) {
                document.execCommand(tag == "B" ? "bold" : (tag == "I" ? "italic" : (tag == "U" ? "underline" : "strike")));
                if (exists(content.container, sel.focusNode, tag)) {
                    let t = split(sel.focusNode, sel.focusOffset, tag);
                    t = t.parentNode.insertBefore(document.createTextNode("\u200B"), t);
                    setCursor(t, true);
                }
            }
            else {
                let f = sel.focusNode;
                if (!exists(content.container, f, tag)) {
                    let t = document.createElement(tag);
                    if (f.nodeType == 3) {
                        f.parentNode.insertBefore(t, f.nextSibling);
                        if (sel.focusOffset != f.textContent.length) {
                            f.parentNode.insertBefore(document.createTextNode(f.textContent.slice(sel.focusOffset)), t.nextSibling);
                            f.textContent = f.textContent.slice(0, sel.focusOffset);
                        };
                    }
                    else {
                        f.insertBefore(t, f.firstChild);
                    }
                    t.appendChild(document.createTextNode("\u200B"));
                    setCursor(t, true);
                }
            }
        }
        else {
            var r;
            var e = [];

            traverseSel(function (n, sOfs, eOfs) {
                if (n.nodeType == 3) {
                    if (!r) r = { sNode: n, sOfs: sOfs };
                    r.eNode = n;
                    r.eOfs = eOfs;
                }

                var t = exists(content.container, n, tag);
                if (t && e.indexOf(t) == -1) e.push(t);
            });

            while (e.length) {
                var n = e.pop();
                while (n.firstChild) n.parentNode.insertBefore(n.removeChild(n.firstChild), n);
                n.remove();
            }

            // add emphasis for selection
            if (!off) {
                r = null;
                traverseSel(function (n, sOfs, eOfs) {
                    if (n.nodeType == 3 && n.nodeValue.length && !exists(content.container, n, tag)) {
                        var t = document.createElement(tag);
                        var text = t.appendChild(document.createTextNode(n.nodeValue.slice(sOfs, eOfs)));

                        if (sOfs) n.parentNode.insertBefore(document.createTextNode(n.nodeValue.slice(0, sOfs)), n);
                        n.parentNode.insertBefore(t, n);
                        if (eOfs) n.parentNode.insertBefore(document.createTextNode(n.nodeValue.slice(eOfs)), n);
                        if (!r) r = { sNode: text, sOfs: 0 };
                        r.eNode = text;
                        r.eOfs = eOfs - sOfs;
                        n.parentNode.removeChild(n);
                    }
                });
            }
            var range = document.createRange();
            range.setStart(r.sNode, r.sOfs);
            range.setEnd(r.eNode, r.eOfs);
            getSelection().removeAllRanges();
            getSelection().addRange(range);
        }
        content.container.normalize();
        onSelectionChange();

        function exists(r, n, t) {
            if (!r.contains(n)) return null;
            if (n.nodeType == 1 && n.tagName == t) return n;
            if (n === r) return null;
            return exists(r, n.parentNode, t);
        }
    }

    function createList(tag) {
        var root;
        var leafs = [];
        traverseSel(function (n) {
            if ((n.nodeType == 3 && n.nodeValue.length) || (n.nodeType == 1 && n.tagName == "BR")) {
                leafs.push(n);
                if (!root) {
                    root = n.parentNode;
                }
                else {
                    while (!n.contains(root)) n = n.parentNode;
                    root = n;
                }
                while (isTextNode(root) || root.tagName == "LI" || root.tagName == "DD") root = root.parentNode;
            }
        });
        if (!tag) {
            if (root?.tagName == "UL" || root?.tagName == "OL") tag = root.tagName;
            else tag = "DL";
        }
        if (!root && getSelection().isCollapsed) {
            root = getSelection().focusNode;
            if (root.tagName == "BR") {
                leafs.push(root);
                root = root.parentNode;
            }
            if (!root.firstChild) {
                let p = root.appendChild(document.createElement("DIV"));
                leafs.push(p.appendChild(document.createElement("BR")));
            }
            else {
                for (let l = root.firstChild; l; l = l.nextSibling) leafs.push(l);
            }
        }
        if (root && content.container.contains(root)) {
            var l, li, n;
            for (var c = root.firstChild, i = 0; c; c = n) {
                n = c.nextSibling;
                if (c.contains(leafs[i])) {
                    while (c.contains(leafs[i])) i++;
                    while (isTextNode(c) && isTextNode(c.previousSibling)) { n = c; c = c.previousSibling; };
                    if (!l) {
                        if (isListNode(root)) {
                            if (c.previousSibling) {
                                l = c.previousSibling.appendChild(document.createElement(tag));
                            }
                            else {
                                var t = root.insertBefore(document.createElement(tag == "DL" ? "DD" : "LI"), c);
                                t.appendChild(document.createElement("BR"));
                                l = t.appendChild(document.createElement(tag));
                            }
                        }
                        else {
                            l = root.insertBefore(document.createElement(tag), c);
                        }
                    }
                    if (!li || !isTextNode(li.firstChild) || !isTextNode(c)) {
                        li = l.appendChild(document.createElement(tag == "DL" ? "DD" : "LI"));
                    }
                    if (isTextNode(c)) {
                        li.appendChild(root.removeChild(c));
                        while (isTextNode(n)) {
                            c = n; n = n.nextSibling;
                            li.appendChild(root.removeChild(c));
                        }
                    }
                    else if (c.tagName == "P" || c.tagName == "DIV") {
                        root.removeChild(c);
                        while (c.firstChild) li.appendChild(c.removeChild(c.firstChild));
                    }
                    else if (isListNode(c)) {
                        li.appendChild(document.createElement("BR"));
                        li.appendChild(root.removeChild(c));
                    }
                    else if (c.tagName == (tag == "DL" ? "DD" : "LI")) {
                        while (c.firstChild) li.appendChild(c.removeChild(c.firstChild));
                        root.removeChild(c);
                    }
                    else {
                        li.appendChild(root.removeChild(c));
                    }
                }
            }
            if (!root.firstChild) root.parentNode.removeChild(root);
            setCursor(l, true);
        }
    }

    function remList(rem) {
        if (rem?.nodeType == 1) {
            while (rem.firstChild) {
                var c = rem.firstChild;
                var p = document.createElement("P");
                if (c.nodeType == 3) {
                    p.appendChild(rem.removeChild(c));
                }
                else {
                    while (c.firstChild) p.appendChild(c.removeChild(c.firstChild));
                }
                rem.parentNode.insertBefore(p, rem);
                rem.removeChild(rem.firstChild);
            }
            rem.parentNode.removeChild(rem);
            onSelectionChange();
        }
    }

    function unIndentSel() {
        var s = [];
        var f = getSelection().focusNode;
        var o = getSelection().focusOffset;
        if (f.nodeType != 3 && f.nodeName != "BR") {
            f = f.firstChild;
            o = 0;
        }
        traverseSel(function (n) {
            if ((n.nodeType == 3 && n.nodeValue.length) || n.nodeName == "BR") {
                s.push(n);
            }
        });
        while (s.length) unIndent(s.shift());
        savedCursor(f, o);
    }

    function unIndent(node) {
        if (content.container.contains(node)) {
            var c = node;
            var r = node.parentNode;
            while (r != content.container && !isListNode(r)) {
                c = r;
                r = r.parentNode;
            }
            if (r != content.container) {
                var p = r.parentNode;

                if (p == content.container) {
                    p = p.insertBefore(document.createElement("P"), r);
                    p.appendChild(r.parentNode.removeChild(r), r);
                }

                var s = r.nextSibling;
                if (s) {
                    while (s) {
                        let save = s.nextSibling
                        c.appendChild(p.removeChild(s));
                        s = save;
                    }
                }

                s = c.nextSibling;
                if (itemForList(p.parentNode) != c.tagName) c = copyNode(r.removeChild(c), itemForList(p.parentNode));
                else r.removeChild(c);
                p.parentNode.insertBefore(c, p.nextSibling);

                if (s) {
                    let n = c.appendChild(document.createElement(r.tagName));
                    while (s) {
                        let save = s.nextSibling
                        n.appendChild(r.removeChild(s));
                        s = save;
                    }
                }
                if (!r.firstChild) p.removeChild(r);
                if (!p.firstChild) p.parentNode.removeChild(p);
            }
        }
    }

    // interface functions

    this.insert = function (text) {
        var s = getSelection();
        if (s && content.container.contains(s.focusNode)) {
            var f = s.focusNode;
            var o = s.focusOffset;
            f.textContent = f.textContent.slice(0, o) + text + f.textContent.slice(o);
            setCursor(f, true);
        }
    }

    this.backspace = function (num) {
        var s = getSelection();
        if (s && content.container.contains(s.focusNode)) {
            var f = s.focusNode;
            var o = s.focusOffset;
            if (s) f.textContent = f.textContent.slice(0, o - num) + f.textContent.slice(o);
            s.collapse(f, o - num);
        }
    }

    this.insertHtml = function (html) {
        var s = getSelection();
        if (s && content.container.contains(s.focusNode)) {
            var focusNode = s.focusNode;
            var focusOfs = s.focusOffset;
            if (s.focusNode.nodeType != 3) {
                focusNode = focusNode.insertBefore(document.createTextNode(""), focusNode.firstChild);
                focusOfs = 0;
            }
            var n = document.createElement("div");
            n.innerHTML = html;
            n.firstChild.style.color = content.container.parentNode.style.color;
            var t = document.createTextNode(focusNode.nodeValue.slice(focusOfs));
            focusNode.nodeValue = focusNode.nodeValue.slice(0, focusOfs);
            focusNode.parentNode.insertBefore(t, focusNode.nextSibling);
            focusNode.parentNode.insertBefore(n.firstChild, focusNode.nextSibling);
            setCursor(t);
        }
    }

    this.disableInputs = function (node) {
        if (!node) node = content.container;
        if (node.nodeName == "INPUT") {
            node.setAttribute("disabled", "true");
            if (node.checked) node.setAttribute("checked", "true");
            else node.removeAttribute("checked");
        }
        for (var n = node.firstChild; n; n = n.nextSibling) this.disableInputs(n);
    }

    this.enableInputs = function (node) {
        if (!node) node = content.container;
        if (node.nodeName == "INPUT") {
            var span = document.createElement("SPAN");
            span.setAttribute("contenteditable", "false");
            node.parentNode.insertBefore(span, node);
            span.appendChild(node.parentNode.removeChild(node));
            node.style.color = content.container.parentNode.style.color;
            node.removeAttribute("disabled");
        }
        var next;
        for (var n = node.firstChild; n; n = next) {
            next = n.nextSibling;
            this.enableInputs(n);
        }
    }

    this.indent = createList;
    this.unIndent = unIndentSel;
    this.l = function (tag, rem) { if (rem) remList(rem); else createList(tag); };
    this.emphasis = emphasis;
    this.text = function (noempty) { return noempty && empty(content.container) ? null : content.container.innerHTML.replace(/<br>/g, "<br/>"); }
    this.setText = function (html, nochange) { content.addHTML(html); if (html != null) setTimeout(function () { setCursor(null, true) }, 10); if (!nochange && onChanged) onChanged(); return this };
    this.focus = function () { content.container.focus(); }
    this.blur = function () { content.container.blur(); }
    this.contentHeight = function () { return content.container.scrollHeight; };
    this.keydown = function (e) { onKeyDown(e) };
    this.cleanupHTML = cleanupHTML;
    this.trimHTML = trimHTML;
    this.normalize = normalize;
    this.setCursor = setCursor;
};
innovaphone.ui1.Editor.prototype = innovaphone.ui1.nodePrototype;
