<?php

// framework for reporting

class ColumnObject {
	
	private $name = FALSE;
	private $caption = FALSE;
	private $order = 50;
	private $sorted = FALSE;
	private $width = FALSE;		// in pixels
	private $source = FALSE;	// calculate the value to be used in this column
	private $render = FALSE;	// render the value - display it
	private $className = FALSE;
	private $visible = TRUE;
	
	public function __construct() {
	}
	
	function name() {				// fieldname. Required
		if (func_num_args()) {
			$this->name = func_get_arg(0);
			return $this;
		}
		return $this->name;
	}
	
	function caption() {			// label/caption. Required
		if (func_num_args()) {
			$this->caption = func_get_arg(0);
			return $this;
		}
		return $this->caption;
	}
	
	function width() {				// width of column (pixels), optional
		if (func_num_args()) {
			$this->width = func_get_arg(0);
			return $this;
		}
		return $this->width;
	}
	function className() {				// width of column (pixels), optional
		if (func_num_args()) {
			$this->className = func_get_arg(0);
			return $this;
		}
		return $this->className;
	}

	function visible() {
		if (func_num_args()) {
			$this->visible = func_get_arg(0);
			return $this;
		}
		return $this->visible;
	}
	
	function render($callable) {				// render function, Callable, optional. 
		$this->render = $callable;
		return $this;
	}
	function source($callable) {				// function to get the value of the field, Callable, optional. 
		$this->source = $callable;
		return $this;
	}
	
	function order() {				// field order
		if (func_num_args()) {
			$this->order = func_get_arg(0);
			return $this;
		}
		return $this->order;
	}
	
	function sort() {				// should column be sorted by default?  asc / desc / false / none
		if (func_num_args()) {
			$this->sorted = func_get_arg(0);
			return $this;
		}
		return $this->sorted;
	}
	
	//to do: action column: will not have data, may not have caption
	public function AsDataTableDefinition() {
		$definition = ['defaultContent' => ''];
		if ($this->name !== FALSE) {
			$definition['data'] = $this->Name();
		}
		if ($this->caption !== FALSE) {
			$definition['caption'] = $this->Caption();
		}
		if ($this->width !== FALSE) {
			$definition['width'] = $this->width . 'px';
		};
		if ($this->className !== FALSE) {
			$definition['className'] = $this->className;
		};
		if (is_callable($this->render)) {
			$definition['render'] = ['_' => 'disp', 'sort' => 'val'];
		} elseif (is_string($this->render)) {
			$definition['render'] = $this->render;		// a javascript function
		}
		if ($this->sorted === 'none') {
			$definition['orderable'] = FALSE;
		}
		if (!$this->visible) {
			$definition['visible'] = FALSE;
		}
		return $definition;
	}
	
	public function __debugInfo() {
		$onrender = is_callable($this->render);
        return [
            'name' => $this->name,
            'caption' => $this->caption,
            'width' => $this->width,
            'order' => $this->order,
            'sort' => $this->sorted,
			'render' => $onrender,
        ];
    }
	
	// get the field value from the current record
	function value($item) {
		if (is_callable($this->source)) {
			$value = call_user_func($this->source, $item);
		} else {
			$fieldname = $this->name();
			if (is_array($item)) {
				$value = $item[$fieldname] ?? NULL;
			} else {
				$value = $item->$fieldname ?? NULL;
			}
		}
		if (is_callable($this->render) && !is_string($this->render)) {
			$display = call_user_func($this->render, $value, $item);
			return is_array($display) ? $display : ['disp' => $display, 'val' => $value];
		} else {
			return $value;
		}
	}
	
	public function isHidden() {
		return $this->width === 0;
	}

}

class ReportObject {
	
	use \modules\database\traits {
			\modules\database\traits::__construct as __ConstructDatabase;
		}
	
	// field definitions
	//		get data from fields
	//		custom formatting of data
	// generate sql - inner joins
	//  custom nav buttons
	// suggested sort column
	// => generate data for datatables
	// => save sort order, filters, pagesize
	private $columns = [];
	private $pagesize = 25;
	private $datasources = [];
	private $sorted = FALSE;
	private $sql;
	private $engine;
	private $autowidth = TRUE;
	private $ondata = NULL;
	
