<?php
namespace KIF\Dao;

use KIF\Verify;
use KIF\Exception\ParamsException;
use KIF\Data\ResultWrapper;

/**
 * 数据库操作的敏捷开发模式抽象类。  敏捷开发(Agile development)
 * 目的:统一数据库表的设计模式,为 增、删、改 提供便捷
 * PS: 通过 cas_token 字段,提供冲突检测机制(乐观锁),保证高并发下的数据安全
 *     这是模仿自 php 的 Memcached 扩展,非常好的功能,故借鉴过来
 * @author gaoxiaogang@gmail.com
 *
 */
abstract class DBAgileDev extends AbstractDao {

    /**
     * 除敏捷模式必须要求的字段外,多增加的字段
     * 该实例变量由继承类填充
     * @var array
     */
    protected $other_field = array();


    /**
     * 数据库里的真实字段
     * 这个实例变量的值是自动计算出来的
     * @var array
     */
    private $real_field;

    /**
     * 
     * Enter description here ...
     * @param unknown_type $master_flag
     * @return KIF\Dao\DBAgileDev
     */
    public function __construct($master_flag = 'default') {
    	parent::__construct($master_flag);

    	# 固定字段
    	$fixed_field = array(
    	    $this->primaryKey,
    	    'extend',
    	    'create_time',
    	    'update_time',
    	    'cas_token',// 通过这个字段,提供冲突检测机制(乐观锁),保证高并发下的数据安全
    	);

    	$this->real_field = array_merge($fixed_field, $this->other_field);
    }

    /**
     *
     * 添加一条记录
     * @param array $info
     * @param string $actionType 动态类型,这个参数控制以下行为中的一种: create、replace、insertIgnore
     *
     * ## 当$actionType的值为parent::PARAM_CREATE_ACTION_ONDUPLICATE时,该值才有意义。##
     * !!! 无法通过指定 $onDuplicate 来修改存储在 extend 字段里的值,因为没法把已存在的 extend 的值取出来,做一个merge。 !!!
     * @param array $onDuplicate 如果重复时,需要更新的信息。如果不指定,则使用$tableInfo的值,即认为要全部更新
     * ##
     *
     * @throws ParamsException actionType 参数非法
     * @return int | boolean
     */
    public function add(array $info, $actionType = parent::PARAM_CREATE_ACTION_INSERT, array $onDuplicate = array()) {

        if (!isset($info['create_time']) || !Verify::unsignedInt($info['create_time'])) {
            $info['create_time'] = time();
        }

        if (!isset($info['update_time']) || !Verify::unsignedInt($info['update_time'])) {
            $info['update_time'] = time();
        }

        $extend = array();
        foreach ($info as $tmpK => $tmpV) {
            if (!in_array($tmpK, $this->real_field)) {
                $extend[$tmpK]  = $tmpV;
                unset($info[$tmpK]);
            }
        }

        # 从0开始。本来打算从1开始的,但考虑到 继续类可能会绕过 add 方法而实现自身的增加记录方法,将 cas_token默认值设为,有助于简化
        $info['cas_token'] = 0;


        $tableInfo = $info;
        $tableInfo['extend'] = serialize($extend);

        switch ($actionType) {
        	case parent::PARAM_CREATE_ACTION_INSERT:
				$id = $this->create($tableInfo);
        		break;
        	case parent::PARAM_CREATE_ACTION_INSERT_IGNORE:
				$id = $this->insertIgnore($tableInfo);
        		break;
        	case parent::PARAM_CREATE_ACTION_REPLACE:
				$id = $this->replace($tableInfo);
        		break;
        	case parent::PARAM_CREATE_ACTION_ONDUPLICATE:
        		if (empty($onDuplicate)) {
        			$onDuplicate = $tableInfo;
        			unset($onDuplicate['create_time']);
        			unset($onDuplicate['cas_token']);
        		} else {
        			# 确保不允许修改 extend 字段里存储的值。因为没法把已存在的 extend 的值取出来,做一个merge。
			        foreach ($onDuplicate as $tmpK => $tmpV) {
			            if (!in_array($tmpK, $this->real_field)) {
			                unset($onDuplicate[$tmpK]);
			            }
			        }
        		}

        		$onDuplicate['update_time'] = $info['update_time'];
        		$onDuplicate['cas_token'] = SqlHelper::wrapperNoQuote("`cas_token` + 1");

        		$id = $this->insertDuplicate($tableInfo, $onDuplicate);
        		break;
        	default:
        		throw new ParamsException("invalid actionType");
        }

        return $id;
    }

