%%% -------------------------------------------------------
%%% @author huangyongxing@yeah.net
%%% @doc
%%% 每日累充消费活动 + 弹珠玩法
%%% @end
%%% -------------------------------------------------------
-module(custom_act_pellet).
%% 每日累充/消费相关接口
-export([
        refresh_act_list_status/3
        ,receive_reward/4
        ,count_reward_progress/4
        ,check_receive_reward_conditions/3
        ,act_end/1
        ,daily_settle/0
        ,role_settle/2
        ,midnight_delay_refresh_act/2
    ]).
%% 弹珠抽奖相关接口
-export([
        pellet_draw_request/2
        ,submit_draw_result/3
        ,pellet_draw_timeout/1
        ,pellet_query_count_status/1
        ,pellet_get_award/2
        ,login/1
        ,check_round_finished/2
    ]).
%% only for debug
-export([
        gm_show_progress/3
        ,gm_settle/1
        ,gm_act_end/2
    ]).
-include("custom_act.hrl").
-include("common.hrl").
-include("server.hrl").
-include("pellet.hrl").
-include("errcode.hrl").
-include("goods.hrl").
-include("def_goods.hrl").
-include("def_module.hrl").
-include("figure.hrl").
-include("predefine.hrl").

-define(DRAW_TIMEOUT, 20000).

%% 活动触发后状态刷新
%% 1. 充值 2. 消费
%% 活动状态实际无特别触发，读取的是统一的充值、消费统计数据，
%% 这里是发生相关刷新事件后，主动去更新客户端数据

%% 充值/消费刷新
-spec refresh_act_list_status(Player, RefreshType, ActInfoList) -> ok when
    Player :: #player_status{},
    RefreshType :: recharge | consume,
    ActInfoList :: [#act_info{}].
refresh_act_list_status(Player, RefreshType, ActInfoList = [_|_]) ->
    lists:foreach(fun(ActInfo = #act_info{}) ->
                refresh_act_status(Player, RefreshType, ActInfo);
            (_) ->
                ignore
        end, ActInfoList);
refresh_act_list_status(_Player, _RefreshType, _ActInfo = []) ->
    ignore.

%% 充值/消费刷新
refresh_act_status(Player, RefreshType, ActInfo = #act_info{
        key = {Type = ?CUSTOM_ACT_TYPE_DAILY_CHARGE_CONSUME, SubType}
    }) ->
    % ?INFO("refresh_act_status:{~w,~w}", [Type, SubType]),
    GradeList = lib_custom_act_util:get_act_reward_grade_list(Type, SubType),
    lists:foreach(fun(GradeId) ->
                refresh_act_status_2(Player, RefreshType, ActInfo, GradeId)
        end, GradeList),
    ok;
refresh_act_status(_Player, _RefreshType, _ActInfo) ->
    ok.

%% 充值/消费刷新检测各GradeId
refresh_act_status_2(Player, RefreshType, ActInfo, GradeId) ->
    #act_info{key = {Type, SubType}} = ActInfo,
    RewardCfg = lib_custom_act_util:get_act_reward_cfg_info(Type, SubType, GradeId),
    case RewardCfg of
        #custom_act_reward_cfg{condition = Condition} ->
            %% 当活动匹配充值/消费类型时才刷新
            %% （但是有些进度满的也会再次刷新，因为没有存储旧进度数据，不好判断）
            NeedSendStatus =
            case lib_custom_act_check:check_act_condtion([type], Condition) of
                [_Type = ?GRADE_TYPE_RECHARGE] when RefreshType =:= recharge ->
                    true;
                [_Type = ?GRADE_TYPE_CONSUME] when RefreshType =:= consume ->
                    true;
                _ ->
                    false
            end,
            case NeedSendStatus of
                true  -> calc_send_grade_status(Player, ActInfo, RewardCfg);
                false -> ignore
            end;
        _ ->
            ignore
    end.

%% 状态刷新通知
calc_send_grade_status(Player, ActInfo, RewardCfg = #custom_act_reward_cfg{
        type = Type
        ,subtype = SubType
        ,grade = GradeId
    }) ->
    #player_status{sid = Sid} = Player,
    {Progress, Status, ReceiveTimes} = lib_custom_act_do_normal:get_grade_status(Player, ActInfo, 0, RewardCfg),
    Data = [Type, SubType, GradeId, Progress, Status, ReceiveTimes],
    % ?INFO("send_grade_status:{~w,~w}, GradeId:~w, Progress:~w,Status:~w,ReceiveTimes:~w", Data),
    lib_custom_act:send_grade_status(Sid, Data).

%% 奖励领取
receive_reward(Player, Type = ?CUSTOM_ACT_TYPE_DAILY_CHARGE_CONSUME, SubType, GradeId) ->
    #player_status{sid = Sid} = Player,
    case lib_custom_act_util:get_custom_act_open_info(Type, SubType) of
        #act_info{key = {Type, SubType}} = ActInfo ->
            case lib_custom_act_util:get_act_reward_cfg_info(Type, SubType, GradeId) of
                #custom_act_reward_cfg{} = RewardCfg ->
                    case check_receive_reward_conditions(Player, ActInfo, RewardCfg) of
                        ok ->
                            case do_recv_reward(Player, ActInfo, RewardCfg) of
                                {false, ErrCode} when is_integer(ErrCode) ->
                                    % ?INFO("ErrCode:~w", [ErrCode]),
                                    send_recv_result(Sid, [ErrCode, Type, SubType, GradeId]),
                                    ok;
                                {false, Error} ->
                                    % ?INFO("Error:~w", [Error]),
                                    send_recv_result(Sid, [?ERRCODE(Error), Type, SubType, GradeId]),
                                    ok;
                                {ok, NewPlayer} ->
                                    % ?INFO("SUCCESS"),
                                    send_recv_result(Sid, [?SUCCESS, Type, SubType, GradeId]),
                                    {ok, NewPlayer}
                            end;
                        {false, ErrCode} ->
                            send_recv_result(Sid, [ErrCode, Type, SubType, GradeId]),
                            ok;
                        _Error ->   % 异常返回，正常不会走
                            % ?INFO("Error:~w", [Error]),
                            send_recv_result(Sid, [?FAIL, Type, SubType, GradeId]),
                            ok
                    end;
                _ ->
                    % ?INFO("config_err"),
                    send_recv_result(Sid, [?ERRCODE(config_err), Type, SubType, GradeId]),
                    ok
            end;
        _ ->
            % ?INFO("err331_act_closed"),
            send_recv_result(Sid, [?ERRCODE(err331_act_closed), Type, SubType, GradeId]),
            ok
    end;
receive_reward(_Player, _Type, _SubType, _GradeId) ->
    ok.

