<?php
defined('IN_WEB') or die('Include Error!');
/**
 * 成就系统
 */
class ModelAchievement
{
    public $contypes = array(
        'com', // '普通场对局'
        'allin', // 'All In次数'
        'win', // '胜利次数'
        'swinchips', // 单局赢多少筹码
        'winchips', // 累计赢多少筹码
        'maxchips', // 最大拥有筹码数
        'straight', // 顺子胜利次数
        'flush', // 同花胜利次数
        'straight_flush', // 同花顺胜利次数
        'matchwin', // 比赛场获得名次
        'recall', // 成功召回好友
        'playslots', // 玩老虎机次数
        'sendfn', // 赠送好友游戏币次数
        'senddealer', // 打赏荷官
        'invites', // 成功邀请好友数
        'paid', // 累计充值
        'hlu', //葫芦
        'stiao', //四条
    );

    public $notIncContypes = array('win', 'maxchips', 'paid');
    public $achievementConfig = array();
    public $errCode = 0;
    public function __construct() {
        $this->dbToConfig();
    }

    /**
     * 获取成就列表
     * @param int $uid
     * @return array
     */
    public function getList($uid) {
        $data = array('code' => -1, 'list' => []);
        if (!$uid || !$this->achievementConfig) {
            return $data;
        }

        $ret = $this->achievementConfig;
        $rewardedStr = $this->getRewarded($uid);
        $rewarded = str_split($rewardedStr);
        $conKeys = array();
        foreach ($ret as $id => &$v) {
            if (!isset($v['subtask']) || !is_array($v['subtask'])) {
                unset($v);
                continue;
            }
            $status = isset($rewarded[$id - 1]) ? (int) $rewarded[$id - 1] : 0;
            foreach ($v['subtask'] as $subid => &$task) {
                $task['num'] = (int) $task[0];
                $task['reward'] = (int) $task[1];
                // 匹配用户已完成的子任务
                if ($status & (1 << $subid - 1)) {
                    $task['rewarded'] = 1;
                } else {
                    $task['rewarded'] = 0;
                }
                unset($task[0], $task[1]);
            }
            $conKeys[] = $v['contype'];
            unset($v['zh_name']);
        }
        $condition = $this->getCondition($uid, $conKeys);
        // 数据重组
        foreach ($ret as $id => &$v) {
            $v['cur'] = (int) $condition[$v['contype']];
        }
        $data['code'] = 1;
        $data['list'] = $ret;
        return $data;
    }


    /**
     * 获取用户领取情况
     * @param int $uid
     * @return array
     */
    public function getRewarded($uid) {
        if (!$uid) return array();
        $cacheKey = okeys::achi($uid);
        $cache = oo::commonOprRedis('userinfo')->get($cacheKey);
        if (!is_null($cache) && $cache !== false) {
            return $cache;
        }
        $tb = otable::achi($uid);
        $query = "select rewarded from {$tb} where uid='{$uid}' limit 1";
        $row = oo::commonOprDb('common')->getOne($query, MYSQLI_ASSOC);
        if ($row) {
            $rewarded = !empty($row['rewarded']) ? $row['rewarded'] : $this->getDefault();
        } else {
            $rewarded = $this->getDefault();
            $query = "insert into {$tb} (uid,rewarded) values ('{$uid}','{$rewarded}')";
            $flg = oo::commonOprDb('common')->query($query);
            if (!$flg)
                return array();
        }
        oo::commonOprRedis('userinfo')->setex($cacheKey, $rewarded, oo::redisRandomExpire(7 * 86400));
        return $rewarded;
    }

    /**
     * 获取用户当前缓存状态
     * @param int $uid
     * @param array $types
     * @return array
     */
    public function getCondition($uid, $types = array()) {
        if (!$uid || !$types || !is_array($types)) {
            return array();
        }
        $cacheKey = okeys::achi($uid.'-');
        if (!oo::commonOprRedis('usercache')->exists($cacheKey)) { // 没有缓存
            $ret = $this->getConditionFromDB($uid, true);
        } else {
            $ret = oo::commonOprRedis('usercache')->hMget($cacheKey, $types);
        }
        $property = array();
//		if (isset($ret['paid'])) {
//			$ret['paid'] = $ret['paid'];  // 泰铢
//		}
        if (in_array('maxchips', $types)) {
            $property or $property = oo::commonOprModel('member')->getUserAssetsInfo($uid);
            $ret['maxchips'] = $property['money'];
        }
        return $ret;
    }