    /**
     * 修改
     * !!!!!!
     * !!! $cas_token 提供了一种“检查并修改”机制,非常重要的优势, 在这样一个冲突检测机制(乐观锁)下, 我们才能保证高并发下的数据安全。
     * !!!!!!
     * 注:$info 或 $condition 里,一定要有一个指定 key 为 $this->primaryKey 对应的值
     * @param array $info
     * @param array $condition 条件
     * @param int $cas_token 如果$cas_token不为null,会执行一个“检查并修改”的操作。因此,仅当 $cas_token 与数据库里的值一致时,才会修改记录。
     * @return ResultWrapper 成功:修改并影响到了记录;
     * 	       失败:数据里含失败描述。
     * 		   另:cas_token检查不通过时,返回'CAS_TOKEN_NOT_MATCH'。这个返回值并不是严格意义上的正确,当$condition指定了条件,并且未匹配时,
     * 		   也会返回这个值。
     */
    public function modify(array $info, array $condition = null, $cas_token = null) {
        if (!isset($info[$this->primaryKey])) {
        	if (!isset($condition[$this->primaryKey])) {
				return ResultWrapper::fail("请指定要修改的记录");
        	}
            $info[$this->primaryKey] = $condition[$this->primaryKey];
        }
        if (!Verify::unsignedInt($info[$this->primaryKey])) {
            return ResultWrapper::fail("请指定有效的记录");
        }
        if (!isset($condition)) {
        	$condition = array();
        }

        $useMasterFlag = $this->db->beginUseMaster();
        $oldSpecialSale = parent::get($info[$this->primaryKey]);
        $this->db->restore($useMasterFlag);
        if (!$oldSpecialSale) {
            return ResultWrapper::fail("不存在的记录:{$info[$this->primaryKey]}");;
        }

        # 找出待更新的扩展字段
        $extend = unserialize($oldSpecialSale['extend']);
        foreach ($info as $tmpK => $tmpV) {
            if (!in_array($tmpK, $this->real_field)) {
                $extend[$tmpK]  = $tmpV;
                unset($info[$tmpK]);
            }
        }

        $info['cas_token'] = SqlHelper::wrapperNoQuote('`cas_token` + 1');

        $tableInfo = $info;
        $tableInfo['extend'] = serialize($extend);
        $tableInfo['update_time'] = time();

        $condition[$this->primaryKey] = $info[$this->primaryKey];
        if (Verify::naturalNumber($cas_token)) {
        	$condition['cas_token'] = $cas_token;
        }

        $result = $this->update($tableInfo, $condition);
        if (!$result) {
            return ResultWrapper::fail("数据库update操作失败");
        }

        $affectedRows = $this->db->affectedRows();
        if (!Verify::unsignedInt($affectedRows)) {
        	if (Verify::naturalNumber($cas_token)) {
        		return ResultWrapper::fail("CAS_TOKEN_NOT_MATCH");
        	} else {
				return ResultWrapper::fail("NOT_MATCH");
        	}
        }

        return ResultWrapper::success();
    }

    /**
     * 获取一条记录
     * @param int $id 记录id
     * @param int $cas_token 引用传值,默认为 null
     * @return false | array
     */
    public function get($id, & $cas_token = null) {
        if (!Verify::unsignedInt($id)) {
            return false;
        }

        $result = $this->gets(array($id));
        if (!$result) {
            return false;
        }

        $tmpResult = array_pop($result);
        $cas_token = $tmpResult['cas_token'];
        return $tmpResult;
    }

    /**
     * 获取一批记录
     * @param array $ids 记录id集
     * @param array $cas_tokens 引用传值,默认为 null
     * @return false | array
     */
    public function gets(array $ids, array & $cas_tokens = null) {
        $result = parent::gets($ids);
        if (!$result) {
            return $result;
        }

        foreach ($result as & $tmpV) {
            $tmpExtend = array();
            $tmpExtend = unserialize($tmpV['extend']);
            unset($tmpV['extend']);

            # 将存在扩展字段里的信息提出来
            if (is_array($tmpExtend)) foreach ($tmpExtend as $tmpKK => $tmpVV) {
            	if (!in_array($tmpKK, $this->real_field)) {
                    $tmpV[$tmpKK] = $tmpVV;
                }
            }

            $cas_tokens[$tmpV[$this->primaryKey]] = $tmpV['cas_token'];
        }

        return $result;
    }

    /**
     *
     * 获取所有id集
     * @param string $order
     * @param mixed $limit
     * @return array
     */
    public function getsIdsAll($order = null, $limit = null) {
		$ids = parent::getsAllIds($order, $limit);
		return $ids;
    }

    public function fetchAll($condition = null, $limit = 10, $order = null, $selectCols = '*') {
        $result = parent::findBy($condition, null, $limit, $selectCols, $order);
        if (!$result) {
            return false;
        }

        foreach ($result as & $tmpV) {
            $tmpExtend = array();
            $tmpExtend = unserialize($tmpV['extend']);
            unset($tmpV['extend']);

            # 将存在扩展字段里的信息提出来
            if (is_array($tmpExtend)) foreach ($tmpExtend as $tmpKK => $tmpVV) {
                if (!in_array($tmpKK, $this->real_field)) {
                    $tmpV[$tmpKK] = $tmpVV;
                }
            }
        }

        return $result;
    }

	/**
     *
     * 获取指定时间$update_time后的最新id集
     * @param mixed $limit
     * @return array
     */
    public function getsLatestIdsByUpdateTime($update_time, $limit = null) {
		$order = 'update_time ASC';
		$condition = array(
			'update_time'	=> SqlHelper::addCompareOperator('>=', $update_time),
		);

		return $this->findIdsBy($condition, $limit, $order);
    }
}