<?php
/**
 * 单人练习赛
 */
namespace Game\Model\Pvp\Mode;

use Framework\Log\LogMark;
use Framework\DB\Handler\PlayerDBHandler;
use Framework\Logic\PacketCacheLogic;
use Framework\Logic\TemplateHelp;
use Game\Config\GameConfig;
use Game\Constant\ClientErrorCode;
use Game\Constant\ConstTemplate\TemplateCar;
use Game\Constant\GameConstantDefine;
use Game\Constant\GameErrorCode;
use Game\Constant\TemplateDefine;
use Game\Data\PvpPacket\PvpMsgData;
use Game\Data\RaceResultData;
use Game\Data\RecordData;
use Game\Data\RoomData;
use Game\Logic\CarLogic;
use Game\Logic\ItemLogic;
use Game\Logic\Pvp\PvpLogic;
use Game\Logic\Pvp\PvpMatchLogic;
use Game\Logic\Pvp\PvpSettlementLogic;
use Game\Method\Player\AddPlayerExp;
use Game\Model\Pvp\PvpBaseModel;
use Game\Model\RoomModel;
use Game\Protobuf\CarShow;
use Game\Protobuf\GCCancelMatch;
use Game\Protobuf\GCRaceResult;
use Game\Protobuf\GCStartMatching;
use Game\Protobuf\GMJoinMatch;
use Game\Protobuf\PacketId;
use Game\Protobuf\PlayerInfo;
use Game\Protobuf\GCPVPMatchingSuccess;
use Game\Protobuf\PlayerShow;
use Game\Protobuf\RewardItem;
use Game\Protobuf\RoomPlayers;

class SinglePracticeModel
{
    use PlayerDBHandler;
    use TemplateHelp;
    use CarLogic;
    use ItemLogic;
    use PvpLogic;
    use PacketCacheLogic;
    use PvpMatchLogic;
    use PvpSettlementLogic;
    use AddPlayerExp;

    public int $playerId;

    private bool $isWin = false;            //胜负标志
    private int $carPositioning;            //车辆定位
    private ?array $constConfigData;        //配置表
    private int $joinId;
    private array $proficiencyArray = array();  //熟练度获取详情

    private array $sendRaceResultList = array();
    private GCRaceResult $sendMessage;
    public GCStartMatching $startMatchingMsg;
    public GCCancelMatch   $cancelMatchMsg;

    public function __construct()
    {
        $this->startMatchingMsg = new GCStartMatching();
        $this->cancelMatchMsg = new GCCancelMatch();
    }

    public function setPlayerId($playerId)
    {
        $this->playerId = $playerId;
    }

    // 加入匹配消息处理
    public function sendJoinMatchPacket(int $joinID, RoomData $room, bool $isRand): bool
    {
        $playerID = $this->playerId;
        $serverID = GameConfig::getInstance()->SERVER_ID();
        $playerInfo = [];
        //生成匹配数据
        foreach($room->playerInfo as $id => $data) {
            $player = new RoomPlayers();
            $player->setPlayerID($id);
            $player->setPlayerShow($this->makePlayerShowString($id, $data));
            $player->setCarShow($this->makeCarShowString($id, $data, $room->mapID));
            $playerInfo[] = $player;
        }

        // 通过相应的protobuf 组装发送匹配数据
        $match = new GMJoinMatch();
        $match->setPlayerID($playerID);
        $match->setServerID($serverID);
        $match->setJoinID($joinID);
        $match->setMapID($room->mapID);
        $match->setMatchType($room->modeType);
        $match->setPlayer($playerInfo);
        $match->setMaxTime($this->getMatchMaxTime($room->modeType));
        $match->setNeedPlayerNum(0);
        $match->setNeedTargetNum(6 - count($playerInfo));
        //发送匹配消息
        if($this->sendJoinMatchingPacket($match)) {
            //发送成功
            LogMark::getInstance()->markInfo(
                "[PVPFlowLog][2][SinglePracticeModel] send to redis, joinMatching success",
                (array)$room + ['joinID' => $joinID]
            );
            $this->joinRoomMsg(ClientErrorCode::CLIENT_SUCCESS);
            $this->addJoinRoomMsg($room->playerInfo);
            $room->setPlayType(RoomModel::ROOM_GAME_MATCH_OR_PLAY);
            if($isRand) {
                //随机地图房间 地图继续显示随机
                $room->mapID = PvpBaseModel::RandMapNum;
            }
            if(!$room->saveDB()) {
                LogMark::getInstance()->markError(
                    GameErrorCode::DATA_UPDATE_ERROR,
                    "[PVPFlowLog][SinglePracticeModel] joinMatching change room play type fail",
                    (array)$room
                );
                return false;
            }
        } else {
            //发送失败
            LogMark::getInstance()->markError(
                GameErrorCode::DATA_UPDATE_ERROR,
                "[PVPFlowLog][SinglePracticeModel]send to redis fail, joinMatching fail",
                (array)$room
            );
            $this->joinRoomMsg(ClientErrorCode::PVP_JOIN_MATCH_ERROR);
            $this->joinRoomMsgSend();
            return false;
        }
        return true;
    }

