<?php
/**
 * btnsort_model
 *
 * Button sort model. The coreid index comes before type therefore always put coreid before the type
 * in the where statement. The linkid has to come after type.
 *
 * @author $Author: dtong $
 * @version $Id: btnsort_model.php,v 1.2 2011/05/09 04:48:52 dtong Exp $
 * @copyright Copyright (c) 2011, Tiller Software Co., Ltd.
*/

class Btnsort_model extends CI_Model {
	const PREFETCH_EXECUTED = '_preexecuted';
	const RET_EMPTY_DATA = 2;
	const ACTION_UP = 1;
	const ACTION_DOWN = 2;
	//Don't like to set it to zero but it works better in query comparison since can't compare null
	const DEFAULT_SEGMENT_VALUE = 0;

	//Turns prefecth on or off. You only want to turn this off when there is only one type
	//in a template since the prefetch works across all types in one single query
	private $pre_fetch = TRUE;
	//This is needed for outcomes and lt
	private $ignore_empty_linkid = FALSE;
	//Optional segment index name, needed for outcomes and lt
	private $segment_index = FALSE;
	//$segmentid2 is for sorting check only, this is usually department id when multiple departments involved
	//like LT which is segment within segment
	//private $segment_index2 = FALSE;
	//Set segmentid for get operation and sorting check
	private $segmentid = FALSE;

	//Always use this realtype
	private $realtype_override = FALSE;

	function __construct() {
		parent::__construct();
		$this->load->library('cms/cms_prefetch');
	}

	function set_realtype_override($type) {
		$this->realtype_override = $type;
	}

	//Set prefecth flag
	function set_pre_fetch($pre_fetch) {
		$this->pre_fetch = $pre_fetch;
	}

	function ignore_empty_linkid($flag) {
		$this->ignore_empty_linkid = $flag;
	}

	//function set_segmentindex($index_name, $index_name2=FALSE) {
	function set_segmentindex($index_name) {
		$this->segment_index = $index_name;
		//$this->segment_index2 = $index_name2;
	}

	function set_segmentid($segmentid) {
		$this->segmentid = $segmentid;
	}

	function get_segmentindex() {
		return $this->segment_index;
	}

	//Get records from btnsort for the current module, uses prefetch by default else it will be very very slow...
	//Imagine with 20 modules on a template and doing the same DB calls 20 times plus other queries...
	//Resource has the same issue and prefetch will be added soon too...
	function get($type, $coreid, $filehash, $file_part) {
		//Always look for the prefetch value since one prefetch in any type will get all the types
		//The is not that expensive so always call even not needed
		//$coreid can be array so check
		/*
		$ret = FALSE;
		if ( osa_is_int($coreid) && is_array($ret = $this->get_cache(__METHOD__, $coreid, $type, $filehash)) ) {
			return $ret;
		}
		if ( $ret == self::RET_EMPTY_DATA ) {
			return FALSE;
		}
		*/

		/*
		if ($this->pre_fetch) {
			$this->db->order_by('type');
			$query = $this->db->get_where('btnsort', array('coreid' => $coreid, 'filehash' => $filehash));
			if ( $query->num_rows()  <= 0 ) {
				$this->save_prefetch(FALSE, __METHOD__, $coreid, $filehash);
			}
			else {
				$this->save_prefetch($query->result(), __METHOD__, $coreid, $filehash);
			}
			if ( is_array($ret = $this->get_cache(__METHOD__, $coreid, $type, $filehash)) ) {
				return $ret;
			}
			if ( $ret == self::RET_EMPTY_DATA ) {
				return FALSE;
			}
			//Should not get here but return false anyway
			return FALSE;
		}
		*/

		//Prefetch DB call
		//If $coreid is array prefetch is set to off from Cms_btnsort but check coreid again anyway
		if ( osa_is_int($coreid) && $this->pre_fetch && $this->config->item('cms_db_prefetch_enable_btnsort') &&
			  $this->cms_prefetch->enabled() ) {
			$table = $this->db->dbprefix('btnsort');
			$sql = "select * from $table where coreid={$coreid} and filehash={$filehash}";
			if ( $this->cms_prefetch->get_stage_hint() ) {
				$this->load->library('cms/cms_template/cms_template');
				if ( ($stage_types = $this->cms_template->stage_types_include_meta($file_part, $this->cms_prefetch->get_stage_hint())) ) {
					$sql .= " and type in ({$stage_types})";
				}
			}
			if ( $this->cms_prefetch->reset(__METHOD__, $coreid, $filehash) ) {
				if ( $this->cms_prefetch->set_sql($sql) ) {
					$tmp = $this->cms_prefetch->get($type);
					return $tmp;
				}
				$this->config->set_item('cms_db_prefetch_enable_btnsort', FALSE);
			}
			$this->config->set_item('cms_db_prefetch_enable_btnsort', FALSE);
		}

		//Non-prefetch DB call
		$where_array = array('coreid' => $coreid, 'type' => $type);
		if ( is_array($coreid) ) {
			unset($where_array['coreid']);
			$this->db->where_in(array('coreid' => $coreid));
		}
		if ( $this->segmentid ) {
			$where_array['segmentid'] =  $this->segmentid;
		}
		$this->db->where($where_array);
		//$query = $this->db->get_where('btnsort', $where_array);
		$query = $this->db->get('btnsort');
		if ( $query->num_rows() <= 0 ) {
			return FALSE;
		}
		return ($query->result());
	}

