%% ---------------------------------------------------------------------------
%% @doc 定制活动-活动数据。活动数据保存在角色进程字典中。
%% 本模块主要包括三部分：
%% 1.> 活动结束数据加载和保存相关;
%% 2.> 活动结束数据清理相关;
%% 3.> 角色进程获得某个活动的数据。
%% @author zhaoyu
%% @since  2018-11-07
%% ---------------------------------------------------------------------------
-module(lib_custom_act_data).

%% 1.> 活动结束数据加载和保存相关部分:
-export([
    load_custom_act_data/1              %% 获得定制活动的统计数据
    ,load_custom_act_data/3             %% 获得定制活动的统计数据
    ,db_save_custom_act_data/2          %% 定制活动的保存的统计数据保存到数据库
    ,db_delete_custom_act_data/2        %% 根据主次类别删除定制活动的保存的统计数据
    ,db_delete_custom_act_data/3        %% 删除定制活动的保存的统计数据
    ,db_update_custom_act_red_gift/4    %% 更新定制活动红点礼包信息
    ,clean_up_data/1                    %% 清理活动数据
]).

%% 2.> 活动结束数据清理相关部分:
-export([
    act_end_clean_up_data/2             %% 活动结束数据清理
    ,clear_act_data_db/2
    ,clean_up_custom_data/3             %% 角色清除活动数据
    ,role_clean_up_custom_data/3        %% 角色清除活动数据
]).

%% 3.> 角色进程获得某个活动的数据部分:
-export([
    custom_act_data/2                   %% 角色进程获得活动数据
    ,custom_act_data/1                  %% 角色进程获得活动数据
    ,save_custom_data/2                 %% 角色进程保存活动数据
    ,put_custom_data/1                  %% 角色进程保存活动数据到进程字典
]).

-export([
    gm/3                                %% gm
    ,save_receive_times/1               %% 角色保存所有领取次数数据
    ,gm_save_receive_times/0            %% Gm让在线玩家保存一次所有领取次数数据
]).

-include("custom_act.hrl").
-include("common.hrl").
-include("server.hrl").
-include("predefine.hrl").


%% ---------------------------------------------------------------------------
%% 角色进程获得某个活动的数据部分
%% ---------------------------------------------------------------------------

%% ---------------------------------------------------------------------------
%% @doc 角色进程获得/保存某个活动的数据。
%% 需要根据配置的清理规则来判断数据是否需要重置。
%% 需要清理数据的活动，各个活动需要各自处理。
-spec custom_act_data(Type, SubType) -> ActData when
    Type        :: integer(),
    SubType     :: integer(),
    ActData     :: #custom_act_data{}.
%% ---------------------------------------------------------------------------
custom_act_data(Type, SubType) ->
    ActData = #custom_act_data{
        time = UpdateTime
    } = get_custom_act_data(Type, SubType),
    %% 如果最后更新时间为0，不重置。
    case UpdateTime of
        0 -> ActData;
        _ -> check_custom_act_data(ActData, false)
    end.

%% ---------------------------------------------------------------------------
%% @doc 角色进程获得某个活动的数据。
-spec custom_act_data(ActInfo) -> ActData when
    ActInfo     :: #act_info{},     %% 活动开启信息
    ActData     :: #custom_act_data{}.
%% ---------------------------------------------------------------------------
custom_act_data(ActInfo) ->
    #act_info{key = {Type, SubType}} = ActInfo,
    ActData = #custom_act_data{
        time = UpdateTime
    } = get_custom_act_data(Type, SubType),
    %% 如果最后更新时间为0，不重置。
    case UpdateTime of
        0 -> ActData;
        _ -> check_custom_act_data(ActData, ActInfo)
    end.

%% 从进程字典中获得活动数据
get_custom_act_data(Type, SubType) ->
    Key = custom_act_key(Type, SubType),
    case erlang:get(Key) of
        ActData = #custom_act_data{} -> ActData;
        _ -> #custom_act_data{type = Type, subtype = SubType}
    end.

%% ---------------------------------------------------------------------------
%% @doc 检查活动数据是否需要重置
%% TODO 考虑下是否用#custom_act_data.time是否在
%% #act_info{stime = Stime, etime = Etime}的活动时间内来判断数据是否还有效。。
-spec check_custom_act_data(ActData, ActInfo) -> NewActData when
    ActData     :: #custom_act_data{},
    ActInfo     :: #act_info{} | false,
    NewActData  :: #custom_act_data{}.
