<?php
/**
 * @class  	树表操作左右值树,返回的数据对接 jsGrid模式
 * 树 主要用 dbWrite 写库
 */
class LibLRTree
{
	protected $dbRead = null;
	protected $dbWrite = null;
	protected $table = null;
	protected $fieldId = null;
	protected $fieldParentId = null;
	protected $fieldText = null;
	protected $fieldKeyword = null;
	protected $fieldLevel = null;
	protected $fieldLft = null;
	protected $fieldRgt = null;
	protected $fieldSort = null;
	protected $order = null;
	protected $rootName = null;
	/**
	  * @param [string] $tableName 表的名称
	  * @param [string] $idName id字段的名称
	  * @param [string] $parentName 父id字段的名称
	  * @param [string] $textName 文本字段的名称
	  */
	function __construct($rootName,$tableName,$fields){
		//需要在之前初始化数据库类
		$this->dbRead = $GLOBALS['mainDbReadLink'];
		$this->dbWrite = $GLOBALS['mainDbWriteLink'];
		$this->table=$tableName;
		$this->fieldId=$fields['fieldId'];
		$this->fieldParentId=$fields['fieldParentId'];
		$this->fieldText=$fields['fieldText'];
		$this->fieldKeyword=$fields['fieldKeyword'];
		$this->fieldLevel=$fields['fieldLevel'];
		$this->fieldLft=$fields['fieldLft'];
		$this->fieldRgt=$fields['fieldRgt'];
		$this->fieldSort=$fields['fieldSort'];
		$this->order=$fields['order'];
	}
	/**
	  * @method 创建节点
	  * @param  [string]  $parent_id  父ID
	  * @return [array]
	  */
	public function newNode($parentId,$beforeFunction){
		if($parentRow = $this->getRowById($parentId)){
			//插入最顶级节点：它的左右值与该树中最大的右值有关：左值=最大右值+1，右值=最大右值+2
			//插入子节点：它的左右值与它的父级有关：左值=父级的右值，右值=当前的左值+1，这时要更新的数据有：父级的右值，所有左值大于父级左级，右值大于父级右值的节点左右值都应该+2；
			//所有左值小于父级左级并且右值大于父级的右值节点右值+2；
			$lft   = $parentRow[$this->fieldRgt];
			$rgt   = $lft+1;
			$level = $parentRow[$this->fieldLevel]+1;
		}else{
			//插入最顶级节点：它的左右值与该树中最大的右值有关：左值=最大右值+1，右值=最大右值+2
			$sql = sprintf("SELECT max(%s) rgt FROM %s", $this->fieldRgt, $this->table);
			$row = $this->dbWrite->Row($sql);
			if($row && $row['rgt']>0){
				$lft   = $row['rgt'] + 1;
				$rgt   = $lft + 1;
				$level = 0;
			}else{
				$lft   = 1;
				$rgt   = 2;
				$level = 0;
			}
		}
		$data = [
			$this->fieldLft => $lft,
			$this->fieldRgt => $rgt,
			$this->fieldLevel => $level
		];
		$id = $beforeFunction($data); //调用者先插入/更新记录,并返回记录的ID值
		if($id && $parentRow){ //做节点更新操作(如果是插入顶级节点不需要更新)
			//更新父级右值
			$this->dbWrite->Update(
				$this->table,
				sprintf("%s=%d",$this->fieldRgt,$parentRow[$this->fieldRgt]+2),
				sprintf("%s='%s'",$this->fieldId,$parentId)
			);
			//所有左值小于父级左级并且右值大于父级的右值节点右值+2；
			$this->dbWrite->Update(
				$this->table,
				sprintf("%s=%s+2",$this->fieldRgt,$this->fieldRgt),
				sprintf("%s<%d AND %s>%d",
					$this->fieldLft,$parentRow[$this->fieldLft],
					$this->fieldRgt,$parentRow[$this->fieldRgt]
				)
			);
			//所有左值大于父级左级并且右值大于父级的节点左右值都应该+2(不包括当前节点)；
			$this->dbWrite->Update(
				$this->table,
				sprintf("%s=%s+2,%s=%s+2",$this->fieldLft,$this->fieldLft,$this->fieldRgt,$this->fieldRgt),
				sprintf("%s<>%d AND %s>%d AND %s>%d",
					$this->fieldId,$id,
					$this->fieldLft,$parentRow[$this->fieldLft],
					$this->fieldRgt,$parentRow[$this->fieldRgt]
				)
			);
		}
	}
	/**
	  * @method 删除节点
	  * @param  [string]  $id  ID
	  * @param  [function]  $beginFunction  删除前callback, 返回true则执行
	  * @return [bool]
	  */
	public function deleteNode($id,$beforeFunction){
		//删除所有左值在当前节点的左值与右值之间的所有节点
		//宽度值= rgt - lft + 1;
		//删除节点的同时该节点右边所有的左右值和该父节点的右值都会减去该节点的宽度值
		//1当前节点宽度值= 右值 - 左值 + 1
		//2删除所有左值在当前节点的左值与右值之间的所有节点
		//3表中所有右值大于当前节点右值的节点，将右值-当前节点宽度值
		//4表中所有左值大于当前节点右值的节点，将左值-当前节点宽度值
		if($id && $beforeFunction()){ //调用者先执行其他内容
			if($row = $this->getRowById($id)){
				$width = $row[$this->fieldRgt] - $row[$this->fieldLft] + 1; //当前节点宽度(节点数的两倍)
				//删除所有左值在当前节点的左值与右值之间的所有节点
				$this->dbWrite->Delete($this->table,sprintf("%s BETWEEN %d AND %d",
					$this->fieldLft,
					$row[$this->fieldLft],
					$row[$this->fieldRgt]
				));
				$this->dbWrite->Update(
					$this->table,
					sprintf("%s=%s-%d",$this->fieldLft,$this->fieldLft,$width),
					sprintf("%s>%d",$this->fieldLft,$row[$this->fieldRgt])
				);
				$this->dbWrite->Update(
					$this->table,
					sprintf("%s=%s-%d",$this->fieldRgt,$this->fieldRgt,$width),
					sprintf("%s>%d",$this->fieldRgt,$row[$this->fieldRgt])
				);
				return true;
			}
		}
		return false;
	}
	/**
	  * @method 重构左右值
	  * @param  [string]  $id  ID
	  * @param  [string]  $parent  新父ID
	  * @param  [string]  $old_parent  老父ID
	  * @param  [string]  $position  新的位置
	  * @param  [string]  $old_position  老的位置
	  * @return [bool]
	  */
	public function restLR($parentId=0){
		if($parentId==0){
			$sql = sprintf("UPDATE %s SET %s=0,%s=0", $this->table,$this->fieldLft, $this->fieldRgt);
			$this->dbWrite->Query($sql);
		}
		$sql = sprintf("SELECT * FROM %s WHERE %s=%d ORDER BY %s", 
			$this->table, 
			$this->fieldParentId, 
			$parentId, 
			$this->order
		);
		$table = $this->dbWrite->Table($sql);
		foreach($table as $row){
			$this->newNode($parentId, function($data)use($row){ 
				$sql = sprintf("UPDATE %s SET %s=%d,%s=%d WHERE %s=%d", 
					$this->table,
					$this->fieldLft,
					$data[$this->fieldLft],
					$this->fieldRgt,
					$data[$this->fieldRgt],
					$this->fieldId,
					$row[$this->fieldId]
				);
				$this->dbWrite->Query($sql);
				return $row[$this->fieldId];
			});
			if($this->hasChildrenByParent($row[$this->fieldId])){
				$this->restLR($row[$this->fieldId]);
			}
		}
	}

