package org.opengroupware.jope.appserver.elements;

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.WORequest;
import org.opengroupware.jope.appserver.WOResponse;

/*
 * WOImageButton
 * 
 * Create HTML form textfields.
 * 
 * Sample:
 *   Country: WOPopUpButton {
 *     name      = "country";
 *     list      = ( "UK", "US", "Germany" );
 *     item      = item;
 *     selection = selectedCountry;
 *   }
 * 
 * Renders:
 *   <select name="country">
 *     <option>...</option>
 *     [sub-template]
 *   </select>
 * 
 * Bindings (WOInput):
 *   name      [in]  - string
 *   value     [io]  - object
 *   disabled  [in]  - boolean
 *   
 * Bindings:
 *   list              [in]  - List
 *   item              [out] - object
 *   selection         [out] - object
 *   string            [in]  - String
 *   noSelectionString [in]  - String
 *   selectedValue     [out] - String
 *   escapeHTML        [in]  - boolean
 *   itemGroup
 */
public class WOPopUpButton extends WOInput {
  
  public static final String WONoSelectionString = "WONoSelectionString";
  
  protected WOAssociation list;
  protected WOAssociation item;
  protected WOAssociation selection;
  protected WOAssociation string;
  protected WOAssociation noSelectionString;
  protected WOAssociation selectedValue;
  protected WOAssociation escapeHTML;
  protected WOAssociation itemGroup;
  protected WOElement     template;

  public WOPopUpButton(String _name, Map<String, WOAssociation> _assocs,
                       WOElement _template)
  {
    super(_name, _assocs, _template);
    
    this.list              = grabAssociation(_assocs, "list");
    this.item              = grabAssociation(_assocs, "item");
    this.selection         = grabAssociation(_assocs, "selection");
    this.string            = grabAssociation(_assocs, "string");
    this.noSelectionString = grabAssociation(_assocs, "noSelectionString");
    this.selectedValue     = grabAssociation(_assocs, "selectedValue");
    this.escapeHTML        = grabAssociation(_assocs, "escapeHTML");
    this.itemGroup         = grabAssociation(_assocs, "itemGroup");
    
    if (this.list == null)
      this.log.warn("missing 'list' binding in WOPopUpButton");
    
    this.template = _template;
  }
  
  /* responder */

  public void takeValuesFromRequest(WORequest _rq, WOContext _ctx) {
    Object cursor = _ctx.cursor();
    if (this.disabled != null) {
      if (this.disabled.booleanValueInComponent(cursor))
        return;
    }
    
    boolean isDebugOn = this.log.isDebugEnabled();
    String formName  = this.elementNameInContext(_ctx);
    String formValue = _rq.stringFormValueForKey(formName);
    if (formValue == null) {
      /* nothing changed, or not in submitted form */
      if (isDebugOn) this.log.debug("found no form value!");

      /* We need to return here and NOT reset the selection. This is because the
       * page might have been invoked w/o a form POST! If we do not, this
       * resets the selection.
       * 
       * TODO: check whether HTML forms MUST submit an empty form value for
       *       popups.
       */
      return;
    }
    
    List   objects = null;
    Object object  = null;
    if (this.list != null)
      objects = WOListWalker.listForValue(this.list.valueInComponent(cursor));
    
    if (this.value != null) {
      /* has a value binding, walk list to find object */
      
      for (int i = 0, toGo = objects.size(); i < toGo; i++) {
        Object cv;
        
        object = objects.get(i);
        if (this.item != null && this.item.isValueSettableInComponent(cursor))
          this.item.setValue(object, cursor);
        
        cv = this.value.stringValueInComponent(cursor);
        if (formValue.equals(cv))
          break; /* found it, 'object' locvar is filled */
        
        // important, reset object otherwise the last item will be preselected!
        object = null;
      }
    }
    else if (formValue != null && !WONoSelectionString.equals(formValue)) {
      /* an index binding? */

      if (isDebugOn) this.log.debug("lookup using index: " + formValue);
      
      if (formValue == null) {
        this.log.error("popup " + formName + ": no form value?");
      }
      else {
        int idx = Integer.parseInt(formValue);
        if (idx < 0 || idx >= objects.size()) {
          this.log.error("popup " + formName + " index out of range: " + idx);
          object = null;
        }
        else
          object = objects.get(idx);
      }
    }
    else {
      if (isDebugOn) {
        this.log.debug("no form value, value binding or selection: " + 
                       formValue);
      }
    }
    
    /* push selected value */
    
    if (this.selectedValue != null && 
        this.selectedValue.isValueSettableInComponent(cursor)) {
      this.selectedValue.setValue(formValue, cursor);
    }
    
    /* process selection */
    
    if (this.selection != null && 
        this.selection.isValueSettableInComponent(cursor)) {
      if (isDebugOn) this.log.debug("pushing popup selection: " + object);
      this.selection.setValue(object, cursor);
    }
    
    if (this.item != null && this.item.isValueSettableInComponent(cursor))
      this.item.setValue(null, cursor);
  }
  
