<?php

namespace loyalsoft;

require_once __DIR__ . '/../service_call/pay/official/pay_op.php';

/**
 * Description of UserProc
 * 玩家数据处理流程
 *
 */
class UserProc {

    const role_Table = 'tab_rolename';

    /**
     * 逻辑分发
     * 所有的Proc中必须有这样一个方法
     * @param Req $req
     */
    public static function procMain($req) {
        switch ($req->cmd) {
            case CmdCode::cmd_user_getzonelist:                                 # 6000 分区列表
                return UserProc::GetZoneList();
            case CmdCode::cmd_user_loginuserinfo:                               # 6001 登录/新玩家直接注册并登录
                return UserProc::loginUserInfo();
            case CmdCode::cmd_user_gameconstinfo:                               # 6002 下载游戏配置
                return UserProc::downloadConstInfo();
            case CmdCode::cmd_user_setAnimation:                                # 6004 片头播放记录
                return UserProc::setAnimation();         
            case CmdCode::cmd_user_replaceHeadImg:                              # 6005 替换头像
                return UserProc::replaceHeadImg();
            case CmdCode::cmd_user_delUserUid:                                  # 6006 删除账号
                return UserProc::delUserUid();                    
            default:
                Err(ErrCode::cmd_err);
        }
    }
    
    /**
     * 6006 删除账号
     * @return type
     */
    public static function delUserUid() {
        TestServer::deleteUserUId(req()->uid, req()->zoneid);
        FightProc::DeleteRankInvalidUser(req()->uid);

        UserProc::updateUserInfo();
        $ret = array();
        return Resp::ok($ret);
    }
    
    /**
     * 6005 替换头像
     * @return type
     */
    public static function replaceHeadImg() {
        list($img) = req()->paras;

        ctx()->baseInfo->headImg = $img;

        FightProc::UpdateRankUserHeadImg(req()->uid, $img);
        UserProc::updateUserInfo();
        $ret = array();
        return Resp::ok($ret);
    }

    /**
     * 6004 设置片头播放记录
     * @return type
     */
    public static function setAnimation() {
        list($tag) = req()->paras;

        if (ctx()->baseInfo->animation == 0 && $tag > 0) {
            ctx()->baseInfo->animation = 1;
        }

        UserProc::updateUserInfo();
        $ret = array();
        return Resp::ok($ret);
    }

    /**
     * 检测遗漏订单
     */
    static function checkMissOrder() {
        $tableName = "tpl_order_tab";

        if (daoInst()->tableExist($tableName)) {
            $arr = daoInst()->select("*")->from($tableName)
                    ->where('uid')->eq(req()->uid)
                    ->andWhere('zoneid')->eq(req()->zoneid)
                    ->andWhere('status')->eq(1)
                    ->andWhere('drawed_ts')->eq(0)
                    ->fetchAll();

            if (count($arr) != null) {
                foreach ($arr as $item) {
                    $result = pay_op::CheckAndDrawOrder(req()->uid, $item->cpOrderId, array(new PayProc, 'distributePayGoods'));
                }
            }
        }
    }

    /**
     * 6016 拉取其他玩家的信息.
     */
    public static function UserOtherPlayerInfo() {
        $zoneId = req()->zoneid;
        list($other_uid) = req()->paras;
        $g = UserProc::getUserGame($zoneId, $other_uid);
        my_Assert(null != $g, ErrCode::user_no_err);                            # 找不到指定的玩家数据
        return Resp::ok($g);
    }

