%% ---------------------------------------
%% desc  : 放置经验公共进程
%% module: exp_dun_drop_mgr.erl
%% author: hh
%% time  :20/01/17
%% ---------------------------------------


-module(exp_dun_drop_mgr).

-behaviour(gen_server).

-export([]).

-include("exp_dun_drop.hrl").
-include("server.hrl").
-include("figure.hrl").
-include("common.hrl").
-include("predefine.hrl").
-include ("errcode.hrl").
-include("def_goods.hrl").

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
%% API
-export([start_link/0]).

-export([
    role_login/1
    ,role_logout/2
    ,update_exp_dun_wave_point/2
    ,role_query_exp_drop_reward_info/1
    ,role_get_exp_drop_reward/2
    ,sync_drop_role_info_after_reward/2
    ,get_fast_exp_drop_limit/2
    ,update_goods_limit/2
    ,get_exp_dun_drop_info/2
    ,update_role_dict_after_db/1
    ,apply_cast/3
    ,update_drop_role_info/2
    ,update_exp_drop_extra_data/2
    ,daily_drop_role_check/0
    ,use_goods_check/2
    ]).

-export([
    get_role_info/1,
    get_role_next_calc_time/1
    ]).

-define(SERVER, ?MODULE).

%%%-------------------------------------------------------------------
%%% API Functions
%%%-------------------------------------------------------------------
start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

%% 玩家上线同步
-spec role_login(ExpDunDrop) -> ok
    when ExpDunDrop :: #drop_role{}.
role_login(ExpDunDrop) ->
    gen_server:cast(?SERVER, {'role_login', ExpDunDrop}).

%% 玩家下线更新
-spec role_logout(RoleId, NowTime) -> ok
    when RoleId :: integer(), NowTime :: integer().
role_logout(RoleId, NowTime) ->
    gen_server:cast(?SERVER, {'role_logout', RoleId, NowTime}).

%% 更新最佳波数时间
-spec update_exp_dun_wave_point(RoleId, Wave) -> ok
    when RoleId :: integer(), Wave :: integer().
update_exp_dun_wave_point(RoleId, Wave) ->
    gen_server:cast(?SERVER, {'update_exp_dun_wave_point', RoleId, Wave}).

%% 玩家放置奖励查看
-spec role_query_exp_drop_reward_info(RoleId) -> ok
    when RoleId :: integer().
role_query_exp_drop_reward_info(RoleId) ->
    gen_server:cast(?SERVER, {'role_query_exp_drop_reward_info', RoleId}).

%% 玩家领取放置奖励
-spec role_get_exp_drop_reward(RoleId, UpdateArgs) -> ok
    when RoleId :: integer(), UpdateArgs :: list().
role_get_exp_drop_reward(RoleId, UpdateArgs) ->
    gen_server:cast(?SERVER, {'role_get_exp_drop_reward', RoleId, UpdateArgs}).

%% 领取奖励后同步状态
sync_drop_role_info_after_reward(RoleId, UpdateArgs) ->
    gen_server:cast(?SERVER, {'sync_drop_role_info_after_reward', RoleId, UpdateArgs}).

%% 快速挂机获取掉落限制信息
-spec get_fast_exp_drop_limit(RoleId, RewardTimes) -> ok
    when RoleId :: integer(), RewardTimes :: integer().
get_fast_exp_drop_limit(RoleId, RewardTimes) ->
    gen_server:cast(?SERVER, {'get_fast_exp_drop_limit', RoleId, RewardTimes}).

-spec update_goods_limit(RoleId, GoodsLimit) -> ok
    when RoleId :: integer(), GoodsLimit :: list().
update_goods_limit(RoleId, GoodsLimit) ->
    gen_server:cast(?SERVER, {'update_goods_limit', RoleId, GoodsLimit}).

-spec get_exp_dun_drop_info(RoleId, {Now, TimeLast, LastTime, NewTimeLast}) -> ok
    when RoleId :: integer(), Now :: integer(), TimeLast :: integer(), LastTime :: integer(),
    NewTimeLast :: integer().
