<?php

/**
 * 玩家邮件
 */

namespace Game\Model;

use Framework\Log\LogMark;
use Framework\Network\SendMessage;
use Framework\DB\Handler\PlayerDBHandler;
use Framework\Lib\Utils;
use Framework\Logic\CommonMailLogic;
use Framework\Logic\PacketCacheLogic;
use Framework\Logic\TemplateHelp;
use Framework\Logic\TimerLogic;
use Game\Config\GameConfig;
use Game\Constant\ClientErrorCode;
use Game\Constant\ConstTemplate\TemplateConst;
use Game\Constant\ConstTemplate\TemplateMail;
use Game\Constant\DBTableDefine;
use Game\Constant\GameErrorCode;
use Game\Constant\ModelTypeDefine;
use Game\Constant\TemplateDefine;
use Game\Data\AccountData;
use Game\Data\MailData;
use Game\Logic\AccountLogic;
use Game\Protobuf\GCMailUpdate;
use Game\Protobuf\MailReward;
use Game\Protobuf\MailSingle;
use Game\Protobuf\PacketId;
use Game\Protobuf\ReplaceString;
use Exception;

class MailModel
{
    use PlayerDBHandler;
    use TimerLogic;
    use TemplateHelp;
    use PacketCacheLogic;
    use CommonMailLogic;
    use AccountLogic;

    public int $playerId;
    private array $sendMailList;    //给自己的
    private array $targetSendMailList;  //给别人的
    private GCMailUpdate $sendMailUpdateMessage;
    private array $enclosureDict;   //附件道具 itemId=>num
    private array $mailConfigDict;

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

    public function __construct()
    {
        $this->sendMailList = array();
        $this->targetSendMailList = array();
        $this->enclosureDict = array();
        $this->sendMailUpdateMessage = new GCMailUpdate();
    }

    /**
     * @param int $tplId 表id
     * @param int $receiveTime 发送时间
     * @param array $enclosure 内含道具  array(itemId=>num,..)
     * @param int $expireTime 失效时间：  time() + 失效时间
     * @param int $toPlayerId 发送给哪个玩家
     * @param int $fromPlayerId 来源,系统为0
     * @param string $content 邮件内容
     * @param string $replaceString 替换内容 json 部分邮件使用 部分会读表
     * @return bool
     */
    public function createMail(int $tplId, int $receiveTime, array $enclosure, int $expireTime,
                               int $toPlayerId, int $fromPlayerId, string $content, string $replaceString): bool
    {
        if($toPlayerId == 0) {
            $toPlayerId = $this->playerId;
        }
        $mail = new MailData($toPlayerId, $this->makeMailId());
        $mail->mailTplId = $tplId;
        $mail->content = $content;
        $mail->replaceString = $replaceString;
        $mail->fromPlayerId = $fromPlayerId;
        if ($receiveTime == 0) {
            $receiveTime = Utils::getServerTimestamp();
        }
        $mail->receiveTime = $receiveTime;
        $configMail = $this->getMailConfig($tplId);
        if ($configMail[TemplateMail::Type] == TemplateMail::MAIL_TYPE_WITH_REWARD && !empty($enclosure)) {
            //检查附件
            $mail->enclosure = json_encode($enclosure);
        }
        //添加过期时间
        if ($expireTime == 0) {
            $expireTime = (int)$this->getTerm(TemplateDefine::TYPE_CONST,
                                               TemplateConst::Const_15_MailExpireTime,
                                               TemplateConst::ConstNum);

        }
        $mail->expireTime = $receiveTime + $expireTime;
        if ($mail->createMail()) {
            if($this->playerId != $toPlayerId) {
                //给其他玩家发邮件
                $tmpPlayerId = $this->playerId;
                $this->playerId = $toPlayerId;
                $this->addTimer(ModelTypeDefine::MAIL, $mail->uid, $mail->expireTime, $toPlayerId);
                $this->addSendMailMessage($mail, false);
                $this->delTopMail();
                $this->playerId = $tmpPlayerId;
                return true;
            }
            $this->addTimer(ModelTypeDefine::MAIL, $mail->uid, $mail->expireTime);
            $this->addSendMailMessage($mail);
            $this->delTopMail();
            return true;
        }
        return false;
    }

    public function getAllMailUid(int $start, int $stop): array
    {
        $result = array();
        if(!$this->searchRankByIndex(DBTableDefine::TABLE_MAIL, $this->playerId, $start, $stop, $result)) {
            return array();
        }
        return $result;
    }

