package org.opengroupware.jope.appserver.elements;

import java.util.HashMap;
import java.util.Map;

import org.opengroupware.jope.appserver.WOActionResults;
import org.opengroupware.jope.appserver.WOAssociation;
import org.opengroupware.jope.appserver.WOContext;
import org.opengroupware.jope.appserver.WODynamicElement;
import org.opengroupware.jope.appserver.WOElement;
import org.opengroupware.jope.appserver.WORequest;
import org.opengroupware.jope.appserver.WOResponse;
import org.opengroupware.jope.foundation.NSKeyValueCoding;
import org.opengroupware.jope.foundation.NSKeyValueCodingAdditions;

/*
 * WOCopyValue
 * 
 * Used to copy values in the component when entering a certain template
 * section. This should be used with care since it embeds "logic" into the
 * template (which is nice for fast hacks but not encouraged ;-)
 * 
 * Sample:
 *   SetupContext: WOCopyValue {
 *      currentDate = "currentItem.date";
 *      copyValues = {
 *        "displayGroup.queryMin.lastModified" = "currentItem.date";
 *        "displayGroup.queryMax.lastModified" = "currentItem.date";
 *      };
 *      finishValues = {
 *        "displayGroup.queryMin.lastModified" = null;
 *        "displayGroup.queryMax.lastModified" = null;
 *      };
 *      resetValues = NO;
 *   }
 *
 * Renders:
 *   This element doesn't render anything.
 * 
 * Bindings:
 *   copyValues   [in] - Map
 *   finishValues [in] - Map
 *   resetValues  [in] - boolean
 *   <extra>      [in] - used a 'prepare' values
 *   
 * TODO: improve efficiency by avoiding reflection.
 */
public class WOCopyValue extends WODynamicElement {
  
  protected WOAssociation copyValues;
  protected WOAssociation finishValues;
  protected WOAssociation resetValues;
  protected WOElement     template;

  public WOCopyValue(String _name, Map<String, WOAssociation> _assocs,
                     WOElement _template)
  {
    super(_name, _assocs, _template);
    
    this.copyValues    = grabAssociation(_assocs, "copyValue");
    this.finishValues  = grabAssociation(_assocs, "finishValues");
    this.resetValues   = grabAssociation(_assocs, "resetValues");
    
    if (_assocs != null && _assocs.size() > 0) {
      // TODO: contains NAME?
      this.extraAttributes = new HashMap<String, WOAssociation>(_assocs);
      _assocs.clear();
    }
  }
  
  /* copy */
  
  protected void copyValues(Object _mapThing, WOContext _ctx) {
    if (_mapThing == null)
      return;
    
    Map map = (Map)_mapThing; // TODO: try some coercion?
    
    Object getCursor = _ctx.cursor();
    Object setCursor = getCursor;
    if (getCursor == null)
      return;
    
    for (Object lhs: map.keySet()) {
      Object rhs;
      Object value = null;
      
      rhs = map.get(lhs);
      
      /* retrieve value */
      
      if (rhs instanceof WOAssociation)
        value = ((WOAssociation)rhs).valueInComponent(getCursor);
      else if (rhs instanceof String) {
        String s = (String)rhs;
        
        if (s.startsWith("const:"))
          value = this.valueForConstString(s);
        else {
          // TODO: cache KVC
          value = NSKeyValueCodingAdditions.Utility
            .valueForKeyPath(getCursor, s);
        }
      }
      else
        value = rhs;
      
      /* apply value */
      
      if (lhs instanceof WOAssociation)
        ((WOAssociation)lhs).setValue(value, setCursor);
      else {
        String s = (String)lhs;
        
        // TODO: cache KVC
        NSKeyValueCodingAdditions.Utility
          .takeValueForKeyPath(setCursor, value, s);
      }
    }
  }
  
  protected String valueForConstString(String _s) {
    return _s.substring(6);
  }
  
  protected void copyValuesInContext(WOContext _ctx) {
    /* copy constant mappings */
    if (this.extraAttributes != null)
      this.copyValues(this.extraAttributes, _ctx);
    
    /* copy dynamic mappings */
    if (this.copyValues != null)
      this.copyValues(this.copyValues.valueInComponent(_ctx.cursor()), _ctx);
  }

  protected void resetValuesInContext(WOContext _ctx) {
    if (this.resetValues == null && this.finishValues == null)
      return;
    
    Object cursor = _ctx.cursor();
    if (cursor == null) return;
    
    /* reset values to nil */
    
    if (this.resetValues.booleanValueInComponent(cursor)) {
      if (this.extraAttributes != null) {
        for (String k: this.extraAttributes.keySet())
          NSKeyValueCoding.Utility.takeValueForKey(cursor, null, k);
      }
    }
    
    /* apply post value copy */
    
    if (this.finishValues != null)
      this.copyValues(this.finishValues.valueInComponent(cursor), _ctx);
  }
  
  /* responder */
  
  public void takeValuesFromRequest(WORequest _rq, WOContext _ctx) {
    this.copyValuesInContext(_ctx);
    
    if (this.template != null)
      this.template.takeValuesFromRequest(_rq, _ctx);
    
    this.resetValuesInContext(_ctx);
  }
  
  public WOActionResults invokeAction(WORequest _rq, WOContext _ctx) {
    WOActionResults result = null;
    
    this.copyValuesInContext(_ctx);
    
    if (this.template != null)
      result = this.template.invokeAction(_rq, _ctx);
    
    this.resetValuesInContext(_ctx);
    return result;
  }
  
  public void appendToResponse(WOResponse _r, WOContext _ctx) {
    this.copyValuesInContext(_ctx);
    
    if (this.template != null)
      this.template.appendToResponse(_r, _ctx);
    
    this.resetValuesInContext(_ctx);
  }
}