	/*
	 * Updates/initializes record(s) from module to btnsort table
	 * Copies the new btnsort record ids into the module data array. This is usually a single record update.
	 *
	 * @param &array $data The module data
	 * @param string $filehash The bigint hash value for template file_part
	 * @param int $type The sort type, this is usually the template type but can be overrided in the template file.
	 * @param string $coreindex The index name for coreid in the module data array.
	 * @param string $linkindex The index name for linkid in the module data array.
	 * @param string $btnid_index The index name for the btnsort record id. This function will add this index.
	 *
	 * @return bool True on success and false otherwise.
	 */
	function update(&$data, $filehash, $type, $coreindex, $linkindex, $sortindex, $btnid_index, $realtypes) {
		if ( !$data ) {
			return FALSE;
		}
		$records = '';
		$sep = '';
		$segment_index = $this->segment_index;
		//$segment_index2 = $this->segment_index2;
		$segment_value = self::DEFAULT_SEGMENT_VALUE;
		$segment_value2 = self::DEFAULT_SEGMENT_VALUE;

		//Sort the array by sortkey, just in case if it is not sorted
		osa_array_natsort($data, $sortindex, TRUE, FALSE, FALSE, 'osa_numeric_str_compare');
		$current = & reset($data);
		foreach ( $data as $value) {
			if ( !($realtype_value = $this->get_first_realtype($realtypes, $value)) ) {
				//get_first_realtype() prints log message already so no need to log again here
				return FALSE;
			}
			//Outcomes and LT can cause $value->$linkindex to be empty since it is skills/lt segment based
			//Like a new benchmark/outcome without lt/outcome
			if ( ( empty($value->$linkindex) || empty($value->$coreindex) ) && !$this->ignore_empty_linkid ) {
				osa_errorlog(__METHOD__ . " Missing linkid.", array($linkindex, $value));
				return FALSE;
			}
			if ( !empty($value->$linkindex) && !empty($value->$coreindex) ) {
				if ( $segment_index ) {
					if ( !isset($value->$segment_index) || !osa_is_int_one($value->$segment_index) ) {
						osa_errorlog(__METHOD__ . " - Missing segment index data.", array($segment_index, $value));
						return FALSE;
					}
					/*
					//This is optional
					if ( $segment_index2 && !osa_is_int_one($value->$segment_index2) ) {
						osa_errorlog(__METHOD__ . " - Missing segment index 2 data.", array($segment_index2, $value));
						return FALSE;
					}*/
					$segment_value = $value->$segment_index;
					/*if ( $segment_index2 ) {
						$segment_value2 = $value->$segment_index2;
					}*/
				}
				$segment_insert_values = '';
				if ($segment_index) {
					$segment_insert_values = ",{$segment_value}";
				}
				/*if ($segment_index2) {
					$segment_insert_values .= ",{$segment_value2}";
				}*/
				$records .= "{$sep}({$value->$coreindex},{$value->$linkindex},{$type}," .
								"{$value->$sortindex},{$filehash},{$realtype_value}{$segment_insert_values})";
				$sep = ',';
			}
			//Do not add more code here (end of this loop) if you don't know what the flag $this->ignore_empty_linkid is for.
		}
		if ( $records == '' && $this->ignore_empty_linkid ) {
			return TRUE;
		}
		if ( $records == '' ) {
			osa_errorlog(__METHOD__ . " - Missing update records.", $data);
			return FALSE;
		}
		$min_sortkey = $current->$sortindex;
		$max_sortkey = $value->$sortindex;

		$table = $this->db->dbprefix('btnsort');
		$fields = 'coreid,linkid,type,sortkey,filehash,realtype';
		if ( $segment_index ) {
			$fields .= ',segmentid';
		}
		/*if ( $segment_index2 ) {
			$fields .= ',segmentid2';
		}*/
		$query = "insert into $table ($fields) values $records";
		if ( !$this->db->query($query) ) {
			osa_errorlog(__METHOD__ . " - Failed to run sql.", $query);
			return FALSE;
		}

		//Now wee need to find the new ids from the inserts and populate them to the module data (passed by reference)
		$this->db->where(array('coreid'=>$value->$coreindex, 'type'=>$type));
		$this->db->where("sortkey >= {$min_sortkey} and sortkey <= $max_sortkey");
		$this->db->order_by('sortkey');
		$query = $this->db->get('btnsort');
		$row_count = $query->num_rows();
		if ( !$this->ignore_empty_linkid ) {
			//Make sure the inserts are the same number as the module data
			if ( $row_count <= 0 || $row_count != count($data) ) {
				return FALSE;
			}
		}
		$result = $query->result();
		$hashed_res = array();
		//Hash the DB result for faster access
		foreach ( $result as $key => $value ) {
			$hashed_res[$value->sortkey] = $result[$key];
		}

		//Loop thru each module data and assign/inject the btnsort id if there is one from the db result (hashed array).
		foreach ($data as $key => $value) {
			if ( !$this->ignore_empty_linkid && !isset($hashed_res[$value->$sortindex]) ) {
				//This could happen if 2 users did some sorting on the same record at the exact same moment
				//But, the chance is alomst nil since we do template page locking and no actual data corruption
				osa_errorlog(__METHOD__ . 'Missing DB record.', $value);
				return FALSE;
			}
			elseif (isset($hashed_res[$value->$sortindex])) {
				$data[$key]->$btnid_index = $hashed_res[$value->$sortindex]->id;
			}
		}
		return TRUE;
	}

