/**
 * Copyright JS Foundation and other contributors, http://js.foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/
(function() {


    // var template = '<script type="text/x-red" data-template-name="_json"></script>';
    var template = '<script type="text/x-red" data-template-name="_json">'+
        '<ul id="red-ui-editor-type-json-tabs"></ul>'+
        '<div id="red-ui-editor-type-json-tab-raw" class="red-ui-editor-type-json-tab-content hide">'+
            '<div class="form-row" style="margin-bottom: 3px; text-align: right;">'+
                '<span class="button-group">'+
                    '<button id="node-input-json-reformat" class="red-ui-button red-ui-button-small"><span data-i18n="jsonEditor.format"></span></button>'+
                '<span class="button-group">'+
            '</div>'+
            '<div class="form-row node-text-editor-row">'+
                '<div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-json"></div>'+
            '</div>'+
        '</div>'+
        '<div id="red-ui-editor-type-json-tab-ui" class="red-ui-editor-type-json-tab-content hide">'+
            '<div id="red-ui-editor-type-json-tab-ui-container"></div>'+
        '</div>'+
    '</script>';

    var activeTab;

    function insertNewItem(parent,index,copyIndex,readOnly) {
        var newValue = "";

        if (parent.children.length > 0) {
            switch (parent.children[Math.max(0,Math.min(parent.children.length-1,copyIndex))].type) {
                case 'string': newValue = ""; break;
                case 'number': newValue = 0; break;
                case 'boolean': newValue = true; break;
                case 'null': newValue = null; break;
                case 'object': newValue = {}; break;
                case 'array': newValue = []; break;
            }
        }
        var newKey;
        if (parent.type === 'array') {
            newKey = parent.children.length;
        } else {
            var usedKeys = {};
            parent.children.forEach(function(child) { usedKeys[child.key] = true })
            var keyRoot = "item";
            var keySuffix = 2;
            newKey = keyRoot;
            while(usedKeys[newKey]) {
                newKey = keyRoot+"-"+(keySuffix++);
            }
        }
        var newItem = handleItem(newKey,newValue,parent.depth+1,parent,readOnly);
        parent.treeList.insertChildAt(newItem, index, true);
        parent.treeList.expand();
    }
    function showObjectMenu(button,item,readOnly) {
        var elementPos = button.offset();
        var options = [];
        if (item.parent) {
            options.push({id:"red-ui-editor-type-json-menu-insert-above", icon:"fa fa-toggle-up", label:RED._('jsonEditor.insertAbove'),onselect:function(){
                var index = item.parent.children.indexOf(item);
                insertNewItem(item.parent,index,index,readOnly);
            }});
            options.push({id:"red-ui-editor-type-json-menu-insert-below", icon:"fa fa-toggle-down", label:RED._('jsonEditor.insertBelow'),onselect:function(){
                var index = item.parent.children.indexOf(item)+1;
                insertNewItem(item.parent,index,index-1,readOnly);
            }});
        }
        if (item.type === 'array' || item.type === 'object') {
            options.push({id:"red-ui-editor-type-json-menu-add-child", icon:"fa fa-plus", label:RED._('jsonEditor.addItem'),onselect:function(){
                insertNewItem(item,item.children.length,item.children.length-1,readOnly);
            }});
        }
        if (item.parent) {
            options.push({id:"red-ui-editor-type-json-menu-copy-path", icon:"fa fa-terminal", label:RED._('jsonEditor.copyPath'),onselect:function(){
                var i = item;
                var path = "";
                var newPath;
                while(i.parent) {
                    if (i.parent.type === "array") {
                        newPath = "["+i.key+"]";
                    } else {
                        if (/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(i.key)) {
                            newPath = i.key;
                        } else {
                            newPath = "[\""+i.key.replace(/"/,"\\\"")+"\"]"
                        }
                    }
                    path = newPath+(path.length>0 && path[0] !== "["?".":"")+path;
                    i = i.parent;
                }
                RED.clipboard.copyText(path,item.element,"clipboard.copyMessagePath");
            }});

            options.push({id:"red-ui-editor-type-json-menu-duplicate", icon:"fa fa-copy", label:RED._("jsonEditor.duplicate"),onselect:function(){
                var newKey = item.key;
                if (item.parent.type === 'array') {
                    newKey = item.parent.children.length;
                } else {
                    var m = /^(.*?)(-(\d+))?$/.exec(newKey);
                    var usedKeys = {};
                    item.parent.children.forEach(function(child) { usedKeys[child.key] = true })
                    var keyRoot = m[1];
                    var keySuffix = 2;
                    if (m[3] !== undefined) {
                        keySuffix = parseInt(m[3]);
                    }
                    newKey = keyRoot;
                    while(usedKeys[newKey]) {
                        newKey = keyRoot+"-"+(keySuffix++);
                    }
                }
                var newItem = handleItem(newKey,convertToObject(item),item.parent.depth+1,item.parent,readOnly);
                var index = item.parent.children.indexOf(item)+1;

                item.parent.treeList.insertChildAt(newItem, index, true);
                item.parent.treeList.expand();
            }});

            options.push({id:"red-ui-editor-type-json-menu-delete", icon:"fa fa-times", label:RED._('common.label.delete'),onselect:function(){
                item.treeList.remove();
            }});
        }
        if (item.type === 'array' || item.type === 'object') {
            options.push(null)
            options.push({id:"red-ui-editor-type-json-menu-expand-children",icon:"fa fa-angle-double-down", label:RED._('jsonEditor.expandItems'),onselect:function(){
                item.treeList.expand();
                item.children.forEach(function(child) {
                    child.treeList.expand();
                })
            }});
            options.push({id:"red-ui-editor-type-json-menu-collapse-children",icon:"fa fa-angle-double-up", label:RED._('jsonEditor.collapseItems'),onselect:function(){
                item.treeList.collapse();
                item.children.forEach(function(child) {
                    child.treeList.collapse();
                })
            }});
        }

        var menuOptionMenu = RED.menu.init({
            id:"red-ui-editor-type-json-menu",
            options: options
        });
        menuOptionMenu.css({
            position: "absolute"
        })
        menuOptionMenu.on('mouseleave', function(){ $(this).hide() });
        menuOptionMenu.on('mouseup', function() { $(this).hide() });
        menuOptionMenu.appendTo("body");
        var top = elementPos.top;
        var height = menuOptionMenu.height();
        var winHeight = $(window).height();
        if (top+height > winHeight) {
            top -= (top+height)-winHeight + 20;
        }
        menuOptionMenu.css({
            top: top+"px",
            left: elementPos.left+"px"
        })
        menuOptionMenu.show();
    }

    function parseObject(obj,depth,parent,readOnly) {
        var result = [];
        for (var prop in obj) {
            if (obj.hasOwnProperty(prop)) {
                result.push(handleItem(prop,obj[prop],depth,parent,readOnly));
            }
        }
        return result;
    }
    function parseArray(obj,depth,parent,readOnly) {
        var result = [];
        var l = obj.length;
        for (var i=0;i<l;i++) {
            result.push(handleItem(i,obj[i],depth,parent,readOnly));
        }
        return result;
    }
    function handleItem(key,val,depth,parent,readOnly) {
        var item = {depth:depth, type: typeof val};
        var container = $('<span class="red-ui-editor-type-json-editor-label">');
        if (key != null) {
            item.key = key;
            var keyText;
            if (typeof key === 'string') {
                keyText = '"'+key+'"';
            } else {
                keyText = key;
            }
            var keyLabel = $('<span class="red-ui-debug-msg-object-key red-ui-editor-type-json-editor-label-key">').text(keyText).appendTo(container);
            keyLabel.addClass('red-ui-debug-msg-type-'+(typeof key));
            if (parent && parent.type === "array") {
                keyLabel.addClass("red-ui-editor-type-json-editor-label-array-key")
            }
            if(readOnly) {
                keyLabel.addClass("readonly")
            }
            keyLabel.on("click", function(evt) {
                if (item.parent.type === 'array') {
                    return;
                }
                if (readOnly) { return; }
                evt.preventDefault();
                evt.stopPropagation();
                var w = Math.max(150,keyLabel.width());
                var keyInput = $('<input type="text" class="red-ui-editor-type-json-editor-key">').css({width:w+"px"}).val(""+item.key).insertAfter(keyLabel).typedInput({types:['str']});
                $(document).on("mousedown.nr-ui-json-editor", function(evt) {
                    var typedInputElement = keyInput.next(".red-ui-typedInput-container")[0];
                    var target = evt.target;
                    while (target.nodeName !== 'BODY' && target !== typedInputElement && !$(target).hasClass("red-ui-typedInput-options")) {
                        target = target.parentElement;
                    }
                    if (target.nodeName === 'BODY') {
                        var newKey = keyInput.typedInput("value");
                        item.key = newKey;
                        var keyText;
                        if (typeof newKey === 'string') {
                            keyText = '"'+newKey+'"';
                        } else {
                            keyText = newKey;
                        }
                        keyLabel.text(keyText);
                        keyInput.remove();
                        keyLabel.show();
                        $(document).off("mousedown.nr-ui-json-editor");
                        $(document).off("keydown.nr-ui-json-editor");
                    }
                });
                $(document).on("keydown.nr-ui-json-editor",function(evt) {
                    if (evt.keyCode === 27) {
                        // Escape
                        keyInput.remove();
                        keyLabel.show();
                        $(document).off("mousedown.nr-ui-json-editor");
                        $(document).off("keydown.nr-ui-json-editor");
                    }
                });
                keyLabel.hide();
            });
            $('<span>').text(" : ").appendTo(container);
        }

        if (Array.isArray(val)) {
            item.expanded = depth < 2;
            item.type = "array";
            item.deferBuild = depth >= 2;
            item.children = parseArray(val,depth+1,item,readOnly);
        } else if (val !== null && item.type === "object") {
            item.expanded = depth < 2;
            item.children = parseObject(val,depth+1,item,readOnly);
            item.deferBuild = depth >= 2;
        } else {
            item.value = val;
            if (val === null) {
                item.type = 'null'
            }
        }

        var valType;
        var valValue = "";
        var valClass;
        switch(item.type) {
            case 'string': valType = 'str'; valValue = '"'+item.value+'"'; valClass = "red-ui-debug-msg-type-string"; break;
            case 'number': valType = 'num'; valValue = item.value; valClass = "red-ui-debug-msg-type-number";break;
            case 'boolean': valType = 'bool'; valValue = item.value; valClass = "red-ui-debug-msg-type-other";break;
            case 'null': valType = item.type; valValue = item.type; valClass = "red-ui-debug-msg-type-null";break;
            case 'object':
                valType = item.type;
                valValue = item.type;//+"{"+item.children.length+"}";
                valClass = "red-ui-debug-msg-type-meta";
            break;
            case 'array':
                valType = item.type;
                valValue = item.type+"["+item.children.length+"]";
                valClass = "red-ui-debug-msg-type-meta";
            break;
        }
        //
        var orphanedChildren;
        var valueLabel = $('<span class="red-ui-editor-type-json-editor-label-value">').addClass(valClass).text(valValue).appendTo(container);
        if (readOnly) {
            valueLabel.addClass("readonly")
        }
        valueLabel.on("click", function(evt) {
            if (readOnly) { return; }
            evt.preventDefault();
            evt.stopPropagation();
            if (valType === 'str') {
                valValue = valValue.substring(1,valValue.length-1);
            } else if (valType === 'array') {
                valValue = "";
            } else if (valType === 'object') {
                valValue = "";
            }
            var w = Math.max(150,valueLabel.width());
            var val = $('<input type="text" class="red-ui-editor-type-json-editor-value">').css({width:w+"px"}).val(""+valValue).insertAfter(valueLabel).typedInput({
                types:[
                    'str','num','bool',
                    {value:"null",label:RED._("common.type.null"),hasValue:false},
                    {value:"array",label:RED._("common.type.array"),hasValue:false,icon:"red/images/typedInput/json.svg"},
                    {value:"object",label:RED._("common.type.object"),hasValue:false,icon:"red/images/typedInput/json.svg"}
                ],
                default: valType
            });
            $(document).on("mousedown.nr-ui-json-editor", function(evt) {
                var typedInputElement = val.next(".red-ui-typedInput-container")[0];
                var target = evt.target;
                while (target.nodeName !== 'BODY' && target !== typedInputElement && !$(target).hasClass("red-ui-typedInput-options")) {
                    target = target.parentElement;
                }
                if (target.nodeName === 'BODY') {
                    valType = val.typedInput("type");
                    valValue = val.typedInput("value");
                    if (valType === 'num') {
                        valValue = valValue.trim();
                        if (isNaN(valValue)) {
                            valType = 'str';
                        } else if (valValue === "") {
                            valValue = 0;
                        }
                    }
                    item.value = valValue;
                    var valClass;
                    switch(valType) {
                        case 'str':    if (item.children) { orphanedChildren = item.children } item.treeList.makeLeaf(true); item.type = "string";  valClass = "red-ui-debug-msg-type-string"; valValue = '"'+valValue+'"'; break;
                        case 'num':    if (item.children) { orphanedChildren = item.children } item.treeList.makeLeaf(true); item.type = "number";  valClass = "red-ui-debug-msg-type-number"; break;
                        case 'bool':   if (item.children) { orphanedChildren = item.children } item.treeList.makeLeaf(true); item.type = "boolean"; valClass = "red-ui-debug-msg-type-other";  item.value = (valValue === "true"); break;
                        case 'null':   if (item.children) { orphanedChildren = item.children } item.treeList.makeLeaf(true); item.type = "null";    valClass = "red-ui-debug-msg-type-null"; item.value = valValue = "null"; break;
                        case 'object':
                            item.treeList.makeParent(orphanedChildren);
                            item.type = "object";
                            valClass = "red-ui-debug-msg-type-meta";
                            item.value = valValue = "object";
                            item.children.forEach(function(child,i) {
                                if (child.hasOwnProperty('_key')) {
                                    child.key = child._key;
                                    delete child._key;
                                    var keyText;
                                    var keyLabel = child.element.find(".red-ui-editor-type-json-editor-label-key");
                                    keyLabel.removeClass("red-ui-editor-type-json-editor-label-array-key");
                                    if (typeof child.key === 'string') {
                                        keyText = '"'+child.key+'"';
                                        keyLabel.addClass('red-ui-debug-msg-type-string');
                                        keyLabel.removeClass('red-ui-debug-msg-type-number');
                                    } else {
                                        keyText = child.key;
                                        keyLabel.removeClass('red-ui-debug-msg-type-string');
                                        keyLabel.addClass('red-ui-debug-msg-type-number');
                                    }
                                    keyLabel.text(keyText);
                                }
                            })
                            break;
                        case 'array':
                            item.treeList.makeParent(orphanedChildren);
                            item.type = "array";
                            valClass = "red-ui-debug-msg-type-meta";
                            item.value = valValue = "array["+(item.children.length)+"]";
                            item.children.forEach(function(child,i) {
                                child._key = child.key;
                                child.key = i;
                                child.element.find(".red-ui-editor-type-json-editor-label-key")
                                    .addClass("red-ui-editor-type-json-editor-label-array-key")
                                    .text(""+child.key)
                                    .removeClass('red-ui-debug-msg-type-string')
                                    .addClass('red-ui-debug-msg-type-number');
                            })
                            break;
                    }
                    valueLabel.text(valValue).removeClass().addClass("red-ui-editor-type-json-editor-label-value "+valClass);
                    val.remove();
                    valueLabel.show();
                    $(document).off("mousedown.nr-ui-json-editor");
                    $(document).off("keydown.nr-ui-json-editor");
                }
            })

            $(document).on("keydown.nr-ui-json-editor",function(evt) {
                if (evt.keyCode === 27) {
                    // Escape
                    val.remove();
                    valueLabel.show();
                    if (valType === 'str') {
                        valValue = '"'+valValue+'"';
                    }
                    $(document).off("mousedown.nr-ui-json-editor");
                    $(document).off("keydown.nr-ui-json-editor");
                }
            });
            valueLabel.hide();
        })
        item.gutter = $('<span class="red-ui-editor-type-json-editor-item-gutter"></span>');
        if(!readOnly) {
            if (parent) {
                $('<span class="red-ui-editor-type-json-editor-item-handle"><i class="fa fa-bars"></span>').appendTo(item.gutter);
            } else {
                $('<span></span>').appendTo(item.gutter);
            }
            $('<button type="button" class="editor-button editor-button-small"><i class="fa fa-caret-down"></button>').appendTo(item.gutter).on("click", function(evt) {
                evt.preventDefault();
                evt.stopPropagation();
                showObjectMenu($(this), item, readOnly);
            });
        }

        item.element = container;
        return item;
    }
    function convertToObject(item) {
        var element;
        switch (item.type) {
            case 'string': element = item.value; break;
            case 'number': element = Number(item.value); break;
            case 'boolean': element = item.value; break;
            case 'null': element = null; break;
            case 'object':
                element = {};
                item.children.forEach(function(child) {
                    element[child.key] = convertToObject(child);
                })
            break;
            case 'array':
                element = item.children.map(function(child) {
                    return convertToObject(child);
                })
            break;
        }
        return element;
    }

    var definition = {
        show: function(options) {
            var value = options.value;
            var onCancel = options.cancel;
            var onComplete = options.complete;
            var type = "_json"
            if ($("script[data-template-name='"+type+"']").length === 0) {
                $(template).appendTo("#red-ui-editor-node-configs");
            }
            RED.view.state(RED.state.EDITING);
            var expressionEditor;
            var changeTimer;

            var checkValid = function() {
                var v = expressionEditor.getValue();
                try {
                    JSON.parse(v);
                    $("#node-dialog-ok").removeClass('disabled');
                    return true;
                } catch(err) {
                    $("#node-dialog-ok").addClass('disabled');
                    return false;
                }
            }
            var rootNode;
            var trayOptions = {
                title: options.title,
                focusElement: options.focusElement,
                width: options.width||700,
                buttons: [
                    {
                        id: "node-dialog-cancel",
                        text: RED._("common.label.cancel"),
                        click: function() {
                            if (onCancel) { onCancel(); }
                            RED.tray.close();
                        }
                    },
                    {
                        id: "node-dialog-ok",
                        text: RED._("common.label.done"),
                        class: "primary",
                        click: function() {
                            if (options.requireValid && !checkValid()) {
                                return;
                            }
                            var result;
                            if (activeTab === "json-ui") {
                                if (rootNode) {
                                    result = JSON.stringify(convertToObject(rootNode),null,4);
                                } else {
                                    result = expressionEditor.getValue();
                                }
                            } else if (activeTab === "json-raw") {
                                result = expressionEditor.getValue();
                            }
                            expressionEditor.saveView();
                            if (onComplete) { onComplete(result,null,expressionEditor) }
                            RED.tray.close();
                        }
                    }
                ],
                resize: function(dimensions) {
                    var height = $(".red-ui-editor-type-json-tab-content").height();
                    $(".node-text-editor").css("height",(height-45)+"px");
                    expressionEditor.resize();
                },
                open: function(tray) {
                    var trayBody = tray.find('.red-ui-tray-body');
                    var dialogForm = RED.editor.buildEditForm(tray.find('.red-ui-tray-body'),'dialog-form',type,'editor');
                    var toolbarButtons = options.toolbarButtons || [];
                    if (toolbarButtons.length) {
                        toolbarButtons.forEach(function (button) {
                            var element = $('<button type="button" class="red-ui-button red-ui-button-small"> </button>')
                                .insertBefore("#node-input-json-reformat")
                                .on("click", function (evt) {
                                    evt.preventDefault();
                                    if (button.click !== undefined) {
                                        button.click.call(element, evt);
                                    }
                                });
                            if (button.id) { element.attr("id", button.id); }
                            if (button.title) { element.attr("title", button.title); }
                            if (button.icon) { element.append($("<i></i>").attr("class", button.icon)); }
                            if (button.label || button.text) {
                                element.append($("<span></span>").text(" " + (button.label || button.text)));
                            }
                        });
                    }
                    var container = $("#red-ui-editor-type-json-tab-ui-container").css({"height":"100%"});
                    var filterDepth = Infinity;
                    var list = $('<div class="red-ui-debug-msg-payload red-ui-editor-type-json-editor">').appendTo(container).treeList({
                        selectable: false,
                        rootSortable: false,
                        sortable: ".red-ui-editor-type-json-editor-item-handle",
                    }).on("treelistchangeparent", function(event, evt) {
                        if (evt.old.type === 'array') {
                            evt.old.element.find(".red-ui-editor-type-json-editor-label-type").text("array["+evt.old.children.length+"]");
                        }
                        if (evt.item.parent.type === 'array') {
                            evt.item.parent.element.find(".red-ui-editor-type-json-editor-label-type").text("array["+evt.item.parent.children.length+"]");
                        }
                    }).on("treelistsort", function(event, item) {
                        item.children.forEach(function(child,i) {
                            if (item.type === 'array') {
                                child.key = i;
                                child.element.find(".red-ui-editor-type-json-editor-label-key")
                                .text(child.key)
                                .removeClass('red-ui-debug-msg-type-string')
                                .addClass('red-ui-debug-msg-type-number');
                            } else {
                                child.element.find(".red-ui-editor-type-json-editor-label-key")
                                    .text('"'+child.key+'"')
                                    .removeClass('red-ui-debug-msg-type-number')
                                    .addClass('red-ui-debug-msg-type-string');
                            }
                        })
                    });

                    expressionEditor = RED.editor.createEditor({
                        id: 'node-input-json',
                        value: value||"",
                        mode:"ace/mode/json",
                        readOnly: !!options.readOnly,
                        stateId: options.stateId,
                        focus: true
                    });

                    if (options.requireValid) {
                        expressionEditor.getSession().on('change', function() {
                            clearTimeout(changeTimer);
                            changeTimer = setTimeout(checkValid,200);
                        });
                        checkValid();
                    }
                    $("#node-input-json-reformat").on("click", function(evt) {
                        evt.preventDefault();
                        var v = expressionEditor.getValue()||"";
                        try {
                            v = JSON.stringify(JSON.parse(v),null,4);
                        } catch(err) {
                            // TODO: do an optimistic auto-format
                        }
                        expressionEditor.getSession().setValue(v||"",-1);
                    });
                    dialogForm.i18n();

                    var finishedBuild = false;
                    var tabs = RED.tabs.create({
                        element: $("#red-ui-editor-type-json-tabs"),
                        onchange:function(tab) {
                            activeTab = tab.id;
                            $(".red-ui-editor-type-json-tab-content").hide();
                            if (finishedBuild) {
                                if (tab.id === "json-raw") {
                                    if (rootNode) {
                                        var result = JSON.stringify(convertToObject(rootNode),null,4);
                                        expressionEditor.getSession().setValue(result||"",-1);
                                    }

                                } else if (tab.id === "json-ui") {
                                    var raw = expressionEditor.getValue().trim() ||"{}";
                                    try {
                                        var parsed = JSON.parse(raw);
                                        rootNode = handleItem(null,parsed,0,null,options.readOnly);
                                        rootNode.class = "red-ui-editor-type-json-root-node"
                                        list.treeList('data',[rootNode]);
                                    } catch(err) {
                                        rootNode = null;
                                        list.treeList('data',[{
                                            label: RED._("jsonEditor.error.invalidJSON")+err.toString()
                                        }]);
                                    }
                                }
                            }
                            tab.content.show();
                            trayOptions.resize();
                        }
                    })

                    tabs.addTab({
                        id: 'json-raw',
                        label: options.readOnly ? RED._('jsonEditor.rawMode-readonly') : RED._('jsonEditor.rawMode'),
                        content: $("#red-ui-editor-type-json-tab-raw")
                    });
                    tabs.addTab({
                        id: 'json-ui',
                        label: options.readOnly ? RED._('jsonEditor.uiMode-readonly') : RED._('jsonEditor.uiMode'),
                        content: $("#red-ui-editor-type-json-tab-ui")
                    });
                    finishedBuild = true;
                },
                close: function() {
                    if (options.onclose) {
                        options.onclose();
                    }
                    expressionEditor.destroy();
                },
                show: function() {}
            }
            RED.tray.show(trayOptions);
        }
    }
    RED.editor.registerTypeEditor("_json", definition);
})();
