package org.jboss.seam.core;

import static org.jboss.seam.ScopeType.STATELESS;
import static org.jboss.seam.annotations.Install.BUILT_IN;

import java.text.MessageFormat;
import java.util.StringTokenizer;

import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.log.LogProvider;
import org.jboss.seam.log.Logging;

/**
 * Interpolates EL expressions in Strings
 * 
 * @author Gavin King
 */
@BypassInterceptors
@Scope(STATELESS)
@Name("org.jboss.seam.core.interpolator")
@Install(precedence=BUILT_IN)
public class Interpolator 
{

    private static final LogProvider log = Logging.getLogProvider(Interpolator.class);

    public static Interpolator instance()
    {
        if (Contexts.isApplicationContextActive()) {
            return (Interpolator) Component.getInstance(Interpolator.class, ScopeType.APPLICATION);         
        } else {
            return new Interpolator(); //for unit testing
        }
    }

    /**
     * Replace all EL expressions in the form #{...} with their evaluated
     * values.
     * 
     * @param string a template
     * @return the interpolated string
     */
    public String interpolate(String string, Object... params) 
    {
        if (params == null) {
            params = new Object[0];
        }

        if (params.length>10) {
            throw new IllegalArgumentException("more than 10 parameters");
        }

        if (string.indexOf('#')>=0 || string.indexOf('{')>=0) {
            string = interpolateExpressions(string, params);
        }

        return string;
    }

    private String interpolateExpressions(String string, Object... params)
    {
        StringTokenizer tokens = new StringTokenizer(string, "#{}", true);
        StringBuilder builder = new StringBuilder(string.length());
        try {
            while (tokens.hasMoreTokens()) {
                String tok = tokens.nextToken();

                if ("#".equals(tok) && tokens.hasMoreTokens()) {
                    String nextTok = tokens.nextToken();

                    while (nextTok.equals("#") && tokens.hasMoreTokens()) {
                        builder.append(tok);
                        nextTok = tokens.nextToken();
                    }
                    
                    if ("{".equals(nextTok)) {
                        String expression = "#{" + tokens.nextToken() + "}";
                        try {
                            Object value = Expressions.instance().createValueExpression(expression).getValue();
                            if (value!=null) builder.append(value);
                        } catch (Exception e) {
                            log.warn("exception interpolating string: " + string, e);
                        }
                        tokens.nextToken(); // the trailing "}"

                    } else if (nextTok.equals("#"))  {
                        // could be trailing # 
                        builder.append("#");

                    } else {
                        int index;
                        try {
                            index = Integer.parseInt(nextTok.substring(0, 1));
                            if (index>=params.length) {
                                //log.warn("parameter index out of bounds: " + index + " in: " + string);
                                builder.append("#").append(nextTok);
                            } else {
                                builder.append(params[index]).append(nextTok.substring(1));
                            }
                        } catch (NumberFormatException nfe) {
                            builder.append("#").append(nextTok);
                        }
                    }
                } else if ("{".equals(tok)) {
                    StringBuilder expr = new StringBuilder();

                    expr.append(tok);
                    int level = 1;

                    while (tokens.hasMoreTokens()) {
                        String nextTok = tokens.nextToken();
                        expr.append(nextTok);

                        if (nextTok.equals("{")) {
                            ++level;
                        } else if (nextTok.equals("}")) {
                            if (--level == 0) {
                                try {
                                    String value = new MessageFormat(expr.toString(), Locale.instance()).format(params);
                                    builder.append(value);
                                } catch (Exception e) {
                                    // if it is a bad message, use the expression itself
                                    builder.append(expr);                             
                                }
                                expr = null;
                                break;
                            }
                        }
                    } 

                    if (expr != null) {
                        builder.append(expr);
                    }
                } else {
                    builder.append(tok);
                }
            }
        } catch (Exception e) {
            log.warn("exception interpolating string: " + string, e);
        }

        return builder.toString();
    }

}