get_exp_dun_drop_info(RoleId, {Now, TimeLast, LastTime, NewTimeLast}) ->
    gen_server:cast(?SERVER, {'get_exp_dun_drop_info', RoleId, {Now, TimeLast, LastTime, NewTimeLast}}).

-spec apply_cast(Mod, Fun, Args) -> ok|skip when
    Mod :: atom(),
    Fun :: atom(),
    Args :: list().
apply_cast(Mod, Fun, Args) ->
    gen_server:cast(?SERVER, {'apply_cast', Mod, Fun, Args}).

-spec get_role_info(RoleId) -> ok when
    RoleId :: integer().
get_role_info(RoleId) ->
    gen_server:cast(?SERVER, {'get_role_info', RoleId}).

-spec get_role_next_calc_time(RoleId) -> ok when
    RoleId :: integer().
get_role_next_calc_time(RoleId) ->
    gen_server:cast(?SERVER, {'get_role_next_calc_time', RoleId}).

-spec update_role_dict_after_db(DropRole) -> ok when
    DropRole :: #drop_role{}.
update_role_dict_after_db(DropRole) ->
    gen_server:cast(?SERVER, {'update_role_dict_after_db', DropRole}).


-spec update_drop_role_info(DropRoleId, KV) -> ok when
    DropRoleId :: integer(),
    KV :: list(). %% [{key, value}, ...]
update_drop_role_info(DropRoleId, KV) ->
    gen_server:cast(?SERVER, {'update_drop_role_info', DropRoleId, KV}).

-spec daily_drop_role_check() -> ok.
daily_drop_role_check() ->
    gen_server:cast(?SERVER, {'daily_drop_role_check'}).

-spec update_exp_drop_extra_data(RoleId, UpdateArgs) -> term() when
    RoleId :: integer(),
    UpdateArgs :: list().
update_exp_drop_extra_data(RoleId, UpdateArgs) ->
    gen_server:cast(?SERVER, {'update_exp_drop_extra_data', RoleId, UpdateArgs}).

use_goods_check(RoleId, [PerAdd, GoodsNum, AddLimit, LastTime, NewTimeLast]) ->
    gen_server:call(?SERVER, {'use_goods_check', RoleId, PerAdd, GoodsNum, AddLimit, LastTime, NewTimeLast}).

%%%-------------------------------------------------------------------
%%% Callback Functions
%%%-------------------------------------------------------------------
init(_Args) ->
    process_flag(trap_exit, true),
    erlang:send(self(), {'do_init'}),
    {ok, #exp_dun_drop_state{}}.

-spec handle_call(Msg, From, State) ->
    {reply, ok, State} when
    Msg :: term(),
    From :: pid() | {pid(), Tag},
    Tag :: term(),
    State :: #exp_dun_drop_state{}.
%% 使用离线挂机加成卡
handle_call({'use_goods_check', RoleId, PerAdd, GoodsNum, AddLimit, LastTime, NewTimeLast}, _From, State) ->
    Ret = case exp_dun_drop_logic:get_role_drop(RoleId) of
        DropRole = #drop_role{} ->
            #drop_role{start_time = StartTime, calc_time = OldCalcTime, extra_data = ExtraData, 
                calc_goods = CalcGoods, off_point = OffPoints} = DropRole,
            if 
                % IsRewarding =/= ?EXP_DROP_NOT_REWARD ->
                %     {fail, ?ERRCODE(err610_exp_drop_rewarding)};
                true ->
                    ModuleKey = {?TYPE_GOODS, ?EXP_DROP_GOODS_SUB_TYPE},
                    case lists:keyfind(ModuleKey, 1, ExtraData) of
                        {_, Val} when Val >= AddLimit ->
                            {fail, ?ERRCODE(err610_exp_drop_offline_limit)};
                        KeyData ->
                            case KeyData of
                                {_, Val1} -> NewVal = min(Val1+PerAdd*GoodsNum, AddLimit);
                                _ -> NewVal = round(PerAdd*GoodsNum)
                            end,
                            case lists:keyfind({?TYPE_EXP, 1}, 1, CalcGoods) of
                                {_, Exp} -> ok;
                                _        -> Exp = 0
                            end,
                            NewExtraData = lists:keystore(ModuleKey, 1, ExtraData, {ModuleKey, NewVal}),
                            NewDropRole = DropRole#drop_role{extra_data = NewExtraData},
                            lib_exp_dun_drop:db_update_drop_role(RoleId, [{extra_data, NewExtraData}]),
                            exp_dun_drop_logic:save_role_drop(NewDropRole),

                            ResourceLimit = lib_exp_dun_drop:get_exp_drop_time_limit(NewExtraData),
                            DefaultData  = lib_exp_dun_drop:get_default_calc_time(OldCalcTime, StartTime),
                            OldTimeLast  = lib_exp_dun_drop:get_calc_time_last(NewExtraData, DefaultData),
                            Now = utime:unixtime(),
                            if LastTime == 0 ->
                                    {_, NewTimeLast1} = lib_exp_dun_drop:calc_new_calc_time_last(OffPoints, OldCalcTime, Now, NewVal, OldTimeLast, ResourceLimit*3600);
                                true ->
                                    NewTimeLast1 = NewTimeLast
                            end,
                            % ?ERR("~p", [NewTimeLast1]),
                            lib_server_send:send_to_uid(RoleId, pt_610, 61052, [StartTime, Exp, NewVal, NewTimeLast1]),
                            true
                    end
            end;
        _ -> {fail, ?ERRCODE(err610_exp_dun_not_pass)}
    end,
    {reply, Ret, State};
