<?php
/**
 * Zend Framework (http://framework.zend.com/)
 *
 * @link      http://github.com/zendframework/zf2 for the canonical source repository
 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
 * @license   http://framework.zend.com/license/new-bsd New BSD License
 */

namespace Zend\Db\Sql\Predicate;

/**
 * @property Predicate $and
 * @property Predicate $or
 * @property Predicate $AND
 * @property Predicate $OR
 * @property Predicate $NEST
 * @property Predicate $UNNEST
 */
class Predicate extends PredicateSet
{
    protected 
$unnest null;
    protected 
$nextPredicateCombineOperator null;

    
/**
     * Begin nesting predicates
     *
     * @return Predicate
     */
    
public function nest()
    {
        
$predicateSet = new Predicate();
        
$predicateSet->setUnnest($this);
        
$this->addPredicate($predicateSet, ($this->nextPredicateCombineOperator) ?: $this->defaultCombination);
        
$this->nextPredicateCombineOperator null;
        return 
$predicateSet;
    }

    
/**
     * Indicate what predicate will be unnested
     *
     * @param  Predicate $predicate
     * @return void
     */
    
public function setUnnest(Predicate $predicate)
    {
        
$this->unnest $predicate;
    }

    
/**
     * Indicate end of nested predicate
     *
     * @return Predicate
     * @throws \RuntimeException
     */
    
public function unnest()
    {
        if (
$this->unnest == null) {
            throw new \
RuntimeException('Not nested');
        }
        
$unnset       $this->unnest;
        
$this->unnest null;
        return 
$unnset;
    }

    
/**
     * Create "Equal To" predicate
     *
     * Utilizes Operator predicate
     *
     * @param  int|float|bool|string $left
     * @param  int|float|bool|string $right
     * @param  string $leftType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_IDENTIFIER {@see allowedTypes}
     * @param  string $rightType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_VALUE {@see allowedTypes}
     * @return Predicate
     */
    
public function equalTo($left$right$leftType self::TYPE_IDENTIFIER$rightType self::TYPE_VALUE)
    {
        
$this->addPredicate(
            new 
Operator($leftOperator::OPERATOR_EQUAL_TO$right$leftType$rightType),
            (
$this->nextPredicateCombineOperator) ?: $this->defaultCombination
        
);
        
$this->nextPredicateCombineOperator null;

        return 
$this;
    }

    
/**
     * Create "Not Equal To" predicate
     *
     * Utilizes Operator predicate
     *
     * @param  int|float|bool|string $left
     * @param  int|float|bool|string $right
     * @param  string $leftType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_IDENTIFIER {@see allowedTypes}
     * @param  string $rightType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_VALUE {@see allowedTypes}
     * @return Predicate
     */
    
public function notEqualTo($left$right$leftType self::TYPE_IDENTIFIER$rightType self::TYPE_VALUE)
    {
        
$this->addPredicate(
            new 
Operator($leftOperator::OPERATOR_NOT_EQUAL_TO$right$leftType$rightType),
            (
$this->nextPredicateCombineOperator) ?: $this->defaultCombination
        
);
        
$this->nextPredicateCombineOperator null;

        return 
$this;
    }

    
/**
     * Create "Less Than" predicate
     *
     * Utilizes Operator predicate
     *
     * @param  int|float|bool|string $left
     * @param  int|float|bool|string $right
     * @param  string $leftType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_IDENTIFIER {@see allowedTypes}
     * @param  string $rightType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_VALUE {@see allowedTypes}
     * @return Predicate
     */
    
public function lessThan($left$right$leftType self::TYPE_IDENTIFIER$rightType self::TYPE_VALUE)
    {
        
$this->addPredicate(
            new 
Operator($leftOperator::OPERATOR_LESS_THAN$right$leftType$rightType),
            (
$this->nextPredicateCombineOperator) ?: $this->defaultCombination
        
);
        
$this->nextPredicateCombineOperator null;

        return 
$this;
    }

    
/**
     * Create "Greater Than" predicate
     *
     * Utilizes Operator predicate
     *
     * @param  int|float|bool|string $left
     * @param  int|float|bool|string $right
     * @param  string $leftType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_IDENTIFIER {@see allowedTypes}
     * @param  string $rightType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_VALUE {@see allowedTypes}
     * @return Predicate
     */
    
public function greaterThan($left$right$leftType self::TYPE_IDENTIFIER$rightType self::TYPE_VALUE)
    {
        
$this->addPredicate(
            new 
Operator($leftOperator::OPERATOR_GREATER_THAN$right$leftType$rightType),
            (
$this->nextPredicateCombineOperator) ?: $this->defaultCombination
        
);
        
$this->nextPredicateCombineOperator null;

        return 
$this;
    }

    
/**
     * Create "Less Than Or Equal To" predicate
     *
     * Utilizes Operator predicate
     *
     * @param  int|float|bool|string $left
     * @param  int|float|bool|string $right
     * @param  string $leftType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_IDENTIFIER {@see allowedTypes}
     * @param  string $rightType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_VALUE {@see allowedTypes}
     * @return Predicate
     */
    
public function lessThanOrEqualTo($left$right$leftType self::TYPE_IDENTIFIER$rightType self::TYPE_VALUE)
    {
        
$this->addPredicate(
            new 
Operator($leftOperator::OPERATOR_LESS_THAN_OR_EQUAL_TO$right$leftType$rightType),
            (
$this->nextPredicateCombineOperator) ?: $this->defaultCombination
        
);
        
$this->nextPredicateCombineOperator null;

        return 
$this;
    }

    
/**
     * Create "Greater Than Or Equal To" predicate
     *
     * Utilizes Operator predicate
     *
     * @param  int|float|bool|string $left
     * @param  int|float|bool|string $right
     * @param  string $leftType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_IDENTIFIER {@see allowedTypes}
     * @param  string $rightType TYPE_IDENTIFIER or TYPE_VALUE by default TYPE_VALUE {@see allowedTypes}
     * @return Predicate
     */
    
public function greaterThanOrEqualTo($left$right$leftType self::TYPE_IDENTIFIER$rightType self::TYPE_VALUE)
    {
        
$this->addPredicate(
            new 
Operator($leftOperator::OPERATOR_GREATER_THAN_OR_EQUAL_TO$right$leftType$rightType),
            (
$this->nextPredicateCombineOperator) ?: $this->defaultCombination
        
);
        
$this->nextPredicateCombineOperator null;

        return 
$this;
    }

    
/**
     * Create "Like" predicate
     *
     * Utilizes Like predicate
     *
     * @param  string $identifier
     * @param  string $like
     * @return Predicate
     */
    
public function like($identifier$like)
    {
        
$this->addPredicate(
            new 
Like($identifier$like),
            (
$this->nextPredicateCombineOperator) ?: $this->defaultCombination
        
);
        
$this->nextPredicateCombineOperator null;

        return 
$this;
    }

    
/**
     * Create an expression, with parameter placeholders
     *
     * @param $expression
     * @param $parameters
     * @return $this
     */
    
public function expression($expression$parameters)
    {
        
$this->addPredicate(
            new 
Expression($expression$parameters),
            (
$this->nextPredicateCombineOperator) ?: $this->defaultCombination
        
);
        
$this->nextPredicateCombineOperator null;

        return 
$this;
    }

    
/**
     * Create "Literal" predicate
     *
     * Literal predicate, for parameters, use expression()
     *
     * @param  string $literal
     * @return Predicate
     */
    
public function literal($literal)
    {
        
// process deprecated parameters from previous literal($literal, $parameters = null) signature
        
if (func_num_args() >= 2) {
            
$parameters func_get_arg(1);
            
$predicate = new Expression($literal$parameters);
        }

        
// normal workflow for "Literals" here
        
if (!isset($predicate)) {
            
$predicate = new Literal($literal);
        }

        
$this->addPredicate(
            
$predicate,
            (
$this->nextPredicateCombineOperator) ?: $this->defaultCombination
        
);
        
$this->nextPredicateCombineOperator null;

        return 
$this;
    }

    
/**
     * Create "IS NULL" predicate
     *
     * Utilizes IsNull predicate
     *
     * @param  string $identifier
     * @return Predicate
     */
    
public function isNull($identifier)
    {
        
$this->addPredicate(
            new 
IsNull($identifier),
            (
$this->nextPredicateCombineOperator) ?: $this->defaultCombination
        
);
        
$this->nextPredicateCombineOperator null;

        return 
$this;
    }

    
/**
     * Create "IS NOT NULL" predicate
     *
     * Utilizes IsNotNull predicate
     *
     * @param  string $identifier
     * @return Predicate
     */
    
public function isNotNull($identifier)
    {
        
$this->addPredicate(
            new 
IsNotNull($identifier),
            (
$this->nextPredicateCombineOperator) ?: $this->defaultCombination
        
);
        
$this->nextPredicateCombineOperator null;

        return 
$this;
    }

    
/**
     * Create "in" predicate
     *
     * Utilizes In predicate
     *
     * @param  string $identifier
     * @param  array|\Zend\Db\Sql\Select $valueSet
     * @return Predicate
     */
    
public function in($identifier$valueSet null)
    {
        
$this->addPredicate(
            new 
In($identifier$valueSet),
            (
$this->nextPredicateCombineOperator) ?: $this->defaultCombination
        
);
        
$this->nextPredicateCombineOperator null;

        return 
$this;
    }

    
/**
     * Create "between" predicate
     *
     * Utilizes Between predicate
     *
     * @param  string $identifier
     * @param  int|float|string $minValue
     * @param  int|float|string $maxValue
     * @return Predicate
     */
    
public function between($identifier$minValue$maxValue)
    {
        
$this->addPredicate(
            new 
Between($identifier$minValue$maxValue),
            (
$this->nextPredicateCombineOperator) ?: $this->defaultCombination
        
);
        
$this->nextPredicateCombineOperator null;

        return 
$this;
    }

    
/**
     * Overloading
     *
     * Overloads "or", "and", "nest", and "unnest"
     *
     * @param  string $name
     * @return Predicate
     */
    
public function __get($name)
    {
        switch (
strtolower($name)) {
            case 
'or':
                
$this->nextPredicateCombineOperator self::OP_OR;
                break;
            case 
'and':
                
$this->nextPredicateCombineOperator self::OP_AND;
                break;
            case 
'nest':
                return 
$this->nest();
            case 
'unnest':
                return 
$this->unnest();
        }
        return 
$this;
    }
}