var SparqlParser = Editor.Parser = (function () {
    function wordRegexp(words) {
        return new RegExp("^(?:" + words.join("|") + ")$", "i");
    }

    var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri",
        "isblank", "isliteral", "union", "a"]);
    var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe",
        "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional",
        "graph", "by", "asc", "desc",]);
    var operatorChars = /[*+\-<>=&|]/;

    var tokenizeSparql = (function () {
        function normal(source, setState) {
            var ch = source.next();
            if (ch == "$" || ch == "?") {
                source.nextWhileMatches(/[\w\d]/);
                return "sp-var";
            }
            else if (ch == "<" && !source.matches(/[\s\u00a0=]/)) {
                source.nextWhileMatches(/[^\s\u00a0>]/);
                if (source.equals(">")) source.next();
                return "sp-uri";
            }
            else if (ch == "\"" || ch == "'") {
                setState(inLiteral(ch));
                return null;
            }
            else if (/[{}\(\),\.;\[\]]/.test(ch)) {
                return "sp-punc";
            }
            else if (ch == "#") {
                while (!source.endOfLine()) source.next();
                return "sp-comment";
            }
            else if (operatorChars.test(ch)) {
                source.nextWhileMatches(operatorChars);
                return "sp-operator";
            }
            else if (ch == ":") {
                source.nextWhileMatches(/[\w\d\._\-]/);
                return "sp-prefixed";
            }
            else {
                source.nextWhileMatches(/[_\w\d]/);
                if (source.equals(":")) {
                    source.next();
                    source.nextWhileMatches(/[\w\d_\-]/);
                    return "sp-prefixed";
                }
                var word = source.get(), type;
                if (ops.test(word))
                    type = "sp-operator";
                else if (keywords.test(word))
                    type = "sp-keyword";
                else
                    type = "sp-word";
                return {style: type, content: word};
            }
        }

        function inLiteral(quote) {
            return function (source, setState) {
                var escaped = false;
                while (!source.endOfLine()) {
                    var ch = source.next();
                    if (ch == quote && !escaped) {
                        setState(normal);
                        break;
                    }
                    escaped = !escaped && ch == "\\";
                }
                return "sp-literal";
            };
        }

        return function (source, startState) {
            return tokenizer(source, startState || normal);
        };
    })();

    function indentSparql(context) {
        return function (nextChars) {
            var firstChar = nextChars && nextChars.charAt(0);
            if (/[\]\}]/.test(firstChar))
                while (context && context.type == "pattern") context = context.prev;

            var closing = context && firstChar == matching[context.type];
            if (!context)
                return 0;
            else if (context.type == "pattern")
                return context.col;
            else if (context.align)
                return context.col - (closing ? context.width : 0);
            else
                return context.indent + (closing ? 0 : indentUnit);
        }
    }

    function parseSparql(source) {
        var tokens = tokenizeSparql(source);
        var context = null, indent = 0, col = 0;

        function pushContext(type, width) {
            context = {prev: context, indent: indent, col: col, type: type, width: width};
        }

        function popContext() {
            context = context.prev;
        }

        var iter = {
            next: function () {
                var token = tokens.next(), type = token.style, content = token.content, width = token.value.length;

                if (content == "\n") {
                    token.indentation = indentSparql(context);
                    indent = col = 0;
                    if (context && context.align == null) context.align = false;
                }
                else if (type == "whitespace" && col == 0) {
                    indent = width;
                }
                else if (type != "sp-comment" && context && context.align == null) {
                    context.align = true;
                }

                if (content != "\n") col += width;

                if (/[\[\{\(]/.test(content)) {
                    pushContext(content, width);
                }
                else if (/[\]\}\)]/.test(content)) {
                    while (context && context.type == "pattern")
                        popContext();
                    if (context && content == matching[context.type])
                        popContext();
                }
                else if (content == "." && context && context.type == "pattern") {
                    popContext();
                }
                else if ((type == "sp-word" || type == "sp-prefixed" || type == "sp-uri" || type == "sp-var" || type == "sp-literal") &&
                    context && /[\{\[]/.test(context.type)) {
                    pushContext("pattern", width);
                }

                return token;
            },

            copy: function () {
                var _context = context, _indent = indent, _col = col, _tokenState = tokens.state;
                return function (source) {
                    tokens = tokenizeSparql(source, _tokenState);
                    context = _context;
                    indent = _indent;
                    col = _col;
                    return iter;
                };
            }
        };
        return iter;
    }

    return {make: parseSparql, electricChars: "}]"};
})();
