<?php
/**
 * TPagedDataSource, TPagedListIterator, TPagedMapIterator classes
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @link http://www.pradosoft.com/
 * @copyright Copyright &copy; 2005-2014 PradoSoft
 * @license http://www.pradosoft.com/license/
 * @package System.Collections
 */

/**
 * TPagedDataSource class
 *
 * TPagedDataSource implements an integer-indexed collection class with paging functionality.
 *
 * Data items in TPagedDataSource can be traversed using <b>foreach</b>
 * PHP statement like the following,
 * <code>
 * foreach($pagedDataSource as $dataItem)
 * </code>
 * The data are fetched from {@link setDataSource DataSource}. Only the items
 * within the specified page will be returned and traversed.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @package System.Collections
 * @since 3.0
 */
class TPagedDataSource extends TComponent implements IteratorAggregate,Countable
{
	/**
	 * @var mixed original data source
	 */
	private $_dataSource=null;
	/**
	 * @var integer number of items in each page
	 */
	private $_pageSize=10;
	/**
	 * @var integer current page index
	 */
	private $_currentPageIndex=0;
	/**
	 * @var boolean whether to allow paging
	 */
	private $_allowPaging=false;
	/**
	 * @var boolean whether to allow custom paging
	 */
	private $_allowCustomPaging=false;
	/**
	 * @var integer user-assigned number of items in data source
	 */
	private $_virtualCount=0;

	/**
	 * @return mixed original data source. Defaults to null.
	 */
	public function getDataSource()
	{
		return $this->_dataSource;
	}

	/**
	 * @param mixed original data source
	 */
	public function setDataSource($value)
	{
		if(!($value instanceof TMap) && !($value instanceof TList))
		{
			if(is_array($value))
				$value=new TMap($value);
			else if($value instanceof Traversable)
				$value=new TList($value);
			else if($value!==null)
				throw new TInvalidDataTypeException('pageddatasource_datasource_invalid');
		}
		$this->_dataSource=$value;
	}

	/**
	 * @return integer number of items in each page. Defaults to 10.
	 */
	public function getPageSize()
	{
		return $this->_pageSize;
	}

	/**
	 * @param integer number of items in each page
	 */
	public function setPageSize($value)
	{
		if(($value=TPropertyValue::ensureInteger($value))>0)
			$this->_pageSize=$value;
		else
			throw new TInvalidDataValueException('pageddatasource_pagesize_invalid');
	}

	/**
	 * @return integer current page index. Defaults to 0.
	 */
	public function getCurrentPageIndex()
	{
		return $this->_currentPageIndex;
	}

	/**
	 * @param integer current page index
	 */
	public function setCurrentPageIndex($value)
	{
		if(($value=TPropertyValue::ensureInteger($value))<0)
			$value=0;
		$this->_currentPageIndex=$value;
	}

	/**
	 * @return boolean whether to allow paging. Defaults to false.
	 */
	public function getAllowPaging()
	{
		return $this->_allowPaging;
	}

	/**
	 * @param boolean whether to allow paging
	 */
	public function setAllowPaging($value)
	{
		$this->_allowPaging=TPropertyValue::ensureBoolean($value);
	}

	/**
	 * @return boolean whether to allow custom paging. Defaults to false.
	 */
	public function getAllowCustomPaging()
	{
		return $this->_allowCustomPaging;
	}

	/**
	 * @param boolean whether to allow custom paging
	 */
	public function setAllowCustomPaging($value)
	{
		$this->_allowCustomPaging=TPropertyValue::ensureBoolean($value);
	}

	/**
	 * @return integer user-assigned number of items in data source Defaults to 0.
	 */
	public function getVirtualItemCount()
	{
		return $this->_virtualCount;
	}

	/**
	 * @param integer user-assigned number of items in data source
	 */
	public function setVirtualItemCount($value)
	{
		if(($value=TPropertyValue::ensureInteger($value))>=0)
			$this->_virtualCount=$value;
		else
			throw new TInvalidDataValueException('pageddatasource_virtualitemcount_invalid');
	}

	/**
	 * @return integer number of items in current page
	 */
	public function getCount()
	{
		if($this->_dataSource===null)
			return 0;
		if(!$this->_allowPaging)
			return $this->getDataSourceCount();
		if(!$this->_allowCustomPaging && $this->getIsLastPage())
			return $this->getDataSourceCount()-$this->getFirstIndexInPage();
		return $this->_pageSize;
	}

	/**
	 * Returns the number of items in the current page.
	 * This method is required by Countable interface.
	 * @return integer number of items in the current page.
	 */
	public function count()
	{
		return $this->getCount();
	}

	/**
	 * @return integer number of pages
	 */
	public function getPageCount()
	{
		if($this->_dataSource===null)
			return 0;
		$count=$this->getDataSourceCount();
		if(!$this->_allowPaging || $count<=0)
			return 1;
		return (int)(($count+$this->_pageSize-1)/$this->_pageSize);
	}

	/**
	 * @return boolean whether the current page is the first page Defaults to false.
	 */
	public function getIsFirstPage()
	{
		if($this->_allowPaging)
			return $this->_currentPageIndex===0;
		else
			return true;
	}