	public function __construct() {
		$this->__ConstructDatabase();
	}
	
	public function Engine() {
		if (func_num_args()) {
			$this->engine = func_get_arg(0);
			return $this;
		}
		return $this->engine;
	}

	function PageSize() {				// rows per page: 10, 25, 50 or 100
		if (func_num_args()) {
			$this->pagesize = func_get_arg(0);
			return $this;
		}
		return $this->pagesize;
	}

	function AutoWidth() {
		if (func_num_args()) {
			$this->autowidth = func_get_arg(0);
			return $this;
		}
		return $this->autowidth;
	}

	// callback to fetch data
	function onData() {
		if (func_num_args()) {
			$this->ondata = func_get_arg(0);
			return $this;
		}
		return $this->ondata;
	}
	
	public function AddColumn() {
		$col = new ColumnObject;
		$this->columns[] = $col;
		$this->sorted = FALSE;
		return $col;
	}

	private function _locateByName($fieldname) {
		$fieldname = strtolower($fieldname);
		foreach($this->columns as $index => $info) {
			if (strtolower($info->name()) == $fieldname) {
				return $index;
			}
		}
		return FALSE;
	}

	public function InsertBefore($fieldname) {
		$pos = $this->_locateByName($fieldname);
		if ($pos === FALSE) return $this->AddColumn();

		$col = new ColumnObject;
		array_splice($this->columns, $pos, 0, [$col]);
		$this->sorted = FALSE;
		return $col;
	}

	public function InsertAfter($fieldname) {
		$pos = $this->_locateByName($fieldname);
		if ($pos === FALSE || $pos == count($this->columns) - 1)
			return $this->AddColumn();

		$col = new ColumnObject;
		array_splice($this->columns, $pos + 1, 0, [$col]);
		$this->sorted = FALSE;
		return $col;
	}
	
	// get the data for the report - use instead of get()
	public function Execute() {
		$result = [];
		if (is_callable($this->ondata)) {
			$items = call_user_func($this->ondata, $this);
		} else {
			if (is_object($this->engine)) {
				$items = $this->engine->get();
			} else {
				$items = $this->get();
			}
			$this->dbErr();
		}
		foreach($items as $item) {
			$node = $this->_parseItem($item);
			$result[] = $node;
		}
		return $result;
	}
	
	private function _parseItem($item) {
		$node = [];
		foreach($this->columns as $col) {
			$fieldname = $col->name();
			$v = $col->value($item);
			if (!is_null($v)) $node[$fieldname] = $v;
		}
		return $node;
	}
	
//	public function SetDatabase($db) {
//		$this->db = $db;
//	}
	
	// Datasource will update sql to provide data for one or more columns
	public function AddDataSource($callback) {
		$this->datasources[] = $callback;
	}
	
	// column order
	private function _sort() {
		if (!$this->sorted) {
			usort($this->columns, function($a, $b) {
				return $a->order() - $b->order();
			});
			$this->sorted = TRUE;
		}
	}
	
	// create a structure for DataTables. Only the definitions, not the data
	public function asDataTablesDefinition(&$res) {
		$this->_sort();
		$columns = [];
		$header = [];
		$sortcol = FALSE;
		// find first sortable column
		foreach($this->columns as $index => $col) {
			if (!$col->isHidden()) {
				$columns[] = $col->AsDataTableDefinition();
				$caption = $col->caption();
				if ($caption === FALSE) $caption = '';
				$header[] = [
					'caption' => $caption, 
					'id' 	  => $index,
				];
				$sort = $col->Sort();
				if ($sort !== 'none' && $sortcol === FALSE) {
					$sortcol = $index;
				}
			}
		}
		if ($sortcol === FALSE) $sortcol = 0;
		
		$res['datatable'] = [
			'columns' => $columns,
			'order' => [[ $sortcol, 'asc' ]],	// to implement
			'pageLength' => $this->PageSize(),
			'autoWidth' => $this->AutoWidth(),
			'bPaginate' => TRUE,
			'bFilter' => TRUE,
			'bInfo' => TRUE,					// Showing x to y of z entries
		];
		$res['header'] = $header;
	}
	
}