/**
 * This class parses simple expressions. The parser can be enhanced by providing any of
 * the following configs:
 *
 *  * `constants`
 *  * `infix`
 *  * `infixRight`
 *  * `postfix`
 *  * `symbols`
 *
 * The parser requires a `{@link Ext.parse.Tokenizer tokenizer}` which can be configured
 * using the `tokenizer` config. The parser keeps the tokenizer instance and recycles it
 * as it is itself reused.
 *
 * See http://javascript.crockford.com/tdop/tdop.html for background on the techniques
 * used in this parser.
 * @private
 */
Ext.define('Ext.parse.Parser', function () {
    var ITSELF = function () { return this; };

return {
    extend: 'Ext.util.Fly',

    requires: [
        'Ext.parse.Tokenizer',
        'Ext.parse.symbol.Constant',
        'Ext.parse.symbol.InfixRight',
        'Ext.parse.symbol.Paren',
        'Ext.parse.symbol.Prefix'
    ],

    isParser: true,

    config: {
        /**
         * @cfg {Object} constants
         * A map of identifiers that should be converted to literal value tokens. The
         * key in this object is the name of the constant and the value is the constant
         * value.
         *
         * If the value of a key is an object, it is a config object for the
         * `{@link Ext.parse.symbol.Constant constant}`.
         */
        constants: {
            'null': null,
            'false': false,
            'true': true
        },

        /**
         * @cfg {Object} infix
         * A map of binary operators and their associated precedence (or binding priority).
         * These binary operators are left-associative.
         *
         * If the value of a key is an object, it is a config object for the
         * `{@link Ext.parse.symbol.Infix operator}`.
         */
        infix: {
            //'===': 40,
            //'!==': 40,
            //'<': 40,
            //'<=': 40,
            //'>': 40,
            //'>=': 40,
            '+': 50,
            '-': 50,
            '*': 60,
            '/': 60
        },

        /**
         * @cfg {Object} infixRight
         * A map of binary operators and their associated precedence (or binding priority).
         * These binary operators are right-associative.
         *
         * If the value of a key is an object, it is a config object for the
         * `{@link Ext.parse.symbol.InfixRight operator}`.
         */
        infixRight: {
            '&&': 30,
            '||': 30
        },

        /**
         * @cfg {Object} prefix
         * A map of unary operators. Typically no value is needed, so `0` is used.
         *
         * If the value of a key is an object, it is a config object for the
         * `{@link Ext.parse.symbol.Prefix operator}`.
         */
        prefix: {
            '!': 0,
            '-': 0,
            '+': 0
        },

        /**
         * @cfg {Object} symbols
         * General language symbols. The values in this object are used as config objects
         * to configure the associated `{@link Ext.parse.Symbol symbol}`. If there is no
         * configuration, use `0` for the value.
         */
        symbols: {
            ':': 0,
            ',': 0,
            ')': 0,
            '[': 0,
            ']': 0,
            '{': 0,
            '}': 0,

            '(end)': 0,

            '(ident)': {
                arity: 'ident',
                isIdent: true,
                nud: ITSELF
            },

            '(literal)': {
                arity: 'literal',
                isLiteral: true,
                nud: ITSELF
            },

            '(': {
                xclass: 'Ext.parse.symbol.Paren'
            }
        },

        /**
         * @cfg {Object/Ext.parse.Tokenizer} tokenizer
         * The tokenizer or a config object used to create one.
         */
        tokenizer: {
            keywords: null  // we'll handle keywords here
        }
    },

    /**
     * @cfg {Ext.parse.Symbol} token
     * The current token. These tokens extend this base class and contain additional
     * properties such as:
     *
     *   * `at` - The index of the token in the text.
     *   * `value` - The value of the token (e.g., the name of an identifier).
     *
     * @readonly
     */
    token: null,

    constructor: function (config) {
        this.symbols = {};

        this.initConfig(config);
    },

    /**
     * Advances the token stream and returns the next `token`.
     * @param {String} [expected] The type of symbol that is expected to follow.
     * @return {Ext.parse.Symbol}
     */
    advance: function (expected) {
        var me = this,
            tokenizer = me.tokenizer,
            token = tokenizer.peek(),
            symbols = me.symbols,
            index = tokenizer.index,
            is, symbol, value;

        if (me.error) {
            throw me.error;
        }
        if (expected) {
            me.expect(expected);
        }

        if (!token) {
            return me.token = symbols['(end)'];
        }

        tokenizer.next();

        is = token.is;
        value = token.value;

        if (is.ident) {
            symbol = symbols[value] || symbols['(ident)'];
        }
        else if (is.operator) {
            if (!(symbol = symbols[value])) {
                me.syntaxError(token.at, 'Unknown operator "' + value + '"');
            }
        }
        else if (is.literal) {
            symbol = symbols['(literal)'];
        }
        else {
            me.syntaxError(token.at, 'Unexpected token');
        }

        me.token = symbol = Ext.Object.chain(symbol);
        symbol.at = index;
        symbol.value = value;

        if (!symbol.arity) {
            symbol.arity = token.type;
        }

        return symbol;
    },

    expect: function (expected) {
        var token = this.token;

        if (expected !== token.id) {
            this.syntaxError(token.at, 'Expected "' + expected + '"');
        }
        return this;
    },

    /**
     *
     * @param {Number} [rightPriority=0] The precedence of the current operator.
     * @return {Ext.parse.Symbol} The parsed expression tree.
     */
    parseExpression: function (rightPriority) {
        var me = this,
            token = me.token,
            left;

        rightPriority = rightPriority || 0;

        me.advance();

        left = token.nud();

        while (rightPriority < (token = me.token).priority) {
            me.advance();
            left = token.led(left);
        }

        return left;
    },

    /**
     * Resets this parser given the text to parse or a `Tokenizer`.
     * @param {String} text
     * @param {Number} [pos=0] The character position at which to start.
     * @param {Number} [end] The index of the first character beyond the token range.
     * @return {Ext.parse.Parser}
     */
    reset: function (text, pos, end) {
        var me = this;

        me.error = me.token = null;
        me.tokenizer.reset(text, pos, end);

        me.advance(); // kick start this.token

        return me;
    },

    /**
     * This method is called when a syntax error is encountered. It updates `error`
     * and returns the error token.
     * @param {Number} at The index of the syntax error (optional).
     * @param {String} message The error message.
     * @return {Object} The error token.
     */
    syntaxError: function (at, message) {
        if (typeof at === 'string') {
            message = at;
            at = this.pos;
        }

        var suffix = (at == null) ? '' : (' (at index ' + at + ')'),
            error = new Error(message + suffix);

        error.type = 'error';

        if (suffix) {
            error.at = at;
        }

        throw this.error = error;
    },

    privates: {
        /**
         * This property is set to an `Error` instance if the parser encounters a syntax
         * error.
         * @property {Object} error
         * @readonly
         */
        error: null,

        addSymbol: function (id, config, type, update) {
            var symbols = this.symbols,
                symbol = symbols[id],
                cfg, length, i;

            if (symbol) {
                // If the symbol was already defined then we need to update it
                // we either use the config provided in the symbol definition
                // or we use the `update` param to build a config object.
                // We usually need to update either `led` or `nud` function
                if(typeof config === 'object'){
                    cfg = config;
                }else if(update && type){
                    update = Ext.Array.from(update);
                    length = update.length;
                    cfg = {};
                    for(i = 0; i < length; i++) {
                        cfg[update[i]] = type.prototype[update[i]];
                    }
                }else{
                    return symbol;
                }
                symbol.update(cfg);
            } else {
                if (config && config.xclass) {
                    type = Ext.ClassManager.get(config.xclass);
                } else {
                    type = type || Ext.parse.Symbol;
                }

                symbols[id] = symbol = new type(id, config);
                symbol.parser = this;
            }

            return symbol;
        },

        addSymbols: function (symbols, type, update) {
            for (var id in symbols) {
                this.addSymbol(id, symbols[id], type, update);
            }
        },

        applyConstants: function (constants) {
            this.addSymbols(constants, Ext.parse.symbol.Constant, 'nud');
        },

        applyInfix: function (operators) {
            this.addSymbols(operators, Ext.parse.symbol.Infix, 'led');
        },

        applyInfixRight: function (operators) {
            this.addSymbols(operators, Ext.parse.symbol.InfixRight, 'led');
        },

        applyPrefix: function (operators) {
            this.addSymbols(operators, Ext.parse.symbol.Prefix, 'nud');
        },

        applySymbols: function (symbols) {
            this.addSymbols(symbols);
        },

        applyTokenizer: function (config) {
            var ret = config;

            if (config && !config.isTokenizer) {
                ret = new Ext.parse.Tokenizer(config);
            }

            this.tokenizer = ret;
        }
    }
}});
