<?php defined('SYSPATH') or die('No direct access allowed.');

/**
 * This class is for rendering a table of data.
 *
 * @package    lnApp
 * @category   Helpers
 * @author     Deon George
 * @copyright  (c) 2009-2013 Deon George
 * @license    http://dev.leenooks.net/license.html
 */
abstract class lnApp_Table {
	private $data = NULL;
	private $columns = array();		// Our columns that we need to display
	private $filters = array();		// Our datafomrating that we need to use
	private $filters_added = FALSE;
	private $jssort = FALSE;		// Use the JS pager and sorter
	private $other = '';			// If we are only listing x items, this will be the other summary line
	private $other_col = NULL;		// If we are only listing x items, this will be the other summary line
	private $po = NULL;			// If we are paging the results, this is the page we are displaying
	private $page = FALSE;			// If we should page the results
	private $page_rows = 0;			// Number of rows per page
	private $prepend = array();		// Our data formating that we need to use, prepended to the value
	private $postproc = array();		// Our data formating that we need to use after the value is determined
	private $select = array();		// Our select form details

	public function __toString() {
		try {
			return (string)$this->render();

		} catch (Exception $e) {
			return $e->getMessage();
		}
	}

	public function columns($cols) {
		$this->columns = $cols;

		return $this;
	}

	public function data($data) {
		$this->data = $data;

		return $this;
	}

	private function evaluate($d,$key) {
		if (count($this->filters) AND ! $this->filters_added) {
			$d->display_filters($this->filters);
			// So we dont keep extending our display_filters
			$this->filters_added = TRUE;
		}

		if (is_array($d) AND isset($d[$key]))
			$x = $d[$key];

		// If the key is a method, we need to eval it
		elseif (preg_match('/\(/',$key) OR preg_match('/-\>/',$key))
			eval("\$x = \$d->$key;");

		elseif (preg_match('/^__VALUE__$/',$key))
			$x = $d;

		else
			$x = isset($d->{$key}) ? $d->display($key) : '';

		// We cant display array values
		if (is_array($x))
			$x = array_shift($x);

		if (isset($this->prepend[$key]) AND $x) {
			foreach ($this->prepend[$key] as $act => $data) {
				switch ($act) {
					case 'checkbox': $x = sprintf('<label class="checkbox">%s %s</label>',Form::checkbox($data,$x,FALSE,array('nocg'=>TRUE)),$x); // @todo Check those not yet checked
						break;

					case 'input':
						if (preg_match('/__VALUE__/',$data['key']))
							$data['key'] = preg_replace('/__VALUE__/',$x,$data['key']);

						$x = Form::input($data['key'],isset($data['values'][$x]) ? $data['values'][$x] : '',array('placeholder'=>$x,'label'=>$x));
						break;

					case 'url': $x = HTML::anchor($data.$x,$x);
						break;

					case 'url_resolve':
						$filtermatchall = array();
						preg_match_all('/%(\w+)(\|.+)?(\/[lUC])?%/U',$data,$filtermatchall);

						foreach ($filtermatchall[1] as $id=>$key)
							$data = str_replace($filtermatchall[0][$id],$d->display($key),$data);

						$x = HTML::anchor($data,$x);
						break;

					default:
						throw HTTP_Exception::factory(501,'Unknown action :action',array(':action'=>$act));
				}
			}
		}

		if (isset($this->postproc[$key]) and $x) {
			foreach ($this->postproc[$key] as $act => $data) {
				switch ($act) {
					case 'yesno':
						$x = StaticList_YesNo::get($x,TRUE);
						break;

					case 'trim':
						if (strlen($x) > $data) {
							$x = sprintf('<abbr title="%s">%s</abbr>',$x,substr($x,0,$data-3).str_repeat('.',3));
						}

						break;

					default:
						throw HTTP_Exception::factory(501,'Unknown action :action',array(':action'=>$act));
				}
			}
		}

		return $x;
	}

	public static function factory() {
		return new Table;
	}

	/**
	 * Apply additional display filters to the results
	 */
	public function filters($cols) {
		$this->filters = $cols;

		return $this;
	}

	public function jssort($table) {
		$this->jssort = $table;

		return $this;
	}

	public function page_items($num) {
		$this->page = TRUE;
		$this->page_rows = $num;

		return $this;
	}

	public function prepend($cols) {
		$this->prepend = $cols;

		return $this;
	}

