<?php

/**
 * 盲盒商店
 */

namespace Game\Logic\Shop;

use Framework\Lib\Utils;
use Framework\Log\LogMark;
use Framework\MVC\ModelManager;
use Game\Constant\ClientErrorCode;
use Game\Constant\ConstTemplate\TemplateBlindBox;
use Game\Constant\ConstTemplate\TemplateConst;
use Game\Constant\ConstTemplate\TemplateItem;
use Game\Constant\ConstTemplate\TemplateShopBlindBox;
use Game\Constant\ConstTemplate\TemplateShopCar;
use Game\Constant\GameConstantDefine;
use Game\Constant\GameErrorCode;
use Game\Constant\ModelTypeDefine;
use Game\Constant\TemplateDefine;
use Game\Data\Shop\ShopBlindBoxData;
use Game\Logic\CarLogic;
use Game\Logic\ItemLogic;
use Game\Logic\MoneyLogic;
use Game\Model\Car\CarModel;
use Game\Model\MoneyModel;
use Game\Model\Shop\ShopBlindBoxModel;
use Game\Protobuf\BlindBoxReward;

trait ShopBlindBoxLogic
{
    use ItemLogic;
    use MoneyLogic;
    use CarLogic;

    //兑换抽奖券
    public function exchangeTreasureTicket(int $num): int
    {
        if ($num <= 0) {
            return ClientErrorCode::SHOP_BUY_NUM_ERROR;
        }
        $maxNum = $this->getTerm(
            TemplateDefine::TYPE_CONST,
            TemplateConst::Const_Exchange_Ticket_Max_Num,
            TemplateConst::ConstNum
        );
        if ($num > $maxNum) {
            return ClientErrorCode::SHOP_BLIND_BOX_OVER_EXCHANGE_MAX;
        }
        $preNum = $this->getTerm(
            TemplateDefine::TYPE_CONST,
            TemplateConst::Const_Exchange_Ticket_Need_Money_Num,
            TemplateConst::ConstNum
        );
        $needNum = $num * $preNum;
        if ($needNum <= 0) {
            return ClientErrorCode::SHOP_BUY_NUM_ERROR;
        }
        //检查需要钻石
        if ($this->getMoney(MoneyModel::TYPE_DIAMOND) < $needNum) {
            return $this->getMoneyNotEnoughCode(MoneyModel::TYPE_DIAMOND);
        }
        //记录消耗来源
        $this->setLogConsumeItemSource(GameConstantDefine::ITEM_CONSUME_SOURCE_EXCHANGE_TREASURE_TICKET);
        $this->setLogAddItemSource(GameConstantDefine::ITEM_ADD_SOURCE_EXCHANGE_TREASURE_TICKET);
        //扣钱加道具
        $this->subMoney(MoneyModel::TYPE_DIAMOND, $needNum);
        $this->gainItem(TemplateItem::ITEM_ID_TREASURE_TICKET, $num);
        return ClientErrorCode::CLIENT_SUCCESS;
    }

    //获取抽奖次数
    public function getDrawNum(int $id): int
    {
        /**
         * @var ShopBlindBoxModel $shopBlindBoxModel
         */
        $shopBlindBoxModel = ModelManager::getInstance()->getModel(ModelTypeDefine::SHOP_BLIND_BOX);
        $drawNum = $shopBlindBoxModel->getDrawNum($id);
        if ($drawNum == 0) {
            $this->initIgnoreItemBoxData($id);
        }
        return $drawNum;
    }

