
class DatabaseCondition implements QueryConditionInterface, Countable {

  protected $conditions = array();
  protected $arguments = array();
  
  protected $updated = TRUE;

  public function __construct($conjunction) {
    $this->conditions['#conjunction'] = $conjunction;
  }
  
  /**
   * Return the size of this conditional.  This is part of the Countable interface.
   *
   * The size of the conditional is the size of its conditional array minus
   * one, because one element is the the conjunction.
   */
  public function count() {
    return count($this->conditions) - 1;
  }
  
  public function condition($field, $value = NULL, $operator = '=') {
    $this->conditions[] = array(
      'field' => $field,
      'value' => $value,
      'operator' => $operator,
    );
    
    $this->updated = TRUE;
    
    return $this;
  }

  public function where($snippet, $args = array()) {
    $this->conditions[] = array(
      'field' => $field,
      'value' => $args,
      'operator' => NULL,
    );

    $this->updated = TRUE;
    
    return $this;
  }
  
  public function conditions() {
    // If there's only one entry, it's just the default #type, which means
    // we don't really have any conditions to speak of.
    return (count($this->conditions) > 1) ? $this->conditions : array();
  }

  public function arguments() {
    return $this->arguments;
  }

  public function compile(DatabaseConnection $connection) {
    // This value is static, so it will increment across the entire request
    // rather than just this query.  That is OK, because we only need definitive
    // placeholder names if we're going to use them for _alter hooks, which we
    // are not.  The alter hook would intervene before compilation.
    static $next_placeholder = 1;
    
    $condition_fragments = array();
    $arguments = array();
    
    $conditions = $this->conditions;
    $conjunction = $conditions['#conjunction'];
    unset($conditions['#conjunction']);
    foreach ($conditions as $condition) {
      if (!empty($condition['operator'])) {
        // It's a structured condition, so parse it out accordingly.
        if ($condition['field'] instanceof QueryConditionInterface) {
          // Compile the sub-condition recursively and add it to the list.
          $return = $condition['field']->compile($connection);
          $condition_fragments[] = $return['fragment'];
          $arguments += $return['arguments'];
        }
        else {
          // For simplicity, we treat all operators as the same data structure.
          // In the typical degenerate case, this won't get changed.
          $operator_defaults = array(
            'prefix' => '',
            'postfix' => '',
            'delimiter' => '',
            'operator' => $condition['operator'],
          );
          $operator = $connection->mapConditionOperator($condition['operator']);
          if (!isset($operator)) {
            $operator = $this->mapConditionOperator($condition['operator']);
          }
          $operator += $operator_defaults;
          
          if ($condition['value'] instanceof SelectQuery) {
            $placeholders[] = (string)$condition['value'];
            $arguments += $condition['value']->arguments();
          }
          // We assume that if there is a delimiter, then the value is an
          // array.  If not, it is a scalar.  For simplicity, we first convert
          // up to an array so that we can build the placeholders in the same way.
          elseif (!$operator['delimiter']) {
            $condition['value'] = array($condition['value']);
          }
          foreach ($condition['value'] as $value) {
            $placeholder = ':db_condition_placeholder_'. $next_placeholder++;
            $arguments[$placeholder] = $value;
            $placeholders[] = $placeholder;
          }
          
          $condition_fragments[] = '(' . $condition['field'] . $operator['operator'] . $operator['prefix'] . implode($condition['delimiter'], $placeholders) . $operator['postfix'] . ')';
          
        }
      }
      else {
        // This condition is a literal string, so let it through as is.
        $condition_fragments[] = '(' . $condition['field'] . ')';
        $arguments += $condition['value'];
        
      }
    }
    
    $this->updated = FALSE;
    
    //$this->stringVersion = implode($conjunction, $condition_fragments);
    //$this->arguments = $arguments;
    
    
     return array(
      'fragment' => implode($conjunction, $condition_fragments),
      'arguments' => $arguments,
    );
    
  }
  
  /**
   * Gets any special processing requirements for the condition operator.
   *
   * Some condition types require special processing, such as IN, because
   * the value data they pass in is not a simple value.  This is a simple
   * overridable lookup function.
   *
   * @param $operator
   *   The condition operator, such as "IN", "BETWEEN", etc.  Case-sensitive.
   * @return
   *   The extra handling directives for the specified operator, or NULL.
   */
  protected function mapConditionOperator($operator) {
    static $specials = array(
      'BETWEEN' => array('delimiter' => ' AND '),
      'IN' => array('delimiter' => ', ', 'prefix' => ' (', 'postfix' => ')'),
      'NOT IN' => array('delimiter' => ', ', 'prefix' => ' (', 'postfix' => ')'),
      'LIKE' => array('operator' => 'LIKE'),
    );

    $return = isset($specials[$operator]) ? $specials[$operator] : array();
    $return += array('operator' => $operator);

    return $return;
  }
  
}