	/**
	 * @return boolean whether the current page is the last page
	 */
	public function getIsLastPage()
	{
		if($this->_allowPaging)
			return $this->_currentPageIndex===$this->getPageCount()-1;
		else
			return true;
	}

	/**
	 * @return integer the index of the item in data source, where the item is the first in
	 * current page
	 */
	public function getFirstIndexInPage()
	{
		if($this->_dataSource!==null && $this->_allowPaging && !$this->_allowCustomPaging)
			return $this->_currentPageIndex*$this->_pageSize;
		else
			return 0;
	}

	/**
	 * @return integer number of items in data source, if available
	 */
	public function getDataSourceCount()
	{
		if($this->_dataSource===null)
			return 0;
		else if($this->_allowCustomPaging)
			return $this->_virtualCount;
		else
			return $this->_dataSource->getCount();
	}

	/**
	 * @return Iterator iterator
	 */
	public function getIterator()
	{
		if($this->_dataSource instanceof TList)
			return new TPagedListIterator($this->_dataSource,$this->getFirstIndexInPage(),$this->getCount());
		else if($this->_dataSource instanceof TMap)
			return new TPagedMapIterator($this->_dataSource,$this->getFirstIndexInPage(),$this->getCount());
		else
			return null;
	}
}



/**
 * TPagedListIterator class
 *
 * TPagedListIterator implements Iterator interface.
 *
 * TPagedListIterator is used by {@link TPagedDataSource}. It allows TPagedDataSource
 * to return a new iterator for traversing the items in a {@link TList} object.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @package System.Collections
 * @since 3.0
 */
class TPagedListIterator implements Iterator
{
	private $_list;
	private $_startIndex;
	private $_count;
	private $_index;

	/**
	 * Constructor.
	 * @param TList the data to be iterated through
	 * @param integer start index
	 * @param integer number of items to be iterated through
	 */
	public function __construct(TList $list,$startIndex,$count)
	{
		$this->_list=$list;
		$this->_index=0;
		$this->_startIndex=$startIndex;
		if($startIndex+$count>$list->getCount())
			$this->_count=$list->getCount()-$startIndex;
		else
			$this->_count=$count;
	}

	/**
	 * Rewinds internal array pointer.
	 * This method is required by the interface Iterator.
	 */
	public function rewind()
	{
		$this->_index=0;
	}

	/**
	 * Returns the key of the current array item.
	 * This method is required by the interface Iterator.
	 * @return integer the key of the current array item
	 */
	public function key()
	{
		return $this->_index;
	}

	/**
	 * Returns the current array item.
	 * This method is required by the interface Iterator.
	 * @return mixed the current array item
	 */
	public function current()
	{
		return $this->_list->itemAt($this->_startIndex+$this->_index);
	}

	/**
	 * Moves the internal pointer to the next array item.
	 * This method is required by the interface Iterator.
	 */
	public function next()
	{
		$this->_index++;
	}

	/**
	 * Returns whether there is an item at current position.
	 * This method is required by the interface Iterator.
	 * @return boolean
	 */
	public function valid()
	{
		return $this->_index<$this->_count;
	}
}

/**
 * TPagedMapIterator class
 *
 * TPagedMapIterator implements Iterator interface.
 *
 * TPagedMapIterator is used by {@link TPagedDataSource}. It allows TPagedDataSource
 * to return a new iterator for traversing the items in a {@link TMap} object.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @package System.Collections
 * @since 3.0
 */
class TPagedMapIterator implements Iterator
{
	private $_map;
	private $_startIndex;
	private $_count;
	private $_index;
	private $_iterator;

	/**
	 * Constructor.
	 * @param array the data to be iterated through
	 */
	public function __construct(TMap $map,$startIndex,$count)
	{
		$this->_map=$map;
		$this->_index=0;
		$this->_startIndex=$startIndex;
		if($startIndex+$count>$map->getCount())
			$this->_count=$map->getCount()-$startIndex;
		else
			$this->_count=$count;
		$this->_iterator=$map->getIterator();
	}

	/**
	 * Rewinds internal array pointer.
	 * This method is required by the interface Iterator.
	 */
	public function rewind()
	{
		$this->_iterator->rewind();
		for($i=0;$i<$this->_startIndex;++$i)
			$this->_iterator->next();
		$this->_index=0;
	}

	/**
	 * Returns the key of the current array item.
	 * This method is required by the interface Iterator.
	 * @return integer the key of the current array item
	 */
	public function key()
	{
		return $this->_iterator->key();
	}

	/**
	 * Returns the current array item.
	 * This method is required by the interface Iterator.
	 * @return mixed the current array item
	 */
	public function current()
	{
		return $this->_iterator->current();
	}

	/**
	 * Moves the internal pointer to the next array item.
	 * This method is required by the interface Iterator.
	 */
	public function next()
	{
		$this->_index++;
		$this->_iterator->next();
	}

	/**
	 * Returns whether there is an item at current position.
	 * This method is required by the interface Iterator.
	 * @return boolean
	 */
	public function valid()
	{
		return $this->_index<$this->_count;
	}
}