    //删除超过上限邮件
    private function delTopMail()
    {
        $result = 0;
        $this->searchRankNum(DBTableDefine::TABLE_MAIL, $this->playerId ,$result);
        $hasMailNum = intval($result);
        $configMailMaxNum = $this->getTerm(TemplateDefine::TYPE_CONST,
                                    TemplateConst::Const_16_MailMaxNum,
                                    TemplateConst::ConstNum);
        if ($hasMailNum > $configMailMaxNum) {
            $mailList = $this->getAllMailUid($configMailMaxNum, -1);
            foreach ($mailList as $uid => $expireTime) {
                $mail = new MailData($this->playerId, $uid);
                $this->deleteMail($mail);
            }
        }
    }

    public function getSingleMail(int $uid) :?MailData
    {
        $mail = new MailData($this->playerId, $uid);
        if ($mail->getMailData()) {
            return $mail;
        }
        return null;
    }

    //校验邮件合法
    public function checkMailLegal(MailData $mail): int
    {
        $now = Utils::getServerTimestamp();
        if ($mail->mailTplId == 0 ||
            $mail->receiveTime == 0 ||
            !property_exists($mail, MailData::DB_KEY_MAP[MailData::EXPIRE_TIME]) ||
            $now > $mail->expireTime
        ) {
            return ClientErrorCode::MAIL_IS_EXPIRE;
        }
        if ($mail->status == MailData::STATUS_DEL) {
            LogMark::getInstance()->markWarn(
                GameErrorCode::MAIL_STATUS_ERROR,
                '[MailModel] mail status is del,but still in db',
                array(
                      "uid" => $mail->uid,
                      "playerId" => $this->playerId,
                      "mail" => $mail
                  )
            );
            return ClientErrorCode::MAIL_IS_EXPIRE;
        }
        return ClientErrorCode::CLIENT_SUCCESS;
    }

    public function updateMailStatus(MailData $mail, int $status)
    {
        $mail->status = $status;
        $mail->saveDB();
        $this->addSendMailMessage($mail);
    }

    public function deleteMail(MailData $mail)
    {
        $mail->status = MailData::STATUS_DEL;
        $mail->delMail();
        $this->delTimer(ModelTypeDefine::MAIL, $mail->uid);
        $this->addSendMailMessage($mail);
    }

    /** 处理过期邮件
     * @param array $expireData (uid->expireTime)
     * @return bool
     */
    public function timerExpire(array $expireData): bool
    {
        if(empty($expireData)) {
            LogMark::getInstance()->markWarn(GameErrorCode::TIMER_EXPIRE_DATA_ERROR,
                                              '[MailModel] timerExpire array empty!');
            return false;
        }
        foreach ($expireData as $uid => $expireTime) {
            //删除邮件
            $mail = new MailData($this->playerId, $uid);
            $mail->getMailData();
            $mail->status = MailData::STATUS_DEL;
            $mail->delMail();
            $this->addSendMailMessage($mail);
        }
        $this->sendMessage();
        return true;
    }

    //同步系统邮件
    public function syncCommonData(int $myIndex, int $commonIndex)
    {
        if ($myIndex >= $commonIndex) {
            return;
        }
        $indexArr = range($myIndex + 1, $commonIndex);
        $commonMailList = $this->getCommonMailByIndex($indexArr);
        //获取玩家注册时间
        $createTime = $this->getAccountProperty($this->playerId, AccountData::DB_CREATE_TIME);
        if (is_null($createTime)) {
            $createTime = Utils::getServerTimestamp();
        }
        $createTime = intval($createTime);
        foreach ($commonMailList as $idx => $commonMailStr) {
            if (false == $commonMailStr) {
                continue;
            }
            $commonMail = json_decode($commonMailStr, true);
            //检查系统邮件创建时间 比玩家注册时间早的 玩家不能添加
            $commonMailCreateTime = $commonMail[MailData::RECEIVE_TIME];
            if ($commonMailCreateTime < $createTime) {
                continue;
            }
            $this->createMail(
                $commonMail[MailData::MAIL_TPL_ID],
                $commonMailCreateTime,
                array(),
                0,
                0,
                0,
                "",
                "[]"
            );
        }
    }