    /**
     * 6000 【移动端】 获取分区列表
     */
    public static function GetZoneList() {
        $defaultZone = new Ins_ZoneInfo(1, 0, "");                             # 新用户默认分区
        $bGetRecommended = false;
        if (count(req()->paras) > 0) {                                           # 是否只拉取推荐分区
            $bGetRecommended = req()->paras[0];
        }
        $zoneList = array();
        $ts = now();
//        foreach (GameConfig::zonelist() as $zoneid => $zone) {
//            isEditor() and $zone = new \sm_zonelist();
//            if ($zone->publicTs > $ts) {
//                continue;
//            }
//            $zone->zoneid = $zoneid;                                            # 把zoneid塞进zone数据结构中
//            if ($bGetRecommended) {
//                if ($zone->isRecommended > 0 && $zone->status == 1) {
//                    $zoneList[] = $zone;
//                } else {
//
//                }
//            } else {
//                $zoneList[] = $zone;
//            }
//            unset($zone->isRecommended);
//        }
//        UserProc::_AddTesterZonelist($zoneList);                                # 添加测试分区
#
// <editor-fold defaultstate="collapsed" desc="    取玩家分区记录    ">
        $userZoneInfo = self::getUserZoneInfo();                                # 玩家分区记录
        $isNewUser = false;
        if ($userZoneInfo == null) {           // 这里使用推荐分区的数据，推荐分区信息，在后台编辑，编辑器可用
            $userZoneInfo = new Data_UserZoneInfo();
            $userZoneInfo->lastZone = $defaultZone;                             # 新用户导向默认分区
            $isNewUser = true;
        } else { # 转换一下格式，去掉key，只保留value的集合
//            $userZoneInfo->playedZones = ArrayInit();
//            array_merge($userZoneInfo->playedZones, array_values((array) $userZoneInfo->playedZones));
        }

// </editor-fold>

        $ret = array(
            'isNewUser' => $isNewUser,
            'zonelist' => json_decode(json_encode($zoneList)),
            'userZoneInfo' => $userZoneInfo
        );

        return Resp::ok($ret);                                                  # 返回值
    }

    private static function _AddTesterZonelist(&$zoneList) {
        if (config::Inst()->isTester(req()->uid)) {                              # 添加测试分区
            $zoneList[] = array('zoneid' => 999, 'name' => '内测专区', 'status' => 2, 'publicTs' => 0);
        }
    }

    /**
     * 6002 客户端下载常量配置信息
     * @return type
     */
    public static function downloadConstInfo() {
        list($clientDataVer) = req()->paras;                                     # 客户端数据版本号,程序版本号
        $serverVer = GameConfig::ver();                                         # 最新数据版本号
        my_Assert($serverVer, ErrCode::err_const_no);                           # 找不到常量数据
        $url = config::CDN_host() . "/cfg/" . req()->CV . "/Client.bytes?" . $serverVer;

        $ret = array(
            'version' => $serverVer,
            'url' => $clientDataVer == $serverVer ? "" : $url, #                # 如果版本一致，url传空，只传回版本号
            'data' => null);
        return Resp::ok($ret);

//                $md5 = md5(json_encode($constInfo));                          # 计算MD5值，多余计算md5
//        $constInfo = GameConfig::client();                       # 取出来的已经是base64过的压缩数据
//        my_Assert($constInfo, ErrCode::err_const_no);                           # 找不到配置数据
    }

    /**
     * 6001 客户端登录并返还玩家信息
     * @return Resp
     */
    public static function loginUserInfo() {
        $game = UserProc::getUserGame(req()->zoneid, req()->uid);
        if ($game == null) {                                                # 新用户, -> 6006创建账号
            $userID = req()->uid;

            $id = gMem()->increment(MemKey_GameRun::Stat_UserCountByZone_int(req()->zoneid));   # 增加玩家数量计数
            $rolename = "No." . sprintf("%03d", req()->zoneid) . sprintf("%07d", $id); # 生成编号
            if (self::checkRoleNameNotExist($rolename)) {                           # 记录玩家
                $game = self::createUser($rolename);
                if (1 == self::regRole(req()->zoneid, $userID, $rolename, "", "", "")) {
                    $resp = Resp::ok($game);
                    self::updtateUserZoneInfo();
                } else {
                    $resp = Resp::err(ErrCode::err_db);
                }
            } else {                                                                # 昵称已存在
                $resp = Resp::ok(array('ret' => '用户已存在.'));
            }
        
            UserProc::updateUserInfo();                                         # 这一步回存操作只有在 userInfo正常存在的情况下才进行

            TaskProc::OnLogin_Daily();
            TaskProc::OnLogin_day7();
            FightProc::Ranking_FightPower();
            return $resp;
        } else {                                                                # 2.如果玩家已存在，则处理普通登录流程
            req()->game = $game;                                                # 给Req挂载玩家数据
            $game->base()->Reset_tilits();                                      # 修正体力ts
            UserProc::checkContidays();                                         # 连续登录,状态检查
            //PayProc::m_refreshChargeOrders();                                   # 刷新订单, 多平台版本
            //PayProc::checkDeltest();                                            # 检查内侧充值记录(函数内部会只检查一次)
            //self::checkMissOrder();                                             #校验是否有漏单
            UserProc::updateUserInfo();                                         # 这一步回存操作只有在 userInfo正常存在的情况下才进行

            FightProc::Ranking_FightPower();
            TaskProc::OnLogin_Daily();
            TaskProc::OnLogin_day7();
            $resp = Resp::ok($game);                                            # 设置返回值
            self::updtateUserZoneInfo();                                        # 1. 更新玩家分区记录
        }
        return $resp;
    }

//
// <editor-fold defaultstate="collapsed" desc="    辅助方法    ">