	public function postproc($cols) {
		$this->postproc = $cols;

		return $this;
	}

	private function process() {
		$result = array();

		$row = 0;
		foreach ($this->data as $o) {
			$row++;
			$c = 0;
			// So our filters are added for this record.
			$this->filters_added = FALSE;

			// If we are HTML paging
			if ($this->page) {
				if ($row < $this->po->current_first_item())
					continue;
				elseif ($row > $this->po->current_last_item())
					break;
			}

			// If we are just listing page_rows items and a summary line as a result of exceeding that
			if ($this->other_col AND $row>$this->page_rows) {
				$this->other += $this->evaluate($o,$this->other_col);

			// Otherwise rendering our rows
			} else {
				foreach ($this->columns as $col => $label) {
					$c++;
	
					$result[$row]['val'][$c] = $this->evaluate($o,$col);
				}
			}
		}

		return $result;
	}

	private function render() {
		$output = '';

		// If we need to page the results
		if ($this->page) {
			$this->po = new Pagination(array(
				'total_items'=>count($this->data),
				'items_per_page'=>$this->page_rows,
			));

			$output .= (string)$this->po;

		}

		if ($this->select)
			$output .= Form::open($this->select['form']);

		$output .= View::factory('table')
			->set('jssort',($this->jssort AND ! $this->page) ? $this->jssort : FALSE)
			->set('other',$this->other)
			->set('th',$this->columns)
			->set('td',$this->process());

		// Use the javascript paging
		if ($this->jssort AND ! $this->page) {
			Script::factory()
				->type('stdin')
				->data('
$(document).ready(function() {
	$.extend($.tablesorter.themes.bootstrap, {
		// these classes are added to the table. To see other table classes available,
		// look here: http://twitter.github.com/bootstrap/base-css.html#tables
		table      : "table table-striped",
		header     : "bootstrap-header", // give the header a gradient background
		footerRow  : "",
		footerCells: "",
		icons      : "", // add "icon-white" to make them white; this icon class is added to the <i> in the header
		sortNone   : "bootstrap-icon-unsorted",
		sortAsc    : "icon-chevron-up",
		sortDesc   : "icon-chevron-down",
		active     : "", // applied when column is sorted
		hover      : "", // use custom css here - bootstrap class may not override it
		filterRow  : "", // filter row class
		even       : "", // odd row zebra striping
		odd        : ""  // even row zebra striping
	});

	$("#list-table'.($this->jssort ? '-'.$this->jssort : '').'").tablesorter({
		theme: "bootstrap",
		widthFixed : true,
		headerTemplate : "{content} {icon}", // Add icon for jui theme; new in v2.7!
		widgets: [ "uitheme", "stickyHeaders", "filter" ],
		widgetOptions : {
			stickyHeaders_offset: ".navbar-fixed-top",
		}

	}).tablesorterPager({
		// target the pager markup - see the HTML block below
		container  : $(".pager'.($this->jssort ? '-'.$this->jssort : '').'"),
		output     : "{startRow} - {endRow} / {filteredRows} ({totalRows})",
		fixedHeight: true,
		removeRows : false,
		cssGoto    : ".gotoPage"

	});
});
				');

			Script::factory()
				->type('file')
				->data('media/vendor/mottie-tablesorter/js/jquery.tablesorter.min.js');

			Script::factory()
				->type('file')
				->data('media/vendor/mottie-tablesorter/js/jquery.tablesorter.widgets.min.js');

			Script::factory()
				->type('file')
				->data('media/vendor/mottie-tablesorter/js/jquery.tablesorter.pager.min.js');

			Style::factory()
				->type('file')
				->data('media/vendor/mottie-tablesorter/css/theme.bootstrap.css');
			Style::factory()
				->type('file')
				->data('media/vendor/mottie-tablesorter/css/jquery.tablesorter.pager.css');
			// @todo The theme should be moved from here, but Controller_Media will need to know about admin requests for media/
			Style::factory()
				->type('file')
				->data('media/theme/baseadmin/css/custom/tablesort.css');
		}

		if ($this->select) {
			$output .= Form::button('submit','Submit',array('class'=>'btn btn-primary','value'=>$this->select['submit']));

			foreach ($this->select['hidden'] as $k=>$v)
				$output .= Form::hidden($k,$v);

			$output .= Form::close();
		}

		return $output;
	}

	public function select($form,$submit='',array $hidden=array()) {
		$this->select['form'] = $form;
		$this->select['submit'] = $submit;
		$this->select['hidden'] = $hidden;

		return $this;
	}

	// @deprecated
	public static function display($data,$rows,array $cols,array $option) {
		$prepend = $headers = array();

		foreach ($cols as $k=>$v) {
			if (isset($v['label']))
				$headers[$k] = $v['label'];
			if (isset($v['url']))
				$prepend[$k] = array('url'=>$v['url']);
		}

		return self::factory()
			->data($data)
			->jssort(TRUE)
			->page_items($rows)
			->columns($headers)
			->prepend($prepend);

/*
// This JS is failing, any pressing of select all, unselect all, toggle is propaging up and submitting the form

					Script::factory()
						->type('stdin')
						->data('
(function($) {
	// Enable Range Selection
	$.fn.enableCheckboxRangeSelection = function() {
		var lastCheckbox = null;
		var followOn = 0;
		var $spec = this;
		$spec.bind("click", function(e) {
			if (lastCheckbox != null && e.shiftKey) {
				x = y = 0;
				if (followOn != 0) {
					if ($spec.index(lastCheckbox) < $spec.index(e.target)) {
						x = 1 - ((followOn == 1) ? 1 : 0);
					} else {
						y = 1 - ((followOn == -1) ? 1 : 0);
					}
				}

				$spec.slice(
					Math.min($spec.index(lastCheckbox) - x, $spec.index(e.target)) + 1,
					Math.max($spec.index(lastCheckbox), $spec.index(e.target)) + y
				).attr("checked",function() { return ! this.checked; })
				.parent().parent().toggleClass("selected");

				followOn = ($spec.index(lastCheckbox) < $spec.index(e.target)) ? 1 : -1;
			} else {
				followOn = 0;
			}
			lastCheckbox = e.target;
		});

		return $spec;
	};

	// Enable Toggle, (De)Select All
	$.fn.check = function(mode) {
		// if mode is undefined, use "on" as default
		var mode = mode || "on";

		switch(mode) {
			case "on":
				$("#select-table tr:not(.head)")
					.filter(":has(:checkbox:not(checked))")
					.toggleClass("selected")
				break;
			case "off":
				$("#select-table tr:not(.head)")
					.filter(":has(:checkbox:checked)")
					.toggleClass("selected")
				break;
			case "toggle":
				$("#select-table tr:not(.head)")
					.toggleClass("selected");
				break;
		}

		return this.each(function(e) {
			switch(mode) {
				case "on":
					this.checked = true;
					break;
				case "off":
					this.checked = false;
					break;
				case "toggle":
					this.checked = !this.checked;
					break;
			}
		});
	};
})(jQuery);

// Bind our actions
$(document).ready(function() {
	$("#select-table :checkbox").enableCheckboxRangeSelection();
	$("#select-menu > #toggle").bind("click",function() {
		$("#select-table :checkbox").check("toggle");
	});
	$("#select-menu > #all_on").bind("click",function() {
		$("#select-table :checkbox").check("on");
	});
	$("#select-menu > #all_off").bind("click",function() {
		$("#select-table :checkbox").check("off");
	});

	// Click to select Row
	$("#select-table tr:not(.head)")
		.filter(":has(:checkbox:checked)")
		.addClass("selected")
		.end()
		.click(function(e) {
			$(this).toggleClass("selected");
			if (e.target.type !== "checkbox") {
				$(":checkbox", this).attr("checked", function() {
					return !this.checked;
				});
			}
		});

	// Double click to select a row
	$("#select-table tr:not(.head)")
		.dblclick(function(e) {
			window.location = $("a", this).attr("href");
		});
});
					');
*/
	}

	// This enables us to page through many selected items
	// @todo This is currently not usable, since our JS above needs to be fixed to work with tablesorter
	public static function page($key) {
		// We have preference for parameters passed to the action.
		if (is_null($id = Request::current()->param('id'))) {
			// First save our POST id data into the session, so we dont need it when going to each page
			if (isset($_POST['id']) AND is_array($_POST['id']))
				Session::instance()->set('page_table_view'.$key,'id');

			if ($ids = Session::instance()->get('page_table_view'.$key)) {
				$pag = new Pagination(array(
					'total_items'=>count($ids),
					'items_per_page'=>1,
				));

				return array($ids[$pag->current_first_item()-1],(string)$pag);
			}
		}

		// If we get here, then there is no previous data to retrieve.
		return array($id,'');
	}
}
?>