    public function addSendMailMessage(MailData $mail, bool $myMail = true)
    {
        $sendMail = new MailSingle();
        $sendMail->setMailId($mail->uid);
        $sendMail->setMailTableId($mail->mailTplId);
        $sendMail->setState($mail->status);
        $sendMail->setRecvTime($mail->receiveTime);
        $sendMail->setContent($mail->content);
        $sendMail->setPlayerID($mail->fromPlayerId);
        $repString=  new ReplaceString();
        try {
            $repString->mergeFromJsonString($mail->replaceString);
        } catch (Exception $e) {
        }
        $sendMail->setRepString($repString);

        // 检查附件
        $enclosure = array();
        $reward = json_decode($mail->enclosure, true);
        foreach ($reward as $itemId => $num) {
            $mr = new MailReward();
            $mr->setItemId($itemId);
            $mr->setNum($num);
            $enclosure[] = $mr;
        }
        $sendMail->setEnclosure($enclosure);
        if ($myMail) {
            $this->sendMailList[] = $sendMail;
        } else {
            $this->targetSendMailList[] = $sendMail;
        }
    }

    public function sendMessage()
    {
        if (empty($this->sendMailList)) {
            return;
        }
        $this->sendMailUpdateMessage->setMail($this->sendMailList);
        SendMessage::getInstance()->sendClient(PacketId::GC_MailUpdate, $this->sendMailUpdateMessage);
    }

    public function addPacketMessage(int $toPlayerId)
    {
        if (!empty($this->sendMailList)) {
            $this->sendMailUpdateMessage->setMail($this->sendMailList);
            $this->addPacket(PacketId::GC_MailUpdate, $this->sendMailUpdateMessage, $this->playerId);
            $this->sendMailList = array();
        }
        if (!empty($this->targetSendMailList) && $toPlayerId != 0) {
            $this->sendMailUpdateMessage->setMail($this->targetSendMailList);
            $this->addPacket(PacketId::GC_MailUpdate, $this->sendMailUpdateMessage, $toPlayerId);
            $this->targetSendMailList = array();
        }
    }


    //检查是否有附件 true有附件
    public function checkHasEnclosure(MailData $mail): bool
    {
        $configMail = $this->getMailConfig($mail->mailTplId);
        //默认邮件--奖励在表里
        if ($configMail[TemplateMail::Type] == TemplateMail::MAIL_TYPE_DEFAULT) {
            return !empty($configMail[TemplateMail::Enclosure]);
        }
        if ($configMail[TemplateMail::Type] == TemplateMail::MAIL_TYPE_WITH_REWARD) {
            return $mail->enclosure != "[]";
        }
        return false;
    }

    //整理附件道具
    public function formatEnclosure(MailData $mail)
    {
        $configMail = $this->getMailConfig($mail->mailTplId);
        $tmpReward = array();
        if ($configMail[TemplateMail::Type] == TemplateMail::MAIL_TYPE_DEFAULT) {
            $tmpReward = $configMail[TemplateMail::Enclosure];
        } elseif ($configMail[TemplateMail::Type] == TemplateMail::MAIL_TYPE_WITH_REWARD &&
                    $mail->status < MailData::STATUS_OVER ) {
            $tmpReward = json_decode($mail->enclosure, true);
        }
        foreach ($tmpReward as $itemId => $num) {
            isset($this->enclosureDict[$itemId]) ?
                $this->enclosureDict[$itemId] += $num :
                $this->enclosureDict[$itemId] = $num;
        }
    }

    public function getEnclosure(): array
    {
        return $this->enclosureDict;
    }

    // 创建唯一id
    public function makeMailId(): int
    {
        $index = $this->getUidIndex();
        return Utils::makeObjectID(GameConfig::getInstance()->SERVER_ID(), DBTableDefine::TABLE_MAIL, $index);
    }

    private function getMailConfig(int $tplId): array
    {
        if (isset($this->mailConfigDict[$tplId])) {
            return $this->mailConfigDict[$tplId];
        }
        $configMail = $this->getTitle(TemplateDefine::TYPE_MAIL, $tplId);
        if (is_null($configMail)) {
            LogMark::getInstance()->markWarn(GameErrorCode::TEMPLATE_ID_NOT_FOUND,
                                             '[MailModel] find mailTpl not found',
                                             array(
                                                 "tplId" => $tplId
                                             )
            );
            return array();
        }
        $this->mailConfigDict[$tplId] = $configMail;
        return $configMail;
    }
}