    /**
     * 检查昵称是否已经存在
     * @param string $roleName
     * @return boolean
     */
    static function checkRoleNameNotExist($roleName) {
        return true;                                                            # 不再检查昵称重复
        static $sqlFormat = "SELECT count(*) as rows FROM `tab_rolename` WHERE roleName='%s';";
        $sql = sprintf($sqlFormat, $roleName);
        $n = daoInst()->query($sql)->fetch();
        return $n->rows <= 0;
    }

    /**
     * 插入玩家新角色
     *
     * @param string $zoneid
     * @param string $userID
     * @param string $nickname
     * @param string $gender
     * @param string $profile_img
     * @param string $plat
     */
    static function regRole($zoneid, $userID, $nickname, $gender, $profile_img, $plat) {
        return daoInst()->insert('tab_rolename')
                        ->data(array(
                            'zoneid' => $zoneid,
                            'userID' => $userID,
                            'roleName' => $nickname,
                            'gender' => $gender,
                            'profile' => $profile_img,
                            'plat' => $plat
                        ))->exec();
    }

    /**
     * 检测连续登录状态，重置必要字段[时间戳自动记录]
     */
    static function checkContidays($isnew = 0) {
        $ret = TimeUtil::totalDays() - TimeUtil::totalDays(ctx()->baseInfo->lastLogin); // 对比登录日期
        if ($ret > 0 || $isnew) {                                               # 当天第一次登录
            self::OnNewDay($isnew);
        } else {                                                                # 更新下登录次数记录(任务计数器)
        }
        if ($ret == 1) {                                                        # 连续登录
        } else if ($ret >= 2) {                                                 # 隔天登录
        }
        ctx()->baseInfo->lastLogin = now();                                     # 更新下访问时间
        //TapDBUtil::SOnUserLogin();                                              # 向tapdb上报玩家登录 2023.5.10

        return $ret;
    }

    /**
     *  处理当天第一次登录
     * @param bool $isnew Description
     */
    static function OnNewDay($isnew) {
        ShopProc::DailyShopItemRand();
        ShopProc::ShopDailyClear();
        //self::clear();
        FightProc::FightDailyClear();
        //TaskProc::initAchieveData();   
        TaskProc::ResetTask();
    }
    
//    static function clear() {      
//        $dic = GameConfig::announcement();
//        foreach ($dic as $mo) {
//            if(now() > $mo->endTs && in_array($mo->id,ctx()->privateState->announcement)){
//                StlUtil::arrayRemove(ctx()->privateState->announcement, $mo->id);
//            }
//        }
//        
//    }

// <editor-fold defaultstate="collapsed" desc="创建新用户">

    /**
     * 创建用户
     * @return Data_UserGame
     */
    static function createUser($rolename) {
        $game = new Data_UserGame();
        req()->game = $game;                                                    # 更新Req挂载的玩家数据,
        $game->initialize();                                                    # 初始化玩家数据
        $game->baseInfo->name = $rolename;
        //$game->baseInfo->headImg = "";
        $game->baseInfo->firstLogin = now();

#Ps 6006是没有获得到Userinfo到Req中的
        UserProc::checkContidays(1);                                            # 每日状态检查
//        UserProc::fetchFromInteract($mem, $req);  # 从interact拉取数据，Ps.初始化的过程应该还拉取不到什么有效数据
        UserProc::updateUserInfo();                                             # 回存用户数据

        return $game;
    }

