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 == null)
      {
         string = "";
      }
      
      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());
      
      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.debug("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
                     {
                        if (params.length == 0)
                        {
                           builder.append(expr.toString());
                        }
                        else
                        {
                           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);
         }
      }
      
      return builder.toString();
   }
   
}