do_recv_reward(Player, ActInfo, RewardCfg = #custom_act_reward_cfg{
        type = Type, subtype = SubType, grade = GradeId, condition = Condition
    }) ->
    ReceiveTimes = lib_custom_act:count_receive_times(Player, ActInfo, RewardCfg),
    case ReceiveTimes > 0 of
        true  ->
            {false, err331_has_no_more_times};
        false ->
            Reward = lib_custom_act_util:count_act_reward(Player, ActInfo, RewardCfg),
            CanGive = lib_goods_api:can_give_goods(Player, Reward),
            if
                CanGive =/= true ->
                    % ?INFO("err150_no_cell"),
                    %% 不能收纳道具（通常是背包空间不足）
                    {false, err150_no_cell};
                true ->
                    % ?INFO("send reward"),
                    %% 需要更新领取次数，使用了公共的领取次数管理接口
                    NewReceiveTimes = ReceiveTimes + 1,
                    PlayerAfterUpdate = lib_custom_act:update_receive_times(
                        Player, Type, SubType, GradeId, NewReceiveTimes
                    ),
                    NowTime = utime:unixtime(),
                    %% 需要存储数据到custom_act_data，为结算返还未领奖励提供依据
                    recv_update_custom_act_data(Player#player_status.id, Type, SubType, GradeId, NowTime),
                    %% 获取额外日志需要的数据
                    #player_status{id = RoleId, figure = #figure{vip = Vip}} = Player,
                    [CondType, GoldNeed, GoldTotal] =
                    case lib_custom_act_check:check_act_condtion([type,gold], Condition) of
                        [?GRADE_TYPE_CONSUME, _Need]  ->
                            ConsumeNum = get_today_consume(RoleId),
                            [2, _Need, ConsumeNum];
                        [?GRADE_TYPE_RECHARGE, _Need] ->
                            TodayPayGold = lib_recharge_api:get_today_pay_gold(RoleId),
                            [1, _Need, TodayPayGold];
                        _ ->    % 上层接口已经检查过领取条件，不可能执行到这个分支
                            [0, 999999999]
                    end,
                    lib_log_api:log_pellet_daily(RoleId, SubType, GradeId,
                        CondType, GoldNeed, GoldTotal, 0, Reward, Vip, NowTime
                    ),
                    %% 发放奖励
                    NewPlayer = lib_custom_act:custom_act_send_reward(
                        PlayerAfterUpdate, RewardCfg#custom_act_reward_cfg{reward = Reward}
                    ),
                    {ok, NewPlayer}
            end
    end.

%% 存储数据到custom_act_data，为结算返还未领奖励提供依据
recv_update_custom_act_data(RoleId, Type, SubType, GradeId, NowTime) ->
    CustomActData = #custom_act_data{
        act_data = ActData
    } = lib_custom_act_data:custom_act_data(Type, SubType),
    NewActData =
    case lists:keytake(GradeId, #custom_act_pellet.grade_id, ActData) of
        {value, CustomActPellet, RemainActData} ->
            #custom_act_pellet{
                got_time = LastGotTime
            } = CustomActPellet,
            NewCustomActPellet = CustomActPellet#custom_act_pellet{
                got_time = NowTime
                ,last_got_time = LastGotTime
            },
            [NewCustomActPellet | RemainActData];
        _ ->
            NewCustomActPellet = #custom_act_pellet{
                grade_id = GradeId
                ,got_time = NowTime
                ,last_got_time = 0
                ,return_midnight = 0
                ,last_return_midnight = 0
            },
            [NewCustomActPellet | ActData]
    end,
    NewCustomActData = CustomActData#custom_act_data{
        act_data = NewActData
    },
    lib_custom_act_data:save_custom_data(RoleId, NewCustomActData).

%% 发送领取结果
send_recv_result(Sid, Data = [_ErrCode, _Type, _SubType, _GradeId]) ->
    {ok, BinData} = pt_331:write(33105, Data),
    lib_server_send:send_to_sid(Sid, BinData).

%% -------------------------------------------------------
%% 结算返还处理
%% -------------------------------------------------------

%% 活动结束回调
%% 处理玩家未领取的结算返还
%% 如果结算时奖励配置消失了（例如配置删除后热更导致活动关闭），则结算不了奖励
%% 注意：活动结束后的触发，可能为第二天，也可能还是当天（依赖于策划的配置）
%%       所以活动结束类的触发，直接结算活动结束日的数据
act_end(ActInfo) ->
    DefaultTimeout = 120000,
    act_end(ActInfo, DefaultTimeout).

act_end(ActInfo = #act_info{key = {Type = ?CUSTOM_ACT_TYPE_DAILY_CHARGE_CONSUME, SubType}, etime = ETime}, Timeout) ->
    RewardCfgList = lib_custom_act_util:get_act_reward_cfg_list(Type, SubType),
    %% 通知各玩家执行相应的奖励发放逻辑
    %% 延迟一段时间结算，避开0点高峰操作（有多个SubType则有多个子进程）
    NewTimeout = erlang:max(erlang:min(?MAX_TIMEOUT, Timeout), 1000),
    proc_lib:spawn_link(fun() ->
                timer:sleep(NewTimeout),
                SettleMidnight = utime:unixdate(ETime),
                NextMidnight = utime:tomorrow(SettleMidnight),
                DayConsumeGoldRoles = lib_consume_data:get_consume_gold_roles(SettleMidnight, ETime),
                DayRechargeRoles = lib_recharge_data:get_recharge_roles(SettleMidnight, NextMidnight),
                settle(ActInfo, RewardCfgList, SettleMidnight, DayConsumeGoldRoles, DayRechargeRoles)
        end),
    ok;
act_end(_, _Timeout) ->
    ok.

%% 定时器每天结算
%% 放crontab中，独立进程运行，每天定时执行昨天数据结算（排除活动第一天）
daily_settle() ->
    %% 计算需要结算的日期的午夜时间
    SettleMidnight = utime:yesterday(),
    daily_settle_sub(SettleMidnight).

daily_settle_sub(SettleMidnight) ->
    Type = ?CUSTOM_ACT_TYPE_DAILY_CHARGE_CONSUME,
    OpenList = lib_custom_act_util:get_open_subtype_list(Type),
    NextMidnight = utime:tomorrow(SettleMidnight),
    %% 需要结算的当天，如果非活动期间，则无意义，所以以 STime =< SettleMidnight 来筛选数据
    %% （实际上存在的情况即活动第一天的时候，尝试结束前一天，这是无意义的）
    NeedSettleList = [
        {
            ActInfo,
            lib_custom_act_util:get_act_reward_cfg_list(TypeX, SubTypeX)
        } ||
        ActInfo = #act_info{key = {TypeX, SubTypeX}, stime = STime} <- OpenList,
        STime =< SettleMidnight
    ],
    case NeedSettleList of
        [_|_] ->
            DayConsumeGoldRoles = lib_consume_data:get_consume_gold_roles(SettleMidnight, NextMidnight - 1),
            DayRechargeRoles = lib_recharge_data:get_recharge_roles(SettleMidnight, NextMidnight),
            lists:foreach(fun({ActInfo, RewardCfgList}) ->
                        settle(ActInfo, RewardCfgList, SettleMidnight, DayConsumeGoldRoles, DayRechargeRoles)
            end, NeedSettleList);
        _ ->
            ignore
    end.

%% 数据结算，返还未领取的奖励
%% 注意：SettleMidnight为需要结算的数据对应日期午夜时间
%% 在调用的上层，需要确保SettleMidnight的正确性
%% 结算的触发分两类：
%% 1. 活动结束时触发延迟结算
%% 2. 活动开放期，每天定时器执行（排除活动第一天）
settle(ActInfo, RewardCfgList, SettleMidnight, DayConsumeGoldRoles, DayRechargeRoles) ->
    %% 分离奖励配置为充值相关、消费相关的列表
    #{
        recharge := RechargeRewardCfgList,
        consume := ConsumeRewardCfgList
    } = lists:foldl(
        fun partition_reward_cfg/2,
        #{recharge => [], consume => []},
        RewardCfgList
    ),
    %% 汇总人物的充值、消费数据
    PelletSettleParamsList = zip_recharge_consume_data(DayConsumeGoldRoles, DayRechargeRoles),
    %% 遍历所有角色，处理数据返还
    settle_each(PelletSettleParamsList,
        ActInfo, RechargeRewardCfgList, ConsumeRewardCfgList, SettleMidnight,
        1
    ).

