/**
 * This class parses the XTemplate syntax and calls abstract methods to process the parts.
 * @private
 */
Ext.define('Ext.util.XTemplateParser', {
    /* eslint-disable dot-notation */
    requires: [
        'Ext.String'
    ],
 
    constructor: function(config) {
        Ext.apply(this, config);
    },
 
    /**
     * @property {Number} level The 'for' or 'foreach' loop context level. This is adjusted
     * up by one prior to calling {@link #doFor} or {@link #doForEach} and down by one after
     * calling the corresponding {@link #doEnd} that closes the loop. This will be 1 on the
     * first {@link #doFor} or {@link #doForEach} call.
     */
 
    /**
     * This method is called to process a piece of raw text from the tpl.
     * @param {String} text 
     * @method doText
     */
    // doText: function (text)
 
    /**
     * This method is called to process expressions (like `{[expr]}`).
     * @param {String} expr The body of the expression (inside "{[" and "]}").
     * @method doExpr
     */
    // doExpr: function (expr)
 
    /**
     * This method is called to process simple tags (like `{tag}`).
     * @method doTag
     */
    // doTag: function (tag)
 
    /**
     * This method is called to process `<tpl else>`.
     * @method doElse
     */
    // doElse: function ()
 
    /**
     * This method is called to process `{% text %}`.
     * @param {String} text 
     * @method doEval
     */
    // doEval: function (text)
 
    /**
     * This method is called to process `<tpl if="action">`. If there are other attributes,
     * these are passed in the actions object.
     * @param {String} action 
     * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
     * @method doIf
     */
    // doIf: function (action, actions)
 
    /**
     * This method is called to process `<tpl elseif="action">`. If there are other attributes,
     * these are passed in the actions object.
     * @param {String} action 
     * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
     * @method doElseIf
     */
    // doElseIf: function (action, actions)
 
    /**
     * This method is called to process `<tpl switch="action">`. If there are other attributes,
     * these are passed in the actions object.
     * @param {String} action 
     * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
     * @method doSwitch
     */
    // doSwitch: function (action, actions)
 
    /**
     * This method is called to process `<tpl case="action">`. If there are other attributes,
     * these are passed in the actions object.
     * @param {String} action 
     * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
     * @method doCase
     */
    // doCase: function (action, actions)
 
    /**
     * This method is called to process `<tpl default>`.
     * @method doDefault
     */
    // doDefault: function ()
 
    /**
     * This method is called to process `</tpl>`. It is given the action type that started
     * the tpl and the set of additional actions.
     * @param {String} type The type of action that is being ended.
     * @param {Object} actions The other actions keyed by the attribute name (such as 'exec').
     * @method doEnd
     */
    // doEnd: function (type, actions) 
 
    /**
     * This method is called to process `<tpl for="action">`. If there are other attributes,
     * these are passed in the actions object.
     * @param {String} action 
     * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
     * @method doFor
     */
    // doFor: function (action, actions)
 
    /**
     * This method is called to process `<tpl foreach="action">`. If there are other
     * attributes, these are passed in the actions object.
     * @param {String} action 
     * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
     * @method doForEach
     */
    // doForEach: function (action, actions)
 
    /**
     * This method is called to process `<tpl exec="action">`. If there are other attributes,
     * these are passed in the actions object.
     * @param {String} action 
     * @param {Object} actions Other actions keyed by the attribute name.
     * @method doExec
     */
    // doExec: function (action, actions)
 
    /**
     * This method is called to process an empty `<tpl>`. This is unlikely to need to be
     * implemented, so a default (do nothing) version is provided.
     * @method
     */
    doTpl: Ext.emptyFn,
 
    parse: function(str) {
        var me = this,
            len = str.length,
            aliases = { elseif: 'elif' },
            topRe = me.topRe,
            actionsRe = me.actionsRe,
            index, stack, s, m, t, prev, frame, subMatch, begin, end, actions,
            prop, expectTplNext;
 
        me.level = 0;
        me.stack = stack = [];
 
        for (index = 0; index < len; index = end) {
            topRe.lastIndex = index;
            m = topRe.exec(str);
 
            if (!m) {
                me.doText(str.substring(index, len));
                break;
            }
 
            begin = m.index;
            end = topRe.lastIndex;
 
            if (index < begin) {
                // In the case of a switch statement, we expect a tpl for each case.
                // However, if we have spaces they will get matched as plaintext, so
                // we want to skip over them here.
                s = str.substring(index, begin);
 
                if (!(expectTplNext && Ext.String.trim(s) === '')) {
                    me.doText(s);
                }
            }
 
            expectTplNext = false;
 
            if (m[1]) {
                end = str.indexOf('%}', begin + 2);
                me.doEval(str.substring(begin + 2, end));
                end += 2;
            }
            else if (m[2]) {
                end = str.indexOf(']}', begin + 2);
                me.doExpr(str.substring(begin + 2, end));
                end += 2;
            }
            else if (m[3]) { // if ('{' token)
                me.doTag(m[3]);
            }
            else if (m[4]) { // content of a <tpl xxxxxx xxx> tag
                actions = null;
 
                while ((subMatch = actionsRe.exec(m[4])) !== null) {
                    s = subMatch[2] || subMatch[3];
 
                    if (s) {
                        s = Ext.String.htmlDecode(s); // decode attr value
                        t = subMatch[1];
                        t = aliases[t] || t;
                        actions = actions || {};
                        prev = actions[t];
 
                        if (typeof prev === 'string') {
                            actions[t] = [prev, s];
                        }
                        else if (prev) {
                            actions[t].push(s);
                        }
                        else {
                            actions[t] = s;
                        }
                    }
                }
 
                if (!actions) {
                    if (me.elseRe.test(m[4])) {
                        me.doElse();
                    }
                    else if (me.defaultRe.test(m[4])) {
                        me.doDefault();
                    }
                    else {
                        me.doTpl();
                        stack.push({ type: 'tpl' });
                    }
                }
                else if (actions['if']) {
                    me.doIf(actions['if'], actions);
                    stack.push({ type: 'if' });
                }
                else if (actions['switch']) {
                    me.doSwitch(actions['switch'], actions);
                    stack.push({ type: 'switch' });
                    expectTplNext = true;
                }
                else if (actions['case']) {
                    me.doCase(actions['case'], actions);
                }
                else if (actions['elif']) {
                    me.doElseIf(actions['elif'], actions);
                }
                else if (actions['for']) {
                    ++me.level;
 
                    // Extract property name to use from indexed item
                    // eslint-disable-next-line no-cond-assign
                    if (prop = me.propRe.exec(m[4])) {
                        actions.propName = prop[1] || prop[2];
                    }
 
                    me.doFor(actions['for'], actions);
                    stack.push({ type: 'for', actions: actions });
                }
                else if (actions['foreach']) {
                    ++me.level;
 
                    // Extract property name to use from indexed item
                    // eslint-disable-next-line no-cond-assign
                    if (prop = me.propRe.exec(m[4])) {
                        actions.propName = prop[1] || prop[2];
                    }
 
                    me.doForEach(actions['foreach'], actions);
                    stack.push({ type: 'foreach', actions: actions });
                }
                else if (actions.exec) {
                    me.doExec(actions.exec, actions);
                    stack.push({ type: 'exec', actions: actions });
                }
                /*
                else {
                    // todo - error
                }
                */
            }
            else if (m[0].length === 5) {
                // if the length of m[0] is 5, assume that we're dealing with an opening tpl tag
                // with no attributes (e.g. <tpl>...</tpl>)
                // in this case no action is needed other than pushing it on to the stack
                stack.push({ type: 'tpl' });
            }
            else {
                frame = stack.pop();
                me.doEnd(frame.type, frame.actions);
 
                if (frame.type === 'for' || frame.type === 'foreach') {
                    --me.level;
                }
            }
        }
    },
 
    // Internal regexes
 
    /* eslint-disable no-useless-escape */
    topRe: /(?:(\{\%)|(\{\[)|\{([^{}]+)\})|(?:<tpl([^>]*)\>)|(?:<\/tpl>)/g,
    actionsRe: /\s*(elif|elseif|if|for|foreach|exec|switch|case|eval|between)\s*\=\s*(?:(?:"([^"]*)")|(?:'([^']*)'))\s*/g,
    propRe: /prop=(?:(?:"([^"]*)")|(?:'([^']*)'))/,
    defaultRe: /^\s*default\s*$/,
    elseRe: /^\s*else\s*$/
});