/*
  Copyright (C) 2006-2007 Helge Hess

  This file is part of JOPE.

  JOPE is free software; you can redistribute it and/or modify it under
  the terms of the GNU Lesser General Public License as published by the
  Free Software Foundation; either version 2, or (at your option) any
  later version.

  JOPE is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
  License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with JOPE; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/

package org.opengroupware.jope.appserver.elements;

import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opengroupware.jope.appserver.associations.WORegExAssociation;
import org.opengroupware.jope.appserver.core.WOAssociation;
import org.opengroupware.jope.appserver.core.WOContext;
import org.opengroupware.jope.appserver.core.WODynamicElement;
import org.opengroupware.jope.appserver.core.WOElement;
import org.opengroupware.jope.appserver.core.WOElementWalker;
import org.opengroupware.jope.appserver.core.WORequest;
import org.opengroupware.jope.appserver.core.WOResponse;

/**
 * WOConditional
 * <p>
 * Render a subsection or not depending on some component state.
 * <p>
 * Sample:
 * <pre>
 *   ShowIfRed: WOConditional {
 *     condition = currentColor;
 *     value     = "red";
 *   }</pre>
 * Renders:<br />
 *   This element does not render anything.
 * <p>
 * Bindings:
 * <pre>
 *   condition [in] - boolean or object if used with value binding
 *   negate    [in] - boolean
 *   value     [in] - object
 *   match     [in] - object (Pattern or Matcher or Pattern-String)</pre>
 * 
 * WOConditional is an aliased element:
 * <pre><#if value="obj.isTeam">...</#></pre>
 * 
 * TODO: would be nice to have NPS style operations, like
 *         operation="isNotEmpty" condition="item.lastname"
 *       since in Java we can't add such to objects using categories.
 *       key1/value1 key2/key2 value3/value3 key4/match4 etc => parser?
 */
public class WOConditional extends WODynamicElement {
  protected static Log log = LogFactory.getLog("WOConditional");
  
  protected WOAssociation condition;
  protected WOAssociation negate;
  protected WOAssociation value;
  protected WOAssociation match;
  protected WOElement     template;

  public WOConditional(String _name, Map<String, WOAssociation> _assocs,
                       WOElement _template)
  {
    super(_name, _assocs, _template);

    this.condition = grabAssociation(_assocs, "condition");
    this.negate    = grabAssociation(_assocs, "negate");
    this.value     = grabAssociation(_assocs, "value");    
    this.match     = grabAssociation(_assocs, "match");    
    this.template  = _template;
    
    if (this.condition == null) {
      if (this.value != null) {
        this.condition = this.value;
        this.value = null;
      }
      else
        log().error("missing 'condition' binding!");
    }
    
    if (this.match != null && this.match.isValueConstant()) {
      // TBD: we might want to have a 'value mode'
      if (!(this.match instanceof WORegExAssociation)) {
        this.match = 
          new WORegExAssociation(this.match.stringValueInComponent(null));
      }
    }
    
    if (this.match != null && this.value != null)
      log().warn("both 'match' and 'value' bindings are set: " + this);
    if (this.template == null)
      log().warn("conditional has not template");
  }

  /* evaluate */
  
  protected boolean doShowInContext(WOContext _ctx) {
    if (this.condition == null) {
      log.error("association has no 'condition' binding!");
      return false;
    }
    
    boolean doShow, doNegate = false;
    Object  cursor = _ctx.cursor();
    
    if (this.negate != null)
      doNegate = this.negate.booleanValueInComponent(cursor);
    
    if (this.match != null) {
      Object  pato    = this.match.valueInComponent(cursor);
      Matcher matcher = null;
      
      if (pato == null) {
        if (log().isInfoEnabled())
          log().info("'match' binding returned no Object: " + this.match);
      }
      else if (pato instanceof Matcher)
        matcher = (Matcher)pato;
      else if (pato instanceof Pattern) {
        matcher = ((Pattern)pato).matcher
          (this.condition.stringValueInComponent(cursor));
      }
      else {
        Pattern pat = Pattern.compile(pato.toString());
        if (pat == null)
          log().warn("could not compile pattern in 'match' binding: " + pato);
        else
          matcher = pat.matcher(this.condition.stringValueInComponent(cursor));
      }
      
      doShow = matcher != null ? matcher.matches() : false;
    }
    else if (this.value != null) {
      Object v = this.value.valueInComponent(cursor);
      Object o = this.condition.valueInComponent(cursor);
      
      if (v == o)
        doShow = true;
      else if (o == null || v == null)
        doShow = false;
      else {
        if (v instanceof Pattern && !(o instanceof Pattern))
          doShow = ((Pattern)v).matcher(v.toString()).matches();
        else
          doShow = o.equals(v);
      }
    }
    else
      doShow = this.condition.booleanValueInComponent(cursor);
    
    return doNegate ? !doShow : doShow;
  }
  
  /* responder */

  @Override
  public void takeValuesFromRequest(WORequest _rq, WOContext _ctx) {
    if (!this.doShowInContext(_ctx) || this.template == null)
      return;
    
    _ctx.appendElementIDComponent("1");
    this.template.takeValuesFromRequest(_rq, _ctx);
    _ctx.deleteLastElementIDComponent();
  }
  
  @Override
  public Object invokeAction(WORequest _rq, WOContext _ctx) {
    // TODO: implement me for WOComponentActions
    log.error("WOConditional not implemented for component actions ...");
    return null;
  }
  
  @Override
  public void appendToResponse(WOResponse _r, WOContext _ctx) {
    if (!this.doShowInContext(_ctx) || this.template == null) {
      log.debug("not showing a conditional ...");
      return;
    }
    
    _ctx.appendElementIDComponent("1");
    this.template.appendToResponse(_r, _ctx);
    _ctx.deleteLastElementIDComponent();
  }
  
  @Override
  public void walkTemplate(WOElementWalker _walker, WOContext _ctx) {
    if (!this.doShowInContext(_ctx) || this.template == null) {
      log.debug("not showing a conditional ...");
      return;
    }
    
    _ctx.appendElementIDComponent("1");
    _walker.processTemplate(this, this.template, _ctx);
    _ctx.deleteLastElementIDComponent();
  }
}