    //抽奖检查
    public function checkDrawBlindBox(int $id, int $num): int
    {
        $config = $this->getTitle(TemplateDefine::TYPE_SHOP_BLIND_BOX, $id);
        if (is_null($config)) {
            return ClientErrorCode::SHOP_BLIND_BOX_ID_NOT_FOUND;
        }
        //检查时间
        $now = Utils::getServerTimestamp();
        if ($now < $config[TemplateShopBlindBox::StartTime]) {
            return ClientErrorCode::SHOP_BLIND_BOX_ITEM_NOT_IN_TIME;
        }
        if ($now > $config[TemplateShopBlindBox::EndTime]) {
            return ClientErrorCode::SHOP_BLIND_BOX_ITEM_OUT_TIME;
        }
        //记录消耗来源
        $this->setLogConsumeItemSource(GameConstantDefine::ITEM_CONSUME_SOURCE_SHOP_BLIND_BOX);
        $this->setLogAddItemSource(GameConstantDefine::ITEM_ADD_SOURCE_SHOP_BLIND_BOX);
        if ($config[TemplateShopBlindBox::MoneyType] != 0) {
            //检查货币
            $hasMoney = $this->getMoney($config[TemplateShopBlindBox::MoneyType]);
            $needMoney = $config[TemplateShopBlindBox::Price] * $num;
            if ($hasMoney < $needMoney) {
                return $this->getMoneyNotEnoughCode($config[TemplateShopCar::ConvertedType]);
            }
            $this->subMoney($config[TemplateShopBlindBox::MoneyType], $needMoney);
        } else {
            //检查道具
            $hasNum = $this->getItemNumByTplID($config[TemplateShopBlindBox::TicketType]);
            $needNum = $config[TemplateShopBlindBox::Price] * $num;
            if ($hasNum < $needNum) {
                return ClientErrorCode::SHOP_BLIND_BOX_TICKET_NOT_ENOUGH;
            }
            $this->subItemByTID($config[TemplateShopBlindBox::TicketType], $needNum);
        }
        return ClientErrorCode::CLIENT_SUCCESS;
    }

    //开始抽奖
    public function drawBlindBox(int $id, int $num): array
    {
        $rewardArr = array();
        /**
         * @var ShopBlindBoxModel $shopBlindBoxModel
         */
        $shopBlindBoxModel = ModelManager::getInstance()->getModel(ModelTypeDefine::SHOP_BLIND_BOX);
        $drawData = $shopBlindBoxModel->getDrawData();
        $drawNum = $drawData[$id] ?? 0;
        $drawGroupNum = isset($drawData[ShopBlindBoxData::DRAW_GROUP_NUM.$id]) ?
            json_decode($drawData[ShopBlindBoxData::DRAW_GROUP_NUM.$id], true) :
            array();
        $drawIgnoreIds = isset($drawData[ShopBlindBoxData::DRAW_IGNORE_ITEM_BOX_ID.$id]) ?
            json_decode($drawData[ShopBlindBoxData::DRAW_IGNORE_ITEM_BOX_ID.$id], true) :
            array();
        for ($i = 1; $i <= $num; $i++) {
            $drawNum++;
            $lotteryReward = $this->lottery($id, $drawNum, $drawGroupNum, $drawIgnoreIds);
            if (empty($lotteryReward)) {
                //抽奖出错!!!,没有抽到,少给一个,能直接发现
                continue;
            }
            [$rewardItemId, $itemNum] = $lotteryReward;
            //先加道具,再继续抽奖,防止再次抽到唯一的同一道具
            $this->gainItem($rewardItemId, $itemNum);
            $reward = new BlindBoxReward();
            $reward->setItemId($rewardItemId);
            $reward->setNum($itemNum);
            $rewardArr[] = $reward;
            //检查是否是服装,服装给男女性别全部的
            $this->checkRewardIsCloth($rewardItemId, $itemNum, $rewardArr);
        }
        //更新抽奖次数
        $data = array(
            $id => $drawNum,
            ShopBlindBoxData::DRAW_GROUP_NUM.$id => json_encode($drawGroupNum),
            ShopBlindBoxData::DRAW_IGNORE_ITEM_BOX_ID.$id => json_encode($drawIgnoreIds),
        );
        $shopBlindBoxModel->updateDrawNum($data);
        //返回数据
        return [$drawNum, $rewardArr];
    }

