<?php

namespace loyalsoft;

/**
 * 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_acceptcontidaysgift:                         # 6003 领取连续登陆奖励
                return UserProc::acceptContiDaysGift();
            case CmdCode::cmd_user_registerNewRole:                             # 6006 注册新角色
                return UserProc::RegisterNewRole();
            case CmdCode::cmd_user_completeNewbieGuide:                         # 6007 提交新手引导步骤
                return UserProc::completeNewbieGuide();
//            case CmdCode::cmd_user_setNewbieGuideCards:                         # 6008 发放新手引导所需卡牌
//                return self::SetNewbieGuideCards($req);                
//            case CmdCode::cmd_user_setNewbieGuideOver:                          # 6009 跳过新手引导
//                return UserProc::setNewbieGuideOver();
            case CmdCode::cmd_user_setNickname:                                 # 6010 设置/修改玩家昵称
                return self::SetUserNickname();
            case CmdCode::cmd_user_SetUserHeadImageBorder:                      # 6011 修改玩家头像框
                return self::SetUserImageBorder();
            case CmdCode::cmd_user_setUserImage:                                # 6012 更换玩家形象
                return self::SetUserImage();
            case CmdCode::cmd_user_changeUserHeadImg:                           # 6013 更换玩家头像
                return self::SetUserHeadImage();
            default:
                Err(ErrCode::cmd_err);
        }
    }

//
// <editor-fold defaultstate="collapsed" desc="玩家信息 修改">

    /**
     * [6013]修改玩家头像 
     */
    static function SetUserHeadImage() {
        list($headImage) = req()->paras;                                         # 参数, 新头像
        req()->userInfo->game->baseInfo->headImg = $headImage;
        UserProc::updateUserInfo();
        return Resp::ok(array('ret' => 'ok'));
    }

    /**
     * [6012]修改玩家形象 
     */
    static function SetUserImage() {
        list($image) = req()->paras;                                             # 参数, 新形象
        req()->userInfo->game->baseInfo->img = $image;
        UserProc::updateUserInfo();
        return Resp::ok(array('ret' => 'ok'));
    }

    /**
     * [6011]修改玩家头像框  
     */
    static function SetUserImageBorder() {
        list($imgborderId) = req()->paras;                                       # 参数, 新头像框ID
        req()->userInfo->game->baseInfo->imgBorderId = $imgborderId;
        UserProc::updateUserInfo();
        return Resp::ok(array('ret' => 'ok'));
    }

    /**
     * [6010] 设置/修改玩家昵称 
     */
    static function SetUserNickname() {
        list($newname) = req()->paras;                                          # 参数: 新昵称, 头像
        my_Assert(isset(glc()->User_SetNickname_Cost), "全局变量中未找到改名消耗钻石数量的配置字段");
        $amt = glc()->User_SetNickname_Cost;                                    # 改名需要消耗钻石
        $ok = req()->userInfo->game->base()->Consume_Cash($amt);                # 扣除钻石
        my_Assert($ok, ErrCode::notenough_cash_msg);
        my_Assert(self::checkRoleNameNotExist($newname), ErrCode::user_nicknameexist); # 昵称已存在
        req()->userInfo->game->baseInfo->name = $newname;
        daoInst()->update(self::role_Table)
                ->data(array('roleName' => $newname))
                ->where('userID')->eq(req()->uid)
                ->andWhere('zoneid')->eq(req()->zoneid)
                ->exec();
        UserProc::updateUserInfo();
        return Resp::ok(array('ret' => 'ok'));
    }

