<?php namespace ProcessWire;

/**
 * ProcessWire PagerNav support classes for MarkupPagerNav module
 *
 * Provides capability for determining pagination information
 *
 * 
 * ProcessWire 3.x, Copyright 2016 by Ryan Cramer
 * https://processwire.com
 *
 *
 */


/**
 * An individual pager item
 *
 */
class PagerNavItem {
	const typeCurrent = 'current';
	const typeFirst = 'first';
	const typePrevious = 'previous';
	const typeNext = 'next';
	const typeLast = 'last';
	const typeSeparator = 'separator';

	protected $data = array(
		'label' => '',
		'pageNum' => 0,
		'type' => '', // first, previous, next, last, current, or separator
	); 

	public function __construct($label, $pageNum, $type = '') {
		$this->data['label'] = (string) $label;
		$this->data['pageNum'] = (int) $pageNum;
		$this->data['type'] = $type;
	}

	public function __get($property) {
		return isset($this->data[$property]) ? $this->data[$property] : false;
	}

	public function __set($property, $value) {
		$this->data[$property] = $value; 
	}
}

/**
 * Collection of Pager items that determines which pagination links should be used
 * 
 * USAGE EXAMPLE:
 * 
 * $pager = new PagerNav(100, 10, 0); 
 * 
 * foreach($pager as $pageLabel => $pageNum) {
 * 	$class = "action";
 * 	if($pageNum == $pager->getCurrentPage()) $class .= " on";
 * 	$out .= "<li><a class='$class' href='$baseUrl$pageNum/'>$pageLabel</a></li>";
 * }
 *
 */
class PagerNav implements \IteratorAggregate {

	protected $totalPages = 0;
	protected $currentPage = 0;
	protected $pager = NULL;
	protected $numPageLinks = 10;
	protected $totalItems = 0;
	protected $firstItem = 0;
	protected $itemsPerPage = 0;
	protected $version = 'MP'; // MP=matjazpotocnik PR #260

	protected $labels = array(
		'previous' => 'prev', 
		'next' => 'next'
	); 

	protected $separator = NULL;

	/**
	 * Construct the PagerNav
 	 *
	 * @param int $totalItems Total number of items in the list to be paginated. 
	 * @param int $itemsPerPage The number of items you want to appear per page. 
	 * @param int $currentPage The current page number (NOTE: 0 based, not 1 based)
	 * @throws WireException if given itemsPerPage of 0
	 *
	 */
	public function __construct($totalItems, $itemsPerPage, $currentPage) {

		// note that the page numbers are zero based.
		// if you are one based, then subtract one from currentPage before passing in here

		if(!$itemsPerPage) throw new WireException("itemsPerPage must be more than 0"); 

		$this->totalItems = $totalItems; 
		$this->currentPage = $currentPage-1;
		$this->itemsPerPage = $itemsPerPage;
		$this->firstItem = $this->currentPage * $this->itemsPerPage;

		/*
		// commented and kept for future reference
		if($totalItems >= ($itemsPerPage * 2)) {
			$this->totalPages = floor($this->totalItems / $this->itemsPerPage)-1; 
		} else {
			//$this->totalPages = ceil($this->totalItems / $this->itemsPerPage)-1; 
			$this->totalPages = ceil($this->totalItems / $this->itemsPerPage); 
		}
		*/

		if($this->totalItems > 0) {
			$this->totalPages = ceil($this->totalItems / $this->itemsPerPage) - 1;
		} else {
			$this->totalPages = 0;
		}

		/*
		// uncomment this section for debugging
		echo 	
			"totalItems: " . $this->totalItems . "<br />" . 
			"totalPages: " . $this->totalPages . "<br />" . 	
			"currentPage: " . $this->currentPage . "<br />" . 
			"itemsPerPage: " . $this->itemsPerPage . "<br />";
		*/

		if($this->totalPages && (($this->totalPages * $this->itemsPerPage) >= $this->totalItems)) 
			$this->totalPages--; // totalPages zero based

		$this->separator = new PagerNavItem('', 0, PagerNavItem::typeSeparator); 
	}	