    //抽奖逻辑
    //排除不满足的奖励组,只从可选择的组中抽取
    private function lottery(int $id, int $drawNum, array &$drawGroupNum, array &$drawIgnoreIds): array
    {
        //排除不满足的奖励组
        $lotteryIndex = array();
        $maxPR = 0;
        $config = $this->getTitle(TemplateDefine::TYPE_SHOP_BLIND_BOX, $id);
        for ($i = 1; $i <= TemplateShopBlindBox::END_NUM; $i++) {
            $boxId = $config[TemplateShopBlindBox::ITEM_KEY . $i];
            if ($boxId == 0) {
                continue;
            }
            if (in_array($boxId, $drawIgnoreIds)) {
                continue;
            }
            $lastDrawNum = $drawGroupNum[$i] ?? 0;   //上次抽中次数
            $gapNum = $drawNum - $lastDrawNum;
            [$max, $min] = $config[TemplateShopBlindBox::MINIMUM_KEY.$i];
            if ($gapNum >= $min) {
                $lotteryIndex[] = $i;
                $maxPR += $config[TemplateShopBlindBox::PR_KEY.$i];
            }
        }
        //配置错误,没有可抽的奖励组
        if (empty($lotteryIndex) || $maxPR < 1) {
            LogMark::getInstance()->markError(
                GameErrorCode::SHOP_BLIND_BOX_LOTTERY_ERROR,
                "[ShopBlindBoxLogic] [lottery] error, not found can lottery ItemBox Id",
                array(
                    "id" => $id,
                    "drawNum" => $drawNum,
                    "drawGroupNum" => $drawGroupNum,
                    "drawIgnoreIds" => $drawIgnoreIds,
                )
            );
            return array();
        }
        $luckNum = random_int(1, $maxPR);
        $pr = 0;
        foreach ($lotteryIndex as $i) {
            if ($i == 1) {
                //检查第一组
                $lastDrawNum = $drawGroupNum[1] ?? 0;   //上次抽中次数
                $gapNum = $drawNum - $lastDrawNum;
                $pr += $config[TemplateShopBlindBox::PR1];
                //检查加权
                [$weightStart, $preWeight] = $config[TemplateShopBlindBox::Weight];
                if ($gapNum >= $weightStart) {
                    $pr += ($gapNum - $weightStart) * $preWeight;
                }
                //检查保底
                [$max, $min] = $config[TemplateShopBlindBox::Minimum1];
                if (($max > 0 && $gapNum >= $max) || $pr >= $luckNum) {
                    $reward = $this->lotteryBox($config[TemplateShopBlindBox::ItemBox1]);
                    if (empty($reward)) {
                        LogMark::getInstance()->markError(
                            GameErrorCode::SHOP_BLIND_BOX_LOTTERY_ERROR,
                            "[ShopBlindBoxLogic] [lottery] error",
                            array(
                                "id" => $id,
                                "boxId" => $config[TemplateShopBlindBox::ItemBox1],
                                "drawNum" => $drawNum,
                                "luckNum" => $luckNum,
                                "drawGroupNum" => $drawGroupNum
                            )
                        );
                        return $reward;
                    }
                    //检查获得道具和对应组内道具是否全部拥有
                    if ($this->checkHaveItemBoxAllItem($config[TemplateShopBlindBox::ItemBox1], $reward[0])) {
                        $drawIgnoreIds[] = $config[TemplateShopBlindBox::ItemBox1];
                    }
                    $drawGroupNum[1] = $drawNum;
                    return $reward;
                }
            } else {
                $lastDrawNum = $drawGroupNum[$i] ?? 0;   //上次抽中次数
                $gapNum = $drawNum - $lastDrawNum;
                $pr += $config[TemplateShopBlindBox::PR_KEY.$i];
                //检查保底
                [$max, $min] = $config[TemplateShopBlindBox::MINIMUM_KEY.$i];
                if (($max > 0 && $gapNum >= $max) || $pr >= $luckNum) {
                    $reward = $this->lotteryBox($config[TemplateShopBlindBox::ITEM_KEY.$i]);
                    if (empty($reward)) {
                        LogMark::getInstance()->markError(
                            GameErrorCode::SHOP_BLIND_BOX_LOTTERY_ERROR,
                            "[ShopBlindBoxLogic] [lottery] error",
                            array(
                                "id" => $id,
                                "boxId" => $config[TemplateShopBlindBox::ITEM_KEY.$i],
                                "drawNum" => $drawNum,
                                "luckNum" => $luckNum,
                                "drawGroupNum" => $drawGroupNum
                            )
                        );
                        return $reward;
                    }
                    //检查获得道具和对应组内道具是否全部拥有
                    if ($this->checkHaveItemBoxAllItem($config[TemplateShopBlindBox::ITEM_KEY.$i], $reward[0])) {
                        $drawIgnoreIds[] = $config[TemplateShopBlindBox::ITEM_KEY.$i];
                    }
                    $drawGroupNum[$i] = $drawNum;
                    return $reward;
                }
            }
        }
        LogMark::getInstance()->markError(
            GameErrorCode::SHOP_BLIND_BOX_LOTTERY_ERROR,
            "[ShopBlindBoxLogic] [lottery] fail",
            array(
                "id" => $id,
                "drawNum" => $drawNum,
                "luckNum" => $luckNum,
                "drawGroupNum" => $drawGroupNum,
                "index" => $lotteryIndex
            )
        );
        return array();
    }

