//  Copyright 2004 The Apache Software Foundation
//
// 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.

package org.apache.tapestry.script;

import org.apache.tapestry.ILocation;
import org.apache.tapestry.util.xml.BaseRule;
import org.apache.tapestry.util.xml.RuleDirectedParser;

/**
 * Base class for the rules that build {@link org.apache.tapestry.script.IScriptToken}s.
 * Used with classes that can contain a mix of text and elements (those that
 * accept "full content").
 * 
 *
 * @author Howard Lewis Ship
 * @version $Id: AbstractTokenRule.java 243791 2004-02-19 17:38:13Z hlship $
 * @since 3.0
 **/

abstract class AbstractTokenRule extends BaseRule
{

    /**
     * Adds a token to its parent, the top object on the stack.
     */
    protected void addToParent(RuleDirectedParser parser, IScriptToken token)
    {
        IScriptToken parent = (IScriptToken) parser.peek();

        parent.addToken(token);
    }

    /**
     * Peeks at the top object on the stack (which must be a {@link IScriptToken}),
     * and converts the text into a series of {@link org.apache.tapestry.script.StaticToken} and
     * {@link org.apache.tapestry.script.InsertToken}s.
     */

    public void content(RuleDirectedParser parser, String content)
    {
        IScriptToken token = (IScriptToken) parser.peek();

        addTextTokens(token, content, parser.getLocation());
    }

    private static final int STATE_START = 0;
    private static final int STATE_DOLLAR = 1;
    private static final int STATE_COLLECT_EXPRESSION = 2;

    /**
     * Parses the provided text and converts it into a series of 
     */
    protected void addTextTokens(IScriptToken token, String text, ILocation location)
    {
        char[] buffer = text.toCharArray();
        int state = STATE_START;
        int blockStart = 0;
        int blockLength = 0;
        int expressionStart = -1;
        int expressionLength = 0;
        int i = 0;
        int braceDepth = 0;

        while (i < buffer.length)
        {
            char ch = buffer[i];

            switch (state)
            {
                case STATE_START :

                    if (ch == '$')
                    {
                        state = STATE_DOLLAR;
                        i++;
                        continue;
                    }

                    blockLength++;
                    i++;
                    continue;

                case STATE_DOLLAR :

                    if (ch == '{')
                    {
                        state = STATE_COLLECT_EXPRESSION;
                        i++;

                        expressionStart = i;
                        expressionLength = 0;
                        braceDepth = 1;

                        continue;
                    }

                    // The '$' was just what it was, not the start of a ${} expression
                    // block, so include it as part of the static text block.

                    blockLength++;

                    state = STATE_START;
                    continue;

                case STATE_COLLECT_EXPRESSION :

                    if (ch != '}')
                    {
                        if (ch == '{')
                            braceDepth++;

                        i++;
                        expressionLength++;
                        continue;
                    }

                    braceDepth--;

                    if (braceDepth > 0)
                    {
                        i++;
                        expressionLength++;
                        continue;
                    }

                    // Hit the closing brace of an expression.

                    // Degenerate case:  the string "${}".

                    if (expressionLength == 0)
                        blockLength += 3;

                    if (blockLength > 0)
                        token.addToken(constructStatic(text, blockStart, blockLength, location));

                    if (expressionLength > 0)
                    {
                        String expression =
                            text.substring(expressionStart, expressionStart + expressionLength);

                        token.addToken(new InsertToken(expression, location));
                    }

                    i++;
                    blockStart = i;
                    blockLength = 0;

                    // And drop into state start

                    state = STATE_START;

                    continue;
            }

        }

        // OK, to handle the end.  Couple of degenerate cases where
        // a ${...} was incomplete, so we adust the block length.

        if (state == STATE_DOLLAR)
            blockLength++;

        if (state == STATE_COLLECT_EXPRESSION)
            blockLength += expressionLength + 2;

        if (blockLength > 0)
            token.addToken(constructStatic(text, blockStart, blockLength, location));
    }

    private IScriptToken constructStatic(
        String text,
        int blockStart,
        int blockLength,
        ILocation location)
    {
        String literal = text.substring(blockStart, blockStart + blockLength);

        return new StaticToken(literal, location);
    }
}
