<?php
/**
 * 无限级分类控制模型 - 业务逻辑模型
 * @example
 * 说明：利用此分类模型，需要指定数据表，相关模型字段
 * 必须属性：
 * table：表名
 * id：主键（字段名）
 * name：分类名称（字段名）
 * pid：上级节点ID（字段名）
 * sort：排序（字段名）
 * 示例：
 * $ctree = model('CateTree', $table);		表名必须指定
 * $ctree = $ctree->setField($param);		不指定字段名，将使用默认字段
 * $data = $ctree->getTree();				获取树形结构
 * @author jason <yangjs17@yeah.net>
 * @version TS3.0
 */
class CateTreeModel {

	private static $tree = array(); 				// 树
	private static $hash = array(); 				// 树的HASH表
	public $cacheKey = ''; 							// 缓存Key
	private $cacheKeyPrefix  = 'CateTree_'; 		// 缓存前缀
	private $_table	= '';							// 默认表
	private $defaultField = array(	
								'id' => 'id', 		// 主键字段名
								'pid' => 'pid', 	// 上级节点ID字段名
								'name' => 'name',	// 分类名称字段名
								'sort' => 'sort',   // 排序字段名
							);
	private $tempHashData = array();				// 临时Hash数据

	/**
	 * 初始化无限极树形模型对象
	 * @param string $table 资源表名
	 * @return void
	 */
	public function __construct($table = '') {
		if(empty($table)) {
			$this->error = L('PUBLIC_CATEGORYFORM_UNSIGN');			// 分类模型表未指定
			return false;
		}
		$this->_table = C('DB_PREFIX').$table;
		$this->cacheKey = $this->cacheKeyPrefix.$table; 
	}
	
	/**
	 * 设置树形数据缓存Key
	 * @param string $key 用户自定义Key值
	 * @return object 无限树形模型对象
	 */
	public function setCacheKey($key) {
		$this->cacheKey = $this->cacheKeyPrefix.$key;
		return $this;
	}

	/**
	 * 设置字段值
	 * @param array $field 用户自定义字段值
	 * @return object 无限树形模型对象
	 */
	public function setField($field = array()) {
		if(empty($field)) {
			return $this;
		}
		foreach($field as $k => $v) {
			$this->defaultField[$k] = $v;
		}

		return $this;
	}
	
	/**
	 * 获取一个树形结构数据
	 * @param integer $rootId 根目录ID
	 * @return array 树形结构
	 */
	public function getTree($rootId = '0') {
		// 减少缓存读取次数
		if(isset(self::$tree[$this->cacheKey])) {	
			return self::$tree[$this->cacheKey][$rootId];
		}
		if($tree = model('Cache')->get($this->cacheKey)) {
			// 缓存命中
			self::$tree[$this->cacheKey] = $tree; 	 
			return $tree[$rootId];
		}
		// 重建树及hash缓存
		$this->createTree();
		return self::$tree[$this->cacheKey][$rootId];
	}

	/**
	 * 获取所有分类的以主键为Key的Hash数组
	 * @return arrat 分类的Hash数组
	 */
	public function getAllHash() {
		$hash = model('Cache')->get($this->cacheKey.'_hash');
		if(!empty($hash)) {
			// 缓存命中
			self::$hash[$this->cacheKey] = $hash; 	 
			return $hash;
		}
		// 重建树及Hash缓存
		$this->createTree();
		return self::$hash[$this->cacheKey];
	}
	
	/**
	 * 获取指定分类ID下的所有子集ID
	 * @param integer $id 分类ID
	 * @return array 指定分类ID下的所有子集ID
	 */
	public function getChildHash($id) {
		$data = $this->getTree($id);
		$this->_getChildHash($data);
		$r = $this->tempHashData;
		$this->tempHashData = array();
		return $r;
	}

	/**
	 * 递归方法获取树形结构下的所有子集ID
	 * @param array $data 子集数据
	 * @return void
	 */
	private function _getChildHash($data) {
		$this->tempHashData[] = $data[$this->defaultField['id']];
		if(!empty($data['_child'])) {
			foreach($data['_child'] as $v) {
				$this->_getChildHash($v);
			}
		}
	}