    //抽奖--道具组 $boxId ItemBox表Id
    private function lotteryBox(int $boxId): array
    {
        $config = $this->getTitle(TemplateDefine::TYPE_BLIND_BOX, $boxId);
        $pr = 0;
        $rewardIdx = 0;
        if ($config[TemplateBlindBox::IsOnly] == TemplateBlindBox::GROUP_ITEM_ONLY) {
            //组内道具唯一,需要检查是否已拥有,排除已拥有的
            $maxPR = 0;
            $lotteryIdx = array();
            for ($i = TemplateBlindBox::START_NUM; $i <= TemplateBlindBox::END_NUM; $i++) {
                if ($config[TemplateBlindBox::ITEM_KEY.$i] == 0) {
                    continue;
                }
                if ($this->checkHasItem($config[TemplateBlindBox::ITEM_KEY.$i])) {
                    continue;
                }
                $maxPR += $config[TemplateBlindBox::PR_KEY.$i];
                $lotteryIdx[] = $i;
            }
            if (empty($lotteryIdx) || $maxPR < 1) {
                LogMark::getInstance()->markError(
                    GameErrorCode::SHOP_BLIND_BOX_LOTTERY_ERROR,
                    "[ShopBlindBoxLogic] [lotteryBox] error, not found can lottery id",
                    array(
                        "boxId" => $boxId,
                    )
                );
                return array();
            }
            $luckNum = random_int(1, $maxPR);
            foreach ($lotteryIdx as $i) {
                $pr += $config[TemplateBlindBox::PR_KEY.$i];
                if ($config[TemplateBlindBox::ITEM_KEY.$i] > 0 && $pr >= $luckNum) {
                    //检查是否已拥有
                    if (!$this->checkHasItem($config[TemplateBlindBox::ITEM_KEY.$i])) {
                        $rewardIdx = $i;
                        break;
                    }
                }
            }
        } else {
            //非唯一 抽取组中的一个
            $luckNum = random_int(1, 1000);
            for ($i = TemplateBlindBox::START_NUM; $i <= TemplateBlindBox::END_NUM; $i++) {
                $pr += $config[TemplateBlindBox::PR_KEY.$i];
                if ($config[TemplateBlindBox::ITEM_KEY.$i] != 0 && $pr >= $luckNum) {
                    $rewardIdx = $i;
                    break;
                }
            }
        }
        //出错,没有抽到奖励
        if ($rewardIdx == 0) {
            LogMark::getInstance()->markError(
                GameErrorCode::SHOP_BLIND_BOX_LOTTERY_ERROR,
                "[ShopBlindBoxLogic] [lotteryBox] error",
                array(
                    "boxId" => $boxId,
                    "luckNum" => $luckNum,
                )
            );
            return array();
        }
        //返回抽到的奖励
        LogMark::getInstance()->markDebug(
            "[ShopBlindBoxLogic] [lotteryBox] success",
            array(
                "boxId" => $boxId,
                "group" => $rewardIdx,
                "luckNum" => $luckNum,
            )
        );
        return array(
            $config[TemplateBlindBox::ITEM_KEY.$rewardIdx],
            $config[TemplateBlindBox::ITEM_NUM.$rewardIdx]
        );
    }

    //检查是否拥有此道具 true有此道具
    private function checkHasItem(int $itemId): bool
    {
        $config = $this->getTitle(TemplateDefine::TYPE_ITEM, $itemId);
        if (is_null($config)) {
            return true;
        }
        //车
        if ($config[TemplateItem::ItemType] == TemplateItem::ITEM_TYPE_CAR) {
            $carId = $config[TemplateItem::UseId][0];
            if (is_null($this->searchCarDataByCarID($carId))) {
                return false;
            }
            return true;
        }
        //改装件
        if ($config[TemplateItem::ItemType] == TemplateItem::ITEM_TYPE_CAR_REFIT) {
            /**
             * @var CarModel $carModel
             */
            $carModel = ModelManager::getInstance()->getModel(ModelTypeDefine::CAR);
            return $carModel->checkHasRefitId($config[TemplateItem::UseId][0]);
        }
        //检查道具
        return $this->getItemNumByTplID($itemId) > 0;
    }