    /**
     * 默认串
     * @return string
     */
    public function getDefault() {
        $maxId = max(array_keys($this->achievementConfig));
        return str_pad('', $maxId, '0');
    }

    /**
     * 获取条件值的数据库记录，或更新至缓存
     * @param int $uid
     * @param bool $toCache
     * @return array
     */
    public function getConditionFromDB($uid, $toCache = false) {
        if (!$uid)
            return array();
        $tb = otable::achi($uid.'-');
        $query = "select * from {$tb} where uid='{$uid}' limit 1";
        $data = oo::commonOprDb('common')->getOne($query, MYSQLI_ASSOC);
        if ($toCache) {
            oo::commonOprRedis('usercache')->delete(okeys::achi($uid));
            unset($data['rewarded']);
            oo::commonOprRedis('usercache')->hMget(okeys::achi($uid), $data);
        }
        return $data;
    }

    /**
     * 设置已领奖励缓存
     * @param int $uid
     * @param int $id
     * @param int $subid
     * @return bool
     */
    public function setRewarded($uid, $id, $subid) {
        if (!$uid || !$id || !$subid) {
            $this->errCode = -201;
            return false;
        }
        $rewardedStr = $this->getRewarded($uid);
        // 如果有新成就则可以补全
        $maxId = max(array_keys($this->achievementConfig));
        $rewardedStr = str_pad($rewardedStr, $maxId, '0');
        $rewarded = str_split($rewardedStr);
        $idx = $id - 1; // ** 重要，存储的字符串是要ID-1
        if (!isset($rewarded[$idx])) {
            $this->errCode = -201;
            return false;
        }
        if ($rewarded[$idx] & (1 << $subid - 1)) { // 已经设置过
            $this->errCode = -202;
            return false;
        }
        $rewarded[$idx] |= 1 << $subid - 1;
        return $this->updateRewarded($uid, implode('', $rewarded));
    }

    /**
     * 更新领取情况
     * @param int $uid
     * @param string $rewarded
     * @return bool
     */
    public function updateRewarded($uid, $rewarded) {
        if (!$uid || empty($rewarded)) {
            return false;
        }
        $rewarded = oo::functions()->escape($rewarded);
        oo::commonOprRedis('userinfo')->delete(okeys::achi($uid));
        $tb = otable::achi($uid);
        $query = "update {$tb} set rewarded='{$rewarded}' where uid='{$uid}' limit 1";
        return oo::commonOprDb('common')->query($query);
    }

    /**
     * 累加条件缓存
     * @param int $uid
     * @param string $type
     * @param int $num
     * @return bool
     */
    public function incCondition($uid, $type, $num = 1) {
        if (!$uid || !in_array($type, $this->contypes)) {
            return false;
        }
        $cacheKey = okeys::achi($uid.'-');
        $curCondition = $this->getCondition($uid, [$type]);
        if ($type == 'winchips' && $curCondition['winchips'] > 100000000) { // 已经超过最大值了
            return false;
        }
        $diffCacheKey = okeys::achiDiff($uid);
        if ($type == 'swinchips') {
            if ($num <= $curCondition['swinchips']) {
                return false;
            } else {
                oo::commonOprRedis('usercache')->hSet($diffCacheKey, $type, $num);
                return oo::commonOprRedis('usercache')->hSet($cacheKey, $type, $num);
            }
        }
        oo::commonOprRedis('usercache')->hIncrBy($diffCacheKey, $type, $num);
        $ret = oo::commonOprRedis('usercache')->hIncrBy($cacheKey, $type, $num);
        return $ret;
    }

    /**
     * 成就用户数据值入库落地(放在用户登录的时候，落地一次)
     * loginHandler.php
     * @param array $arr
     * @return bool
     */
    public function recordCondition($arr) {
        $uid = (int) $arr['uid'];
        if (!$uid)
            return false;
        $cacheKey = okeys::achiDiff($uid);
        $cache = oo::commonOprRedis('usercache')->hGetAll($cacheKey);
        if ($cache === false) { // 没有缓存
            return false;
        }
        oo::commonOprRedis('usercache')->delete($cacheKey); // 删除差值，重新累加
        $tb = otable::achi($uid);
        if (!is_array($cache) || !$cache)
            return false;
        $insert = "";
        $update = "";
        foreach ($cache as $k => $v) {
            $v = (int) $v;
            if (!in_array($k, $this->contypes) || ($v <= 0)) {
                continue;
            }
            $insert .= "`{$k}`='{$v}',";
            if ($k == 'swinchips') { // 更新不累加
                $update .= "`{$k}`='{$v}',";
            } else {
                $update .= "`{$k}`=`{$k}`+'{$v}',";
            }
        }
        if (empty($insert))
            return false;
        $insert = rtrim($insert, ',');
        $update = rtrim($update, ',');
        $query = "insert into {$tb} set uid='{$uid}',{$insert} on duplicate key update {$update}";
        return oo::commonOprDb('achi')->query($query);
    }

