<?php namespace Laravel;

class Paginator {

	/**
	 * The results for the current page.
	 *
	 * @var array
	 */
	public $results;

	/**
	 * The current page.
	 *
	 * @var int
	 */
	protected $page;

	/**
	 * The last page available for the result set.
	 *
	 * @var int
	 */
	protected $last;

	/**
	 * The total number of results.
	 *
	 * @var int
	 */
	protected $total;

	/**
	 * The number of items per page.
	 *
	 * @var int
	 */
	protected $per_page;

	/**
	 * The values that should be appended to the end of the link query strings.
	 *
	 * @var array
	 */
	protected $appends;

	/**
	 * The compiled appendage that will be appended to the links.
	 *
	 * This consists of a sprintf format  with a page place-holder and query string.
	 *
	 * @var string
	 */
	protected $appendage;

	/**
	 * The pagination elements that will be generated.
	 *
	 * @var array
	 */
	protected $elements = array('first', 'previous', 'status', 'next', 'last');

	/**
	 * Create a new Paginator instance.
	 *
	 * @param  array  $results
	 * @param  int    $last
	 * @param  int    $page
	 * @param  int    $total
	 * @param  int    $per_page
	 * @return void
	 */
	protected function __construct($results, $page, $total, $per_page, $last)
	{
		$this->page = $page;
		$this->last = $last;
		$this->total = $total;
		$this->results = $results;
		$this->per_page = $per_page;
	}

	/**
	 * Create a new Paginator instance.
	 *
	 * @param  array      $results
	 * @param  int        $total
	 * @param  int        $per_page
	 * @return Paginator
	 */
	public static function make($results, $total, $per_page)
	{
		return new static($results, static::page($total, $per_page), $total, $per_page, ceil($total / $per_page));
	}

	/**
	 * Get the current page from the request query string.
	 *
	 * @param  int  $total
	 * @param  int  $per_page
	 * @return int
	 */
	public static function page($total, $per_page)
	{
		$page = Input::get('page', 1);

		// The page will be validated and adjusted if it is less than one or greater
		// than the last page. For example, if the current page is not an integer or
		// less than one, one will be returned. If the current page is greater than
		// the last page, the last page will be returned.
		if (is_numeric($page) and $page > $last = ceil($total / $per_page))
		{
			return ($last > 0) ? $last : 1;
		}

		return ($page < 1 or filter_var($page, FILTER_VALIDATE_INT) === false) ? 1 : $page;
	}

	/**
	 * Create the HTML pagination links.
	 *
	 * @return string
	 */
	public function links()
	{
		if ($this->last <= 1) return '';

		// Each pagination element is created by an element method. This allows
		// us to keep this class clean and simple, because pagination code can
		// become a mess. We would rather keep it simple and beautiful.
		foreach ($this->elements as $element)
		{
			$elements[] = $this->$element(Lang::line("pagination.{$element}")->get());
		}

		return '<div class="pagination">'.implode(' ', $elements).'</div>'.PHP_EOL;
	}

	/**
	 * Get the "status" pagination element.
	 *
	 * @param  string  $text
	 * @return string
	 */
	protected function status($text)
	{
		return str_replace(array(':current', ':last'), array($this->page, $this->last), $text);
	}

	/**
	 * Create the "first" pagination element.
	 *
	 * @param  string  $text
	 * @return string
	 */
	protected function first($text)
	{
		return $this->backwards(__FUNCTION__, $text, 1);
	}

	/**
	 * Create the "previous" pagination element.
	 *
	 * @param  string  $text
	 * @return string
	 */
	protected function previous($text)
	{
		return $this->backwards(__FUNCTION__, $text, $this->page - 1);
	}

	/**
	 * Create the "next" pagination element.
	 *
	 * @param  string  $text
	 * @return string
	 */
	protected function next($text)
	{
		return $this->forwards(__FUNCTION__, $text, $this->page + 1);
	}

	/**
	 * Create the "last" pagination element.
	 *
	 * @param  string  $text
	 * @return string
	 */
	protected function last($text)
	{
		return $this->forwards(__FUNCTION__, $text, $this->last);
	}

	/**
	 * Create a "backwards" paginatino element.
	 *
	 * This function handles the creation of the first and previous elements.
	 *
	 * @param  string  $element
	 * @param  string  $text
	 * @param  int     $last
	 * @return string
	 */
	protected function backwards($element, $text, $last)
	{
		return $this->element($element, $text, $last, function($page) { return $page <= 1; });
	}

	/**
	 * Create a "forwards" paginatino element.
	 *
	 * This function handles the creation of the next and last elements.
	 *
	 * @param  string  $element
	 * @param  string  $text
	 * @param  int     $last
	 * @return string
	 */
	protected function forwards($element, $text, $last)
	{
		return $this->element($element, $text, $last, function($page, $last) { return $page >= $last; });
	}

	/**
	 * Create a chronological pagination element.
	 *
	 * @param  string   $element
	 * @param  string   $text
	 * @param  int      $page
	 * @param  Closure  $disabler
	 * @return string
	 */
	protected function element($element, $text, $page, $disabler)
	{
		$class = "{$element}_page";

		if ($disabler($this->page, $this->last))
		{
			return HTML::span($text, array('class' => "disabled {$class}"));
		}
		else
		{
			// We will assume the page links should use HTTPS if the current request
			// is also using HTTPS. Since pagination links automatically point to
			// the current URI, this makes pretty good sense.
			list($uri, $secure) = array(URI::get(), Request::secure());

			return HTML::link($uri.$this->appendage($element, $page), $text, array('class' => $class), $secure);
		}
	}

	/**
	 * Create the pagination link "appendage" for an element.
	 *
	 * @param  string  $element
	 * @param  int     $page
	 * @return string
	 */
	protected function appendage($element, $page)
	{
		// The appendage string contains the query string, but it also contains a
		// place-holder for the page number. This will be used to insert the
		// correct page number based on the element being created.
		if (is_null($this->appendage))
		{
			$this->appendage = '?page=%s'.http_build_query((array) $this->appends);
		}

		return sprintf($this->appendage, $page);
	}

	/**
	 * Set the items that should be appended to the link query strings.
	 *
	 * This provides a convenient method of maintaining sort or passing other information
	 * to the route handling pagination.
	 *
	 * @param  array      $values
	 * @return Paginator
	 */
	public function appends($values)
	{
		$this->appends = $values;

		return $this;
	}

	/**
	 * Set the elements that should be included when creating the pagination links.
	 *
	 * The available elements are "first", "previous", "status", "next", and "last".
	 *
	 * @param  array   $elements
	 * @return string
	 */
	public function elements($elements)
	{
		$this->elements = $elements;

		return $this;
	}

}