    // 匹配返回结果处理
    public function pvpMatchingRecv(array $msgData): bool
    {
        $playerList = $msgData[PvpMsgData::PLAYER_LIST];
        $MatchRecvMsg = new GCPVPMatchingSuccess();
        $MatchRecvMsg->setIp($msgData[PvpMsgData::IP]);
        $MatchRecvMsg->setPort($msgData[PvpMsgData::PORT]);
        $MatchRecvMsg->setSceneId($msgData[PvpMsgData::MAP_ID]);
        $MatchRecvMsg->setMatchType(GameConstantDefine::MATCH_MODE_SINGLE_PRACTISE);
        $players = array();
        $joinID = 0;
        foreach ($playerList as $playerData) {
            if($this->playerId == $playerData[PvpMsgData::PLAYER_ID]) {
                $joinID = (int)$playerData[PvpMsgData::JOIN_ID];
            }
            /**
             * @var PlayerShow $playerShow
             */
            $playerShow = $playerData[PvpMsgData::PLAYER_SHOW];
            /**
             * @var CarShow $carShow
             */
            $carShow = $playerData[PvpMsgData::CAR_SHOW];
            $player = new PlayerInfo();
            $player->setPlayerId($playerData[PvpMsgData::PLAYER_ID])
                ->setNickName($playerShow->getNickName())
                ->setHead($playerShow->getHead())
                ->setCarTplId($carShow->getCarTplID())
                ->setGroupID($playerData[PvpMsgData::GROUP_ID])
                ->setExteriorRefit($carShow->getExteriorRefit())
                ->setGender($playerShow->getGender())
                ->setCloth($playerShow->getCloth())
                ->setSkinColor($playerShow->getSkinColor())
                ->setProficiencyLv($carShow->getProficiencyLv())
                ->setProficiency($carShow->getProficiency())
                ->setContinuityWin($playerShow->getContinuityWin())
                ->setLevel($playerShow->getLevel())
                ->setDan($playerShow->getDan());
            $players[] = $player;
        }
        $MatchRecvMsg->setJoinId($joinID);
        $MatchRecvMsg->setPInfo($players);
        $MatchRecvMsg->setAInfo($msgData[PvpMsgData::AI_INFO]);
        if ($this->addPacket(PacketId::GC_PVPMatchingSuccess, $MatchRecvMsg, $this->playerId)) {
            LogMark::getInstance()->markInfo(
                '[PVPFlowLog][11][SinglePracticeModel] match msg add packet success:',
                array("packet" => $MatchRecvMsg->serializeToJsonString()));
            $changeData = array(
                RecordData::MATCHING_DATA => $MatchRecvMsg->serializeToJsonString()
            );
            $this->updateRecordData($this->playerId, $joinID, $changeData);
            return true;
        }
        LogMark::getInstance()->markInfo(
            '[PVPFlowLog][SinglePracticeModel] match msg add packet fail:',
            array("packet" => $MatchRecvMsg->serializeToJsonString()));
        return false;
    }

