package org.opengroupware.jope.appserver.elements;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.opengroupware.jope.appserver.WOAssociation;
import org.opengroupware.jope.appserver.WOContext;
import org.opengroupware.jope.appserver.WOElement;
import org.opengroupware.jope.appserver.WOResponse;
import org.opengroupware.jope.appserver.associations.WOQualifierAssociation;
import org.opengroupware.jope.appserver.associations.WOValueAssociation;
import org.opengroupware.jope.eocontrol.EOQualifier;

/*
 * WOGenericElement
 *
 * This renders an arbitary (HTML) tag. It allows you to make attributes of the
 * tag dynamic. WOGenericElement is for "empty" elements (like <br/>), for
 * elements with subelements use WOGenericContainer.
 * 
 * Sample:
 *   DynHR: WOGenericElement {
 *     elementName = "hr";
 *     border      = currentBorderWidth;
 *   }
 *   
 * Renders:
 *   <hr border="[1]" />
 *   
 * Bindings:
 *   tagName [in] - string
 *   - all other bindings are mapped to tag attributes
 * 
 * Special +/- binding hack:
 * 
 * This element treats all bindings starting with either + or - as conditions
 * which decide whether a value should be rendered. For example:
 * 
 *   Font: WOGenericElement {
 *     elementName = "span";
 *     
 *     +style = isCurrent;
 *     style  = "current"; // only applied if isCurrent is true
 *   }
 *   
 * Further, this hack treats constant string conditions as
 * WOQualifierConditions allowing stuff like this:
 * 
 *   FontOfScheduler: WOGenericElement {
 *     elementName = "span";
 *     
 *     +style = "context.page.name = 'Scheduler'";
 *     style  = "current"; // only applied if isCurrent is true
 *   }
 * 
 * TODO: maybe this doesn't make a lot of sense, but we need some mechanism
 *       to switch CSS classes based on some condition ;-)
 * TODO: at least the WOQualifierCondition thing should be moved to the parser
 *       level
 */
public class WOGenericElement extends WOHTMLDynamicElement {
  
  protected WOAssociation tagName;
  protected Map<String,WOAssociation> extraAttributePlusConditions;
  protected Map<String,WOAssociation> extraAttributeMinusConditions;

  public WOGenericElement(String _n, Map<String, WOAssociation> _assocs,
                          WOElement _template)
  {
    super(_n, _assocs, _template);
    
    if ((this.tagName = grabAssociation(_assocs, "elementName")) == null)
      // TODO: improve log
      System.err.println("no elementName association in WOGenericElement?!");
    
    if (_assocs != null && _assocs.size() > 0) {
      this.extraAttributePlusConditions =
        extractAttributeConditions(_assocs, "+");
      this.extraAttributeMinusConditions =
        extractAttributeConditions(_assocs, "-");
      
      // TODO: contains NAME?
      this.extraAttributes = new HashMap<String, WOAssociation>(_assocs);
      _assocs.clear();
    }
  }
  
  /* extract extra attribute conditions */
  
  protected static Map<String,WOAssociation> extractAttributeConditions
    (Map<String, WOAssociation> _assocs, String _prefix)
  {
    Map<String,WOAssociation> conditions = null;
    List<String> toBeRemoved = null;

    for (String key: _assocs.keySet()) {
      if (!key.startsWith(_prefix))
        continue;
      
      if (conditions == null) {
        conditions  = new HashMap<String, WOAssociation>(2);
        toBeRemoved = new ArrayList<String>(2);
      }
      toBeRemoved.add(key);
             
      /* Dirty hack, treat constant string and EOQualifier values as
       * EOQualifierAssociations ...
       * Would be better to deal with that at the parser level.
       */
      WOAssociation assoc = _assocs.get(key);
      if (assoc instanceof WOValueAssociation) {
        Object v = assoc.valueInComponent(null);
        if (v instanceof String)
          assoc = new WOQualifierAssociation((String)v);
        else if (v instanceof EOQualifier) /* very unlikely */
          assoc = new WOQualifierAssociation((EOQualifier)v);
      }
      
      key = key.substring(_prefix.length());
      conditions.put(key, assoc);
    }
    
    if (toBeRemoved != null) {
      for (String key: toBeRemoved)
        _assocs.remove(key);
    }
    
    return conditions;
  }
  
  /* generate response */
  
  public void appendExtraAttributesToResponse(WOResponse _r, WOContext _c) {
    if (this.extraAttributes == null)
      return;
    
    Object cursor = _c.cursor();
    
    if (this.extraAttributeMinusConditions == null &&
        this.extraAttributePlusConditions == null) {
      for (String k: this.extraAttributes.keySet()) {
        String v = this.extraAttributes.get(k).stringValueInComponent(cursor);
        if (v != null) _r.appendAttribute(k, v);
      }
    }
    else {
      /* complex variant, consider per-attribute conditions */
      
      for (String k: this.extraAttributes.keySet()) {
        WOAssociation condition = null;
        boolean isPositive = true;
        
        /* check whether this attribute has a condition attached */
        
        if (this.extraAttributePlusConditions != null)
          condition = this.extraAttributePlusConditions.get(k);
        if (condition == null && this.extraAttributeMinusConditions != null) {
          condition = this.extraAttributeMinusConditions.get(k);
          isPositive = false;
        }
        
        /* check whether the condition is true */
        
        if (condition != null) {
          boolean flag = condition.booleanValueInComponent(cursor);
          if (isPositive) {
            if (!flag) continue;
          }
          else {
            if (flag) continue;
          }
        }
        
        /* add attribute */
        
        String v = this.extraAttributes.get(k).stringValueInComponent(cursor);
        _r.appendAttribute(k, v);
      }
    }
  }
  
  public void appendToResponse(WOResponse _r, WOContext _ctx) {
    if (_ctx.isRenderingDisabled())
      return;

    String s = null;
    
    if (this.tagName != null)
      s = this.tagName.stringValueInComponent(_ctx);

    if (s != null) {
      _r.appendBeginTag(s);
      this.appendExtraAttributesToResponse(_r, _ctx);
      _r.appendBeginTagClose();
    }
  }
}