	/**
 	 * Returns an array contantaining $label => $pageNum
	 *
	 * Rather than access this function directly, it is prefereable to iterate the object. 
	 *
	 * @return array
 	 *
	 */
	public function getPager() {

		// returns array($pageLabel => $pageNum, ...)

		if($this->totalItems <= $this->itemsPerPage) return array();
		if(!is_null($this->pager)) return $this->pager;
		$this->pager = array();

		if($this->numPageLinks) {
			$numPageLinks = $this->numPageLinks-1;
			$numHalf = (int) floor($numPageLinks / 2); 
			$startPage = $this->currentPage - $numHalf; 

			if($this->version === 'MP') {
				if($startPage < 0) $startPage = 0;
				if($numHalf >= ($this->currentPage - 1)) $startPage = 0;
				if($this->currentPage + $this->numPageLinks - $numHalf >= $this->totalPages) $startPage++;
				if($this->currentPage == $this->totalPages - $numPageLinks) $startPage--; // to prevent 32 33 34 ... and 31 is missing
				if($startPage < 0) $startPage = 0; // just in case
				$endPage = $startPage + $numPageLinks;
				if($this->currentPage == $endPage) $endPage++; // to prevent 1 2 3 ... and 4 is missing
			} else {
				if($startPage <= 0) {
					$startPage = 0;
				} else {
					$numPageLinks--;
				}
				$endPage = $startPage + $numPageLinks;
			}

			if($endPage > $this->totalPages) {
				$endPage = $this->totalPages; 
				$startPage = $endPage - $numPageLinks;
				if($startPage < 0) $startPage = 0;
			}		

		} else {
			$startPage = 0; 
			$endPage = $this->totalPages; 
		}

		/*
		// uncomment for debugging purposes
		echo 	
			"numPageLinks=$numPageLinks<br />" . 
			"numHalf=$numHalf<br />" . 
			"currentPage={$this->currentPage}<br />". //MP
			"pageNum=". ($this->currentPage+1) . "<br />". //MP
			"startPage=$startPage<br />" . 
			"endPage=$endPage<br />" . 
			"totalPages={$this->totalPages}<br />" . 
			"totalItems={$this->totalItems}<br />";
		*/

		if($this->version === 'MP') {
			for($n = $startPage; $n <= $endPage; $n++) { //MP
				$type = $n == ($this->currentPage) ? PagerNavItem::typeCurrent : '';
				$this->pager[] = new PagerNavItem($n + 1, $n, $type);
			}
		} else {
			for($n = $startPage; $n <= ($endPage+1); $n++) {
				$type = $n == ($this->currentPage + 1) ? PagerNavItem::typeCurrent : '';
				if($n) $this->pager[] = new PagerNavItem($n, $n - 1, $type);
			}
		}

		if($this->currentPage < $this->totalPages) {
			$useLast = true; 
			$item = null;

			foreach($this->pager as $item) {
				if($item->pageNum == $this->totalPages) $useLast = false;
			}

			if($this->version === 'MP') {
				if($item && $item->pageNum == ($this->totalPages - 1)) {
					$this->pager[] = new PagerNavItem($this->totalPages + 1, $this->totalPages);
					$useLast = false;
				}
			} else {
				/* not used but for reference
				if($item && $item->pageNum == ($this->totalPages-1)) {
					unset($this->pager[$key]); 
					$this->pager[] = $this->separator; 
					$this->pager[] = new PagerNavItem($this->totalPages+1, $this->totalPages); 
					$useLast = false; 
				}
				*/
			}

			if($useLast) {
				$this->pager[] = $this->separator; 
				$this->pager[] = new PagerNavItem($this->totalPages+1, $this->totalPages, PagerNavItem::typeLast); 
			}

			if($this->getLabel('next')) $this->pager[] = new PagerNavItem($this->getLabel('next'), $this->currentPage+1, PagerNavItem::typeNext); 
		}

		if(count($this->pager) > 1) {

			$firstPageLink = false;

			foreach($this->pager as $item) {
				// convert from 0-based to 1-based
				if($item->type != 'separator') $item->pageNum = $item->pageNum+1;
				if($item->pageNum == 1) $firstPageLink = true; 
			}

			if(!$firstPageLink) {
				// if the first page in pager is page 2, then get rid of it because we're already adding a page 1 (via typeFirst)
				// and leaving it here would result in 1 ... 2
				$item = reset($this->pager); 
				if($this->version === 'MP') {
					if($item->pageNum != 2) array_unshift($this->pager, $this->separator); // prev 1 2 3 4 5 6 next
				} else {
					if($item->pageNum == 2) array_shift($this->pager);
					array_unshift($this->pager, $this->separator);
				}
				array_unshift($this->pager, new PagerNavItem(1, 1, PagerNavItem::typeFirst)); // add reference to page 1
			}

			if($this->currentPage > 0 && $this->getLabel('previous')) {
				array_unshift($this->pager, new PagerNavItem($this->getLabel('previous'), $this->currentPage, PagerNavItem::typePrevious));
			}

		} else {
			$this->pager = array();
		}

	
		return $this->pager; 	
	}

	#[\ReturnTypeWillChange]
	public function getIterator() { return new \ArrayObject($this->getPager()); }
	public function getFirstItem() { return $this->firstItem; }
	public function getItemsPerPage() { return $this->itemsPerPage; }
	public function getCurrentPage() { return $this->currentPage; }
	public function getTotalPages() { return $this->totalPages+1; }
	public function getLabel($key) { return isset($this->labels[$key]) ? $this->labels[$key] : ''; }
	
	public function setNumPageLinks($numPageLinks) { $this->numPageLinks = $numPageLinks; }

	/**
	 * Set the labels to use for the 'prev' and 'next' links
	 *
	 * @param string $previous 'Previous' label
	 * @param string $next 'Next' label
	 *
	 */
	public function setLabels($previous, $next) {
		$this->labels['previous'] = $previous;
		$this->labels['next'] = $next;
	}
}
