%%%--------------------------------------
%%% @Module  : lib_login_reward
%%% @Author  : hyh
%%% @Created : 2018.01.12
%%% @Description:  七天登录
%%%--------------------------------------
-module(lib_login_reward).

-include("server.hrl").
-include("errcode.hrl").
-include("predefine.hrl").
-include("common.hrl").
-include("figure.hrl").
-include("scene.hrl").
-include("goods.hrl").
-include("def_module.hrl").
-include("def_goods.hrl").
-include("login_reward.hrl").
-include("rec_event.hrl").
-include("def_event.hrl").

-export([
    login_reward_login/1,
    login_reward_sql/1,
    gm_add_day/1,
    gm_reset_loginday/1,
    filter_reward/5,
    % get_login_reward_cfg/0,
    reload_config/0,
    handle_event/2,
    transaction_reward_do/4,
    notice_day_info_when_reload_cfg/1,
    calc_day_states/3
    ]).

%% @doc 事件处理
%% 上线处理
handle_event(Status, #event_callback{type_id = ?EVENT_LOGIN_CAST}) ->
    NStatus = refresh_data(Status),
    {ok, NStatus};

%% 0点处理：进入下一轮每日签到/重新计算在线奖励类型
handle_event(Status, #event_callback{type_id = ?EVENT_ZERO_CLOCK}) ->
    NStatus = refresh_data(Status),
    pp_login_reward:handle(17500, NStatus, []),
    {ok, NStatus};

handle_event(Status, #event_callback{type_id = ?EVENT_LV_UP}) ->
    #player_status{figure = #figure{lv = RoleLv}} = Status,
    CfgLv = lib_module:get_open_lv(?MOD_LOGIN_REWARD),
    if CfgLv == RoleLv ->
            NStatus = refresh_data(Status),
            {ok, NStatus};
        true ->
            {ok, Status}
    end;

handle_event(Player, _) ->
    {ok, Player}.


refresh_data(Status)->
    #player_status{login_reward = LoginReward, figure = #figure{lv = RoleLv}} = Status,
    #login_reward{refresh_days_time = RefTime, login_days = LoginDays} = LoginReward,
    MidNight = utime:unixdate(),
    case utime:is_same_day(RefTime, MidNight) of
        true ->
            NewLoginReward = LoginReward;
        false ->
            %% 检查最新配置
            CfgMaxDays = get_cfg_max_login_day(),
            CfgLv = lib_module:get_open_lv(?MOD_LOGIN_REWARD),
            if RoleLv < CfgLv -> %% 等级不足不触发
                    NewLoginReward = LoginReward;
                LoginDays >= CfgMaxDays -> %% 登录天数已达配置最大天数
                    {Change, NewLoginReward} = calc_new_login_data_with_cfg(CfgMaxDays, LoginReward),
                    Change == true andalso lib_login_reward:login_reward_sql(NewLoginReward);
                true ->
                    NewLoginDays = LoginDays + 1,
                    NewRefTime = MidNight,
                    NewLoginReward = LoginReward#login_reward{refresh_days_time = NewRefTime, login_days = NewLoginDays},
                    lib_login_reward:login_reward_sql(NewLoginReward)
            end
    end,

    NStatus = Status#player_status{login_reward = NewLoginReward},
    NStatus.

%% 获取配置最大的登录天数
get_cfg_max_login_day() ->
    CfgLoginDays = data_login_reward:get_login_reward_day_list(),
    if CfgLoginDays == [] ->
            0;
        true ->
            lists:max(CfgLoginDays)
    end.

calc_new_login_data_with_cfg(CfgMaxDays, LoginReward) ->
    #login_reward{day_states = DayStates, login_days = LoginDays} = LoginReward,
    NewDayStates = lists:filter(fun(DayId) -> DayId =< CfgMaxDays end, DayStates),
    NewLoginDays = min(CfgMaxDays, LoginDays),
    NewLoginReward = LoginReward#login_reward{day_states = NewDayStates, login_days = NewLoginDays},
    Change = NewDayStates =/= DayStates orelse NewLoginDays =/= LoginDays,
    {Change, NewLoginReward}.

%% 登录初始化
login_reward_login(Ps) ->
    #player_status{id = RoleId} = Ps,
    ReSql = io_lib:format(?SQL_SELECT_LOGIN_REWARD, [RoleId]),
    case db:get_all(ReSql) of
        [[_RoleId, GetDate, RefTime, LoginDays, DayStatesBin]|_] ->
            NewLoginReward = #login_reward{
                role_id = RoleId, get_date = GetDate, day_states = util:bitstring_to_term(DayStatesBin),
                refresh_days_time = RefTime, login_days = LoginDays
            };
        _ ->
            NewLoginReward = #login_reward{role_id = RoleId}
    end,
    NewPs = Ps#player_status{login_reward = NewLoginReward},
    NewPs.

%% 保存数据到数据库
login_reward_sql(LoginReward) ->
    #login_reward{
        role_id = RoleId, get_date = GetDate, day_states = DayStates,
        refresh_days_time = RefTime, login_days = LoginDays
    } = LoginReward,
    db:execute(?SQL_REPLACE_LOGIN_REWARD, [RoleId, GetDate, RefTime, LoginDays, util:term_to_bitstring(DayStates)]),
    ok.


%%% ===============
%%%     筛选奖励
%%% ===============
filter_reward(_RewardType, [], _Lv, _Career, _CombatPower) -> [];
%% 指定等级
filter_reward(?REWARD_TYPE_LV, [{[LvMin, LvMax], RewardL}|_T], Lv, _Career, _CombatPower)
    when Lv >= LvMin andalso Lv =< LvMax ->
        RewardL;
    % case lists:keyfind(Career, 1, RewardL) of
    %     {Career, RewardList} ->
    %         RewardList;
    %     _ ->
    %         []
    % end;
%% 指定玩家战力
filter_reward(?REWARD_TYPE_COMBAT, [{[PowerMin, PowerMax], RewardL}|_T], _Lv, _Career, CombatPower)
    when CombatPower >= PowerMin andalso CombatPower =< PowerMax ->
        RewardL;
    % case lists:keyfind(Career, 1, RewardL) of
    %     {Career, RewardList} ->
    %         RewardList;
    %     _ ->
    %         []
    % end;
%% 指定玩家落后世界等级
filter_reward(?REWARD_TYPE_WLV, {WLvMin, RewardL}, Lv, _Career, _CombatPower) ->
    WorldLv = util:get_world_lv(),
    DifferLv = WorldLv - Lv,
    case DifferLv >= WLvMin of
        true ->
            RewardL;
            % case lists:keyfind(Career, 1, RewardL) of
            %     {Career, RewardList} ->
            %         RewardList;
            %     _ ->
            %         []
            % end;
        false ->
            []
    end;
filter_reward(RewardType, [_H|T], Lv, Career, CombatPower)
    when RewardType == ?REWARD_TYPE_LV orelse RewardType == ?REWARD_TYPE_COMBAT ->
    filter_reward(RewardType, T, Lv, Career, CombatPower);
filter_reward(_RewardType, _RewardList, _Lv, _Career, _CombatPower) ->
    ?ERR("no match reward type Type:~p RewardList:~p~n", [_RewardType, _RewardList]),
    [].


reload_config() ->
    case config:get_cls_type() of
        ?NODE_GAME -> util:cast_event_to_players_helper({'apply_cast', ?APPLY_CAST_SAVE, ?MODULE, notice_day_info_when_reload_cfg, []});
        _          -> skip
    end.

notice_day_info_when_reload_cfg(Player) ->
    #player_status{figure = #figure{lv = RoleLv}, login_reward = LoginReward} = Player,
    #login_reward{login_days = LoginDays} = LoginReward,
    CfgMaxDays = get_cfg_max_login_day(),
    CfgLv = lib_module:get_open_lv(?MOD_LOGIN_REWARD),
    % ?ERR("reload_config ~p", [LoginReward]),
    if
        RoleLv < CfgLv -> %% 等级不足不触发
            {ok, Player};
        LoginDays < CfgMaxDays ->
            {ok, Player};
        true -> %% 登录天数已达配置最大天数
            {Change, NewLoginReward} = calc_new_login_data_with_cfg(CfgMaxDays, LoginReward),
            if Change == true ->
                    NewPlayer = Player#player_status{login_reward = NewLoginReward},
                    pp_login_reward:handle(17500, NewPlayer, []),
                    {ok, NewPlayer};
                true ->
                    {ok, Player}
            end
    end.

%% gm命令，登录天数加一
gm_add_day(PS) ->
    #player_status{id = RoleId, login_reward = LoginReward, figure = Figure} = PS,
    #figure{lv = Lv} = Figure,
    #login_reward{login_days = LoginDays, day_states = DayStates} = LoginReward,
    case lib_module:is_open(Lv, ?MOD_LOGIN_REWARD) of
        true ->
            NewLoginDays = LoginDays + 1,
            NewLoginReward = LoginReward#login_reward{login_days = NewLoginDays},
            lib_login_reward:login_reward_sql(NewLoginReward),
            ResPs = PS#player_status{login_reward = NewLoginReward},
            DayIdList = data_login_reward:get_login_reward_day_list(),
            MaxCfgDay = lists:max(DayIdList),
            DayInfos = calc_day_states(DayStates, NewLoginDays, MaxCfgDay),
            {ok, Bin} = pt_175:write(17500, [?SUCCESS, DayInfos, 1, NewLoginDays]),
            lib_server_send:send_to_uid(RoleId, Bin),
            NResPs = lib_role_visus:trigger_visus(ResPs, login),
            NResPs;
        false ->
            skip
    end.

gm_reset_loginday(Player) ->
    #player_status{id = RoleId, figure = Figure} = Player,
    #figure{lv = Lv} = Figure,
    case lib_module:is_open(Lv, ?MOD_LOGIN_REWARD) of
        true ->
            NewLoginDays = 1,
            RefTime = utime:unixdate(),
            NewLoginReward = #login_reward{role_id = RoleId, refresh_days_time = RefTime, login_days = NewLoginDays},
            lib_login_reward:login_reward_sql(NewLoginReward),
            NewPlayer = Player#player_status{login_reward = NewLoginReward},
            {ok, Bin} = pt_175:write(17500, [?SUCCESS, [{NewLoginDays, ?LOGIN_REWARD_UNGET}], 1, NewLoginDays]),
            lib_server_send:send_to_uid(RoleId, Bin),
            {ok, NewPlayer};
        _ ->
            skip
    end.

%% DayStates :: list() 已领取天数
%% LoginDays :: int()  已累积登录天
%% MaxCfgDay :: int()  最新最大天数配置
calc_day_states(DayStates, LoginDays, MaxCfgDay) ->
    NewLoginDays = min(MaxCfgDay, LoginDays),
    NewDayStates = lists:filter(fun(DayX) -> DayX =< NewLoginDays end, DayStates),
    if NewLoginDays == 0 ->
            [];
        true ->
            AllDays = lists:seq(1, NewLoginDays),
            CanGets = lists:filter(fun(DayId) -> not lists:member(DayId, NewDayStates) end, AllDays),
            CanGetDays = pack_days_info(?LOGIN_REWARD_UNGET, CanGets),
            GettedDays = pack_days_info(?LOGIN_REWARD_GETED, NewDayStates),
            lists:append(GettedDays, CanGetDays)
    end.

pack_days_info(Type, Days) ->
    lists:foldl(fun(DayId, Acc) ->
        [{DayId, Type}|Acc]
     end, [], Days).

%% 七天登录奖励&登录数据更新事务操作
%% 先前出现过大批量玩家只更新到了登录数据而未能获得奖励
%% 因此将奖励发放及七天登录数据更新放在一个事务中
transaction_reward_do(Player, Produce, DayId, TodayDate) ->
    #player_status{login_reward = #login_reward{day_states = DayStates} = LoginReward} = Player,
    #produce{reward = RewardList, type = ProduceType, remark = Remark} = Produce,
    LoginRewardsSql = get_update_reward_sql(LoginReward, DayId, TodayDate),
    {MoneyL, CurrencyL, GoodsL} = spilt_goods(RewardList),
    NewPlayer = do_send_reward_currency(Player, ProduceType, Remark, MoneyL ++ CurrencyL),
    case do_send_reward_goods(NewPlayer, GoodsL, Produce, [LoginRewardsSql]) of
        {true, NewPlayer1} ->
            NewLoginReward = LoginReward#login_reward{day_states = [DayId|DayStates], get_date = TodayDate},
            {ok, NewPlayer1#player_status{login_reward = NewLoginReward}};
        {false, ErrorCode} ->
            {false, ErrorCode}
    end.

get_update_reward_sql(LoginReward, DayId, TodayDate) ->
    #login_reward{role_id = RoleId, day_states = DayStates, refresh_days_time= RefTime, login_days = LoginDays} = LoginReward,
    NewDayStates = [DayId|DayStates],
    Sql = io_lib:format(?SQL_REPLACE_LOGIN_REWARD_WITH_QUOTA, [RoleId, TodayDate, RefTime, LoginDays, util:term_to_bitstring(NewDayStates)]),
    Sql.

do_send_reward_currency(Player, _, _, []) -> Player;
do_send_reward_currency(Player, ProduceType, Remark, [H|CurrencyL]) ->
    NewPlayer = goods_add:send_reward_currency(Player, H, ProduceType, Remark),
    do_send_reward_currency(NewPlayer, ProduceType, Remark, CurrencyL).

do_send_reward_goods(Player, GoodsL, Produce, Sqls) ->
    GoodsL1 = change_goods_format(GoodsL),
    give_rewards_goods(Player, GoodsL1, Produce, Sqls).

%% desc
%% Player    :: #player_status{}
%% GoodsList :: [ [{Type, GoodsTypeId, Num}] |_]
%% Produce   :: #produce{}
%% Sql       :: list()
give_rewards_goods(Player, GoodsList = [_|_], Produce, Sqls) ->
    #produce{type = Type, subtype = SubType, remark = Remark} = Produce,
    Res = give_rewards_goods_do(Player, GoodsList, Sqls, Type),
    case Res of
        {ErrorCode, GoodsInfoList} when ErrorCode == 1 ->
            NowTime = utime:unixtime(),
            ProduceTypeId = data_goods:get_produce_type(Type),
            case Type =/= [] of
                true ->
                    F = fun(OneAdd, AccList) ->
                         case change_log_goods_format(OneAdd) of
                            {GoodsTypeId, GoodsNum} when GoodsTypeId =/= 0 orelse GoodsNum =/= 0 ->
                                [[NowTime, ProduceTypeId, SubType, GoodsTypeId, GoodsNum, Player#player_status.id, Remark]|AccList];
                            _ ->
                                AccList
                         end
                    end,
                    LogList = lists:foldl(F, [], GoodsList),
                    goods_add:updata_dict_add(Produce, GoodsInfoList),
                    lib_log_api:log_goods(LogList);
                false -> skip
            end,
            {true, Player};
        ErrorCode when is_integer(ErrorCode) ->
            {false, ErrorCode};
        _A ->
            ?ERR("give reward error ~p", [_A]),
            {false, ?FAIL}
    end;
give_rewards_goods(_Player, GoodsList, _Produce, _Sqls) ->
    ?ERR("param error:~p", [GoodsList]),
    {false, ?FAIL}.

change_log_goods_format({goods, TypeId, Num}) -> {TypeId, Num};
change_log_goods_format({goods, TypeId, Num, _Bind}) -> {TypeId, Num};
change_log_goods_format({goods_attr, TypeId, Num, _DyAttrList}) -> {TypeId, Num};
change_log_goods_format({TypeId, Num}) -> {TypeId, Num};
change_log_goods_format({?TYPE_GOODS, TypeId, Num}) -> {TypeId, Num};
change_log_goods_format({?TYPE_BIND_GOODS, TypeId, Num}) -> {TypeId, Num};
change_log_goods_format([{goods, TypeId, Num}|_]) -> {TypeId, Num};
change_log_goods_format([{goods, TypeId, Num, _Bind}|_]) -> {TypeId, Num};
change_log_goods_format([{goods_attr, TypeId, Num, _DyAttrList}|_]) -> {TypeId, Num};
change_log_goods_format([{?TYPE_GOODS, TypeId, Num}|_]) -> {TypeId, Num};
change_log_goods_format([{?TYPE_BIND_GOODS, TypeId, Num}|_]) -> {TypeId, Num};
change_log_goods_format(_) -> {0, 0}.

give_rewards_goods_do(_Player, GoodsList, Sqls, Type) ->
    GoodsStatus = lib_goods_do:get_goods_status(),
    case give_rewards_goods_do_logic(GoodsStatus, GoodsList, Sqls) of
        {ok, NewStatus, UpdateGoodsList} ->
            lib_goods_do:set_goods_status(NewStatus),
            lib_goods_api:notify_client(NewStatus#goods_status.player_id, UpdateGoodsList, Type),
            % lib_goods_api:send_tv_tip(Player#player_status.id, 2, GoodsList),
            {?SUCCESS, UpdateGoodsList};
        %% 物品类型不存在
        {db_error, {error,{_, not_found}}} ->
            ?ERRCODE(err150_no_goods_type);
        %% 背包格子不足
        {db_error, {error,{cell_num, not_enough}}} ->
            ?ERRCODE(err153_bag_not_enough);
        % %% 动态属性设置权限不足
        % {db_error, {error,{_Type, cannot_overlap}}} ->
        %     4;
        %% 背包类型配置错误
        {db_error, {error,{_, bag_location_fail}}} ->
            ?ERRCODE(err150_bag_location_fail);
        Error ->
            ?ERR("give_more error:~p", [Error]),
            ?FAIL
    end.


give_rewards_goods_do_logic(GoodsStatus, GoodsList, Sqls) ->
    case check_give_goods_list(GoodsList) of
        true ->
            F = fun() ->
                ok = lib_goods_dict:start_dict(),
                NewGoodsStatus = lists:foldl(fun(GoodsL, TmpGoodsStatus) ->
                    {ok, NewTmpStatus} = lib_goods_check:list_handle(fun goods_add:give_goods/2, TmpGoodsStatus, GoodsL),
                    NewTmpStatus
                end, GoodsStatus, GoodsList),
                lists:map(fun goods_transaction:add_sql/1, Sqls),
                {Dict, UpdateGoodsL} =
                    lib_goods_dict:handle_dict_and_notify(NewGoodsStatus#goods_status.dict),
                NewGoodsStatus2 = NewGoodsStatus#goods_status{dict = Dict, temp_bag = #{}},
                {ok, NewGoodsStatus2, UpdateGoodsL}
                end,
            goods_transaction:transaction(F);
        _ ->
            {false, ?FAIL}
    end.

check_give_goods_list(GoodsList) ->
    lists:all(fun(GoodsL) ->
        case lib_goods_check:check_give_list(GoodsL) of
            {ok} -> true;
            _    -> false %% 列表中GoodsTypeId重复
        end
     end, GoodsList).

change_goods_format(GoodsL) ->
    F = fun
            ({ {?TYPE_GOODS, GoodsTypeId}, GoodsNum}) ->
                [{goods, GoodsTypeId, GoodsNum}];
            ({ {?TYPE_BIND_GOODS, GoodsTypeId}, GoodsNum}) ->
                [{goods, GoodsTypeId, GoodsNum, ?BIND}];
            ({ {?TYPE_ATTR_GOODS, GoodsTypeId}, GoodsNum, Args}) ->
                [{goods_attr, GoodsTypeId, GoodsNum, Args}]
        end,
    GoodsL1 = lists:map(F, GoodsL),
    GoodsL1.

spilt_goods(RewardList) ->
    spilt_goods_do(RewardList, [], [], []).

spilt_goods_do([], MoneyL, CurrencyL, GoodsL) -> {MoneyL, CurrencyL, GoodsL};
spilt_goods_do([H|Rest], MoneyL, CurrencyL, GoodsL) ->
    case H of
        {Type = ?TYPE_GOODS, GoodsTypeId, GoodsNum} ->
            NewGoodsL = case lists:keyfind({Type, GoodsTypeId}, 1, GoodsL) of
                               {_, Num} -> [{ {Type, GoodsTypeId}, GoodsNum+Num}| lists:keydelete({Type, GoodsTypeId}, 1, GoodsL)];
                               false    -> [{ {Type, GoodsTypeId}, GoodsNum} | GoodsL]
                           end,
            spilt_goods_do(Rest, MoneyL, CurrencyL, NewGoodsL);
        {Type = ?TYPE_BIND_GOODS, GoodsTypeId, GoodsNum} ->
            NewGoodsL = case lists:keyfind({Type, GoodsTypeId}, 1, GoodsL) of
                               {_, Num} -> [{ {Type, GoodsTypeId}, GoodsNum+Num}| lists:keydelete({Type, GoodsTypeId}, 1, GoodsL)];
                               false    -> [{ {Type, GoodsTypeId}, GoodsNum} | GoodsL]
                           end,
            spilt_goods_do(Rest, MoneyL, CurrencyL, NewGoodsL);
        {Type = ?TYPE_ATTR_GOODS, GoodsTypeId, GoodsNum, Args} ->
            NewGoodsL = [{ {Type, GoodsTypeId}, GoodsNum, Args} | GoodsL],
            spilt_goods_do(Rest, MoneyL, CurrencyL, NewGoodsL);
        Currency -> %% 增加虚拟货币
            {NewMoneyL, NewCurrencyL} = spilt_currency_do(Currency, MoneyL, CurrencyL),
            spilt_goods_do(Rest, NewMoneyL, NewCurrencyL, GoodsL)
    end.

spilt_currency_do(H, MoneyL, CurrencyL) ->
    case H of
        {?TYPE_COIN, _, _Coin}               -> {[H|MoneyL], CurrencyL};
        {?TYPE_GOLD, _, _Gold}               -> {[H|MoneyL], CurrencyL};
        {?TYPE_BGOLD, _, _BGold}             -> {[H|MoneyL], CurrencyL};
        {?TYPE_FGOLD, _, _FGold}             -> {[H|MoneyL], CurrencyL};
        {?TYPE_GFAME, _, _GFame}             -> {MoneyL, [H|CurrencyL]};
        {?TYPE_EXP, _, _Exp}                 -> {MoneyL, [H|CurrencyL]};
        {?TYPE_FAME, _, _Num}                -> {MoneyL, [H|CurrencyL]};
        {?TYPE_CHARM, _, _Num}               -> {MoneyL, [H|CurrencyL]};
        {?TYPE_GFUNDS, _, _Gfunds}           -> {MoneyL, [H|CurrencyL]};
        {?TYPE_HONOUR, _, _Num}              -> {MoneyL, [H|CurrencyL]};
        {?TYPE_GUILD_GROWTH, _, _Val}        -> {MoneyL, [H|CurrencyL]};
        {?TYPE_MATE, _, _Val}                -> {MoneyL, [H|CurrencyL]};
        {?TYPE_DESIGN, _GoodsId, _Val}       -> {MoneyL, [H|CurrencyL]};
        {?TYPE_DESIGN, _GoodsId}             -> {MoneyL, [H|CurrencyL]};
        {?TYPE_FASHION, _GoodsId, _Val}      -> {MoneyL, [H|CurrencyL]};
        {?TYPE_FASHION, _GoodsId}            -> {MoneyL, [H|CurrencyL]};
        {?TYPE_VIP_EXP, _, _Val}             -> {MoneyL, [H|CurrencyL]};
        {?TYPE_CURRENCY, _CurrencyId, _Val}  -> {MoneyL, [H|CurrencyL]};
        _                                    -> {MoneyL, CurrencyL}
    end.