    /**
     * PVP结算--单人练习赛
     * @param array $playerList [playerId=>raceResultsData]
     * @param array $breakingRecordInfo [playerId=>RecordBreakingInfo]
     * @return bool
     */
    public function matchSettlement(array $playerList, array $breakingRecordInfo): bool
    {
        LogMark::getInstance()->markDebug(
            "[PVPFlowLog][SinglePracticeModel] start pvp single practice matchSettlement",
            $playerList
        );
        //获取配置
        $this->constConfigData = $this->getTable(TemplateDefine::TYPE_CONST);
        if (is_null($this->constConfigData)) {
            LogMark::getInstance()->markWarn(
                GameErrorCode::TEMPLATE_ID_NOT_FOUND,
                "[PVPFlowLog][SinglePracticeModel] TYPE_CONST tpl table not found",
                array(
                    "TemplateTable" => TemplateDefine::TYPE_CONST
                )
            );
            return false;
        }
        $myRaceResult = null;
        $pIdRank = [];      //playerId=>rank
        $allPlayerParameter = array();  //结算参数
        $no1PlayerId = null;
        foreach ($playerList as $pid => $raceResult) {
            /**
             * @var RaceResultData $raceResult
             */
            //玩家自己结算消息处理
            if ($pid == $this->playerId) {
                $myRaceResult = $raceResult;
                $this->joinId = $raceResult->_joinId;
            }
            if ($raceResult->_rank == 1) {
                $no1PlayerId = $raceResult->_playerId;
            }
            $pIdRank[$raceResult->_playerId] = $raceResult->_rank;
            //添加战斗通用返回消息
            $this->addRaceResultMessage($raceResult);
            $allPlayerParameter[] = $raceResult->parameter;
        }
        //玩家比赛结算
        $record = $this->matchSettlementPlayer($myRaceResult, $pIdRank);

        //每个玩家结算参数
        $this->sendMessage->setParameter($allPlayerParameter);
        //破记录信息
        $this->sendMessage->setMyBreakingInfo($breakingRecordInfo[$this->playerId]);
        //单人模式为第一名数据
        $this->sendMessage->setMvpBreakingInfo($breakingRecordInfo[$no1PlayerId]);
        //发送消息
        $this->sendMessage($this->playerId);
        //记录打点日志
        $this->addCompletaMatchLog($record, $myRaceResult, $playerList);
        return true;
    }

    //玩家比赛结算
    public function matchSettlementPlayer(RaceResultData $raceResult, array $pIdRank): RecordData
    {
        //检查是否获胜
        if ($raceResult->_rank <= 3) {
            $this->isWin = true;
        }
        //车辆信息 获取玩家当前默认的车
        $carConfigData = $this->getTitle(TemplateDefine::TYPE_CAR, $raceResult->_carId);
        $this->carPositioning = $carConfigData[TemplateCar::Positioning];

        //获取道具奖励
        $reward = array();
        $this->getReward($reward, $raceResult->_carId);
        // 比赛模式获取道具
        $this->getModeReward($reward, $raceResult->_modeType);
        $rewardMessage = array();
        $this->setLogAddItemSource(GameConstantDefine::ITEM_ADD_SOURCE_PVP_SETTLEMENT);
        $this->gainItemArr($reward);
        foreach ($reward as $itemId => $num) {
            //道具奖励消息
            $ri = new RewardItem();
            $ri->setId($itemId);
            $ri->setNum($num);
            $rewardMessage[] = $ri;
        }

        //车辆熟练度
        $carProficiency = $this->getCarProficiency($raceResult, $raceResult->_rank <= 3);
        $this->addCarProficiency($raceResult->_carId, $carProficiency, $raceResult->_modeType);

        // 经验
        $exp = $this->getAccountLvExp($raceResult->_rank <= 3, $raceResult->_modeType);
        $this->addPlayerExp($exp, $raceResult->_modeType);

        //计算车辆评分
        $carScore = $this->getCarScore($raceResult, $carProficiency);

        //添加玩家结算奖励消息
        $this->sendMessage = new GCRaceResult();
        $this->sendMessage->setCarProficiencyArray($this->proficiencyArray);
        $this->sendMessage->setAddExp($exp);
        $this->sendMessage->setAddCarProficiency($carProficiency);
        $this->sendMessage->setCarScore($carScore);
        $this->sendMessage->setItem($rewardMessage);

        //比赛统计数据
        $this->tidyStatisticsData($raceResult);
        $record = $this->getRecordData();
        $this->sendMessage->setMatchId($record->matchUID);
        //更新排行榜
        $this->changeRankMap($raceResult->_costTime, $record->mapID);
        $this->updateRankData($raceResult);
        //更新房间状态
        $this->changePlayerAndRoomState($record->roomID);
        //更新比赛记录
        $this->updateRecord($raceResult, $this->playerId);
        //清空比赛状态
        $this->clearJoinId();
        //更新好友比赛记录
        $this->updateLastGame($pIdRank, $this->playerId, $record->mapID, $record->matchingData);
        //触发任务
        $this->triggerFinishPVPEvent($raceResult, $record, $this->isWin, $this->playerId);
        return $record;
    }


    //匹配消息
    public function joinRoomMsg(int $code) {
        $this->startMatchingMsg->setCode($code);
    }

    public function joinRoomMsgSend() {
        $this->addPacket(PacketId::GC_StartMatching, $this->startMatchingMsg, $this->playerId);
    }

    public function addJoinRoomMsg(array $players) {
        foreach($players as $id => $p) {
            $this->addPacket(PacketId::GC_StartMatching, $this->startMatchingMsg, $id);
        }
    }
}