/*
Copyright 2010 Mike Brevoort http://mike.brevoort.com (twitter:@mbrevoort)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

This is an indirect collective derivative of the other parses in this package

*/

// The XQuery parser uses the xquery tokenizer in tokenizexquery.js. Given the
// stream of tokens, it makes decisions to override the tokens styles by evaluating
// it's context with respect to the other tokens. 

var XqueryParser = Editor.Parser = (function() {
    function xqueryLexical(startColumn, currentToken, align, previousToken, encloseLevel) {
        this.startColumn = startColumn;
        this.currentToken = currentToken;
        if (align != null)
        this.align = align;
        this.previousToken = previousToken;
        this.encloseLevel = encloseLevel;
    }

    // Xquery indentation rules.
    function indentXquery(lexical) {
        return function(firstChars, curIndent, direction) {
            // test if this is next row after the open brace
            if (lexical.encloseLevel !== 0 && firstChars === "}") {
                return lexical.startColumn - indentUnit;
            }

            return lexical.startColumn;
        };
    }

    function parseXquery(source) {
        var tokens = tokenizeXquery(source);

        var column = 0;
        // tells the first non-whitespace symbol from the
        // start of row.
        var previousToken = null;
        var previousTokens = [];
        //mb
        var align = false;
        // tells if the text after the open brace
        var encloseLevel = 0;
        // tells curent opened braces quantity
        var cc = [statements];
        var consume,
        marked;

        var iter = {
            next: function() {
                var token = tokens.next();
                // since attribute and elements can be named the same, assume the
                // following word of each is a variable
                if (previousToken && (previousToken.content == "attribute" || previousToken.content == "element") && previousToken.type == "xqueryKeywordC") {
                    token.type = "variable";
                    token.style = "xqueryVariable";
                }

                else if (previousToken && previousToken.content == "xquery" && token.content == "version") {
                    //token.type="variable";
                    token.style = "xqueryModifier";
                }

                else if (token.type == "word" && (getPrevious(3).style == "xml-attribute" || (previousToken && previousToken.type == "xml-tag-open")) &&
                previousToken.content.substring(previousToken.content.length - 1) != ">") {
                    token.style = "xml-attribute";
                }
                else if (previousToken && previousToken.content == "=" && previousTokens.length > 2
                && getPrevious(2).style == "xml-attribute") {
                    token.style = "xml-attribute-value";
                }
                else if(token.type == "string" && previousToken && previousToken.type == "}") {
                    // looking for expressions within a string and detecting if the expression is within an attribute
                    var i=0;
                    while(i++ < previousTokens.length-1) {
                        if(getPrevious(i).style == "xml-attribute-value" ) {
                            token.style = "xml-attribute-value";
                            break;
                        } 
                        else if(getPrevious(i).type == "string") {
                            break;
                        }                            
                    }
                }    
                else if(token.type == "string") {
                    // brute force check for strings inside XML TODO... something else
                    var i=0;
                    var closeCount = 0;
                    while(i++ < previousTokens.length-1) {
                        var prev = getPrevious(i);                  
                        if(prev.type == "xml-tag-open") {
                            if(closeCount == 0) {
                                token.style = "word";
                                break;
                            } else {
                                closeCount--;
                            }
                        }    
                        else if(prev.type == "xml-tag-close") {
                            closeCount++;
                        }
                        else if(prev.content == ":=" || prev.content == "return" || prev.content == "{")
                            break;
                    }
                }
                else if(getPrevious(2).content == "module" && getPrevious(1).content == "namespace") 
                    token.style="xqueryFunction";
                else if(token.content == "=" && getPrevious(1).style == "xml-attribute")
                    token.style="xml-attribute"

                if (token.type == "whitespace") {
                    if (token.value == "\n") {
                        // test if this is end of line
                        if (previousToken !== null) {
                            if (previousToken.type === "{") {
                                // test if there is open brace at the end of line
                                align = true;
                                column += indentUnit;
                                encloseLevel++;
                            }
                            else
                            if (previousToken.type === "}") {
                                // test if there is close brace at the end of line
                                align = false;
                                if (encloseLevel > 0) {
                                    encloseLevel--;
                                }
                                else {
                                    encloseLevel = 0;
                                }
                            }
                            var lexical = new xqueryLexical(column, token, align, previousToken, encloseLevel);
                            token.indentation = indentXquery(lexical);
                        }
                    }
                    else
                    column = token.value.length;
                }

                // maintain the previous tokens array so that it doesn't continue to leak
                // keep only the last 5000
                if(previousTokens.length > 5000) previousTokens.shift();

                while (true) {
                    consume = marked = false;
                    // Take and execute the topmost action.
                    cc.pop()(token.type, token.content);
                    if (consume) {
                        // Marked is used to change the style of the current token.
                        if (marked)
                        token.style = marked;
                        // Here we differentiate between local and global variables.
                        previousToken = token;
                        previousTokens[previousTokens.length] = token;
                        return token;
                    }
                }

            },

            copy: function() {
                var _cc = cc.concat([]),
                _tokenState = tokens.state,
                _column = column;

                return function copyParser(_source) {
                    cc = _cc.concat([]);
                    column = indented = _column;
                    tokens = tokenizeXquery(_source, _tokenState);
                    return iter;
                };

            },

        };

        function statements(type) {
            return pass(statement, statements);
        }

        function statement(type) {
            cont();
        }

        function push(fs) {
            for (var i = fs.length - 1; i >= 0; i--)
            cc.push(fs[i]);
        }

        function cont() {
            push(arguments);
            consume = true;
        }

        function pass() {
            push(arguments);
            consume = false;
        }
        

        function getPrevious(numberFromCurrent) {
            var l = previousTokens.length;
            if (l - numberFromCurrent >= 0)
            return previousTokens[l - numberFromCurrent];
            else
            return {
                type: "",
                style: "",
                content: ""
            };
        }

        return iter;


    }
    return {
        make: parseXquery
    };
})();