handle_call(_Request, _From, State) ->
    {reply, ok, State}.

-spec handle_cast(Msg, State) ->
    {noreply, State}
    | {noreply, State, Timeout}
    | {stop, Reason, State} when
    Msg :: term(),
    State :: #exp_dun_drop_state{},
    Timeout :: non_neg_integer() | infinity,
    Reason :: term().
handle_cast(Msg, State) ->
    case catch do_handle_cast(Msg, State) of
        NewState = #exp_dun_drop_state{} ->
            {noreply, NewState};
        {noreply, NewState} ->
            {noreply, NewState};
        Result when Result == ok orelse Result == skip ->
            {noreply, State};
        {stop, _Reason, State} ->
            _Reason =/= normal andalso ?ERR("exp_dun_drop_mgr process stop reason:~n ~p", [_Reason]),
            {stop, _Reason, State};
        _Reason ->
            ?ERR("exp_dun_drop_mgr process msg: ~n ~p ~n reason: ~n ~p", [Msg, _Reason]),
            {noreply, State}
    end.

-spec handle_info(Msg, State) ->
    {noreply, State}
    | {stop, Reason, State} when
    Msg :: term(),
    Reason :: term(),
    State :: #exp_dun_drop_state{}.
handle_info(Msg, State) ->
    case catch do_handle_info(Msg, State) of
        NewState = #exp_dun_drop_state{} ->
            {noreply, NewState};
        {noreply, NewState} ->
            {noreply, NewState};
        Result when Result == ok orelse Result == skip ->
            {noreply, State};
        {stop, _Reason, State} ->
            _Reason =/= normal andalso ?ERR("exp_dun_drop_mgr process stop reason:~n ~p", [_Reason]),
            {stop, _Reason, State};
        _Reason ->
            ?ERR("exp_dun_drop_mgr process msg: ~n ~p ~n reason: ~n ~p", [Msg, _Reason]),
            {noreply, State}
    end.

