<?php
namespace SabaiApps\Framework\Criteria;

/**
 * CompositeCriteria class for combining multiple criteria with logical operators.
 * Supports AND/OR conditions and visitor pattern for evaluation.
 */
class CompositeCriteria extends AbstractCriteria {

	/**
	 * @var array List of criteria elements.
	 */
	protected $_elements = array();
	/**
	 * @var array List of conditions (AND/OR) for each element.
	 */
	protected $_conditions = array();
	/**
	 * @var int Current index for elements/conditions.
	 */
	protected $_index = -1;

	/**
	 * Constructor.
	 *
	 * @param array  $elements Array of criteria elements.
	 * @param string $condition Logical operator (AND/OR) to use for all elements.
	 */
	public function __construct( array $elements = array(), $condition = AbstractCriteria::CRITERIA_AND ) {
		if ( ! empty( $elements ) ) {
			if ( $condition === AbstractCriteria::CRITERIA_OR ) {
				foreach ( array_keys( $elements ) as $i ) {
					$this->addOr( $elements[ $i ] );
				}
			} else {
				foreach ( array_keys( $elements ) as $i ) {
					$this->addAnd( $elements[ $i ] );
				}
			}
		}
	}

	/**
	 * Gets all criteria elements.
	 *
	 * @return array
	 */
	public function getElements() {
		return $this->_elements;
	}

	/**
	 * Gets all conditions (AND/OR) for the elements.
	 *
	 * @return array
	 */
	public function getConditions() {
		return $this->_conditions;
	}

	/**
	 * Adds a criteria element with an AND condition.
	 *
	 * @param AbstractCriteria $criteria
	 * @return $this
	 */
	public function addAnd( AbstractCriteria $criteria ) {
		++$this->_index;
		$this->_elements[ $this->_index ]   = $criteria;
		$this->_conditions[ $this->_index ] = AbstractCriteria::CRITERIA_AND;
		return $this;
	}

	/**
	 * Adds a criteria element with an OR condition.
	 *
	 * @param AbstractCriteria $criteria
	 * @return $this
	 */
	public function addOr( AbstractCriteria $criteria ) {
		++$this->_index;
		$this->_elements[ $this->_index ]   = $criteria;
		$this->_conditions[ $this->_index ] = AbstractCriteria::CRITERIA_OR;
		return $this;
	}

	/**
	 * Checks if there are no criteria elements.
	 *
	 * @return bool
	 */
	public function isEmpty() {
		return empty( $this->_elements );
	}

	/**
	 * Accepts a Visitor object for traversing or evaluating the criteria.
	 *
	 * @param IVisitor $visitor Visitor object.
	 * @param mixed    $valuePassed Value to pass to the visitor.
	 * @return mixed
	 */
	public function acceptVisitor( IVisitor $visitor, &$valuePassed ) {
		return $visitor->visitCriteriaComposite( $this, $valuePassed );
	}

	/**
	 * Gets the current index.
	 *
	 * @return int
	 */
	public function getIndex() {
		return $this->_index;
	}

	/**
	 * Gets a criteria element by index.
	 *
	 * @param int $index
	 * @return AbstractCriteria
	 */
	public function get( $index ) {
		return $this->_elements[ $index ];
	}

	/**
	 * Removes a criteria element by index, or clears all if no index is given.
	 *
	 * @param int|null $index Index to remove, or null to clear all.
	 * @param bool     $return Whether to return the removed element.
	 * @return $this|AbstractCriteria|null
	 */
	public function remove( $index = null, $return = false ) {
		if ( isset( $index ) ) {
			if ( $return ) {
				$ret = $this->_elements[ $index ];
				unset( $this->_elements[ $index ], $this->_conditions[ $index ] );
				return $ret;
			}
			unset( $this->_elements[ $index ], $this->_conditions[ $index ] );
		} else {
			$this->_elements = $this->_conditions = array();
		}
		return $this;
	}
}
