// Distributed under an MIT license: http://codemirror.net/LICENSE

/**
 * @file freemarkermixed.js
 */

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

            CodeMirror
                    .defineMode("freemarkermixed", function(config) {
                        var htmlMixedMode = CodeMirror.getMode(config, "htmlmixed");
                        var freemarkerMode = CodeMirror.getMode(config, "freemarker");

                        var settings = {
                            leftDelimiter : '<', rightDelimiter : '>',    tagSyntax : 1
                        // 1 angle_bracket,2 square_bracket
                        };

                        if (config.hasOwnProperty("tagSyntax") ) {
                            if (config.tagSyntax === 2 ) {
                                settings.tagSyntax = 2;
                                settings.leftDelimiter = '[';
                                settings.rightDelimiter = ']';
                            }
                        }
                        
                        var freemarkerFlagArray = ["#","/#","@","/@"];
                        
                        function regEsc(str) {
                            return str.replace(/[^\s\w]/g, "\\$&");
                        }
                        
                        var regLeftArray=[],htmlHasLeftDelimeterRegArray=[];
                        
                        for (var i = 0; i < freemarkerFlagArray.length; i++) {
                            regLeftArray.push( new RegExp(".*" + regEsc(settings.leftDelimiter + freemarkerFlagArray[i])));
                            htmlHasLeftDelimeterRegArray.push( new RegExp("[^<>]*" + regEsc(settings.leftDelimiter + freemarkerFlagArray[i])));
                        }
                        
                        var helpers = {
                            chain : function(stream, state, parser) {
                                state.tokenize = parser;
                                return parser(stream, state);
                            },

                            cleanChain : function(stream, state, parser) {
                                state.tokenize = null;
                                state.localState = null;
                                state.localMode = null;
                                return (typeof parser == "string") ? (parser ? parser : null) : parser(stream, state);
                            },

                            maybeBackup : function(stream, pat, style) {
                                var cur = stream.current();
                                var close = cur.search(pat) , m;
                                if (close > -1 ) {
                                    stream.backUp(cur.length - close);
                                } else if (m = cur.match(/<\/?$/) ) {
                                    stream.backUp(cur.length);
                                    if (!stream.match(pat, false) ) {
                                        stream.match(cur[0]);
                                    }
                                }
                                return style;
                            }
                        };

                        var parsers = {
                            html : function(stream, state) {
                                if (!state.noparse) {
                                    var htmlTagName = state.htmlMixedState.htmlState.context && state.htmlMixedState.htmlState.context.tagName
                                    ? state.htmlMixedState.htmlState.context.tagName
                                    : null;
                                    for (var i = 0; i < freemarkerFlagArray.length; i++) {
                                        if ( (stream.match(htmlHasLeftDelimeterRegArray[i], false) && htmlTagName === null ) || stream.match(settings.leftDelimiter + freemarkerFlagArray[i], false)) {
                                            state.tokenize = parsers.freemarker;
                                            state.localMode = freemarkerMode;
                                            state.localState = freemarkerMode.startState(htmlMixedMode.indent(state.htmlMixedState, "", ""));
                                            return helpers.maybeBackup(stream, settings.leftDelimiter + freemarkerFlagArray[i], freemarkerMode.token(stream, state.localState));
                                        } else if (stream.match("${", false)) {
                                            state.tokenize = parsers.freemarker;
                                            state.localMode = freemarkerMode;
                                            state.localState = freemarkerMode.startState(htmlMixedMode.indent(state.htmlMixedState, "", ""));
                                            return helpers.maybeBackup(stream, "${", freemarkerMode.token(stream, state.localState));
                                        }
                                    }
                                }
                                return htmlMixedMode.token(stream, state.htmlMixedState);
                            },

                            freemarker : function(stream, state) {
                                if (stream.match(settings.leftDelimiter + "#--", false) ) {
                                    return helpers.chain(stream, state, parsers.inBlock("comment", "--"
                                                + settings.rightDelimiter));
                                } else if (stream.match(settings.rightDelimiter, false) ) {
                                    stream.eat(settings.rightDelimiter);
                                    state.tokenize = parsers.html;
                                    state.localMode = htmlMixedMode;
                                    state.localState = state.htmlMixedState;
                                    return "tag";
                                } else if (stream.match("}", false) ) {
                                    stream.eat("}");
                                    state.tokenize = parsers.html;
                                    state.localMode = htmlMixedMode;
                                    state.localState = state.htmlMixedState;
                                    return "keyword";
                                }

                                return helpers.maybeBackup(stream, settings.rightDelimiter, freemarkerMode
                                        .token(stream, state.localState));
                            },

                            inBlock : function(style, terminator) {
                                return function(stream, state) {
                                    while (!stream.eol()) {
                                        if (stream.match(terminator) ) {
                                            helpers.cleanChain(stream, state, "");
                                            break;
                                        }
                                        stream.next();
                                    }
                                    return style;
                                };
                            }
                        };

                        return {
                            startState : function() {
                                var state = htmlMixedMode.startState();
                                return {
                                    token : parsers.html, localMode : null, localState : null, htmlMixedState : state,
                                    tokenize : null, noparse : false
                                };
                            },

                            copyState : function(state) {
                                var local = null , tok = (state.tokenize || state.token);
                                if (state.localState ) {
                                    local = CodeMirror
                                            .copyState((tok != parsers.html ? freemarkerMode : htmlMixedMode), state.localState);
                                }
                                return {
                                    token : state.token, tokenize : state.tokenize, localMode : state.localMode,
                                    localState : local,
                                    htmlMixedState : CodeMirror.copyState(htmlMixedMode, state.htmlMixedState),
                                    noparse : state.noparse
                                };
                            },

                            token : function(stream, state) {
                                if (stream.match(settings.leftDelimiter+"#", false) ) {
                                    if (!state.noparse && stream.match("#noparse", true)) {
                                      state.noparse = true;
                                      return "keyword";
                                    } else if (state.noparse && stream.match("/#noparse", true)) {
                                      state.noparse = false;
                                      return "keyword";
                                    }
                                }
                                if (state.noparse && state.localState != state.htmlMixedState ) {
                                    state.tokenize = parsers.html;
                                    state.localMode = htmlMixedMode;
                                    state.localState = state.htmlMixedState;
                                }
                                var style = (state.tokenize || state.token)(stream, state);
                                return style;
                            },

                            indent : function(state, textAfter) {
                                if (state.localMode == freemarkerMode || (state.noparse && !state.localMode) ) {
                                    for (var i = 0; i < regLeftArray.length; i++) {
                                        if(regLeftArray[i].test(textAfter)){
                                            return CodeMirror.Pass;
                                        }
                                    }
                                }
                                return htmlMixedMode.indent(state.htmlMixedState, textAfter, "");
                            },

                            innerMode : function(state) {
                                return {
                                    state : state.localState || state.htmlMixedState,
                                    mode : state.localMode || htmlMixedMode
                                };
                            }
                        };
                    }, "htmlmixed", "freemarker");

            CodeMirror.defineMIME("text/freemarker", "freemarkermixed");
            // vim: et ts=2 sts=2 sw=2

        });