// </editor-fold>
//
// <editor-fold defaultstate="collapsed" desc="新手引导">
//    /**
//     * [6009] 新手引导 发送n张碎片到玩家身上 
//     */
//    static function SetNewbieGuideCards() {
//        Err(ErrCode::function_notopen_msg);                                     # 功能已经废弃 -wg
//        $private = req()->userInfo->game->privateState;
//        if (!property_exists($private, 'newbieguideCards')) {                   # 逻辑检查, 是否已经发放过
//            $private->newbieguideCards = true;
//            req()->userInfo->game->privateState = $private;                      # 回写私有数据
//            $sp = explode(';', glc()->User_SetNewbieGuideCards);                # 碎片
//            foreach ($sp as $cardinfo) {
//                $arr = explode(',', $cardinfo);
//                $itemid = $arr[0];
//                $num = $arr[1];
//                StoreProc::addSegmentIntoStore(req()->userInfo->game->store, $itemid, $num);
//            }
//            $exp = explode(';', glc()->User_SetNewbieGuideExps);                # 经验卡
//            foreach ($exp as $expinfo) {
//                $arr = explode(',', $expinfo);
//                $itemid = $arr[0];
//                $num = $arr[1];
//                StoreProc::PutOverlyingItemInStore($itemid, $num);
//            }
//            UserProc::updateUserInfo();                                         # 回存玩家数据
//            return Resp::ok(array('store' => req()->userInfo->game->store));
//        }
//        return Resp::err(ErrCode::active_hasgetted);
//    }

    /**
     * [6009] 增加 设置引导结束的标志位 
     */
    public static function setNewbieGuideOver() {
        Err(ErrCode::function_notopen_msg);                                     # 功能已经废弃 -wg
//        $user = new Data_UserGame(req()->userInfo->game);                        # user
//        if (!CommUtil::isPropertyExists($user, "NewbieGuideOver")) {            # 防御: 变量未初始化
//            $user->NewbieGuideOver = 1;
//        }
//        $user->NewbieGuideOver = 1;
//        req()->userInfo->game = $user;
//        UserProc::updateUserInfo();                                         # 回写数据
//        return Resp::ok(array(#                                                 # 返回值
//                    'result' => "succeed"
//        ));
    }

    /**
     * [6007] 更新初始的强制的新手引导阶段步骤
     * 第一阶段的引导 
     */
    public static function completeNewbieGuide() {
        $guideIndex = req()->paras[0];                                          # 参数: 新手引导步骤
        $user = new Data_UserGame(req()->userInfo->game);                       # user
        my_default_Obj($user->NewbieGuide);                                     # 防御: 变量未初始化
        $NewbieGuide = $user->NewbieGuide;
        if (!CommUtil::isPropertyExists($NewbieGuide, "guideStep")) {           # 防御: 变量未初始化
            $NewbieGuide->guideStep = 0;
        }
        my_Assert($guideIndex >= $NewbieGuide->guideStep, ErrCode::user_settutorialscompletedfail_err);

        $NewbieGuide->guideStep = $guideIndex;
        $user->NewbieGuide = $NewbieGuide;
        req()->userInfo->game = $user;
        UserProc::updateUserInfo();                                             # 回写数据
        return Resp::ok(array(
                    "store" => $user->store, #                                  # 目前来看只涉及到items变化
                    "heros" => $user->heros, #                                  # 英雄数据
        ));
    }