    /**
     * 整理平台玩家记录集
     * @param int $isnew
     */
    private static function updatePlatUserRecord($isnew = 0) {
        $zoneid = req()->zoneid;
        $uid = req()->uid;
        $user = ctx()->baseInfo;
        $day = totalDays();
        $level = $user->level;
        $platUser = ObjectInit();
        $platUser->uid = $uid;                                                  #
        $platUser->name = $user->name;                                          #
        $platUser->level = $level;
        $platUser->img = $user->headImg;                                            # 头像字段
        $platUser->cash = $user->cash;
        $platUser->gold = $user->gold;
        $platUser->tili = $user->tili;
        $platUser->ts = now();
        $platUser->isnew = $isnew;
        gMem()->delete(MemKey_GameRun::DailyLoginUser_byUID_hash($zoneid, $day - 108));
        gMem()->hset(MemKey_GameRun::DailyLoginUser_byUID_hash($zoneid), $uid, $platUser);
        gMem()->delete(MemKey_GameRun::DailyLoginUser_byLevel_hash($zoneid, $level, $day - 108));
        gMem()->hset(MemKey_GameRun::DailyLoginUser_byLevel_hash($zoneid, $level), $uid, $platUser);
    }

// </editor-fold>
//
// <editor-fold defaultstate="collapsed" desc="读写玩家数据">

    /**
     * 取玩家数据
     * @param type $zoneid
     * @param type $uid
     * @return Data_UserGame
     */
    public static function getUserGame($zoneid, $uid) {
        $key = MemKey_User::Info_hash($zoneid, $uid);
//        $pf = req()->getPlatStr();
//        if ($pf == "tap") {                                        # taptap平台
//            $oldkey = MemKey_User::Info_hash($zoneid, req()->getPlatOid());
//            if (gMem()->exists($oldkey)) {
//                gMem()->rename($oldkey, $key);                                  # 做下数据迁移
//            }
//        }
        $a = new Data_UserGame();
        if (null == $a->readDataFromMem($key)) {                                # ps.下面这一段代码和经常删号会有冲突,因此关闭了 --gwang 2022.2.28
            $collection = "userInfoBack";
            $cursor = gMongo()->find($collection, ['key' => $key], ['sort' => array('ts' => -1), 'limit' => 1]); # 提取备份数据
            $cursor->rewind();
            if ($cursor->valid()) {
                $v = $cursor->current();
                $a->LoadFrom($v->value);                                    # 加载
                $a->updateDataFull($key);                                   # 反向写回redis
            } else {
                return null;
            }
        }
        return new Data_UserGame($a);
    }

    /**
     * 更新用户数据(设置标志位,最后统一更新-gwang 2017.07.18)
     */
    public static function updateUserInfo() {
        my_Assert(req(), "req()为空");
        my_Assert(req()->game, "[" . req()->cmd . "] 玩家数据正在被清空!" . req()->uid);
        req()->userInfoChanged = TRUE;                                          # 设置回写标志位
    }

    /**
     * 回写玩家数据
     * @param Data_UserGame $game
     */
    public static function setUserInfo($game) {
        $OK = false;
        if ($game) {
            $zoneid = req()->zoneid;
            $uid = req()->uid;
            $game->baseInfo->lastSaveTs = now();
            $key = MemKey_User::Info_hash($zoneid, $uid);
            $OK = $game->updateDataFull($key);                                  # 向Redis回写玩家数据
            if ($OK) {
//                CLog::info($msg);
                self::backupUserInfoMongo();                                    # 向MongoDB备份数据
                gMem()->expire($key, 3600);                                     # 设置过期时间1小时
            } else {
                // redo Logic
                CLog::err("写入数据时版本已过期!!!");
            }
        }
        return $OK;
    }

// </editor-fold>
//
// <editor-fold defaultstate="collapsed" desc="玩家分区记录">

    /**
     * 读取玩家的分区记录
     * @return Data_UserZoneInfo Description
     */
    public static function getUserZoneInfo() {
        $ret = gMem()->get(MemKey_User::Union_PlayedZoneInfo_normal(req()->uid));
        return $ret;
    }