    /**
     *	数据库内容读入配置文件
     */
    public function dbToConfig()
    {
        $key = okeys::achievementConfig();
        $cacheData = oo::commonOprRedis('config')->get($key);
        if ($cacheData) {
            $tempArr = json_decode($cacheData, 1);
            foreach ($tempArr as $id => &$info) {
                $info['name'] = oo::getLang('achievement_'.$info['contype']);
                $info['desc'] = oo::getLang('achievement_desc_'.$info['contype']);
            }
            $this->achievementConfig = $tempArr;
            return;
        }
        $table = otable::achievementConfig();
        $sql = "SELECT * FROM {$table} ORDER BY aid ASC ";
        $list = oo::commonOprDb('common')->getAll($sql, 1);
        $i = 1;
        $taskList = array();
        foreach ($list as $ke => $value) {
            $value['id'] = $taskid = intval($value['id']);
            $value['sort'] = intval($value['sort']);
            $value['name'] = oo::getLang('achievement_'.$value['contype']);
            $value['desc'] = oo::getLang('achievement_desc_'.$value['contype']);
            $subtask = json_decode($value['subtask'], 1);
            $value['subtask'] = $subtask;
            $taskList[$taskid] = $value;
            $i++;
        }
        $taskListJson = json_encode($taskList);
        oo::commonOprRedis('config')->set($key, $taskListJson, 3600);
        $this->achievementConfig = $taskList;
        return $taskList;
    }

    /**
     *	配置文件写入数据库
     */
    public function configToDb()
    {
        $table = otable::achievementConfig();
        foreach ($this->achievementConfig as $key => $info) {
            $name = $info['name'];
            $zh_name = $info['zh_name'];
            $contype = $info['contype'];
            $image = $info['image'];
            $sort = $info['sort'];
            $desc = $info['desc'];
            $subtask = json_encode( $info['subtask'] );
            $sql = "INSERT INTO {$table} VALUES({$key}, '{$name}', '{$zh_name}', '{$contype}', '{$image}', {$sort}, '{$desc}', '{$subtask}')";
            oo::commonOprDb('common')->query($sql);
            echo $key."\r\n";
        }
    }


    // -----------------------------------------------------------------------------------------------------------------

    /**
     * 成就配置
     * @param string $aid
     * @return mixed
     * Created by: Owen
     * Created on: 2019/6/13 17:50
     * Description:
     */
    public function getAchieveConfig($aid = ''){
        $cacheKey = okeys::AchieveConfig();
        $arrJson = oo::commonOprRedis('config')->hGetAll($cacheKey);
        $arr = funs::getArrFromJsonArr($arrJson);
        if(empty($arr)||($aid!==''&&empty($arr[$aid]))){
            $tb = otable::achievementConfig();
            $sql = "SELECT * FROM {$tb}";
            $ret = oo::commonOprDb('config')->getAll($sql,1);
            if(!empty($ret)){
                foreach ($ret as $k=>$v){
                    $arr[$v['aid']] = $v;
                    $cache[$v['aid']] = json_encode($v);
                }
                oo::commonOprRedis('config')->hMset($cacheKey,$cache);
                oo::commonOprRedis('config')->expire($cacheKey,oo::redisRandomExpire(7*86400));
            }
        }
        $data = $aid===''?$arr:$arr[$aid];
        return $data;
    }