settle_each([PelletSettleParams | RemainList],
    ActInfo, RechargeRewardCfgList, ConsumeRewardCfgList, SettleMidnight,
    Order) ->
    #pellet_settle_params{
        role_id = RoleId
        ,recharge_gold = RechargeGold
        ,consume_gold = ConsumeGold
    } = PelletSettleParams,
    if
        RechargeGold =< 0 andalso ConsumeGold =< 0 ->
            ignore;
        RechargeGold =< 0 ->
            %% 只有消费
            NewPelletSettleParams = PelletSettleParams#pellet_settle_params{
                recharge_reward_cfgs = []
                ,consume_reward_cfgs = ConsumeRewardCfgList
                ,settle_midnight = SettleMidnight
            },
            settle_role_apply(RoleId, ActInfo, NewPelletSettleParams);
        ConsumeGold =< 0 ->
            %% 只有充值
            NewPelletSettleParams = PelletSettleParams#pellet_settle_params{
                recharge_reward_cfgs = RechargeRewardCfgList
                ,consume_reward_cfgs = []
                ,settle_midnight = SettleMidnight
            },
            settle_role_apply(RoleId, ActInfo, NewPelletSettleParams);
        true ->
            %% 同时有充值和消费
            NewPelletSettleParams = PelletSettleParams#pellet_settle_params{
                recharge_reward_cfgs = RechargeRewardCfgList
                ,consume_reward_cfgs = ConsumeRewardCfgList
                ,settle_midnight = SettleMidnight
            },
            settle_role_apply(RoleId, ActInfo, NewPelletSettleParams)
    end,
    case Order rem 20 =:= 0 of
        true  -> timer:sleep(500);
        false -> ignore
    end,
    settle_each(
        RemainList,
        ActInfo, RechargeRewardCfgList, ConsumeRewardCfgList, SettleMidnight,
        Order + 1
    );
settle_each([], _, _, _, _, _) ->
    ok.