    /**
     * 更新玩家分区记录
     */
    public static function updtateUserZoneInfo() {
        $req = req();
        $userZoneInfo = self::getUserZoneInfo();                                # 取玩家分区记录
        if (!$userZoneInfo) {
            $userZoneInfo = new Data_UserZoneInfo;
        }
        $level = 0;
        $zoneid = $req->zoneid;
        $playerName = "";
        $headImg = "";
        if (null != ctx()) {                                                     # 防御确保玩家数据不为空
            $level = ctx()->baseInfo->level;
            $playerName = ctx()->baseInfo->name;
            $headImg = ctx()->baseInfo->headImg;
        } else {
            Err('玩家数据为空!' . __CLASS__ . '.' . __FUNCTION__);
        }
        if (is_null($level)) {
            $level = 0;
        }
        $userZoneInfo->lastZone = new Ins_ZoneInfo($zoneid, $level, $playerName, $headImg); # 更新玩家分区记录
        $userZoneInfo->playedZones->$zoneid = $userZoneInfo->lastZone;          # 玩过的分区集合
        gMem()->set(MemKey_User::Union_PlayedZoneInfo_normal($req->uid), $userZoneInfo); # 回写数据
    }

// </editor-fold>
// <editor-fold defaultstate="collapsed" desc=" 用户数据备份 ">

    /**
     * 备份玩家数据,(玩家数据落地),一天一份,当天记为最后一次登录时的状态而非最后一次操作的状态
     * @history
     *           version 3.0.13 mysql版备份玩家数据, (性能优化后表现还不错)
     *                      除非用脚本在redis中实现备份,否则直接写入mysql.
     *                      主要是mysql版本利用存储过程之后性能已经和redis版相差不多.
     *           version 2.0.0 Redis storage 从MySQL转到Redis中存储. 这个备份数据没有大规模导出硬盘的需求
     *                      因为mysql版本实在是负载太大了, 上百毫秒. 所以改在redis中了.
     *           version 1.0.0 Mysql storagef 1. 分表不做了, 2. 存储过程不用了 3. 先能用再说
     */
    public static function backupUserInfo() {
        $tsday = TimeUtil::totalDays();                                         # 精确到天,保留10个最近记录.
        $value = base64_encode(gzdeflate(JsonUtil::encode(ctx())));             # blob数据,序列化下
        $sql = sprintf("call insertUserInfo('%s', %d, %d, '%s');", #            # 所以直接将序列化后的结果存进去吧,
                req()->uid, req()->zoneid, $tsday, $value);                     # zoneid, uid, tsday
        daoInst()->exec($sql);                                                  # 也可以用exec($sql)
    }

    /**
     * 备份玩家数据,(玩家数据落地),一天一份,当天记为最后一次登录时的状态而非最后一次操作的状态
     * @history
     *           version 4.0.0 切换到MongoDB存储
     *           version 3.0.13 mysql版备份玩家数据, (性能优化后表现还不错)
     *                      除非用脚本在redis中实现备份,否则直接写入mysql.
     *                      主要是mysql版本利用存储过程之后性能已经和redis版相差不多.
     *           version 2.0.0 Redis storage 从MySQL转到Redis中存储. 这个备份数据没有大规模导出硬盘的需求
     *                      因为mysql版本实在是负载太大了, 上百毫秒. 所以改在redis中了.
     *           version 1.0.0 Mysql storagef 1. 分表不做了, 2. 存储过程不用了 3. 先能用再说
     */
    public static function backupUserInfoMongo() {
        $collectionName = "userInfoBack";                                       # 表名
        $key = MemKey_User::Info_hash(req()->zoneid, req()->uid);
        $doc = array('key' => $key, #                                           # 最新文档
            'ts' => TimeUtil::dtCurrent(), #                                    # 更新时间
            'stVer' => ctx()->stVer, #                                          # 增加版本号
            'value' => ctx());                                                  # 玩家数据
        $bok = gMongo()->insert($collectionName, $doc);                                # 插入备份
//        CLog::err($collectionName . "备份玩家数据" . req()->uid . ($bok ? "成功" : "失败"));
        if (ctx()->stVer % 100 == 1) {                                          # 每100条记录清理一次(delete耗时比较长-gwang.2023年3月10日)
            $delFilter = array('key' => $key, 'stVer' => ['$lt' => ctx()->stVer - 100]); # 保留最后一百次变更记录
            gMongo()->delete($collectionName, $delFilter);
        }
//        $filter = array('key' => $key);                                         # 指定条件
//        gMongo()->update($collectionName, $filter, $doc, true);                 # 更新
    }

// </editor-fold>
//</editor-fold>
//
}