    /**
     * 成就列表
     * @param $uid
     * @return array
     * Created by: Owen
     * Created on: 2020/4/7 18:27
     */
    public function getAchieveList($uid){
        $achieveConfig = self::getAchieveConfig();
        $achieveUser = self::getUserAchieve($uid);
        $achieve = [];
        $lang = oo::getDefinedLang($uid);
        foreach ($achieveConfig as $row){
            $reward = json_decode($row['reward']);
            $stage = explode(',',$row['stage']);
            $i = 0;
            $process = ($achieveUser[$row['aid']]['process'])??0;
            foreach ($stage as $k){
                if($process>=$k){
                    $i++;
                }
            }
            $userStage = ($achieveUser[$row['aid']]['stage'])??0;
            if($userStage >= count($stage)){
                $userStage = $userStage - 1;
            }

            if($lang == 'zh'){
                $designation = $row['designation'];
                $content = $row['content'];
            }else{
                $designation = $row['designation_'.$lang];
                $content = $row['content_'.$lang];
            }
            $temp = [
                'id'    		=>$row['aid'],
                'rewardTimes'   =>$i,
                'name'          =>$designation,
                'desc'          =>$content,
                'spinsNum'      =>$reward[$userStage]->spin,
                'coinsNum'      =>$reward[$userStage]->coin,
                'progress'      =>$process,
                'target'        =>$stage[$userStage],
                'curRewardTimes'=>($achieveUser[$row['aid']]['stage'])??0
            ];
            $achieve[] = $temp;
        }
        return $achieve;
    }

    /**
     * 获取用户成就
     * @param $uid
     * @param $aid
     * @return mixed
     * Created by: Owen
     * Created on: 2019/6/13 17:50
     * Description:
     */
    public function getUserAchieve($uid,$aid = ''){
        $cacheKey = okeys::UserAchieve($uid);
        $data     = oo::commonOprRedis('Usercache')->hGetAll($cacheKey);
        if(empty($data)){
            $tb   = otable::achievement($uid);
            $data = oo::commonOprDb('achievement')->getAll("SELECT * FROM {$tb} WHERE uid={$uid}",MYSQLI_ASSOC);
            if(empty($data)){
                if($aid !== ''){
                    return self::InsertUserAchieve($uid,$aid);
                }else if($aid == ''){
                    return [];
                }
            }
            foreach ($data as $row){
                oo::commonOprRedis('Usercache')->hSetNx($cacheKey,$row['aid'],json_encode($row));
            }
            oo::commonOprRedis('Usercache')->expire($cacheKey,oo::redisRandomExpire(3*2*60*60));
        }else{
            $data = funs::getArrFromJsonArr($data);
        }
        $out = [];
        foreach ($data as $key=>$row){
            if($key == $aid && $aid !== ''){
                return $row;
            }
            $out[$key] = $row;
        }
        if($aid === ''){
            return $out;
        }else{
            return self::InsertUserAchieve($uid,$aid);
        }
    }

    /**
     * 插入成就
     * @param $uid
     * @param $aid
     * @return array
     * Created by: Owen
     * Created on: 2020/7/3 14:52
     */
    public function InsertUserAchieve($uid,$aid){
        $cacheKey = okeys::UserAchieve($uid);
        $ret = [
            'uid'     => $uid,
            'aid'     => $aid,
            'process' => 0,
            'stage'   => 0
        ];
        $res = oo::commonOprRedis('Usercache')->hSetNx($cacheKey,$aid,json_encode($ret));
        if($res){//并发锁
            $tb = otable::achievement($uid);
            oo::commonOprDb('achievement')->query("INSERT INTO {$tb} (uid,aid,process) VALUES ({$uid},{$aid},0)",false);
        }
        return $ret;
    }
    
    /**
     * 更新成就缓存
     * @param $uid
     * @param $aid
     * @param $ret
     * @return bool
     * Created by: Owen
     * Created on: 2020/4/7 18:19
     */
    public function updateUserAchieve($uid,$aid,$ret){
        $cacheKey = okeys::UserAchieve($uid);
        $ret['isLanding'] = 1;
        $bool = oo::commonOprRedis('Usercache')->hSet($cacheKey,$aid,json_encode($ret));
        oo::commonOprRedis('Usercache')->expire($cacheKey,oo::redisRandomExpire(3*2*60*60));
        /** 数据落地标识 */
        oo::commonOprRedis('Usercache')->zAdd(okeys::Landing("ACHIEVE", $uid),time(),$uid);
        return $bool;
    }