partition_reward_cfg(RewardCfg = #custom_act_reward_cfg{condition = Condition}, MapAcc) ->
    case lib_custom_act_check:check_act_condtion([type], Condition) of
        [_Type = ?GRADE_TYPE_CONSUME] ->
            #{consume := ConsumeList} = MapAcc,
            MapAcc#{consume => [RewardCfg | ConsumeList]};
        [_Type = ?GRADE_TYPE_RECHARGE] ->
            #{recharge := RechargeList} = MapAcc,
            MapAcc#{recharge => [RewardCfg | RechargeList]};
        _ ->
            MapAcc
    end;
partition_reward_cfg(_, MapAcc) ->
    MapAcc.

%% 汇总充值消费数据 -> [#pellet_settle_params{}]
zip_recharge_consume_data(DayConsumeGoldRoles, DayRechargeRoles) ->
    zip_recharge_consume_data2(DayConsumeGoldRoles, DayRechargeRoles, []).
zip_recharge_consume_data2([
        [RoleId, CostGold, _CostBGold, _CostFGold | _] | ConsumeGoldRoles
    ], RechargeRoles, RechargeConsumeDataAcc) ->
    NewAcc =
    case lists:keytake(RoleId, #pellet_settle_params.role_id, RechargeConsumeDataAcc) of
        {value, SettleParams, RemainAcc} ->
            NewSettleParams = SettleParams#pellet_settle_params{consume_gold = CostGold},
            [NewSettleParams | RemainAcc];
        _ ->
            NewSettleParams = #pellet_settle_params{role_id = RoleId, consume_gold = CostGold},
            [NewSettleParams | RechargeConsumeDataAcc]
    end,
    zip_recharge_consume_data2(ConsumeGoldRoles, RechargeRoles, NewAcc);
zip_recharge_consume_data2([_|ConsumeGoldRoles], RechargeRoles, RechargeConsumeDataAcc) ->
    zip_recharge_consume_data2(ConsumeGoldRoles, RechargeRoles, RechargeConsumeDataAcc);
zip_recharge_consume_data2([], [[RoleId, Gold | _] | RechargeRoles], RechargeConsumeDataAcc) ->
    NewAcc =
    case lists:keytake(RoleId, #pellet_settle_params.role_id, RechargeConsumeDataAcc) of
        {value, SettleParams, RemainAcc} ->
            NewSettleParams = SettleParams#pellet_settle_params{recharge_gold = Gold},
            [NewSettleParams | RemainAcc];
        _ ->
            NewSettleParams = #pellet_settle_params{role_id = RoleId, recharge_gold = Gold},
            [NewSettleParams | RechargeConsumeDataAcc]
    end,
    zip_recharge_consume_data2([], RechargeRoles, NewAcc);
zip_recharge_consume_data2([], [], RechargeConsumeDataAcc) ->
    RechargeConsumeDataAcc.

%% -------------------------------------------------------
%% 结算部分：离线、在线玩家数据处理具体的执行流程

%% 结算对应玩家执行MFA（离线在线都调用此接口，内部进行区别处理）
settle_role_apply(RoleId, ActInfo, PelletSettleParams) ->
    case misc:get_player_process(RoleId) of
        Pid when is_pid(Pid) ->
            lib_player:apply_cast(Pid, ?APPLY_CAST,
                ?MODULE, role_settle, [ActInfo, PelletSettleParams]);
        _ ->
            offline_settle(ActInfo, PelletSettleParams)
    end.

%% 在线玩家进程中处理数据结算
role_settle(ActInfo, PelletSettleParams) ->
    % ?INFO("role online do settle ----------------- start check role:~w", [PelletSettleParams#pellet_settle_params.role_id]),
    check_return(ActInfo, PelletSettleParams),
    ok.

%% 离线玩家处理数据结算（在公共结算进程中执行）
offline_settle(ActInfo = #act_info{key = {Type, SubType}},
    PelletSettleParams = #pellet_settle_params{role_id = RoleId}) ->
    %% 清理数据
    lib_custom_act_data:clean_up_custom_data(0, Type, SubType),
    %% 加载玩家数据
    lib_custom_act_data:load_custom_act_data(RoleId, Type, SubType),
    % ?INFO("role offline do settle ----------------- start check role:~w", [RoleId]),
    check_return(ActInfo, PelletSettleParams),
    ok;
offline_settle(_ActInfo, _PelletSettleParams) ->
    ok.

check_return(ActInfo, #pellet_settle_params{
        role_id = RoleId
        ,recharge_gold = RechargeGold
        ,consume_gold = ConsumeGold
        ,recharge_reward_cfgs = RechargeRewardCfgList
        ,consume_reward_cfgs = ConsumeRewardCfgList
        ,settle_midnight = SettleMidnight
    }) ->
    NextMidnight = utime:tomorrow(SettleMidnight),
    check_return2(recharge, RoleId, RechargeGold, SettleMidnight, NextMidnight, ActInfo, RechargeRewardCfgList),
    check_return2(consume, RoleId, ConsumeGold, SettleMidnight, NextMidnight, ActInfo, ConsumeRewardCfgList),
    ok;
check_return(_ActInfo, _PelletSettleParams) ->
    ok.

%% 累计充值/消费返还未领奖励
check_return2(_ReturnType, _RoleId, GoldNum, _Midnight, _NextMidnight, _ActInfo, _CfgList) when GoldNum =< 0 ->
    ignore;
check_return2(ReturnType, RoleId, GoldNum, SettleMidnight, NextMidnight, ActInfo, [RewardCfg | RemainList]) ->
    #custom_act_reward_cfg{condition = Condition} = RewardCfg,
    {NeedCheckReturn, GoldNeed} =
    case lib_custom_act_check:check_act_condtion([type, gold], Condition) of
        [?GRADE_TYPE_CONSUME, NeedGold] when ReturnType == consume ->
            % ?INFO("尝试返还消费类型的活动奖励"),
            {GoldNum >= NeedGold, NeedGold};
        [?GRADE_TYPE_RECHARGE, NeedGold] when ReturnType == recharge ->
            % ?INFO("尝试返还充值类型的活动奖励"),
            {GoldNum >= NeedGold, NeedGold};
        _ ->
            {false, 999999999}
    end,
    % ?INFO("本次是否需要检测返还：~w", [NeedCheckReturn]),
    case NeedCheckReturn of
        true  -> do_return(RoleId, ReturnType, GoldNum, GoldNeed, SettleMidnight, NextMidnight, ActInfo, RewardCfg);
        false -> ignore
    end,
    check_return2(ReturnType, RoleId, GoldNum, SettleMidnight, NextMidnight, ActInfo, RemainList);
check_return2(_ReturnType, _RoleId, _GoldNum, _Midnight, _NextMidnight, _ActInfo, []) ->
    ok.

do_return(RoleId, ReturnType, GoldNum, GoldNeed, SettleMidnight, NextMidnight, ActInfo, RewardCfg = #custom_act_reward_cfg{
        type = Type
        ,subtype = SubType
        ,grade = GradeId
    }) ->
    CustomActData = #custom_act_data{
        act_data = ActData
    } = lib_custom_act_data:custom_act_data(Type, SubType),
    case lists:keyfind(GradeId, #custom_act_pellet.grade_id, ActData) of
        #custom_act_pellet{
            got_time = GotTime
            ,last_got_time = LastGotTime
            ,return_midnight = ReturnMidnight
            ,last_return_midnight = LastReturnMidnight
        } = CustomActPellet ->
            % ?INFO("RoleId:~w, 有数据", [RoleId]),
            %% 多判断多一个日期，是防止活动结算与daily_settle在同一日操作可能引发的一些问题
            if
                GotTime >= SettleMidnight andalso GotTime < NextMidnight ->
                    ignore;
                LastGotTime >= SettleMidnight andalso LastGotTime < NextMidnight ->
                    ignore;
                ReturnMidnight =:= SettleMidnight ->
                    ignore;
                LastReturnMidnight =:= SettleMidnight ->
                    ignore;
                true ->
                    % ?INFO("RoleId:~w, 需要返还", [RoleId]),
                    MidnightList = [SettleMidnight, ReturnMidnight, LastReturnMidnight],
                    NewReturnMidnight = lists:max(MidnightList),
                    NewLastReturnMidnight = lists:max(lists:delete(NewReturnMidnight, MidnightList)),
                    NewCustomActPellet = CustomActPellet#custom_act_pellet{
                        return_midnight = NewReturnMidnight
                        ,last_return_midnight = NewLastReturnMidnight
                    },
                    NewActData = lists:keystore(GradeId,
                        #custom_act_pellet.grade_id,
                        ActData,
                        NewCustomActPellet
                    ),
                    NewCustomActData = CustomActData#custom_act_data{act_data = NewActData},
                    lib_custom_act_data:save_custom_data(RoleId, NewCustomActData),
                    %% 发放奖励
                    do_return_send_award(RoleId, ReturnType, GoldNum, GoldNeed, ActInfo, RewardCfg)
            end;
        _ ->    % 无领取奖励数据，即未领取任意一天的奖励
            % ?INFO("RoleId:~w, 无领取奖励数据，即未领取任意一天的奖励", [RoleId]),
            CustomActPellet = #custom_act_pellet{
                grade_id = GradeId
                ,return_midnight = SettleMidnight
            },
            NewActData = [CustomActPellet | ActData],
            NewCustomActData = CustomActData#custom_act_data{act_data = NewActData},
            lib_custom_act_data:save_custom_data(RoleId, NewCustomActData),
            %% 发放奖励
            do_return_send_award(RoleId, ReturnType, GoldNum, GoldNeed, ActInfo, RewardCfg)
    end,
    ok.

do_return_send_award(RoleId, ReturnType, GoldNum, GoldNeed, _ActInfo, RewardCfg) ->
    % ?INFO("do return send award: role ~w, ActInfo:~w", [RoleId, _ActInfo]),
    %% 严格来说这里要使用玩家等级，不过此活动要求使用通用format，所以实际上不需要
    % RewardParam = #reward_param{player_lv = 1},
    % AwardList = lib_custom_act_util:count_act_reward(RewardParam, ActInfo, RewardCfg),
    #custom_act_reward_cfg{subtype = SubType, grade = GradeId, reward = AwardList} = RewardCfg,
    return_log_pellet_daily(RoleId, SubType, GradeId, ReturnType, GoldNeed, GoldNum, AwardList),
    Title = utext:get(?PELLET_MAIL_TITLE),
    Content = utext:get(?PELLET_MAIL_CONTENT),
    lib_mail:send_sys_mail([RoleId], ?MOD_AC_CUSTOM, ?MOD_NONE, Title, Content, AwardList),
    ok.

%% -------------------------------------------------------
%% 活动基础模块调用，用于计算进度、判断最终领取状态
%% -------------------------------------------------------
%% 计算奖励进度
count_reward_progress(
    _Player = #player_status{id = RoleId},
    _ActInfo = #act_info{key = {?CUSTOM_ACT_TYPE_DAILY_CHARGE_CONSUME, _SubType}},
    _CommonProgress,
    _RewardCfg = #custom_act_reward_cfg{condition = Condition} ) ->
    case lib_custom_act_check:check_act_condtion([type, gold], Condition) of
        [_Type = ?GRADE_TYPE_CONSUME, GoldNeed] ->
            %% 获取当天累计消费
            ConsumeNum = get_today_consume(RoleId),
            % ?INFO("[_Type = 2, GoldNeed:~w], ConsumeNum:~w", [GoldNeed, ConsumeNum]),
            erlang:min(ConsumeNum, GoldNeed);
        [_Type = ?GRADE_TYPE_RECHARGE, GoldNeed] ->
            %% 获取当天累计充值
            TodayPayGold = lib_recharge_api:get_today_pay_gold(RoleId),
            % ?INFO("[_Type = 1, GoldNeed:~w], TodayPayGold:~w", [GoldNeed, TodayPayGold]),
            erlang:min(TodayPayGold, GoldNeed);
        _ ->
            %% 错误配置
            % ?INFO("config_err"),
            0
    end;
count_reward_progress(_Player, _ActInfo, _CommonProgress, _RewardCfg) ->
    0.

%% 活动基础模块调用，用于判断最终领取状态
check_receive_reward_conditions(
    _Player = #player_status{id = RoleId},
    _ActInfo = #act_info{key = {?CUSTOM_ACT_TYPE_DAILY_CHARGE_CONSUME, _SubType}},
    _RewardCfg = #custom_act_reward_cfg{condition = Condition} ) ->
    case lib_custom_act_check:check_act_condtion([type, gold], Condition) of
        [_Type = ?GRADE_TYPE_CONSUME, GoldNeed] ->
            % ?INFO("[_Type = 2, GoldNeed:~w]", [GoldNeed]),
            %% 获取当天累计消费
            ConsumeNum = get_today_consume(RoleId),
            case ConsumeNum >= GoldNeed of
                true  -> ok;
                false -> {false, ?ERRCODE(err331_act_can_not_get)}
            end;
        [_Type = ?GRADE_TYPE_RECHARGE, GoldNeed] ->
            % ?INFO("[_Type = 1, GoldNeed:~w]", [GoldNeed]),
            %% 获取当天累计充值
            TodayPayGold = lib_recharge_api:get_today_pay_gold(RoleId),
            case TodayPayGold >= GoldNeed of
                true  -> ok;
                false -> {false, ?ERRCODE(err331_act_can_not_get)}
            end;
        _ ->
            % ?INFO("config_err"),
            %% 错误配置
            {false, ?ERRCODE(config_err)}
    end;
check_receive_reward_conditions(_, _, _) ->
    % ?INFO("err331_act_can_not_get"),
    {false, ?ERRCODE(err331_act_can_not_get)}.
%% -------------------------------------------------------

%% 获取当天消费数
get_today_consume(RoleId) ->
    NowTime = utime:unixtime(),
    Midnight = utime:unixdate(NowTime),
    lib_consume_data:get_gold_consume_between(RoleId, Midnight, NowTime).

%% -------------------------------------------------------
%% 弹珠活动
%% -------------------------------------------------------

%% 弹珠抽奖请求
-spec pellet_draw_request(Player, SubType) -> {ErrCode, Time, Key, NewPlayer} when
    Player :: #player_status{},
    SubType :: non_neg_integer(),
    ErrCode :: non_neg_integer(),
    Time :: non_neg_integer(),
    Key :: binary(),
    NewPlayer :: #player_status{}.
pellet_draw_request(Player, SubType) ->
    Type = ?CUSTOM_ACT_TYPE_DAILY_CHARGE_CONSUME,
    case lib_custom_act_util:get_custom_act_open_info(Type, SubType) of
        #act_info{} ->
            #player_status{pellet = Pellet} = Player,
            #pellet{round = Round} = Pellet,
            case lib_custom_act_util:get_act_cfg_info(Type, SubType) of
                #custom_act_cfg{} = CustomActCfg ->
                    CostList = find_draw_goods(CustomActCfg),
                    case CostList of
                        [_|_] ->
                            NewRound =
                            case draw_check_round(Pellet) of
                                true  -> Round + 1;
                                false -> Round;
                                Error -> Error
                            end,
                            case NewRound of
                                {false, ErrCode} ->
                                    {ErrCode, 0, <<>>, Player};
                                _ ->
                                    RoundMin = data_pellet:round_scope(NewRound),
                                    AwardWeightList = data_pellet:draw_all_weight_ids(RoundMin),
                                    pellet_draw_cost(Player, SubType, Round, NewRound, CostList, AwardWeightList)
                            end;
                        _ ->
                            {?ERRCODE(config_err), 0, <<>>, Player}
                    end;
                _ ->
                    {?ERRCODE(config_err), 0, <<>>, Player}
            end;
        _ ->
            {?ERRCODE(err331_act_closed), 0, <<>>, Player}
    end.

%% -> true下一轮 | false本轮可抽 | {false, ErrCode}错误
draw_check_round(#pellet{round = Round, count = Count, got_ids = GotIds}) ->
    AwardIdCountList = data_pellet:round_ids(Round),
    case check_round_finished(GotIds, Round, AwardIdCountList) of
        over ->
            %% 全部领取完成，不能再抽
            {false, ?ERRCODE(err331_all_round_over)};
        false ->
            AwardStatusList = pellet_to_award_status(Count, AwardIdCountList, GotIds),
            case lists:keyfind(0, 2, AwardStatusList) of
                {_CountNeed, 0} ->
                    % ?INFO("还有未达成的，可以继续抽奖"),
                    %% 还有未达成的，可以继续抽奖
                    false;
                _ ->
                    % ?INFO("没有未达成的，不能继续抽奖，需要先领取完奖励"),
                    %% 没有未达成的，不能继续抽奖，需要先领取完奖励
                    %% TODO 临时借用调试，需要加新错误码
                    {false, ?ERRCODE(err331_lucky_flop_cant_reset)}
            end;
        true ->
            true
    end.

pellet_draw_cost(Player, SubType, OldRound, NewRound, CostList, _AwardWeightList = [_|_]) ->
    #player_status{
        id = RoleId, pid = Pid
        ,pellet = Pellet = #pellet{
            count = OldCount
            ,got_ids = OldGotIds
        }
    } = Player,
    NewDrawTime = utime:unixtime(),
    case is_drawing(Pellet) of
        true  ->    %% 当前正在抽奖中，不能重复操作
            {?FAIL, 0, <<>>, Player};
        false ->
            case lib_goods_api:cost_object_list_with_check(Player, CostList, pellet_draw, "") of
                {true, PlayerAfCost, _} ->
                    %% 计算抽奖key，记录抽奖信息
                    RandChar = urand:rand_char10(),
                    %% 如果轮次发生了变更，清空之前领取过的奖励信息
                    {NewCount, NewGotIds} =
                    case OldRound =/= NewRound of
                        true  -> {1, []};
                        false -> {OldCount + 1, OldGotIds}
                    end,
                    %% 记录抽奖数据到人物活动数据中
                    NewPellet = Pellet#pellet{
                        round = NewRound,
                        count = NewCount,
                        got_ids = NewGotIds,
                        draw_time = NewDrawTime,
                        draw_subtype = SubType,
                        draw_key = RandChar,
                        draw_over = 0
                    },
                    db_save_pellet(RoleId, NewPellet),
                    NewPlayer = PlayerAfCost#player_status{pellet = NewPellet},
                    %% 等待客户端提交抽奖请求（如果超时不发则由服务端触发发送）
                    Timer = new_draw_timer(Pid, ?DRAW_TIMEOUT),
                    reset_draw_timer(Timer),
                    %% 刷新前端界面
                    pp_custom_act:handle(33180, NewPlayer, []),
                    %% 触发其他系统变更
                    mod_hi_point:success_end(RoleId, ?MOD_RECHARGE_ACT, 2),
                    custom_act_adventure:do_trigger_adventure_task(RoleId, ?MOD_RECHARGE_ACT, 2),
                    {?SUCCESS, NewDrawTime, RandChar, NewPlayer};
                {false, ErrCode, NewPlayer} ->
                    {ErrCode, 0, <<>>, NewPlayer}
            end
    end;
pellet_draw_cost(Player, _SubType, _OldRound, _NewRound, _CostList, _AwardWeightList) ->
    {?ERRCODE(config_err), 0, <<>>, Player}.

%% 提交抽奖结果
-spec submit_draw_result(Player, DrawGrade, Sign) -> {ErrCode, AwardId, NewPlayer} when
    Player :: #player_status{},
    DrawGrade :: 0 .. 5,
    Sign :: binary(),
    ErrCode :: non_neg_integer(),
    AwardId :: non_neg_integer(),
    NewPlayer :: #player_status{}.
submit_draw_result(Player, DrawGrade, Sign) ->
    #player_status{
        id = RoleId
        ,figure = #figure{name = Name, vip = Vip}
        ,pellet = Pellet
    } = Player,
    #pellet{
        round = Round
        ,count = Count
        ,draw_time = DrawTime
        ,draw_key = DrawKey
        ,draw_subtype = DrawSubType
        ,draw_over = DrawOver
    } = Pellet,
    if
        DrawOver > 0 -> % 当前没有抽奖
            {?FAIL, 0, Player};
        true ->
            RoundMin = data_pellet:round_scope(Round),
            %% DrawGrade 如果不在1~5之间，表示由服务端随机，此类情况不用检验sign
            %% 因为运营大佬说想要验证有没有人会作弊，所以不能开有效性检查，纯作日志记录
            _Vaild = check_draw_sign(RoleId, DrawSubType, DrawKey, DrawGrade, DrawTime, Sign),
            IsClientDraw = DrawGrade >= 1 andalso DrawGrade =< 5,
            AwardWeightList =
            case IsClientDraw of
                true  -> data_pellet:draw_weight_ids(DrawGrade, RoundMin);
                false -> data_pellet:draw_all_weight_ids(RoundMin)
            end,
            AwardId = urand:rand_with_weight(AwardWeightList),
            NowTime = utime:unixtime(),
            if
                is_integer(AwardId) ->
                    reset_draw_timer(undefined),
                    AwardList = data_pellet:draw_award(AwardId),
                    NewPellet = Pellet#pellet{
                        draw_over = NowTime,
                        draw_key = <<>>     % 已经没用了，干掉可以节省一点
                    },
                    db_save_pellet(RoleId, NewPellet),
                    PlayerUpPellet = Player#player_status{pellet = NewPellet},
                    lib_log_api:log_pellet_draw(RoleId, Round, 1, AwardId, AwardList, Count, Vip,
                        util:boolean_to_integer(_Vaild), DrawGrade, DrawTime, NowTime
                    ),
                    NewPlayer = lib_goods_api:send_reward(PlayerUpPellet, AwardList, pellet_draw),%% 广播
                    case data_pellet:is_draw_broadcast(AwardId) of
                        1 ->
                            broadcast_draw_awards(?LANG_DRAW_SUB_ID, RoleId, Name, AwardList),
                            %% 记录奖励信息
                            custom_act_pellet_mgr:record_awards(RoleId, Name, AwardList, 0, DrawTime);
                        _ ->
                            ignore
                    end,
                    {?SUCCESS, AwardId, NewPlayer};
                true ->
                    %% 配置缺失（运营选择不做容错发放其他奖励，直接提示错误即可）
                    {?ERRCODE(missing_config), 0, Player}
            end
    end.