	/**
	  * @method 排序
	  * @param  [string]  $id  ID
	  * @param  [string]  $parent  新父ID
	  * @param  [string]  $old_parent  老父ID
	  * @param  [string]  $position  新的位置
	  * @param  [string]  $old_position  老的位置
	  * @return [bool]
	  */
	public function sortNode($parentId=0){
		if($parentId==0){
			$sql = sprintf("UPDATE %s SET %s=0,%s=0", $this->table,$this->fieldLft, $this->fieldRgt);
			$this->dbWrite->Query($sql);
		}
		$sql = sprintf("SELECT * FROM %s WHERE %s=%d ORDER BY %s", 
			$this->table, 
			$this->fieldParentId, 
			$parentId, 
			$this->order
		);
		$table = $this->dbWrite->Table($sql);
		foreach($table as $row){
			$this->newNode($parentId, function($data)use($row){ 
				$sql = sprintf("UPDATE %s SET %s=%d,%s=%d WHERE %s=%d", 
					$this->table,
					$this->fieldLft,
					$data[$this->fieldLft],
					$this->fieldRgt,
					$data[$this->fieldRgt],
					$this->fieldId,
					$row[$this->fieldId]
				);
				$this->dbWrite->Query($sql);
				return $row[$this->fieldId];
			});
			if($this->hasChildrenByParent($row[$this->fieldId])){
				$this->restLR($row[$this->fieldId]);
			}
		}
	}