%% ---------------------------------------------------------------------------
check_custom_act_data(ActData, ActInfo) ->
    #custom_act_data{type = Type, subtype = SubType, time = UpdateTime} = ActData,
    Now = utime:unixtime(),
    IsDataValid = case lib_custom_act_util:get_act_cfg_info(Type, SubType) of
        #custom_act_cfg{clear_type = ?CUSTOM_ACT_CLEAR_ZERO} ->
            ZeroHour = 0,
            utime:is_logic_same_day(Now, UpdateTime, ZeroHour);
        #custom_act_cfg{clear_type = ?CUSTOM_ACT_CLEAR_FOUR} ->
            ZeroHour = 4,
            utime:is_logic_same_day(Now, UpdateTime, ZeroHour);
        _ when is_record(ActInfo, act_info) -> %% 活动期内有效
            #act_info{stime = Stime, etime = Etime} = ActInfo,
            UpdateTime >= Stime andalso UpdateTime =< Etime;
        _ -> %% 活动期内有效
            case lib_custom_act_util:get_custom_act_open_info(Type, SubType) of
                #act_info{stime = Stime, etime = Etime} ->
                    UpdateTime >= Stime andalso UpdateTime =< Etime;
                _ -> %% 活动没开。。
                    false
            end
    end,
    case IsDataValid of
        true -> %% 是同一天/在有效期内，不用重置数据
            ActData;
        _ ->
            %% 需要重置，需要各个活动各自重置数据。因为每个活动重置的方式不一样，
            %% 不能统一置空。
            reset_data(ActData)
    end.


%% ---------------------------------------------------------------------------
%% @doc 活动数据重置.
%% 如果这个重置流程不能满足的话，活动需要自己进行数据重置.
-spec reset_data(ActData) -> NewActData when
    ActData     :: #custom_act_data{},
    NewActData  :: #custom_act_data{}.
