package org.opengroupware.jope.eocontrol;

import java.util.Map;
import java.util.Set;

import org.opengroupware.jope.foundation.NSKeyValueCodingAdditions;

/*
 * EOKeyValueQualifier
 * 
 * Compares a value of a given object with a constant.
 * 
 * TODO: document
 */
// TODO: add operation

public class EOKeyValueQualifier extends EOQualifier
  implements EOQualifierEvaluation
{

  protected String key;
  protected Object value;
  protected ComparisonOperation operation;

  public EOKeyValueQualifier
    (String _key, ComparisonOperation _op, Object _value)
  {
    this.key       = _key;
    this.value     = _value;
    this.operation = _op;
  }
  public EOKeyValueQualifier(String _key, Object _value) {
    this(_key, ComparisonOperation.EQUAL_TO, _value);
  }
  public EOKeyValueQualifier(String _key, String _op, Object _value) {
    this(_key, operationForString(_op), _value);
  }
    
  /* accessors */
  
  public String key() {
    return this.key;
  }
  public Object value() {
    return this.value;
  }
  
  public ComparisonOperation operation() {
    return this.operation;
  }
  
  public EOQualifierVariable variable() {
    if (this.value == null)
      return null;
    
    if (!(this.value instanceof EOQualifierVariable))
      return null; /* value is not a variable */
    
    return (EOQualifierVariable)this.value;
  }
  
  /* evaluation */
  
  public boolean evaluateWithObject(Object _object) {
    Object ov;
    
    if (_object == null)
      ov = null;
    else if (_object instanceof NSKeyValueCodingAdditions)
      ov = ((NSKeyValueCodingAdditions)_object).valueForKeyPath(this.key);
    else
      ov = NSKeyValueCodingAdditions.Utility.valueForKeyPath(_object, this.key);

    EOQualifier.ComparisonSupport comparisonSupport;
    if (ov != null)
      comparisonSupport = supportForClass(ov.getClass());
    else
      comparisonSupport = supportForClass(null);
    
    // TODO: do something when the value is a variable?

    return comparisonSupport.compareOperation(this.operation, ov, this.value);
  }
  
  /* keys */
  
  public void addQualifierKeysToSet(Set<String> _keys) {
    if (_keys == null) return;
    if (this.key != null) _keys.add(this.key);
  }
  
  /* bindings */
  
  public void addBindingKeysToSet(Set<String> _keys) {
    EOQualifierVariable var = this.variable();
    if (var == null) return;
    
    String boundKey = var.key();
    if (boundKey != null)
      _keys.add(boundKey);
  }
  
  public String keyPathForBindingKey(String _variable) {
    if (_variable == null) return null;
    
    EOQualifierVariable var = this.variable();
    if (var == null) return null;
    
    if (_variable.equals(var.key())) return this.key();
    return null;
  }
  
  public EOQualifier qualifierWithBindings(Object _vals, boolean _requiresAll) {
    /* Hm, we can't replace keys? Might make sense, because a JDBC prepared
     *     statement can't do this either (right?).
     */
    
    EOQualifierVariable var = this.variable();
    if (var == null) return this; /* nothing to replace */
    
    /* evaluate variable */
    
    String boundKey   = var.key();
    Object boundValue = null;
    
    /* lookup value in bindings */
    
    if (_vals != null) {
      if (_vals instanceof NSKeyValueCodingAdditions) {
        boundValue =
          ((NSKeyValueCodingAdditions)_vals).valueForKeyPath(boundKey);
      }
      else if (_vals instanceof Map) {
        boundValue = ((Map)_vals).get(boundKey);
      }
      else {
        boundValue =
          NSKeyValueCodingAdditions.Utility.valueForKeyPath(_vals, boundKey);
      }
    }
    
    /* check if the value was found */
    
    if (boundValue == null) {
      if (_requiresAll)
        throw new EOQualifierBindingNotFoundException(boundKey);
      
      return null;
    }
    
    return new EOKeyValueQualifier(this.key, this.operation, boundValue);
  }

  /* string representation */
  
  public boolean appendStringRepresentation(StringBuffer _sb) {
    this.appendIdentifierToStringRepresentation(_sb, this.key);
    
    if (this.value == null && this.operation == ComparisonOperation.EQUAL_TO)
      _sb.append(" IS NULL");
    else {
      _sb.append(" ");
      _sb.append(stringForOperation(this.operation));
      _sb.append(" ");
      return this.appendConstantToStringRepresentation(_sb, this.value);
    }
    return true;
  }
  
  /* description */

  public void appendAttributesToDescription(StringBuffer _d) {
    super.appendAttributesToDescription(_d);
    _d.append(" key="   + this.key);
    _d.append(" op='"   + stringForOperation(this.operation) + "'");
    _d.append(" value=" + this.value);
  }
}