	public function getRowById($id){
		$sql = sprintf("SELECT * FROM %s WHERE %s=%d LIMIT 0,1", $this->table, $this->fieldId,$id);
		return $this->dbWrite->Row($sql);
	}
	public function getLevelById($id){
		$sql = sprintf("SELECT %s FROM %s WHERE %s=%d LIMIT 0,1", $this->fieldLevel, $this->table, $this->fieldId,$id);
		return $this->dbWrite->One($sql);
	}
	/**
	  * @method 取所有子节点的查询条件语句
	  * @param  [string|array]  $value 可以是ID 或者 row
	  * @return [bool]
	  */
	public function getChildrensWhereById($value,$level=-1){
		if(!is_array($value))
			$row = $this->getRowById($value);
		else
			$row = $value;
		$where = sprintf("%s>%s AND %s<%s", $this->fieldLft, $row[$this->fieldLft], $this->fieldRgt, $row[$this->fieldRgt]);
		if($level>-1) {
			$where .= sprintf(" AND %s<=%d", $this->fieldLevel, $level);
		}
		return $where;
	}
	/**
	  * @method 取所有子节点的记录
	  * @param  [string|array]  $id 可以是ID 或者 row
	  * @return [array]
	  */
	public function getChildrensById($id,$level=-1){
		$where = $this->getChildrensWhereById($id,$level);
		return $this->getList($where." ORDER BY ". $this->order);
	}
	/**
	  * @method 取所有父节点的查询条件语句
	  * @param  [string|array]  $value 可以是ID 或者 row
	  * @return [bool]
	  */
	public function getParentsWhereById($value,$level=-1){
		if(!is_array($value))
			$row = $this->getRowById($value);
		else
			$row = $value;
		$where = sprintf("%s<%s AND %s>%s", $this->fieldLft, $row[$this->fieldLft], $this->fieldRgt, $row[$this->fieldRgt]);
		if($level>-1) {
			$where .= sprintf(" AND %s>=%d", $this->fieldLevel, $level);
		}
		return $where;
	}
	/**
	  * @method 取所有父节点的记录
	  * @param  [string|array]  $id 可以是ID 或者 row
	  * @return [array]
	  */
	public function getParentsById($id,$level=-1){
		$where = $this->getParentsWhereById($id,$level);
		return $this->getList($where);
	}

	/**
	  * @method 根据条件取记录
	  * @param  [string]  $where
	  * @return [array]
	  */
	public function getList($where){
		$sql = sprintf("SELECT * FROM %s WHERE %s", $this->table, $where);
		return $this->dbWrite->Table($sql);
	}

	/**
	  * @method 是否有子节点
	  * @param  [string|array] $id|$row
	  * @return [bool]
	  */
	public function hasChildren($id){
		if(is_array($id))
			$row = $id;
		else
			$row = $this->getRowById($id);
		return ($row[$this->fieldRgt] - $row[$this->fieldLft] > 1);
	}
	public function hasChildrenByParent($id){
		$sql = sprintf("SELECT 1 FROM %s WHERE %s=%s limit 1", $this->table, $this->fieldParentId, $id);
		return $this->dbWrite->One($sql) ? true : false;
	}

	/**
	  * @method 返回所有等于父ID的行数据格式
	  * @param  [array]  $row  行记录
	  * @return [bool]
	  */
	public function formatData($row){
	}

    public function searchNodeForJsTree($text,$parentId=0){
    	$level = -1;
		$text = str_replace("'","\'",$text);
		if($this->fieldKeyword){
			$sql = sprintf("SELECT * FROM %s where %s like '%%%s%%'", $this->table, $this->fieldKeyword,$text);
		}else{
			$sql = sprintf("SELECT * FROM %s where %s like '%%%s%%'", $this->table, $this->fieldText,$text);
		}
		if($parentId>0){
			$row = $this->getRowById($parentId);
			$level = $row[$this->fieldLevel];
			$sql .= sprintf(" AND %s>%s AND %s<%s", $this->fieldLft, $row[$this->fieldLft], $this->fieldRgt, $row[$this->fieldRgt]); 
		}
		$sql .= " limit 0,200";
		$data = [];
		if($table = $this->dbRead->Table($sql)){
			foreach($table as $row){
				$prows = $this->getParentsById($row,$level);
				foreach ($prows as $prow) {
					$data[] = $prow;
				}
				$data[] = $row;
			}
		}
		$ids[] = '#';
		foreach($data as $v){
			if(!in_array($v[$this->fieldId],$ids)){
				$ids[] = $v[$this->fieldId];
			}
		}
		return $ids;
	}

}