    //初始抽奖忽略ItemBox组Id
    //$id ShopBox表Id
    private function initIgnoreItemBoxData(int $id)
    {
        $config = $this->getTitle(TemplateDefine::TYPE_SHOP_BLIND_BOX, $id);
        if (is_null($config)) {
            return;
        }
        $ignoreId = array();
        //获取包含的ItemBox组Id
        for ($i = 1; $i <= TemplateShopBlindBox::END_NUM; $i++) {
            $boxId = $config[TemplateShopBlindBox::ITEM_KEY . $i];
            if ($boxId > 0) {
                $itemBoxConfig = $this->getTitle(TemplateDefine::TYPE_BLIND_BOX, $boxId);
                if ($itemBoxConfig[TemplateBlindBox::IsOnly] == TemplateBlindBox::GROUP_ITEM_ONLY) {
                    $hasAll = true;
                    //检查是否拥有全部唯一道具
                    for ($j = TemplateBlindBox::START_NUM; $j <= TemplateBlindBox::END_NUM; $j++) {
                        if ($itemBoxConfig[TemplateBlindBox::ITEM_KEY . $j] == 0) {
                            continue;
                        }
                        if (!$this->checkHasItem($itemBoxConfig[TemplateBlindBox::ITEM_KEY . $j])) {
                            $hasAll = false;
                            break;
                        }
                    }
                    if ($hasAll) {
                        $ignoreId[] = $boxId;
                    }
                }
            }
        }
        if (empty($ignoreId)) {
            return;
        }
        /**
         * @var ShopBlindBoxModel $shopBlindBoxModel
         */
        $shopBlindBoxModel = ModelManager::getInstance()->getModel(ModelTypeDefine::SHOP_BLIND_BOX);
        $data = array(
            ShopBlindBoxData::DRAW_IGNORE_ITEM_BOX_ID.$id => json_encode($ignoreId),
        );
        $shopBlindBoxModel->updateDrawNum($data);
    }

    //检查ItemBox组内道具是否全部拥有
    //$rewardItemId 刚抽到的道具Id
    private function checkHaveItemBoxAllItem(int $boxId, int $rewardItemId): bool
    {
        $itemBoxConfig = $this->getTitle(TemplateDefine::TYPE_BLIND_BOX, $boxId);
        if ($itemBoxConfig[TemplateBlindBox::IsOnly] == TemplateBlindBox::GROUP_ITEM_ONLY) {
            $hasAll = true;
            //检查是否拥有全部唯一道具
            for ($j = TemplateBlindBox::START_NUM; $j <= TemplateBlindBox::END_NUM; $j++) {
                $itemId = $itemBoxConfig[TemplateBlindBox::ITEM_KEY . $j];
                if ($itemId == 0) {
                    continue;
                }
                if ($itemId == $rewardItemId) {
                    continue;
                }
                if (!$this->checkHasItem($itemId)) {
                    $hasAll = false;
                    return $hasAll;
                }
            }
            return $hasAll;
        }
        return false;
    }

    //检查是否是服装,服装给男女性别全部的
    //表配的为女性服装,对应男性服装 Id+10000
    private function checkRewardIsCloth(int $itemId, int $itemNum, array &$rewardArr)
    {
        $itemConfig = $this->getTitle(TemplateDefine::TYPE_ITEM, $itemId);
        if ($itemConfig[TemplateItem::ItemType] != TemplateItem::ITEM_TYPE_CLOTH) {
            return;
        }
        $maleClothId = $itemId + TemplateItem::MALE_CLOTH_ID_ADD;
        //检查是否有此服装
        if ($this->checkHasItem($maleClothId)) {
            return;
        }
        $this->gainItem($maleClothId, $itemNum);
        $reward = new BlindBoxReward();
        $reward->setItemId($maleClothId);
        $reward->setNum($itemNum);
        $rewardArr[] = $reward;
    }
}
