/*-
 * ~~~~~~licensing~~~~~~
 * entaxy-management-plugin
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
// Distributed under an MIT license: http://codemirror.net/LICENSE

/**
 * freemarker
 */

(function(mod) {
    if (typeof exports == "object" && typeof module == "object" ) // CommonJS
        mod(require("../../lib/codemirror"));
    else if (typeof define == "function" && define.amd ) // AMD
        define([ "../../lib/codemirror" ], mod);
    else
        // Plain browser env
        mod(CodeMirror);
})(function(CodeMirror) {
    "use strict";

    CodeMirror
            .defineMode("freemarker", function(config) {
                "use strict";

                // our default settings; check to see if they're overridden
                var settings = {
                    leftDelimiter : '[', rightDelimiter : ']', tagSyntax : 2
                // 1 angle_bracket,2 square_bracket
                };
                if (config.hasOwnProperty("tagSyntax") ) {
                    if (config.tagSyntax === 1 ) {
                        settings.tagSyntax = 1;
                        settings.leftDelimiter = '<';
                        settings.rightDelimiter = '>';
                    }
                }

                var keyFunctions = [ "assign", "attempt", "autoesc", "break", "case", "compress", "default", "else",
                        "elseif", "escape", "fallback", "function", "flush", "ftl", "global", "if", "import",
                        "include", "items", "list", "local", "lt", "macro", "nested","noautoesc", "noescape", "noparse", "nt","outputformat",
                        "recover", "recurse", "return", "rt", "sep", "setting", "stop", "switch", "t", "visit" ];
                var specialVariables = [ "auto_esc" , "caller_template_name", "current_template_name", "data_model", "error", "get_optional_template", "globals", "lang", "locale",
                        "locale_object", "locals", "main", "main_template_name", "namespace", "node", "now",
                        "output_encoding " , "output_format" , "template_name" , "time_zone" , "url_escaping_charset", "vars", "version" ];

                var freemarkerStartTagArray = [ "#", "@" ];

                var freemarkerEndTagArray = [ "/#", "/@", "/>" ];

                var last;
                var freemarkerMode;
                var regs = {
                    operatorChars : /[+\-*&%=<>!?:;,|&]/, validIdentifier : /[a-zA-Z0-9_]/, stringChar : /['"]/
                };

                var helpers = {
                    cont : function(style, lastType, lastFreemarkerMode) {
                        last = lastType;
                        freemarkerMode = lastFreemarkerMode;
                        return style;
                    }, chain : function(stream, state, parser) {
                        state.tokenize = parser;
                        return parser(stream, state);
                    }
                };

                // our various parsers
                var parsers = {

                    // the main tokenizer
                    tokenizer : function(stream, state) {
                        if (stream.match(settings.leftDelimiter, true) ) {
                            if (stream.match("#--", true) ) {
                                return helpers.chain(stream, state, parsers.inBlock("comment", "--"
                                        + settings.rightDelimiter));
                            } else {
                                for (var i = 0; i < freemarkerStartTagArray.length; i++) {
                                    if (stream.match(freemarkerStartTagArray[i], false) ) {
                                        state.tokenize = parsers.freemarkerTemplate;
                                        if (freemarkerStartTagArray[i] == "@" ) {
                                            freemarkerMode = "macro";
                                        } else {
                                            freemarkerMode = "tag";
                                        }
                                        last = "startTag";
                                        return "tag";
                                    }
                                }
                                for (var i = 0; i < freemarkerEndTagArray.length; i++) {
                                    if (stream.match(freemarkerEndTagArray[i], false) ) {
                                        state.tokenize = parsers.freemarkerTemplate;
                                        if (freemarkerEndTagArray[i] == "/@" ) {
                                            freemarkerMode = "macro";
                                        } else {
                                            freemarkerMode = "tag";
                                        }
                                        last = "endTag";
                                        return "tag";
                                    }
                                }
                            }
                        } else if (stream.match("${", false) ) {
                            state.tokenize = parsers.freemarkerTemplate;
                            last = "startTag";
                            freemarkerMode = "echo";
                            return "keyword";
                        }
                        stream.next();
                        return null;
                    },

                    // parsing freemarker content
                    freemarkerTemplate : function(stream, state) {
                        if (stream.match(settings.rightDelimiter, true) ) {
                            state.depth--;
                            if (state.depth <= 0 ) {
                                state.tokenize = parsers.tokenizer;
                            }
                            return helpers.cont("tag", null, null);
                        } else if ("echo" == state.freemarkerMode && stream.match("}", true) ) {
                            state.depth--;
                            if (state.depth <= 0 ) {
                                state.tokenize = parsers.tokenizer;
                            }
                            return helpers.cont("keyword", null, null);
                        }

                        if (stream.match(settings.leftDelimiter, true) ) {
                            for (var i = 0; i < freemarkerStartTagArray.length; i++) {
                                if (stream.match(freemarkerStartTagArray[i], false) ) {
                                    state.depth++;
                                    if (freemarkerStartTagArray[i] == "@" ) {
                                        return helpers.cont("tag", "startTag", "macro");
                                    } else {
                                        return helpers.cont("tag", "startTag", "tag");
                                    }
                                }
                            }
                            for (var i = 0; i < freemarkerEndTagArray.length; i++) {
                                if (stream.match(freemarkerEndTagArray[i], false) ) {
                                    state.depth++;
                                    if (freemarkerEndTagArray[i] == "/@" ) {
                                        return helpers.cont("tag", "endTag", "macro");
                                    } else {
                                        return helpers.cont("tag", "endTag", "tag");
                                    }
                                }
                            }
                        } else if (stream.match("${", true) ) {
                            state.depth++;
                            return helpers.cont("keyword", "startTag", "echo");
                        }
                        var ch = stream.next();
                        if ("." == ch ) {
                            if("echo" == state.freemarkerMode || "whitespace" == state.last ||"operator"== state.last){
                                for (var i = 0; i < specialVariables.length; i++) {
                                    if(stream.match(specialVariables[i],true)){
                                        return helpers.cont("keyword", "variable", state.freemarkerMode);
                                    }
                                }
                            }
                            if("keyword"==state.last && stream.eatWhile(regs.validIdentifier)){
                                return helpers.cont("keyword", null, state.freemarkerMode);
                            }else{
                                return helpers.cont("operator", "childVariable", state.freemarkerMode);
                            }
                        } else if (regs.stringChar.test(ch) ) {
                            state.tokenize = parsers.inAttribute(ch);
                            return helpers.cont("string", "string", state.freemarkerMode);
                        } else if (regs.operatorChars.test(ch) ) {
                            if ("?" === ch ) {
                                return helpers.cont("operator", "builtin", state.freemarkerMode);
                            } else {
                                return helpers.cont("operator", "operator", state.freemarkerMode);
                            }
                        } else if ("[" == ch || "{" == ch|| "(" == ch ) {
                            return helpers.cont("bracket", "bracket", state.freemarkerMode);
                        } else if ("]" == ch || "}" == ch || ")" == ch  ) {
                            return helpers.cont("bracket", "variable", state.freemarkerMode);
                        } else if ("/" == ch ) {
                            return helpers.cont("tag", "endTag", state.freemarkerMode);
                        } else if ("@" == ch && "macro" == state.freemarkerMode ) {
                            stream.eatWhile(regs.validIdentifier)
                            return helpers.cont("keyword", "keyword", state.freemarkerMode);
                        } else if (/\d/.test(ch) ) {
                            stream.eat(/x/i)
                            stream.eatWhile(/\d/);
                            return helpers.cont("number", "number", state.freemarkerMode);
                        } else if("tag" == state.freemarkerMode && "whitespace" == state.last && (stream.match("as",true) || stream.match("in",true)|| stream.match("using",true) )) {
                            return helpers.cont("keyword", "operator", state.freemarkerMode);
                        } else if("tag" == state.freemarkerMode && "whitespace" == state.last && (stream.match("gte",true) || stream.match("lte",true) || stream.match("gt",true) || stream.match("lt",true) )) {
                            return helpers.cont("operator", "operator", state.freemarkerMode);
                        } else {
                            if ("builtin" == state.last ) {
                                stream.eat("?");
                                stream.eatWhile(regs.validIdentifier);
                                return helpers.cont("builtin", "variable", state.freemarkerMode);
                            } else if ("whitespace" == state.last||"bracket" == state.last) {
                                if ("macro" == state.freemarkerMode ) {
                                    stream.eatWhile(regs.validIdentifier);
                                    return helpers.cont("attribute", "attribute", state.freemarkerMode);
                                }  else {
                                    stream.eatWhile(regs.validIdentifier);
                                    return helpers.cont("variable-2", "variable", state.freemarkerMode);
                                }
                            } else if ("operator" == state.last ) {
                                stream.eatWhile(regs.validIdentifier);
                                return helpers.cont("variable-2", "variable", state.freemarkerMode);
                            } else if ("childVariable" == state.last ) {
                                stream.eatWhile(regs.validIdentifier);
                                return helpers.cont("variable-3", "variable", state.freemarkerMode);
                            } else if (/\s/.test(ch) ) {
                                last = "whitespace";
                                return null;
                            } else if ("string" == state.last ) {
                                stream.eatWhile(regs.validIdentifier);
                                return helpers.cont("attribute", "attribute", state.freemarkerMode);
                            } else {
                                if ("startTag" == state.last || "endTag" == state.last ) {
                                    if ("echo" == state.freemarkerMode ) {
                                        stream.eatWhile(regs.validIdentifier)
                                        return helpers.cont("variable-2", "variable", state.freemarkerMode);
                                    }
                                }
                                if ("tag" == state.freemarkerMode ) {
                                    var str = "";
                                    if (ch != "/" ) {
                                        str += ch;
                                    }
                                    var c = null;
                                    while (c = stream.eat(regs.validIdentifier)) {
                                        str += c;
                                    }
                                    for (var i = 0 ; i < keyFunctions.length; i++) {
                                        if ("#"+keyFunctions[i] == str ) {
                                            return helpers.cont("keyword", "keyword", state.freemarkerMode);
                                        }
                                    }
                                }
                            }
                            return helpers.cont("error", "tag", state.freemarkerMode);
                        }
                    },

                    inAttribute : function(quote) {
                        return function(stream, state) {
                            var prevChar = null;
                            var currChar = null;
                            while (!stream.eol()) {
                                currChar = stream.peek();
                                if (stream.next() == quote && '\\' !== prevChar ) {
                                    state.tokenize = parsers.freemarkerTemplate;
                                    break;
                                }
                                prevChar = currChar;
                            }
                            return "string";
                        };
                    },

                    inBlock : function(style, terminator) {
                        return function(stream, state) {
                            while (!stream.eol()) {
                                if (stream.match(terminator) ) {
                                    state.tokenize = parsers.tokenizer;
                                    break;
                                }
                                stream.next();
                            }
                            return style;
                        };
                    }
                };

                // the public API for CodeMirror
                return {
                    startState : function() {
                        return {
                            tokenize : parsers.tokenizer, mode : "freemarker", last : null, freemarkerMode : null,
                            depth : 0
                        };
                    }, token : function(stream, state) {
                        state.last = last;
                        state.freemarkerMode = freemarkerMode;
                        return state.tokenize(stream, state);
                    }, electricChars : ""
                };
            });

    CodeMirror.defineMIME("text/freemarker", "freemarker");

});