    /**
     * 更新成就数值
     * @param $uid
     * @param $aid
     * @param int $num
     * @param string $opt
     * @return bool
     * Created by: Owen
     * Created on: 2020/4/7 10:42
     */
    public function updateAchi($uid,$aid,$num = 1,$opt = '+'){
        $ret = self::getUserAchieve($uid,$aid);
        if($opt == '+'){
            $ret['process'] = $ret['process'] + $num;
        }else if($opt == '='){
            $ret['process'] = $num;
        }
        self::updateUserAchieve($uid,$aid,$ret);
        $ret = self::getAchieveConfig($aid);
        $stage = explode(',',$ret['stage']);
        if($ret['process'] <= $stage[count($stage)-1]){
            if(count($stage) > $ret['stage'] && $ret['process'] >= $stage[$ret['stage']]){
                $is_push = oo::commonOprRedis('Usercache')->hGet(okeys::redPointLock($uid,'Achi-'.$aid),$ret['stage']+1);
                if(!$is_push){
                    oo::commonOprModel('Workerman')->push($uid,ocmd::$redPoint,['type'=>10]);
                    oo::commonOprRedis('Usercache')->hSet(okeys::redPointLock($uid,'Achi-'.$aid),$ret['stage']+1,1);
                    oo::commonOprRedis('usercache')->expire(okeys::redPointLock($uid,'Achi-'.$aid),oo::redisRandomExpire(7*86400));
                }
            }
        }
        return ($ret)?true:false;
    }

    /**
     * 领取成就奖品
     * @param $uid
     * @param $aid
     * @return array
     * Created by: Owen
     * Created on: 2019/6/13 18:33
     * Description:
     */
    public function getAchieveReward($uid,$aid){
        $achieve = self::getUserAchieve($uid,$aid);
        if(empty($achieve)){
            return ['code'=>-1,'msg'=>'未达标，无法领取奖品'];
        }
        $rewardStage = $achieve['stage'];
        $config = self::getAchieveConfig($aid);
        $configStage = explode(',',$config['stage']);
        if(count($configStage) == $rewardStage){
            return ['code'=>-1,'msg'=>'已领取最高等级奖励'];
        }
        $lang = oo::getDefinedLang($uid);
        if($configStage[$rewardStage]<=$achieve['process']){
            $achieve['stage'] = $rewardStage + 1;
            $ret = self::updateUserAchieve($uid,$aid,$achieve);
            if($ret){
                $reward = json_decode($config['reward'],true);
                $nextReward = $reward[$rewardStage+1];
                $rewardTemp = $reward[$rewardStage];
                $reward = [
                    'spins' => $rewardTemp['spin'],
                    'money' => $rewardTemp['coin'],
                ];
                oo::commonOprModel('member')->optProperty($uid,$reward,'+',102,'领取成就奖励:'.$aid.'-'.$rewardStage);
                $i = 0;
                $process = $achieve['process'];
                $stage = explode(',',$config['stage']);
                foreach ($stage as $k){
                    if($process>$k){
                        $i++;
                    }
                }
                $data = [
                    'coin' =>$reward['money'],
                    'coins'=>$reward['money'],
                    'spins'=>$reward['spins'],
                ];
                if($lang == 'zh'){
                    $designation = $config['designation'];
                    $content = $config['content'];
                }else{
                    $designation = $config['designation_'.$lang];
                    $content = $config['content_'.$lang];
                }
                $data['achieve'] = [
                    'id'    		=>$aid,
                    'rewardTimes'   =>$i,
                    'name'          =>$designation,
                    'desc'          =>$content,
                    'spinsNum'      =>$nextReward['spin']??0,
                    'coinsNum'      =>$nextReward['coin']??0,
                    'progress'      =>$process,
                    'target'        =>$stage[$rewardStage+1]??0,
                    'curRewardTimes'=>($rewardStage+1)
                ];
                oo::commonOprRedis('Usercache')->hDel(okeys::redPointLock($uid,'Achi-'.$aid),$rewardStage+1);
                return ['code'=>1,'msg'=>'领取奖品成功','data'=>$data];
            }
        }else{
            return ['code'=>-1,'msg'=>'未达标，无法领取奖品'];
        }
    }

    /**
     *  1.5.0 新版配置 --------------------------------------------------------------------------------------------------
     */

    /**
     * 成就列表
     * @param $uid
     * @return array
     * Created by: Owen
     * Created on: 2020/4/7 18:27
     */
    public function getAchieveList2($uid){
        $achieveConfig = self::getAchieveConfig();
        $achieveUser = self::getUserAchieve($uid);
        $achieve = [];
        foreach ($achieveConfig as $row){
            $process = ($achieveUser[$row['aid']]['process'])??0;
            $temp = [
                'id'    		=>$row['aid'],
                'progress'      =>$process,
                'curRewardTimes'=>($achieveUser[$row['aid']]['stage'])??0
            ];
            $achieve[] = $temp;
        }
        return $achieve;
    }
}