%% 超时发放奖励
pellet_draw_timeout(Player) ->
    {_ErrCode, _AwardId, NewPlayer} = submit_draw_result(Player, 0, <<>>),
    {ok, NewPlayer}.

%% 是否正在抽奖中
is_drawing(#pellet{draw_time = DrawTime, draw_over = DrawOver}) ->
    DrawOver =:= 0 andalso DrawTime > 0.

%% 校验抽奖sign，确认抽奖结果是否合法
check_draw_sign(_, _, _, 0, _, _) ->
    true;
check_draw_sign(RoleId, SubType, DrawKey, DrawResult, DrawTime, Sign) ->
    %% sign的验证:
    %% md5(roleId + draw_key + subtype + draw_result + time后3位)
    Tail3Char = binary_to_list(iolist_to_binary(
            io_lib:format("~3..0w", [DrawTime rem 1000])
        )),
    RealSign = util:md5(lists:concat([
                RoleId,
                binary_to_list(DrawKey),
                SubType,
                DrawResult,
                Tail3Char
            ])),
    list_to_binary(RealSign) =:= Sign.

%% 写入抽奖信息到数据库
db_save_pellet(RoleId, Pellet = #pellet{}) ->
    DataFmtFun = fun(#pellet{
                round = Round
                ,count = Count
                ,got_ids = GotIds
                ,got_time = GotTime
                ,draw_time = DrawTime
                ,draw_subtype = DrawSubType
                ,draw_key = DrawKey
                ,draw_over = DrawOver
            }) ->
            GotIdsStr = util:term_to_string(GotIds),
            util:fmt_bin("(~w,~w,~w,'~ts',~w,~w,~w,'~s',~w)", [
                    RoleId, Round, Count, GotIdsStr, GotTime,
                    DrawTime, DrawSubType, DrawKey, DrawOver
                ])
    end,
    udb:save_data_list([Pellet], ?SQL_REPLACE_ROLE_PELLET_HEAD, DataFmtFun).

%% 弹珠活动状态
%% -> {Round, CurrCount, AwardStatusList}
pellet_query_count_status(#player_status{pellet = Pellet}) ->
    #pellet{round = Round} = Pellet,
    AwardCountList = data_pellet:round_ids(Round),
    NewPellet = check_upgrade_pellet(AwardCountList, Pellet),
    #pellet{
        round = NewRound
        ,count = NewCount
        ,got_ids = NewGotIds
    } = NewPellet,
    NewAwardCountList = case Round =/= NewRound of
        true  -> data_pellet:round_ids(NewRound);
        false -> AwardCountList
    end,
    AwardStatusList = pellet_to_award_status(NewCount, NewAwardCountList, NewGotIds),
    {NewRound, NewCount, AwardStatusList}.

pellet_to_award_status(CurrCount, AwardCountList, GotIds) ->
    [
        {
            CountNeed,
            pellet_count_status(CurrCount, AwardId, CountNeed, GotIds)
        } ||
        {AwardId, CountNeed} <- AwardCountList
    ].

%% 计算次数奖励领取状态
%% 以Id来决定是否领取，而非次数
pellet_count_status(CurrCount, AwardId, CountNeed, GotIds) ->
    case lists:member(AwardId, GotIds) of
        true  -> ?PELLET_COUNT_GOT;                 % 已领
        false ->
            case CurrCount >= CountNeed of
                true  -> ?PELLET_COUNT_CAN_GET;     % 未领
                false -> ?PELLET_COUNT_NOT_ENOUGH   % 未达成
            end
    end.

%% 领取弹珠活动累计次数奖励
%% -> {ErrCode, AwardId, NewPlayer}
pellet_get_award(Player, ReqtCount) ->
    Type = ?CUSTOM_ACT_TYPE_DAILY_CHARGE_CONSUME,
    OpenList = lib_custom_act_util:get_open_subtype_list(Type),
    case OpenList of
        [#act_info{} | _] ->
            #player_status{
                id = RoleId
                ,figure = #figure{name = RoleName, vip = Vip}
                ,pellet = Pellet
            } = Player,
            #pellet{
                round = Round
                ,count = Count
                ,got_ids = GotIds
            } = Pellet,
            AwardCountList = data_pellet:round_ids(Round),
            case lists:keyfind(ReqtCount, 2, AwardCountList) of
                {AwardId, CountNeed} ->
                    case pellet_count_status(Count, AwardId, CountNeed, GotIds) of
                        ?PELLET_COUNT_CAN_GET ->    % 已达成未领取，可以领取
                            %% 是不是可发放
                            {AwardList, IsBroadcast} = data_pellet:pellet_count_award(AwardId),
                            CanGive = lib_goods_api:can_give_goods(Player, AwardList),
                            case CanGive =/= true of
                                true  ->
                                    {?ERRCODE(err150_no_cell), AwardId, Player};
                                false ->
                                    %% 更新活动数据
                                    NewGotIds = [AwardId | GotIds],
                                    NewGotTime = utime:unixtime(),
                                    TmpPellet = Pellet#pellet{
                                        got_ids = NewGotIds,
                                        got_time = NewGotTime
                                    },
                                    %% 检测是否升级到下一轮次
                                    NewPellet = check_upgrade_pellet(AwardCountList, TmpPellet),
                                    db_save_pellet(RoleId, NewPellet),
                                    %% 发放活动返还
                                    Produce = #produce{
                                        type = pellet_draw_count,
                                        subtype = Type,
                                        reward = AwardList,
                                        remark = ""
                                    },
                                    PlayerUpPellet = Player#player_status{pellet = NewPellet},
                                    lib_log_api:log_pellet_draw(
                                        RoleId, Round, 2, AwardId, AwardList, Count, Vip,
                                        1, 0, 0, NewGotTime
                                    ),
                                    NewPlayer = lib_goods_api:send_reward(PlayerUpPellet, Produce),
                                    %% 广播
                                    case IsBroadcast of
                                        1 ->
                                            TvSubId = ?LANG_DRAW_COUNT_SUB_ID,
                                            broadcast_draw_awards(TvSubId, RoleId, RoleName, AwardList),
                                            %% 记录奖励
                                            custom_act_pellet_mgr:record_awards(
                                                RoleId, RoleName, AwardList, 1, NewGotTime
                                            );
                                        _ ->
                                            ignore
                                    end,
                                    %% 刷新前端界面
                                    pp_custom_act:handle(33180, NewPlayer, []),
                                    {?SUCCESS, AwardId, NewPlayer}
                            end;
                        ?PELLET_COUNT_GOT ->    % 已经领取过了
                            {?ERRCODE(err331_already_get_reward), AwardId, Player};
                        _ ->    % 未达成
                            {?ERRCODE(err331_act_can_not_get), AwardId, Player}
                    end;
                _ ->    % 没有该奖励
                    {?ERRCODE(err331_act_can_not_get), 0, Player}
            end;
        _ ->
            {?ERRCODE(err331_act_closed), 0, Player}
    end.