  public void appendOptionsToResponse(WOResponse _r, WOContext _ctx) {
    Object  cursor          = _ctx.cursor();
    boolean escapesHTML     = true;
    boolean hasSettableItem;
    String  previousGroup   = null;
    
    if (this.escapeHTML != null)
      escapesHTML = this.escapeHTML.booleanValueInComponent(cursor);
    
    if (this.item != null)
      hasSettableItem = this.item.isValueSettableInComponent(cursor);
    else
      hasSettableItem = false;
    
    /* determine selected object */
    
    boolean byVal = false;
    Object  sel   = null;
    
    if (this.selection == null) {
      if (this.selectedValue != null) {
        byVal = true;
        sel   = this.selectedValue.valueInComponent(cursor);
      }
    }
    else if (this.selectedValue != null) {
      byVal = true;
      sel   = this.selectedValue.valueInComponent(cursor);
      this.log.warn("both, 'selection' and 'selectedValue' bindings active.");
    }
    else
      sel = this.selection.valueInComponent(cursor);
    
    /* process noSelectionString option */
    
    String nilStr = null;
    if (this.noSelectionString != null)
      nilStr = this.noSelectionString.stringValueInComponent(cursor);
    
    if (nilStr != null) {
      if (this.itemGroup != null) {
        String group;
        
        if (hasSettableItem)
          this.item.setValue(null, cursor);
        
        group = this.itemGroup.stringValueInComponent(cursor);
        
        if (group != null) {
          _r.appendBeginTag("optgroup");
          _r.appendContentString(" label=\"");
          if (escapesHTML)
            _r.appendContentHTMLString(group);
          else
            _r.appendContentString(group);
          _r.appendContentCharacter('"');
          _r.appendBeginTagEnd();
          
          previousGroup = group;
        }
      }
      
      _r.appendBeginTag("option");
      _r.appendAttribute("value", WONoSelectionString);
      _r.appendBeginTagEnd();
      
      if (escapesHTML)
        _r.appendContentHTMLString(nilStr);
      else
        _r.appendContentString(nilStr);
      _r.appendEndTag("option");
      // FIXME (stephane) Shouldn't we set the 'selected' if
      //                  selArray/selValueArray is empty?
    }
    
    /* loop over options */
    
    if (this.list != null) {
      List array =
        WOListWalker.listForValue(this.list.valueInComponent(cursor));
      int  toGo  = array.size();
      
      for (int i = 0; i < toGo; i++) {
        Object object = array.get(i);
        
        if (hasSettableItem)
          this.item.setValue(object, cursor);
        
        /* determine value */
        
        Object v;
        String vs;
        if (this.value != null) {
          v  = this.value.valueInComponent(cursor);
          vs = v.toString();
        }
        else {
          vs = "" + i;
          v  = vs;
        }
        
        /* determine selection (compare against value or item) */
        
        boolean isSelected;
        if (sel == (byVal ? v : object)) // Note: also matches null==null
          isSelected = true;
        else if (sel == null || (byVal ? v : object) == null)
          isSelected = false;
        else
          isSelected = sel.equals(byVal ? v : object);
        
        /* display string */
        
        String displayV = null;
        if (this.string != null)
          displayV = this.string.stringValueInComponent(cursor);
        else if (object != null)
          displayV = object.toString();
        else
          displayV = escapesHTML ? "<null>" : "&lt;null&gt;";
        
        /* grouping */
        
        String group = null;
        if (this.itemGroup != null)
          group = this.itemGroup.stringValueInComponent(cursor);
        
        if (group != null) {
          boolean groupChanged = false;
          
          if (previousGroup == null)
            groupChanged = true;
          else if (!group.equals(previousGroup)) {
            _r.appendEndTag("optgroup");
            groupChanged = true;
          }
          if (groupChanged) {
            _r.appendBeginTag("optgroup");
            _r.appendContentString(" label=\"");
            if (escapesHTML)
              _r.appendContentHTMLString(group);
            else
              _r.appendContentString(group);
            _r.appendContentCharacter('"');
            _r.appendBeginTagEnd();
            
            previousGroup = group;
          }
        }
        else if (previousGroup != null) {
          _r.appendEndTag("optgroup");
          previousGroup = null;
        }
        
        /* option tag */
        
        _r.appendBeginTag("option");
        _r.appendAttribute("value", vs);
        if (isSelected) _r.appendAttribute("selected", "selected");
        _r.appendBeginTagEnd();
        
        if (escapesHTML)
          _r.appendContentHTMLString(displayV);
        else
          _r.appendContentString(displayV);
        
        _r.appendEndTag("option");
      }
    }
    
    /* close optgroups */
    
    if (previousGroup != null)
      _r.appendEndTag("optgroup");
    
    if (this.item != null && this.item.isValueSettableInComponent(cursor))
      this.item.setValue(null, cursor);
  }
  
  public void appendToResponse(WOResponse _r, WOContext _ctx) {
    /* start tag */
    
    _r.appendBeginTag("select");
    _r.appendAttribute("name",  this.elementNameInContext(_ctx));
    
    if (this.disabled != null) {
      if (this.disabled.booleanValueInComponent(_ctx.cursor()))
        _r.appendAttribute("disabled", "disabled");
    }
    
    this.appendExtraAttributesToResponse(_r, _ctx);
    // TODO: otherTagString
    
    _r.appendBeginTagEnd();
    
    /* content */
    
    this.appendOptionsToResponse(_r, _ctx);
    
    if (this.template != null)
      this.template.appendToResponse(_r, _ctx);
    
    /* close tag */
    
    _r.appendEndTag("select");
  }
  
  /* description */
  
  public void appendAttributesToDescription(StringBuffer _d) {
    super.appendAttributesToDescription(_d);
    
    this.appendAssocToDescription(_d, "list", this.list);
    this.appendAssocToDescription(_d, "item", this.item);
  }  
}
