<?php

namespace loyalsoft;

include_once __DIR__ . '/../main.php';                                          # 导入主要工具库

/**
 *  API入口
 * @author gwang (mail@wanggangzero.cn)
 */
class AppServer {

    /**
     * The Main Entrance of App
     * @return Resp
     */
    public function api() {
        $uniq = uniqid();                                                       # 自己生成一个唯一串，作为此次处理过程的标记。
        $GLOBALS['deal-uid'] = $uniq;                                           # 写入日志的时候带上，方便辨认同一个处理过程.
        $GLOBALS['zoneid'] = req()->zoneid;                                     # 暂时未想到更好的方案
        $err = self::BeforeProc();                                              # 预处理
        my_Assert(ErrCode::ok == $err, $err);                                   # 如果tk校验未通过
        $retry = 0;
        do {
            $retry++;
            if ($retry > 1) {
                new Req(req()->toString());                                     # 如果是重试,需要重新刷新下req的数据
                Resp::$ext_events = array();                                    # fix:客户端发现event播放2次的情况.
            }
            $resp = self::Route();                                              # 分发逻辑
            if ($retry > 12) {                                                  # 最大重试次数
                Err(ErrCode::err_retry, "数据写入失败, 重试{$retry}次");
                break;
            }
        } while (!self::AfterProc($resp));                                      # 执行后处理逻辑
        return $resp;
    }

    /**
     * 预处理逻辑
     */
    private static function BeforeProc() {
        my_Assert(!config::Inst()->isBaned(req()->uid), ErrCode::err_server_updating); # 检查封号
        if (self::isUpdating()) {                                               # 检查是否处于更新阶段，暂停对玩家请求的响应。
            if (req()->uid && !config::Inst()->isTester(req()->uid)) {                               # 排除测试号
                return ErrCode::err_server_updating;
            }
        }
        $ssd = GameConfig::service_schedule_getItem(1);                         # 服务计划(固定只有1条)
        if (now() > $ssd->startts && now() < $ssd->endts) {                     # 在维护期间
            Err(ErrCode::err_server_maintaining, $ssd->reason);
        }
//        if (!Index::$isDebugging && abs(now() - req()->ts) > OFFSET_MSGTIME) {       # 如果服务端客户端时间戳超过规定误差，则消息非法
//            return ErrCode::err_outtime;
//        }
        // todo: 检查clientVersion 判断是否应该强制升级
        return self::checkVersion();                                            # 最后检查下客户端版本
    }

    /**
     * 后处理逻辑
     * @param Resp $resp
     */
    private static function AfterProc(&$resp) {
        if (req()->userInfoChanged || (null != ctx() && ctx()->NeedSave())) {   # 回写玩家数据
            if (!UserProc::setUserInfo(ctx())) {                                # 失败, 改写返回值
                return false;
            }
        }
        $resp->AfterProc();

        self::LogCmdMongo($resp);                                               # 向MongoDB写入操作日志
        CLog::flush();                                                          # flush日志
        return true;
    }

    /**
     * 新版: 使用pdo_mysql+dao版本
     * @param Resp $resp
     */
    private static function LogCmd($resp) {
        $tablename = 'tab_op_log' . date('Ymd');                                # 今天的表名
        $old_tablename = 'tab_op_log' . date('Ymd', now(-86400 * 21));          # 待删除的表名 日志保留21天
        $sql = sprintf('create table if not exists %s like `tpl_op_log_tab`;', $tablename); # 创建今天的表
        $sql .= sprintf('drop table if exists %s;', $old_tablename);            # 循环删除以前的表
        $sql .= sprintf("insert into %s (`uid`,`zoneid`,`cmd`,`days`,`param`,`ret`) values ('%s', %d, %d, %d, '%s', '%s');", #
                $tablename, req()->uid, req()->zoneid, req()->cmd, #            # Uid, zoneid, cmd
                (null != ctx() ? (totalDays() - totalDays(ctx()->baseInfo->firstLogin)) : 0), #  # ps.留存天数
                JsonUtil::encode(req()->paras), #                               # req->paras
                JsonUtil::encode($resp->result)); #                             # resp->result
        daoInst()->exec($sql);                                                  # 执行sql
    }

    /**
     * 新版: MongoDB版本
     * @param Resp $resp
     */
    private static function LogCmdMongo($resp) {
        $tablename = 'tab_op_log' . date('Ym');                        # 当月的日志表名
        $arr = array(
            'uid' => req()->uid,
            'req' => req()->storage(),
            'ret' => $resp,
            'ts' => TimeUtil::dtCurrent(),
        );
        gMongo()->insert($tablename, $arr);
    }

    /**
     * 路由方法
     */
    private static function Route() {
        $opc = substr(req()->cmd, 0, 2);
        $proc = OpeCode::getProc($opc);                                   # 映射处理模块.
//        DebugHelper::print_stack_trace();
        my_Assert($proc != "err", ErrCode::ope_err);                            # 未能找到对应的处理模块
        try {
            $resp = call_user_func(array($proc, 'procMain'), req());            # 调用对应的处理逻辑
            if (!($resp instanceof Resp)) {                                     # 异常返回值
                Err(ErrCode::err_innerfault, JsonUtil::encode($resp));
            }
            return $resp;                                                       # 返回值
        } catch (\Exception $ex) {                                              # 异常信息写入日志
            $msg = CommUtil::str2UTF8($ex->getMessage());                       # 异常信息转下码
            Err(ErrCode::err_unknownn, "call_user_func got Exception: $msg");   # 返回给客户端
        }
    }

    /**
     *  返回是否处于更新中
     * @return boolean
     */
    private static function isUpdating() {
        $ts = now();
        if ($ts > glc()->updatingBeginTs && $ts < glc()->updatingEndTs) {
            return true;
        }
        return false;
    }

    /**
     * 检查客户端版本是否需要强制更新
     * @return int/ErrCode
     */
    private static function checkVersion() {
        $ret = ErrCode::ok;
        if (req()->CV == "unityEditor" || req()->CV == "unity") {
            return $ret;
        }
        if (req()->CV == "nil") {
            return ErrCode::clientversionlow_err;
        }

        $limit_Ver = "1.1.11";                                                  # 限制客户端最低版本
        $clientArr = CommUtil::split(req()->CV, ".");
        $serverArr = CommUtil::split($limit_Ver, ".");
        $client = $clientArr[0] * 1000 * 1000 + $clientArr[1] * 1000 + $clientArr[2];
        $server = $serverArr[0] * 1000 * 1000 + $serverArr[1] * 1000 + $serverArr[2];
        if ($server > $client) {                                                # 更新游戏状态
            if ($clientArr[0] < $serverArr[0]                                   # 主版本号小
                    || $clientArr[1] < $serverArr[1]) {                         # 或者次版本号小
                $ret = ErrCode::clientversionlow_err;                           # 返回错误码，强制更新1
                req()->updateInfo = "请更新游戏后再继续!";
            } else if ($clientArr[2] < $serverArr[2]                            # 小版本号不一致
                    && req()->cmd == CmdCode::cmd_user_getzonelist) {           # 只有在请求分区列表的时候提示下, 其他消息不强制更新
                req()->updateInfo = "游戏有更新!";                               # 提示更新，可以咱不更新
                $ret = ErrCode::clientversionlow_err;                           # 返回错误码，强制更新1
            }
        }
        return $ret;
    }
}