terminate(_Reason, _State) ->
    util:cancel_timer(_State#exp_dun_drop_state.ref),
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%%-------------------------------------------------------------------
%%% Internal Functions
%%%-------------------------------------------------------------------

do_handle_cast({'role_login', ExpDunDrop}, State) ->
    NewState = exp_dun_drop_logic:role_login(ExpDunDrop, State),
    {noreply, NewState};

do_handle_cast({'role_logout', RoleId, NowTime}, State) ->
    NewState = exp_dun_drop_logic:role_logout(RoleId, NowTime, State),
    {noreply, NewState};

do_handle_cast({'update_exp_dun_wave_point', RoleId, Wave}, State) ->
    #exp_dun_drop_state{calc_maps = GroupMaps} = State,
    exp_dun_drop_logic:update_exp_dun_wave_point(RoleId, Wave),
    NewGroupMaps = exp_dun_drop_logic:update_calc_maps(RoleId, GroupMaps),
    {noreply, State#exp_dun_drop_state{calc_maps = NewGroupMaps}};

do_handle_cast({'role_query_exp_drop_reward_info', RoleId}, State) ->
    exp_dun_drop_logic:role_query_exp_drop_reward_info(RoleId),
    {noreply, State};

do_handle_cast({'role_get_exp_drop_reward', RoleId, UpdateArgs}, State) ->
    exp_dun_drop_logic:role_get_exp_drop_reward(RoleId, UpdateArgs),
    {noreply, State};

do_handle_cast({'sync_drop_role_info_after_reward', RoleId, UpdateArgs}, State) ->
    exp_dun_drop_logic:sync_drop_role_info_after_reward(RoleId, UpdateArgs),
    {noreply, State};

do_handle_cast({'get_fast_exp_drop_limit', RoleId, RewardTimes}, State) ->
    case exp_dun_drop_logic:get_role_drop(RoleId) of
        #drop_role{goods_limit = GoodsLimit} -> ok;
        _ -> GoodsLimit = []
    end,
    lib_player:apply_cast(RoleId, ?APPLY_CAST_SAVE, fast_exp_drop, get_fast_drop_reward, [GoodsLimit, RewardTimes]),
    {noreply, State};

do_handle_cast({'update_goods_limit', RoleId, GoodsLimit}, State) ->
    exp_dun_drop_logic:update_role_drop(RoleId, [{update_goods_limit, GoodsLimit}]),
    {noreply, State};

do_handle_cast({'get_exp_dun_drop_info', RoleId, {Now, _TimeLast, LastTime, NewTimeLast}}, State) ->
    case exp_dun_drop_logic:get_role_drop(RoleId) of
        #drop_role{start_time = StartTime, calc_time = OldCalcTime, calc_goods = CalcGoods, off_point = OffPoints, extra_data = ExtraData} -> ok;
        _ -> StartTime = 0, OldCalcTime = 0, CalcGoods = [], ExtraData = [], OffPoints = []
    end,
    case lists:keyfind({?TYPE_EXP, 1}, 1, CalcGoods) of
        {_, Exp} -> ok;
        _        -> Exp = 0
    end,
    ResourceLimit = lib_exp_dun_drop:get_exp_drop_time_limit(ExtraData),
    RestBuffTime = lib_exp_dun_drop:get_rest_off_buff_time(ExtraData),
    DefaultData  = lib_exp_dun_drop:get_default_calc_time(OldCalcTime, StartTime),
    OldTimeLast  = lib_exp_dun_drop:get_calc_time_last(ExtraData, DefaultData),
    if LastTime == 0 ->
            {NewRestBuffTime, NewTimeLast1} = lib_exp_dun_drop:calc_new_calc_time_last(OffPoints, OldCalcTime, Now, RestBuffTime, OldTimeLast, ResourceLimit*3600);
        true ->
            NewTimeLast1 = NewTimeLast, NewRestBuffTime = RestBuffTime
    end,
    lib_server_send:send_to_uid(RoleId, pt_610, 61052, [StartTime, Exp, NewRestBuffTime, NewTimeLast1]),
    lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, lib_exp_dun_drop, role_exp_dun_drop_info_update, [NewTimeLast1, Now]),
    {noreply, State};

do_handle_cast({'apply_cast', Mod, Fun, Args}, State) ->
    case catch apply(Mod, Fun, [State|Args]) of
        {noreply, NewState} ->
            {noreply, NewState};
        Res when Res == ok orelse Res == skip ->
            {noreply, State};
        _A ->
            {noreply, State}
    end;

do_handle_cast({'get_role_info', RoleId}, State) ->
    DropRole = exp_dun_drop_logic:get_role_drop(RoleId),
    ?INFO_TTY("~p", [DropRole]),
    {noreply, State};

do_handle_cast({'get_role_next_calc_time', RoleId}, State) ->
    #exp_dun_drop_state{start_time = StartTime, ref = Ref} = State,
    Now = utime:unixtime(),
    NextCalcTimeRest = erlang:read_timer(Ref),
    if is_integer(NextCalcTimeRest) ->
            NextGroupId = exp_dun_drop_logic:get_calc_group_id(Now+round(NextCalcTimeRest/1000), StartTime),
            GroupId = exp_dun_drop_logic:get_role_group(RoleId),
            GroupIntervalSec = round(?EXP_DUN_DROP_INTERVAL/?EXP_DUN_DROP_GROUP),
            RestTime = if NextGroupId > GroupId ->
                    round((GroupId - NextGroupId + ?EXP_DUN_DROP_GROUP)*GroupIntervalSec + NextCalcTimeRest/1000);
                NextGroupId < GroupId ->
                    round((GroupId - NextGroupId)*GroupIntervalSec + NextCalcTimeRest/1000);
                true ->
                    round(NextCalcTimeRest/1000)
            end,
            ?INFO_TTY("next group ~p role group ~p next calc time ~p", [NextGroupId, GroupId, RestTime]);
        true ->
            skip
    end,
    {noreply, State};

do_handle_cast({'update_role_dict_after_db', DropRole}, State) ->
    exp_dun_drop_logic:save_role_drop(DropRole),
    {noreply, State};

do_handle_cast({'update_drop_role_info', DropRoleId, KV}, State) ->
    case exp_dun_drop_logic:get_role_drop(DropRoleId) of
        DropRole = #drop_role{} ->
            NewDropRole = lib_exp_dun_drop:do_update_role_drop(KV, DropRole),
            exp_dun_drop_logic:save_role_drop(NewDropRole);
        _ ->
            skip
    end,
    {noreply, State};

%% 定期检测热数据
%% 超过3天未领取得且暂时不在线的从公共进程中移除
do_handle_cast({'daily_drop_role_check'}, State) ->
    #exp_dun_drop_state{calc_maps = Maps} = State,
    Now = utime:unixtime(),
    NewMaps = maps:fold(fun(K, V, TempMaps) -> 
        NewV = lists:foldl(fun(RoleId, Acc) -> 
            case exp_dun_drop_logic:get_role_drop(RoleId) of
                #drop_role{calc_time = CalcTime} ->
                    LastTime = Now - CalcTime,
                    RolePid = misc:get_player_process(RoleId),
                    IsOnline = is_pid(RolePid) andalso misc:is_process_alive(RolePid),
                    if LastTime >= ?EXP_DROP_REMOVE andalso not IsOnline ->
                            exp_dun_drop_logic:delete_role_drop(RoleId),
                            Acc;
                        true ->
                            [RoleId|Acc]
                    end;
                _ -> %% 无该玩家数据
                    Acc
            end
        end, [], V),
        maps:put(K, NewV, TempMaps)
    end, #{}, Maps),
    % ?ERR("~p", [NewMaps]),
    NewState = State#exp_dun_drop_state{calc_maps = NewMaps},
    {noreply, NewState};

do_handle_cast({'update_exp_drop_extra_data', RoleId, UpdateArgs}, State) ->
    case exp_dun_drop_logic:get_role_drop(RoleId) of
        DropRole = #drop_role{} ->
            NewDropRole = lib_exp_dun_drop:do_update_role_drop([{update_extra_data, UpdateArgs}], DropRole),
            exp_dun_drop_logic:save_role_drop(NewDropRole);
        _ ->
            skip
    end,
    {noreply, State};

do_handle_cast(_Msg, State) ->
    ?ERR("unmatch cast msg ~n ~p", [_Msg]),
    {noreply, State}.



do_handle_info({'do_init'}, State) ->
    Now         = utime:unixtime(),
    Ref         = exp_dun_drop_logic:send_new_ref(State#exp_dun_drop_state.ref),
    NewState    = State#exp_dun_drop_state{start_time = Now, ref = Ref},
    {noreply, NewState};

do_handle_info({'timer_calc'}, State) ->
    % ?ERR("timer calc", []),
    NewState = exp_dun_drop_logic:timer_calc(State),
    {noreply, NewState};

do_handle_info({'EXIT', From, Reason}, State) ->
    Reason =/= normal andalso ?ERR("error happen from ~p ~n ~p", [From, Reason]),
    {noreply, State};

do_handle_info(_Msg, State) ->
    ?ERR("unmatch info msg ~n ~p", [_Msg]),
    {noreply, State}.