	/**
	 * 移动一个树形节点，即更改一个节点的父节点，移动规则：父节点不能移动到自己的子树下面
	 * @param integer $id 节点ID
	 * @param integer $newPid 新的父节点ID
	 * @param integer $newSort 新的排序值
	 * @param array $data 表示其他需要保存的字段，由实现此model的类完成
	 * @return boolean 是否移动成功
	 */
	public function moveTree($id, $newPid, $newSort='255', $data = array()) {
		// 参数验证
		if(!$id || (!$newPid && $newPid!=0)){
			$this->error  = L('PUBLIC_DATA_MOVE_ERROR');			// 分类树移动的参数出错
			return false;
		}
		// 更新数据库信息
		$map[$this->defaultField['id']] = $id;
		$data[$this->defaultField['pid']] = $newPid;
		$data[$this->defaultField['sort']] = $newSort;
		
		if(D('')->table($this->_table)->where($map)->save($data)){
			// TODO:可优化成更新缓存而不是清除缓存
			$this->cleanCache();
		}

		return true;
	}
	
	/**
	 * 批量移动树的节点，移动规则：父节点不能移动到自己的子树下面
	 * ids，newPids的Key值必须对应
	 * @param array $ids 节点ID数组
	 * @param array $newPids 新的父节点ID数组
	 * @param array $newSorts 新的排序值数组
	 * @return boolean 是否移动成功
	 */
	public function moveTreeByArr($ids,$newPids,$newSorts = array()) {
		foreach($ids as $k => $v) {
			$sort = isset($newSorts[$k]) && !empty($newSorts[$k]) ? $newSorts[$k] : 255;
			$this->moveTree($v, $newPids[$k], $sort, false);
		}
		$this->cleanCache();

		return true;
	}
	
	/**
	 * 清除树形结构的缓存，不允许删除单个子树，由于一棵树是缓存在一个缓存里面
	 * @return boolean 是否清除成功
	 */
	public function cleanCache() {
		unset(self::$tree[$this->cacheKey]);
		unset(self::$tree[$this->cacheKey.'_hash']);
		self::$tree = array();
		model('Cache')->rm($this->cacheKey);
		model('Cache')->rm($this->cacheKey.'_hash');
		return true;
	}
		
	/*** 私有方法 ***/
	/**
	 * 双数组生成整棵树，缓存操作
	 * @return void
	 */ 
	private function createTree() {
		// 从数据库取树的数据 
		$data = $this->_getTreeData();
		if(empty($data)) {
			return array();	
		}
		$tree = array();		// 临时树
		$child = array();		// 所有节点的子节点
		$hash = array();		// Hash缓存数组
		foreach($data as $dv) {
			$hash[$dv[$this->defaultField['id']]] = $dv;
			$tree[$dv[$this->defaultField['id']]] = $dv;
			!isset($child[$dv[$this->defaultField['id']]]) && $child[$dv[$this->defaultField['id']]] = array();
			$tree[$dv[$this->defaultField['id']]]['_child'] = &$child[$dv[$this->defaultField['id']]];
			$child[$dv[$this->defaultField['pid']]][] = &$tree[$dv[$this->defaultField['id']]];		
		}
		//整个树，其根节点ID为0
		$tree[0]['_child'] = $child[0];
		
		self::$tree[$this->cacheKey] = $tree;
		self::$hash[$this->cacheKey] = $hash;
		// 生成缓存
		model('Cache')->set($this->cacheKey,$tree);
		model('Cache')->set($this->cacheKey.'_hash',$hash);	
	}

	/**
	 * 获取树形数据，从数据库中取出
	 * @return array 所有分类的数据
	 */
	private function _getTreeData() {
		// 下面是从数据库读取的 分类很少会有大量数据
		$data = D('')->table($this->_table)->order($this->defaultField['sort'].' ASC')->findAll();
		return $data;
	}

}