	//Deletes btnsort record so it is sync with the actual data
	//Usually a single delete
	function delete($data) {
		if ( !$data ) {
			return FALSE;
		}
		$tmp = array();
		foreach ( $data as $value ) {
			$tmp[] = $value->id;
		}
		$this->db->where_in('id', $tmp);
		return $this->db->delete('btnsort');
	}

	//Get the 2 records that need to be swaped
	function get_swap_data($id, $action) {
		if ( $action == self::ACTION_UP ) {
			$op = '<=';
			$direction = 'desc';
		}
		else {
			$op = '>=';
			$direction = 'asc';
		}
		$table = $this->db->dbprefix('btnsort');
		/*$sql = "select b1.* from $table b1 use index (ind_coreid_type,ind_type) " .
				 "join $table b2 on b2.id={$id} and b1.coreid=b2.coreid and b1.type=b2.type " .
				 "where b1.sortkey $op b2.sortkey and b1.segmentid=b2.segmentid and " .
				 	'b1.segmentid2=b2.segmentid2 ' .
				 "order by b1.sortkey $direction limit 2";*/
		$sql = "select b1.* from $table b1 use index (ind_coreid_type,ind_type) " .
				 "join $table b2 on b2.id={$id} and b1.coreid=b2.coreid and b1.type=b2.type " .
				 "where b1.segmentid=b2.segmentid and b1.sortkey $op b2.sortkey " .
				 "order by b1.sortkey $direction limit 2";
		$query = $this->db->query($sql);
		$numrows = $query->num_rows();
		if ( $numrows <= 0 ) {
			return FALSE;
		}
		if ( $numrows == 1 ) {
			return TRUE;
		}
		$result = $query->result();
		if ( $result[0]->id != $id ) {
			return FALSE;
		}
		return $result;
	}

