/*

 Syntax highlighting based on <https://highlightjs.org>
 API docs at <http://highlightjs.readthedocs.io/en/latest/>

 */

function Highlight(source){

    // Callbacks for building arrays of 'span'-like results, which can be nested.
    var newState = function() {
        var state = new Object();

        state.value = [];
        state.startClass = function(cls){
            //print("start", cls);
            this.value.push({"start": cls});
        };

        state.endClass = function(cls){
            //print("end", cls);
            this.value.push({"end": cls ? cls : "?"}); // Fill in something so that the check for a non-null/undefined 'end' key will work below.
        };

        state.append = function(content){
            // Maybe can be a sub-result or plain text
            if (typeof content == "string") {
                if (content == '') {
                    return;
                }
                //print("append", "'" + content + "'");
                this.value.push({"content": content});
            } else {
                if (content.value.length == 0) {
                    return;
                }
                //print("append", JSON.stringify(content)); // Object.getOwnPropertyNames(content));
                this.value = this.value.concat(content.value);
            }
            //print("value now", JSON.stringify(this.value));
        };

        return state;
    };

    // We don't always get a class name in endClass. Convert the final state to ranges of active classes.
    var state = hljs.highlight("javascript", source, true, null, newState);
    var result = [];

    //print("state.value");
    //print(JSON.stringify(state.value));

    if (state.value) {
        var offset = 0;
        var activeClasses = [];

        // No built-in for this... here we know both inputs are arrays of strings
        function equalArrays(a,b) {
            if (a.length != b.length) {
                return false;
            }
            for (idx in a) {
                if (a[idx] != b[idx]) {
                    return false;
                }
            }
            return true;
        }

        function addEntry(){
            // If there is an entry at the end with the same classes, then this entry is redundant.
            if (result.length > 0) {
                var entry = result[result.length - 1];
                if (equalArrays(entry.classes, activeClasses)) {
                    return;
                }
            }
            result.push({"offset": offset, "classes": activeClasses.slice() });
        };
        state.value.forEach(function(entry){
            var cls = entry.start;
            if (cls) {
                activeClasses.push(cls);
                //print("start", cls, " --> ", JSON.stringify(activeClasses));
                return;
            }
            if (entry.end) {
                activeClasses.pop(); // We assume that if we did get a class, it matches.
                //print("end --> ", JSON.stringify(activeClasses));
                return;
            }
            var content = entry.content;
            if (content) {
                addEntry();
                offset += content.length;
                return;
            }

            //print("???", JSON.stringify(entry));
        });

        // Add a ending offset
        if (result.length > 0) {
            var entry = result[result.length - 1];
            if (entry.offset != offset) {
                addEntry();
            }
        }
    }

//    console.log("result.value", result.value);
    return result;
}

/* For debugging with

 jsc highlight.js/build/lib/highlight.js highlight.js/build/lib/languages/javascript.js highlight.js/build/lib/languages/json.js 'OmniGroup/Frameworks/OmniJS/Text Editor/SyntaxHighlighter.js'

 */

//var output = Highlight("var qq = \"é😀\";\nfunction x(a, b){\n  return a+b;\n  }");
//print(JSON.stringify(output));