// </editor-fold>
//

    /**
     * 6006 注册新角色 
     */
    public static function RegisterNewRole() {
        $userID = req()->uid;
        list($rolename, $gender, $profile_img) = req()->paras;                   # 参数: 昵称,性别,头像
        $id = gMem()->increment(MemKey_GameRun::Stat_UserCountByZone_int(req()->zoneid));   # 增加玩家数量计数
        $rolename = "No." . sprintf("%03d", req()->zoneid) . sprintf("%07d", $id); # 生成编号
        if (self::checkRoleNameNotExist($rolename)) {                           # 记录玩家
            $userinfo = self::createUser($rolename, $gender, $profile_img);
            if (1 == self::regRole(req()->zoneid, $userID, $rolename, $gender, $profile_img, $userinfo->getPlatStr())) {
                $resp = Resp::ok($userinfo);
                self::updtateUserZoneInfo();
            } else {
                $resp = Resp::err(ErrCode::err_db);
            }
//            StatProc::UserGuidStep($userinfo->user->firstLogOn, $req->zoneid, -2, 1);
        } else {                                                                # 昵称已存在
            $resp = Resp::ok(array('ret' => '用户已存在.'));
        }
        return $resp;
    }

    /**
     * 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;

//            $err = self::RegisterNewUser($req);  # 新用户添加到总表中
//            if ($err != ErrCode::succeed) {
//                return ResponseVo::myErrResponse($req, $err);
//            }
# 统计模块添加记录
//            StatProc::UserGuidStep(CommUtil::tsCurrent(), $req->zoneid, -1, 1); // 第一次进入游戏
        } 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);                           # 找不到常量数据
        if ($clientDataVer == $serverVer) {                                         # 如果版本一致，数据体抹掉，只传回版本号
//                $md5 = md5(json_encode($constInfo));                          # 计算MD5值，多余计算md5
            $ret = array(
                'version' => $clientDataVer,
                'data' => null);
            return Resp::ok($ret);
        }
        $constInfo = GameConfig::client();                                      # 取出来的已经是base64过的压缩数据
        my_Assert($constInfo, ErrCode::err_const_no);                           # 找不到配置数据 
        return Resp::ok(array('data' => $constInfo));
    }

    /**
     * 6001 客户端登录并返还玩家信息 
     * @return Resp
     */
    public static function loginUserInfo() {
        $userInfo = UserProc::getUserInfo(req()->zoneid, req()->uid);     # 取玩家信息
        if ($userInfo == null) {                                                # 新用户, -> 6006创建账号
            $ret = array(
                'isNewUser' => true
            );
            return Resp::ok($ret);
        } else {                                                                # 2.如果玩家已存在，则处理普通登录流程
            req()->userInfo = $userInfo;                                         # 给Req挂载userInfo
            UserProc::checkContidays();                                     # 连续登录,状态检查
            PayProc::m_refreshChargeOrders();                                   # 刷新订单, 多平台版本
            PayProc::checkDeltest();                                        # 检查内侧充值记录(函数内部会只检查一次)
            UserProc::updateUserInfo();                                         # 这一步回存操作只有在 userInfo正常存在的情况下才进行
            $resp = Resp::ok($userInfo);                                        # 设置返回值
            self::backupUserInfo();                                         # 数据回写
            self::updtateUserZoneInfo();                                    # 1. 更新玩家分区记录
        }
        return $resp;
    }

    /**
     * 6003 领取连续登录奖励 
     */
    public static function acceptContiDaysGift() {
        $resp = Resp::err(ErrCode::err_method_notimplement);

        return $resp;
    }

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

    /**
     * 检查昵称是否已经存在
     * @param string $roleName
     * @return boolean
     */
    static function checkRoleNameNotExist($roleName) {
        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::tsDay() - TimeUtil::tsDay(req()->userInfo->game->baseInfo->lastLogin); // 对比登录日期
        if ($ret > 0) {                                                         # 当天第一次登录
            self::OnNewDay($isnew);
        } else {                                                                # 更新下登录次数记录(任务计数器)
        }
        if ($ret == 1) {                                                        # 连续登录
        } else if ($ret >= 2) {                                                 # 隔天登录
        }
        req()->userInfo->game->baseInfo->lastLogin = now();                     # 更新下访问时间
        TaskProc::ResetDailyTask();                                             # 重置每日任务
        TaskProc::OnUserLogin();
        return $ret;
    }

    /**
     *  处理当天第一次登录 
     * @param bool $isnew Description
     */
    static function OnNewDay($isnew) {
        self::updatePlatUserRecord($isnew);                               # 添加到到当天活跃玩家记录集合
        ActiveProc::DailyReset();                                               # 抽奖、每日任务状态,计数器...
        FightProc::ClearByDay();                                                # 战斗相关状态每日清理
        ShopProc::DailyCheck();
    }

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

    /**
     * 创建用户 
     * @return UserInfoMo
     */
    static function createUser($rolename, $gender, $profile_img) {
        $userInfo = new UserInfoMo();
        req()->userInfo = $userInfo;                                             # 更新Req的userinfo,
//        $userInfo->uid = req()->uid;
//        $userInfo->zoneid = req()->zoneid;
        $userInfo->game = new Data_UserGame();
        $userInfo->game->initialize();                                          # 初始化玩家数据
        $userInfo->game->baseInfo->name = $rolename;
        $userInfo->game->baseInfo->gender = $gender;
        $userInfo->game->baseInfo->headImg = $profile_img;
        $userInfo->game->baseInfo->firstLogin = now();
//        $userInfo->game->pvp->socre = PVPProc::_getScore_by_uid($req, $req->uid); # 初始化pvp积分
//        $userInfo->game->NewbieGuideOver = 1;
//        UserProc::_intiHeroManual($userInfo->game);                             # 初始化图鉴 
#Ps 6006是没有获得到Userinfo到Req中的
        UserProc::checkContidays(1);                                            # 每日状态检查
//        UserProc::fetchFromInteract($mem, $req);  # 从interact拉取数据，Ps.初始化的过程应该还拉取不到什么有效数据
        UserProc::updateUserInfo();                                         # 回存用户数据
//        FriendProc::Init($req);
//        StatProc::                                                            ...
//        GoalsDailyProc::dealDailyLogin($mem, $req, $resp);                   # 每日登录任务

        return $userInfo;
    }

    /**
     * 整理平台玩家记录集 
     * @param int $isnew
     */
    private static function updatePlatUserRecord($isnew = 0) {
        $zoneid = req()->zoneid;
        $uid = req()->uid;
        $user = req()->userInfo->game->baseInfo;
        $day = tsDay();
        $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 UserInfoMo 
     */
    public static function getUserInfo($zoneid, $uid) {
        $key = MemKey_User::Info_hash($zoneid, $uid);
        $a = new Data_UserGame();
        if (null == $a->readDataFromMem($key)) {
            return null;
        }
        $usrInfo = new UserInfoMo();
//        $usrInfo->zoneid = $zoneid;
//        $usrInfo->uid = $uid;
        $g = new Data_UserGame($a);
        $usrInfo->game = $g;
        return $usrInfo;
    }

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

    /**
     * 回写玩家数据
     * @param UserInfoMo $userInfo
     */
    public static function setUserInfo($userInfo) {
        if ($userInfo) {
            $zoneid = req()->zoneid;
            $uid = req()->uid;
            $userInfo->game->baseInfo->lastSaveTs = now();
            $key = MemKey_User::Info_hash($zoneid, $uid);
            return $userInfo->game->updateData($key);
        }
        return false;
    }

// </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 (isset($req->userInfo)) {                                            # 防御确保玩家数据不为空
            $level = $req->userInfo->game->baseInfo->level;
            $playerName = $req->userInfo->game->baseInfo->name;
            $headImg = $req->userInfo->game->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::tsDay();                                             # 精确到天,保留10个最近记录.
        $value = base64_encode(gzdeflate(JsonUtil::encode(req()->userInfo)));    # blob数据,序列化下
        $sql = sprintf("call insertUserInfo('%s', %d, %d, '%s');", #            # 所以直接将序列化后的结果存进去吧,
                req()->uid, req()->zoneid, $tsday, $value);                       # zoneid, uid, tsday
        daoInst()->exec($sql);                                                  # 也可以用exec($sql)
    }

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