%% 在必要的时候，升级数据到下一轮次（一般为领取完之后触发）
check_upgrade_pellet(AwardCountList, Pellet = #pellet{
        round = Round
        ,got_ids = GotIds
    }) ->
    case check_round_finished(GotIds, Round, AwardCountList) of
        over ->     % 全部领取完成，不需要更新数据
            Pellet;
        false ->    % 保留当前轮次，不需要更新数据
            Pellet;
        true ->     % 需要升级到下一轮次
            Pellet#pellet{
                round = Round + 1,
                count = 0,
                got_ids = []
            }
    end.

%% 登录加载数据
login(Player = #player_status{id = RoleId, pid = Pid, last_login_time = LastLoginTime}) ->
    Data =
    case db:get_row(io_lib:format(?SQL_SELECT_ROLE_PELLET, [RoleId])) of
        [Round, Count, GotIdsBin, GotTime, DrawTime, DrawSubType, DrawKey, DrawOver] ->
            GotIds =
            case util:bitstring_to_term(GotIdsBin) of
                [_|_] = GotIdsX -> GotIdsX;
                _ -> []
            end,
            [Round, Count, GotIds, GotTime, DrawTime, DrawSubType, DrawKey, DrawOver];
        _ -> [1, 0, [], 0, 0, 0, <<>>, 0]
    end,
    Pellet = to_pellet(Data),
    case is_drawing(Pellet) of
        true  ->
            %% 登录时，LastLoginTime实际上就是当前时间NowTime
            RemainTime = (Pellet#pellet.draw_time + 20) - LastLoginTime,
            Timeout = max(2000, RemainTime * 1000),    % 防止时间过期和时间过快达到
            Timer = new_draw_timer(Pid, Timeout),
            put_draw_timer(Timer);
        false -> ignore
    end,
    Player#player_status{pellet = Pellet}.

to_pellet([Round, Count, GotIds, GotTime, DrawTime, DrawSubType, DrawKey, DrawOver]) ->
    #pellet{
        round = Round
        ,count = Count
        ,got_ids = GotIds
        ,got_time = GotTime
        ,draw_time = DrawTime
        ,draw_subtype = DrawSubType
        ,draw_key = DrawKey
        ,draw_over = DrawOver
    }.

%% 查找抽奖消耗道具
find_draw_goods(#custom_act_cfg{condition = Condition}) ->
    case lib_custom_act_util:keyfind_act_condition(Condition, pellet_cost) of
        {_, CostList} ->
            CostList;
        _False ->
            false
    end;
find_draw_goods(_) ->
    false.

%% 检查某一轮次是否都领完了
%% -> true本轮结束 | false本轮未结束 | over全结束
check_round_finished(GotIds, 0) ->
    check_round_finished(GotIds, 1);
check_round_finished(GotIds, Round) ->
    AwardIdCountList = data_pellet:round_ids(Round),
    check_round_finished(GotIds, Round, AwardIdCountList).

check_round_finished(GotIds, Round, AwardIdCountList) ->
    AwardIdList = [AwardId || {AwardId, _Count} <- AwardIdCountList],
    case AwardIdList -- GotIds of
        [] ->   % 本轮已经全部领取完毕
            MaxRound = data_pellet:round_max(),
            case Round >= MaxRound of
                true  ->    % 所有轮次全部领取完
                    over;
                false ->    % 开启下一轮
                    true
            end;
        _ ->    % 本轮尚未全部领取完毕
            false
    end.

midnight_delay_refresh_act(Player, NowTime) ->
    Type = ?CUSTOM_ACT_TYPE_DAILY_CHARGE_CONSUME,
    case lib_custom_act_api:get_open_custom_act_infos(Type, NowTime) of
        [_|_] = ActList ->
            refresh_act_list_status(Player, recharge, ActList),
            refresh_act_list_status(Player, consume, ActList);
        _ ->
            ignore
    end,
    ok.

%% 弹珠活动充值消费日志
return_log_pellet_daily(RoleId, SubType, GradeId, ReturnType, GoldNeed, GoldNum, AwardList) ->
    #figure{vip = Vip} = lib_role:get_role_figure(RoleId),
    CondType =
    case ReturnType of
        recharge -> 1;
        consume  -> 2;
        _ -> 0
    end,
    NowTime = utime:unixtime(),
    lib_log_api:log_pellet_daily(RoleId, SubType, GradeId,
        CondType, GoldNeed, GoldNum, 1, AwardList, Vip, NowTime
    ).

%% -------------------------------------------------------
%% 抽奖定时器数据管理
%% -------------------------------------------------------

new_draw_timer(Pid, Timeout) ->
    Msg = {'mod', ?MODULE, pellet_draw_timeout, []},
    erlang:send_after(Timeout, Pid, Msg).

put_draw_timer(Timer) ->
    erlang:put(pellet_draw_timer, Timer).

get_draw_timer() ->
    erlang:get(pellet_draw_timer).

%% use Timer = undefined to cancel draw timer
%% otherwise set draw timer with Timer
reset_draw_timer(Timer) ->
    OldTimer = get_draw_timer(),
    util:cancel_timer(OldTimer),
    case is_reference(Timer) of
        true  -> put_draw_timer(Timer);
        false -> erase(pellet_draw_timer)
    end.

%% -------------------------------------------------------
%% 广播TV消息
%% -------------------------------------------------------

%% 广播TV消息（即lib_chat send_TV的效果，合并了消息）
broadcast_draw_awards(TvSubId, RoleId, RoleName, TVList) ->
    BinDataAll = pack_awards_tv(TvSubId, RoleId, RoleName, TVList),
    lib_server_send:send_to_all(BinDataAll),
    ok.

%% 打包奖励列表的TV消息数据
pack_awards_tv(TvSubId, RoleId, Name, AwardList) ->
    BinDataList = [
        pack_award_tv(TvSubId, RoleId, Name, Award) ||
        Award = {_Type, _GoodsTypeId, _GoodsNum} <- AwardList
    ],
    list_to_binary(BinDataList).

pack_award_tv(TvSubId, _RoleId, Name, {_Type, GoodsTypeId, GoodsNum}) ->
    ModuleId = ?MOD_AC_CUSTOM,
    Msg = [Name, GoodsTypeId, GoodsNum],
    lib_chat:make_tv(ModuleId, TvSubId, Msg).

%% -------------------------------------------------------
%% GM调试
%% -------------------------------------------------------

%% gm命令，打印在线玩家的活动进度
gm_show_progress(RoleId, SubType, GradeId) ->
    Type = ?CUSTOM_ACT_TYPE_DAILY_CHARGE_CONSUME,
    ActInfo = lib_custom_act_util:get_custom_act_open_info(Type, SubType),
    RewardCfg = lib_custom_act_util:get_act_reward_cfg_info(Type, SubType, GradeId),
    Fun = fun(PS) ->
            Progress = custom_act_pellet:count_reward_progress(PS, ActInfo, 0, RewardCfg),
            ?INFO("RoleId:~w, Progress:~w~n", [RoleId, Progress])
    end,
    gm_util:gm_role_replace_state(RoleId, Fun).

%% 手工调用settle，指定需要结算对应数据的归属日期零点
gm_settle(SettleMidnight) ->
    case utime:unixdate(SettleMidnight) of
        SettleMidnight -> daily_settle_sub(SettleMidnight);
        _ ->
            ?INFO("SettleMidnight is not a valid midnight time!"),
            ok
    end.

%% 手工调用act_end，但是可能需要修改活动的结束时间为
%% 今天或指定时间（至少玩家对应时间得有数据，才有结算的意义）
gm_act_end(ActInfo, 0) ->
    act_end(ActInfo, 1000);
gm_act_end(ActInfo, EndTime) ->
    NewActInfo = ActInfo#act_info{etime = EndTime},
    act_end(NewActInfo, 1000).

%% -------------------------------------------------------