%% ---------------------------------------------------------------------------
%% 例子
reset_data(ActData = #custom_act_data{type = for_example}) ->
    ActData#custom_act_data{act_data = []};
reset_data(ActData = #custom_act_data{type=?CUSTOM_ACT_TYPE_LUCKY_CAT})->
    ActData#custom_act_data{act_data = [], time = utime:unixtime()};
reset_data(ActData = #custom_act_data{type=?CUSTOM_ACT_TYPE_HOPE_GIFT})->
    ActData#custom_act_data{act_data = [], time = utime:unixtime()};
reset_data(ActData) ->
    ActData.



%% ---------------------------------------------------------------------------
%% @doc 保存活动数据.
-spec save_custom_data(RoleId, ActData) -> ok when
    RoleId      :: integer(),
    ActData     :: #custom_act_data{}.
%% ---------------------------------------------------------------------------
save_custom_data(RoleId, ActData) ->
    put_custom_data(ActData),
    db_save_custom_act_data(RoleId, ActData),
    ok.


%% ---------------------------------------------------------------------------
%% @doc 保存活动数据到进程字典
-spec put_custom_data(ActData) -> ok when
    ActData     :: #custom_act_data{}.
%% ---------------------------------------------------------------------------
put_custom_data(ActData) ->
    #custom_act_data{type = Type, subtype = SubType} = ActData,
    Key = custom_act_key(Type, SubType),
    erlang:put(Key, ActData),
    ok.

%% 活动数据进程字典key
custom_act_key(Type, SubType) -> {custom_act_data, Type, SubType}.


%% ---------------------------------------------------------------------------
%% @doc 角色清除活动数据
-spec clean_up_custom_data(Role, Type, SubType) -> ok when
    Role        :: any(),   %% 冗余字段，兼容历史接口
    Type        :: integer(),
    SubType     :: integer().
%% ---------------------------------------------------------------------------
clean_up_custom_data(_Role, Type, SubType) ->
    Key = custom_act_key(Type, SubType),
    erlang:erase(Key),
    ok.


%% ---------------------------------------------------------------------------
%% @doc 角色清除活动数据
-spec role_clean_up_custom_data(Role, Type, SubType) -> ok when
    Role        :: #player_status{},
    Type        :: integer(),
    SubType     :: integer().
%% ---------------------------------------------------------------------------
role_clean_up_custom_data(Role = #player_status{id = RoleId}, Type, SubType) ->
    db_delete_custom_act_data(RoleId, Type, SubType),
    clean_up_custom_data(Role, Type, SubType),
    ok.


%% ---------------------------------------------------------------------------
%% 活动结束数据加载和保存相关部分
%% ---------------------------------------------------------------------------

%% ---------------------------------------------------------------------------
%% @doc 获得定制活动的统计数据
-spec load_custom_act_data(RoleId) -> DataList when
    RoleId      :: integer(),
    DataList    :: [#custom_act_data{}].
%% ---------------------------------------------------------------------------
load_custom_act_data(RoleId) ->
    SQL = io_lib:format(?SQL_SELECT_CUSTOM_ACT_DATA, [RoleId]),
    DbList = db:get_all(SQL),
    DataList = load_custom_act_data_do(DbList, []),
    DataList.

load_custom_act_data_do([], DataList) ->
    DataList;
load_custom_act_data_do([OneActData | T], DataList) ->
    ActData = make_record(custom_act_data, OneActData),
    put_custom_data(ActData),
    DataList2 = [ActData | DataList],
    load_custom_act_data_do(T, DataList2).

make_record(custom_act_data, [Type, SubType, Time, Data, RedGiftBin]) ->
    ActData0 = util:bitstring_to_term(Data),
    ActData  = record_match(ActData0),
    RedGift  = util:bitstring_to_term(RedGiftBin),
    #custom_act_data{
        type = Type, subtype = SubType, time = Time, act_data = ActData,
        red_gift = RedGift
    }.

%%有数据结构变化的走这里
record_match({for_example,A,B})->
    {for_example,A,B,0};
record_match(Data)->Data.

%% ---------------------------------------------------------------------------
%% @doc 获得定制活动的统计数据
-spec load_custom_act_data(RoleId, Type, SubType) -> DataList when
    RoleId      :: integer(),
    Type        :: integer(),
    SubType     :: integer(),
    DataList    :: [#custom_act_data{}].
%% ---------------------------------------------------------------------------
load_custom_act_data(RoleId, Type, SubType) ->
    SQL = io_lib:format(?SQL_SELECT_CUSTOM_ACT_DATA_2, [RoleId, Type, SubType]),
    DbList = db:get_all(SQL),
    DataList = load_custom_act_data_do(DbList, []),
    DataList.


%% ---------------------------------------------------------------------------
%% @doc 定制活动的保存的统计数据保存到数据库
-spec db_save_custom_act_data(Role, CustomActData) -> ok when
    Role            :: #player_status{},
    CustomActData   :: #custom_act_data{}.
%% ---------------------------------------------------------------------------
db_save_custom_act_data(Role = #player_status{}, CustomActData) ->
    #player_status{id = RoleId} = Role,
    db_save_custom_act_data(RoleId, CustomActData);
db_save_custom_act_data(RoleId, CustomActData) ->
    #custom_act_data{
        type = Type, subtype = SubType, time = Time, act_data = Data, red_gift = RedGift
    } = CustomActData,
    NewData = util:term_to_string(Data),
    RedGiftBin = util:term_to_string(RedGift),
    SQL = io_lib:format(
        ?SQL_SAVE_CUSTOM_ACT_DATA, [RoleId, Type, SubType, Time, NewData, RedGiftBin]
    ),
    db:execute(SQL),
    ok.


%% ---------------------------------------------------------------------------
%% @doc 根据主次类别删除定制活动的保存的统计数据
-spec db_delete_custom_act_data(Type, SubType) -> ok when
    Type        :: integer(),
    SubType     :: integer().
%% ---------------------------------------------------------------------------
db_delete_custom_act_data(Type, SubType) ->
    SQL = io_lib:format(?SQL_DELETE_CUSTOM_ACT_DATA_1, [Type, SubType]),
    db:execute(SQL),
    ok.


%% ---------------------------------------------------------------------------
%% @doc 删除定制活动的保存的统计数据
-spec db_delete_custom_act_data(RoleId, Type, SubType) -> ok when
    RoleId    :: integer() | #player_status{},
    Type        :: integer(),
    SubType     :: integer().
%% ---------------------------------------------------------------------------
db_delete_custom_act_data(RoleId, Type, SubType) when is_integer(RoleId) ->
    SQL = io_lib:format(?SQL_DELETE_CUSTOM_ACT_DATA_2, [RoleId, Type, SubType]),
    db:execute(SQL),
    ok;
db_delete_custom_act_data(Role, Type, SubType) ->
    #player_status{id = RoleId} = Role,
    db_delete_custom_act_data(RoleId, Type, SubType).

%% ----------------------------------------------------------------------------
%% @doc 更新定制活动通用红点礼包奖励信息
-spec db_update_custom_act_red_gift(RoleId, Type, SubType, RedGift) -> ok when
    RoleId :: integer() | #player_status{},
    Type :: integer(),
    SubType :: integer(),
    RedGift :: list().
%% ----------------------------------------------------------------------------
db_update_custom_act_red_gift(RoleId, Type, SubType, RedGift) when is_integer(RoleId) ->
    RedGiftBin = util:term_to_string(RedGift),
    SQL = io_lib:format(?SQL_UPDATE_CUSTOM_ACT_RED_GIFT, [RedGiftBin, RoleId, Type, SubType]),
    db:execute(SQL),
    ok;
db_update_custom_act_red_gift(Role, Type, SubType, RedGift) ->
    #player_status{id = RoleId} = Role,
    db_update_custom_act_red_gift(RoleId, Type, SubType, RedGift).

%% ---------------------------------------------------------------------------
%% 活动结束数据清理相关部分
%% ---------------------------------------------------------------------------

%% ---------------------------------------------------------------------------
%% @doc 活动结束数据清理
-spec act_end_clean_up_data(CloseType, ActInfo) -> ok when
    CloseType   :: integer(),
    ActInfo     :: #act_info{}.
%% ---------------------------------------------------------------------------
act_end_clean_up_data(CloseType, ActInfo) ->
    #act_info{key = {Type, SubType}} = ActInfo,
    case lib_custom_act_util:get_act_cfg_info(Type, SubType) of
        %% 特殊的强制不清理活动类型
        #custom_act_cfg{type = ?CUSTOM_ACT_TYPE_GROUP_BUY} -> ignore;
        #custom_act_cfg{type = ?CUSTOM_ACT_TYPE_DAILY_CHARGE_CONSUME} -> ignore;
        #custom_act_cfg{type = ?CUSTOM_ACT_TYPE_RECHARGE} -> ignore;
        #custom_act_cfg{type = ?CUSTOM_ACT_THEME_FIRST_RECHARGE} -> ignore;
        #custom_act_cfg{type = ?CUSTOM_ACT_TYPE_CONSUME} -> ignore;
        #custom_act_cfg{type = ?CUSTOM_ACT_TYPE_LOANS} -> ignore;
        #custom_act_cfg{type = ?CUSTOM_ACT_TYPE_SMALL_RECHARGE} -> ignore;
        %% 按清理类型控制
        #custom_act_cfg{clear_type = ?CUSTOM_ACT_CLEAR_CLOSE} ->
            clean_up_data(CloseType, ActInfo);
        _ ->
            ok
    end,
    ok.

clean_up_data(_CloseType, ActInfo) ->
    #act_info{key = {Type, SubType}} = ActInfo,
    db_delete_custom_act_data(Type, SubType),
    %% 删除领取次数数据库表数据.（角色身上的数据不用清理，会根据时间判断有效期）
    clear_act_data_db(Type, SubType),
    lib_player:cast_online_role_apply(
        ?APPLY_CAST_STATUS, ?MODULE, clean_up_custom_data, [Type, SubType]
    ),
    ok.

clear_act_data_db(Type, SubType) ->
    db:execute(?delete_custom_act_reward_receive_by_stype, [Type, SubType]),
    ?INFO("act_end_clean_up_data:~w", [{Type, SubType}]).
%% ---------------------------------------------------------------------------
%% @doc 清理活动数据
-spec clean_up_data(ActInfo) -> ok when
    ActInfo     :: #act_info{}.
%% ---------------------------------------------------------------------------
clean_up_data(ActInfo) ->
    #act_info{key = {Type, SubType}} = ActInfo,
    db_delete_custom_act_data(Type, SubType),
    lib_player:cast_online_role_apply(
        ?APPLY_CAST_STATUS, ?MODULE, clean_up_custom_data, [Type, SubType]
    ),
    ?INFO("act_end_clean_up_data:~w", [{Type, SubType}]),
    ok.


%% ---------------------------------------------------------------------------
%% @doc 角色保存所有领取次数数据
-spec save_receive_times(Role) -> ok when
    Role        :: #player_status{}.
%% ---------------------------------------------------------------------------
save_receive_times(Role) ->
    #player_status{id = RoleId, status_custom_act = StatusCustomAct} = Role,
    RewardMap = StatusCustomAct#status_custom_act.reward_map,
    RewardList = maps:to_list(RewardMap),
    SqlArgs = [
        [RoleId, Type, SubType, GradeId, Times, Utime] ||
        {
            {Type, SubType, GradeId}
            ,#reward_status{receive_times = Times, utime = Utime}
        } <- RewardList
    ],
    Sql = <<"replace into custom_act_receive_reward (role_id, type, subtype"
    ", grade, receive_times, utime)">>,
    SqlArgs =/= [] andalso db:insert_batch(Sql, SqlArgs),
    ok.


%% ---------------------------------------------------------------------------
%% @doc Gm
-spec gm(RoleId, Type, SubType) -> Return when
    RoleId      :: integer(),
    Type        :: integer(),
    SubType     :: integer(),
    Return      :: term().
%% ---------------------------------------------------------------------------
gm(RoleId, Type, SubType) ->
    {
        dictionary, DictionaryList
    } = erlang:process_info(misc:get_player_process(RoleId), dictionary),
    Key = custom_act_key(Type, SubType),
    lists:keyfind(Key, 1, DictionaryList).


%% ---------------------------------------------------------------------------
%% @doc Gm让在线玩家保存一次所有领取次数数据
-spec gm_save_receive_times() -> ok.
%% ---------------------------------------------------------------------------
gm_save_receive_times() ->
    lib_player:cast_online_role_apply(
        ?APPLY_CAST_STATUS, ?MODULE, save_receive_times, []
    ),
    ok.
