package org.opengroupware.jope.appserver.elements;

import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.Format;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Map;

import org.opengroupware.jope.appserver.WOAssociation;
import org.opengroupware.jope.appserver.WOContext;
import org.opengroupware.jope.appserver.WODynamicElement;
import org.opengroupware.jope.foundation.NSJavaRuntime;
import org.opengroupware.jope.foundation.NSObject;
import org.opengroupware.jope.foundation.NSClassLookupContext;

/*
 * WOFormatter
 * 
 * Helper class which deals with formatting attributes of WODynamicElement's.
 * It is based upon java.text.Format.
 * 
 * THREAD: remember that Format objects are usually not thread-safe.
 */
public abstract class WOFormatter extends NSObject {
  
  public static WOFormatter formatterForAssociations
    (Map<String, WOAssociation> _assocs)
  {
    WOAssociation dateformat, numberformat, formatter, formatterClass;
    
    dateformat     = WODynamicElement.grabAssociation(_assocs,"dateformat");
    numberformat   = WODynamicElement.grabAssociation(_assocs,"numberformat");
    formatter      = WODynamicElement.grabAssociation(_assocs,"formatter");
    formatterClass = WODynamicElement.grabAssociation(_assocs,"formatterClass");
    
    // TODO: warn about multiple formatters, OR: wrap multiple formatters in an
    //       compound formatter and return that.

    if (dateformat != null)
      new WODateFormatter(dateformat);
    
    if (numberformat != null)
      new WONumberFormatter(numberformat);
    
    if (formatterClass != null)
      new WOClassFormatter(formatterClass, formatter);
    
    if (formatter != null)
      new WOObjectFormatter(formatter);
    
    return null;
  }
  
  public WOFormatter() {
  }
  
  /* methods */
  
  public abstract Format formatInContext(WOContext _ctx);
  
  /* NSFormatter like wrappers */

  public Object objectValueForString(String _s, WOContext _ctx)
    throws ParseException
  {
    if (_s == null)
      return null;
    
    Format fmt = this.formatInContext(_ctx);
    if (fmt == null)
      return _s;
    
    return fmt.parseObject(_s);
  }
  
  public String stringForObjectValue(Object _o, WOContext _ctx) {
    Format fmt = this.formatInContext(_ctx);
    if (fmt == null)
      return _o != null ? _o.toString() : null;
    
    return fmt.format(_o);
  }
  
  public String editingStringForObjectValue(Object _o, WOContext _ctx) {
    return this.stringForObjectValue(_o, _ctx);
  }

  
  /* implementations */
  
  static class WODateFormatter extends WOFormatter {
    // TODO: this does not seem to work properly
    
    protected WOAssociation format = null;
    
    public WODateFormatter(WOAssociation _fmt) {
      this.format = _fmt;
    }
    
    /* creating Format */
    
    public Format formatInContext(WOContext _ctx) {
      // TODO: we should probably cache some formatter objects?
      // TODO: find out how to do date patterns
      //
      // eg: DateFormat expiresFormat1
      //     = new SimpleDateFormat("E, dd-MMM-yyyy k:m:s 'GMT'", Locale.US)
      String fmt = this.format.stringValueInComponent(_ctx.cursor());
      
      if (fmt == null)
        return null;
      
      int mode = DateFormat.SHORT;
      if (fmt.endsWith("SHORT"))
        mode = DateFormat.SHORT;
      else if (fmt.endsWith("MEDIUM"))
        mode = DateFormat.MEDIUM;
      else if (fmt.endsWith("LONG"))
        mode = DateFormat.LONG;
      else if (fmt.endsWith("FULL"))
        mode = DateFormat.FULL;
      
      if (fmt.startsWith("DATETIME"))
        return DateFormat.getDateTimeInstance(mode, mode, _ctx.locale());

      if (fmt.startsWith("TIME"))
        return DateFormat.getTimeInstance(mode, _ctx.locale());
      
      return DateFormat.getDateInstance(mode, _ctx.locale());
    }
  }
  
  static class WONumberFormatter extends WOFormatter {
    
    protected WOAssociation format = null;
    
    public WONumberFormatter(WOAssociation _fmt) {
      this.format = _fmt;
    }
    
    /* creating Format */
    
    public Format formatInContext(WOContext _ctx) {
      // TODO: we should probably cache some formatter objects?
      // TODO: find out how to do date patterns
      String fmt = this.format.stringValueInComponent(_ctx.cursor());
      
      if (fmt == null)
        return null;
      
      NumberFormat lf = NumberFormat.getInstance(_ctx.locale());
      if (lf == null)
        return null;
      
      if (lf instanceof DecimalFormat)
        ((DecimalFormat)lf).applyPattern(fmt);
      
      return lf;
    }
  }
  
  static class WOObjectFormatter extends WOFormatter {
    
    protected WOAssociation formatter = null;
    
    public WOObjectFormatter(WOAssociation _fmt) {
      this.formatter = _fmt;
    }

    /* creating Format */
    
    public Format formatInContext(WOContext _ctx) {
      if (this.formatter == null)
        return null;
      
      return (Format)this.formatter.valueInComponent(_ctx.cursor());
    }
  }
  
  static class WOClassFormatter extends WOFormatter {
    
    protected WOAssociation formatterClass = null;
    protected WOAssociation format = null;
    
    public WOClassFormatter(WOAssociation _fmt, WOAssociation _val) {
      this.formatterClass = _fmt;
      this.format = _val;
    }

    /* creating Format */
    
    public Format formatInContext(WOContext _ctx) {
      if (this.formatterClass == null)
        return null;
      
      /* determine class */
      
      Object tmp = this.formatterClass.valueInComponent(_ctx.cursor());
      Class  cls = null;
      if (tmp instanceof Class)
        cls = (Class)tmp;
      else {
        NSClassLookupContext clsctx = _ctx.component().resourceManager();
        cls = clsctx.lookupClass(tmp.toString());
        if (cls == null) {
          // TODO: improve logging
          System.err.println("ERROR: did not find formatter: " + tmp);
          return null;
        }
      }
      
      /* instantiate */
      
      if (this.format == null) /* formatter w/o argument */
        return (Format)NSJavaRuntime.NSAllocateObject(cls);
      
      String fmt = this.format.stringValueInComponent(_ctx.cursor());
      return (Format)NSJavaRuntime.NSAllocateObject(cls, String.class, fmt);
    }
  }
}