	//Swap 2 records' sortkeys based on the result returned by get_swap_data(0
	function swap($result) {
		$table = $this->db->dbprefix('btnsort');
		$sql = "update $table b1,$table b2 " .
				 "set b1.sortkey={$result[1]->sortkey},b2.sortkey={$result[0]->sortkey} " .
				 "where b1.id={$result[0]->id} and b2.id={$result[1]->id}";
		return $this->db->simple_query($sql);
	}

	//There can be multiple types in a data set (imagine eq and resource sort together)
	//and this function loop thru the possible types and returns the value of the first matching one.
	//If somehow in a row containg 2 mathcing types, this whole thing will break. All type indexes have to be unique.
	//private function get_data_type($row) {
	function get_first_realtype($realtypes, $row) {
		if ( $this->realtype_override ) {
			return $this->realtype_override;
		}
		foreach($realtypes as $type) {
			if ( isset($row->$type) && osa_is_int_one($row->$type) ) {
				return $row->$type;
			}
		}
		osa_errorlog(__METHOD__ . ' - Invalid row data or indexes while finding real type.', array($realtypes, $row));
		return FALSE;
	}
/*
	private function get_cache($method, $coreid, $type, $filehash) {
		if ( !$this->pre_fetch ) {
			return FALSE;
		}
		$cachekey = "{$coreid}_{$type}";
		if ( is_array($cached_array = $this->cms_cache->db_get($method, $cachekey)) ) {
			return $cached_array;
		}
		if ( $this->cms_cache->db_get($method, "{$coreid}_{$filehash}" . self::PREFETCH_EXECUTED) == TRUE ) {
			//No records at all
			return self::RET_EMPTY_DATA;
		}
		return FALSE;
	}

	//Always expect the result is already sorted by type,sortkey
	private function save_prefetch($result, $method, $coreid, $filehash) {
		if ( !$this->pre_fetch ) {
			return FALSE;
		}
		//This indicates the prefetch was already executed
		$this->cms_cache->db_add($method, "{$coreid}_{$filehash}" . self::PREFETCH_EXECUTED, TRUE);
		if ( !$result ) {
			return;
		}
		$last_type = '';
		$tmp = array();
		foreach ($result as $key => $record) {
			if ($last_type === '') {
				$last_type = $record->type;
			}
			if ( $last_type != $record->type ) {
				$cachekey = "{$coreid}_{$last_type}";
				$this->cms_cache->db_add($method, $cachekey, $tmp);
				$tmp = array();
				$last_type = $record->type;
			}
			$tmp[] = $result[$key];
		}
		$cachekey = "{$coreid}_{$record->type}";
		$this->cms_cache->db_add($method, $cachekey, $tmp);
	}
*/
}