package org.opengroupware.jope.foundation;

import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opengroupware.jope.foundation.kvc.MissingPropertyException;

/*
 * NSKeyValueStringFormatter
 * 
 * Sample formats:
 *   "%(firstname)s %(lastname)s"
 * 
 * Inefficient, crappy implementation, but worx ;-)
 */
public class NSKeyValueStringFormatter extends NSObject {
  protected static final Log log = 
    LogFactory.getLog("NSKeyValueStringFormatter");
  
  protected static abstract class ValueHandler {
    protected boolean  lastWasKeyMiss = false;
    
    public abstract Object valueForKey(String _key);
  }
  
  protected static class ArrayValueHandler extends ValueHandler {
    protected int      posArgCursor   = 0;
    protected Object[] valArray;
    
    public ArrayValueHandler(Object[] _array) {
      this.valArray = _array;
    }
    public ArrayValueHandler(List<Object> _list) {
      this.valArray = _list.toArray(new Object[0]);
    }
    
    public Object valueForKey(String _key) {
      Object value = null;
      
      if (_key != null) {
        if ("length".equals(_key) || "size".equals(_key))
          value = new Integer(this.valArray.length);
        else {
          int idx = Integer.parseInt(_key);
          if (idx < 0 || idx >= this.valArray.length)
            this.lastWasKeyMiss = true;
          else
            value = this.valArray[idx];
        }
      }
      else {
        if (this.posArgCursor >= this.valArray.length)
          this.lastWasKeyMiss = true;
        else {
          value = this.valArray[this.posArgCursor];
          this.posArgCursor++;
        }
      }
      return value;
    }
  }
  
  protected static class KeyValueHandler extends ValueHandler {
    protected NSKeyValueCodingAdditions kvc;
    protected Object object;
    
    public KeyValueHandler(Object _object) {
      if (_object == null)
        ;
      else if (_object instanceof NSKeyValueCodingAdditions)
        this.kvc = (NSKeyValueCodingAdditions)_object;
      else
        this.object = _object;
    }
    
    public Object valueForKey(String _key) {
      if (_key == null) {
        log.error("missing keypath for %(key)s style format!");
        return null;
      }
      
      Object value = null;
      try {
        if (this.kvc != null)
          value = this.kvc.valueForKeyPath(_key);
        else {
          value = NSKeyValueCodingAdditions.Utility
            .valueForKeyPath(this.object, _key);
        }
      }
      catch (MissingPropertyException e) {
        this.lastWasKeyMiss = true;
      }
      
      return value;
    }
  }
  

  @SuppressWarnings("unchecked")
  public static String format
    (String _pattern, Object _values, boolean _requiresAll)
  {
    if (_pattern == null)
      return null;
    if (_pattern.indexOf('%') == -1)
      return _pattern;
    
    char[] pattern = _pattern.toCharArray();
    
    ValueHandler values = null;
    if (_values != null) {
      if (_values instanceof Object[])
        values = new ArrayValueHandler((Object[])_values);
      else if (_values instanceof List)
        values = new ArrayValueHandler((List)_values);
      else
        values = new KeyValueHandler(_values);
    }
    
    StringBuffer sb = new StringBuffer(pattern.length * 2);
    for (int i = 0; i < pattern.length; i++) {
      char c = pattern[i];
      
      if (c != '%') {
        // TODO: improve efficiency, we should delay the adds
        sb.append(c);
        continue;
      }
      
      /* found a marker */
      
      int avail = pattern.length - i - 1 /* consume % */;
      if (avail == 0) {
        /* last char */
        sb.append("%");
        continue;
      }
      
      int pos = i + 1;
      c = pattern[pos];
      if (c == '%') {
        // a quoted per-cent, %%
        i++;
        sb.append("%");
        continue;
      }
      
      /* check for a keypath */
      
      String key = null;
      if (c == '(' && avail >= 4) { /* %(n)i */
        pos++;
        int j;
        for (j = pos; j < pattern.length && pattern[j] != ')'; j++)
          ;
        if (j == pattern.length) { /* EOF, lparen not closed */
          log.info("pattern was not closed: " + _pattern);
          return null; // TODO: add some log
        }
        
        if (j - pos > 0)
          key = new String(pattern, pos, j - pos);
        //System.err.println("KEY: " + key);
        
        pos = j + 1; /* skip ')' */
      }
      
      /* determine value */
      
      boolean keyMiss = false;
      Object value;
      if (values != null) {
        if ((value = values.valueForKey(key)) == null)
          keyMiss = values.lastWasKeyMiss;
      }
      else {
        keyMiss = true;
        value   = null;
      }
      
      if (keyMiss && _requiresAll) {
        log.info("missed required key: " + key); 
        return null;
      }
      
      /* format */

      if (pos == pattern.length) { /* lparen not closed */
        log.info("missing format char in pattern: " + _pattern);
        return null;
      }
      
      c = pattern[pos];
      switch (c) {
        case 'i':
          if (value == null)
            sb.append("0");
          else
            sb.append(NSJavaRuntime.intValueForObject(value));
          break;
        
        case '@':
        case 's':
          if (value == null)
            sb.append("<null>");
          else
            sb.append(value);
          break;
          
        default:
          log.error("unknown format specifier: " + c);
          return null;
      }
      
      /* skip format */
      i = pos;
    }
    
    return sb.toString();
  }
  
  public static String format(String _pattern, Object _values) {
    return format(_pattern, _values, true /* require all bindings */);
  }
  
  public static String format(String _pattern, Object... _args) {
    if (_args == null || _args.length == 0)
      return _pattern;
    
    return format(_pattern, (Object)_args);
  }
}
