/* ***** BEGIN LICENSE BLOCK *****
 * Distributed under the BSD license:
 *
 * Copyright (c) 2014, Ajax.org B.V.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Ajax.org B.V. nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ***** END LICENSE BLOCK ***** */

define(function (require, exports, module) {
    "use strict";

    exports.MaskHighlightRules = MaskHighlightRules;

    var oop = require("../lib/oop");
    var lang = require("../lib/lang");
    var TextRules = require("./text_highlight_rules").TextHighlightRules;
    var JSRules = require("./javascript_highlight_rules").JavaScriptHighlightRules;
    var CssRules = require("./css_highlight_rules").CssHighlightRules;
    var MDRules = require("./markdown_highlight_rules").MarkdownHighlightRules;
    var HTMLRules = require("./html_highlight_rules").HtmlHighlightRules;

    var token_TAG = "keyword.support.constant.language",
        token_COMPO = "support.function.markup.bold",
        token_KEYWORD = "keyword",
        token_LANG = "constant.language",
        token_UTIL = "keyword.control.markup.italic",
        token_ATTR = "support.variable.class",
        token_PUNKT = "keyword.operator",
        token_ITALIC = "markup.italic",
        token_BOLD = "markup.bold",
        token_LPARE = "paren.lparen",
        token_RPARE = "paren.rparen";

    var const_FUNCTIONS,
        const_KEYWORDS,
        const_CONST,
        const_TAGS;
    (function () {
        const_FUNCTIONS = lang.arrayToMap(
            ("log").split("|")
        );
        const_CONST = lang.arrayToMap(
            (":dualbind|:bind|:import|slot|event|style|html|markdown|md").split("|")
        );
        const_KEYWORDS = lang.arrayToMap(
            ("debugger|define|var|if|each|for|of|else|switch|case|with|visible|+if|+each|+for|+switch|+with|+visible|include|import").split("|")
        );
        const_TAGS = lang.arrayToMap(
            ("a|abbr|acronym|address|applet|area|article|aside|audio|b|base|basefont|bdo|" +
            "big|blockquote|body|br|button|canvas|caption|center|cite|code|col|colgroup|" +
            "command|datalist|dd|del|details|dfn|dir|div|dl|dt|em|embed|fieldset|" +
            "figcaption|figure|font|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|" +
            "header|hgroup|hr|html|i|iframe|img|input|ins|keygen|kbd|label|legend|li|" +
            "link|map|mark|menu|meta|meter|nav|noframes|noscript|object|ol|optgroup|" +
            "option|output|p|param|pre|progress|q|rp|rt|ruby|s|samp|script|section|select|" +
            "small|source|span|strike|strong|style|sub|summary|sup|table|tbody|td|" +
            "textarea|tfoot|th|thead|time|title|tr|tt|u|ul|var|video|wbr|xmp").split("|")
        );
    }());

    function MaskHighlightRules() {

        this.$rules = {
            "start": [
                Token("comment", "\\/\\/.*$"),
                Token("comment", "\\/\\*", [
                    Token("comment", ".*?\\*\\/", "start"),
                    Token("comment", ".+")
                ]),

                Blocks.string("'''"),
                Blocks.string('"""'),
                Blocks.string('"'),
                Blocks.string("'"),

                Blocks.syntax(/(markdown|md)\b/, "md-multiline", "multiline"),
                Blocks.syntax(/html\b/, "html-multiline", "multiline"),
                Blocks.syntax(/(slot|event)\b/, "js-block", "block"),
                Blocks.syntax(/style\b/, "css-block", "block"),
                Blocks.syntax(/var\b/, "js-statement", "attr"),

                Blocks.tag(),

                Token(token_LPARE, "[[({>]"),
                Token(token_RPARE, "[\\])};]", "start"),
                {
                    caseInsensitive: true
                }
            ]
        };
        var rules = this;

        addJavaScript("interpolation", /\]/, token_RPARE + "." + token_ITALIC);
        addJavaScript("statement", /\)|}|;/);
        addJavaScript("block", /\}/);
        addCss();
        addMarkdown();
        addHtml();

        function addJavaScript(name, escape, closeType) {
            var prfx = "js-" + name + "-",
                rootTokens = name === "block" ? ["start"] : ["start", "no_regex"];
            add(
                JSRules
                , prfx
                , escape
                , rootTokens
                , closeType
            );
        }

        function addCss() {
            add(CssRules, "css-block-", /\}/);
        }

        function addMarkdown() {
            add(MDRules, "md-multiline-", /("""|''')/, []);
        }

        function addHtml() {
            add(HTMLRules, "html-multiline-", /("""|''')/);
        }

        function add(Rules, strPrfx, rgxEnd, rootTokens, closeType) {
            var next = "pop";
            var tokens = rootTokens || ["start"];
            if (tokens.length === 0) {
                tokens = null;
            }
            if (/block|multiline/.test(strPrfx)) {
                next = strPrfx + "end";
                rules.$rules[next] = [
                    Token("empty", "", "start")
                ];
            }
            rules.embedRules(
                Rules
                , strPrfx
                , [Token(closeType || token_RPARE, rgxEnd, next)]
                , tokens
                , tokens == null ? true : false
            );
        }

        this.normalizeRules();
    }

    oop.inherits(MaskHighlightRules, TextRules);

    var Blocks = {
        string: function (str, next) {
            var token = Token(
                "string.start"
                , str
                , [
                    Token(token_LPARE + "." + token_ITALIC, /~\[/, Blocks.interpolation()),
                    Token("string.end", str, "pop"),
                    {
                        defaultToken: "string"
                    }
                ]
                , next
            );
            if (str.length === 1) {
                var escaped = Token("string.escape", "\\\\" + str);
                token.push.unshift(escaped);
            }
            return token;
        },
        interpolation: function () {
            return [
                Token(token_UTIL, /\s*\w*\s*:/),
                "js-interpolation-start"
            ];
        },
        tagHead: function (rgx) {
            return Token(token_ATTR, rgx, [
                Token(token_ATTR, /[\w\-_]+/),
                Token(token_LPARE + "." + token_ITALIC, /~\[/, Blocks.interpolation()),
                Blocks.goUp()
            ]);
        },
        tag: function () {
            return {
                token: 'tag',
                onMatch: function (value) {
                    if (void 0 !== const_KEYWORDS[value])
                        return token_KEYWORD;
                    if (void 0 !== const_CONST[value])
                        return token_LANG;
                    if (void 0 !== const_FUNCTIONS[value])
                        return "support.function";
                    if (void 0 !== const_TAGS[value.toLowerCase()])
                        return token_TAG;

                    return token_COMPO;
                },
                regex: /([@\w\-_:+]+)|((^|\s)(?=\s*(\.|#)))/,
                push: [
                    Blocks.tagHead(/\./),
                    Blocks.tagHead(/#/),
                    Blocks.expression(),
                    Blocks.attribute(),

                    Token(token_LPARE, /[;>{]/, "pop")
                ]
            };
        },
        syntax: function (rgx, next, type) {
            return {
                token: token_LANG,
                regex: rgx,
                push: ({
                    "attr": [
                        next + "-start",
                        Token(token_PUNKT, /;/, "start")
                    ],
                    "multiline": [
                        Blocks.tagHead(/\./),
                        Blocks.tagHead(/#/),
                        Blocks.attribute(),
                        Blocks.expression(),
                        Token(token_LPARE, /[>\{]/),
                        Token(token_PUNKT, /;/, "start"),
                        Token(token_LPARE, /'''|"""/, [next + "-start"])
                    ],
                    "block": [
                        Blocks.tagHead(/\./),
                        Blocks.tagHead(/#/),
                        Blocks.attribute(),
                        Blocks.expression(),
                        Token(token_LPARE, /\{/, [next + "-start"])
                    ]
                })[type]
            };
        },
        attribute: function () {
            return Token(function (value) {
                return /^x\-/.test(value)
                    ? token_ATTR + "." + token_BOLD
                    : token_ATTR;
            }, /[\w_-]+/, [
                Token(token_PUNKT, /\s*=\s*/, [
                    Blocks.string('"'),
                    Blocks.string("'"),
                    Blocks.word(),
                    Blocks.goUp()
                ]),
                Blocks.goUp()
            ]);
        },
        expression: function () {
            return Token(token_LPARE, /\(/, ["js-statement-start"]);
        },
        word: function () {
            return Token("string", /[\w-_]+/);
        },
        goUp: function () {
            return Token("text", "", "pop");
        },
        goStart: function () {
            return Token("text", "", "start");
        }
    };


    function Token(token, rgx, mix) {
        var push, next, onMatch;
        if (arguments.length === 4) {
            push = mix;
            next = arguments[3];
        }
        else if (typeof mix === "string") {
            next = mix;
        }
        else {
            push = mix;
        }
        if (typeof token === "function") {
            onMatch = token;
            token = "empty";
        }
        return {
            token: token,
            regex: rgx,
            push: push,
            next: next,
            onMatch: onMatch
        };
    }

});
