%% ---------------------------------------------------------------------------
%% @doc 巅峰竞技匹配进程(兼容单服与跨服两种环境)
%% @author hek
%% @since  2016-11-14
%% @deprecated
%% ---------------------------------------------------------------------------
-module(mod_top_pk_match).
-behaviour(gen_server).
-include("common.hrl").
-include("errcode.hrl").
-include("predefine.hrl").
-include("top_pk.hrl").
-include("career.hrl").
-include("figure.hrl").
-include("attr.hrl").
-include("def_module.hrl").
-include("activitycalen.hrl").
-include("def_gen_server.hrl").

%% API
-export([start_link/0, act_start/1,  enter_match/3, cancel_match/2, create_battle/2,
        get_act_info/1, send_error/3, gm_start/1, gm_end/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

start_link() ->
    gen_server:start_link({local,?MODULE}, ?MODULE, [], []).
act_start(Args) ->
    gen_server:cast(?MODULE, {act_start, Args}).
gm_start(Time) ->
    gen_server:cast(?MODULE, {gm_start, Time}).
gm_end() ->
    gen_server:cast(?MODULE, {gm_end}).
enter_match(Node, RoleId, Args) ->
    gen_server:cast(?MODULE, {enter_match, Node, RoleId, Args}).
cancel_match(Node, RoleId) ->
    gen_server:cast(?MODULE, {cancel_match, Node, RoleId}).
create_battle(Role1, Role2) ->
    gen_server:cast(?MODULE, {create_battle, Role1, Role2}).
get_act_info(Sid) ->
    gen_server:cast(?MODULE, {get_act_info, Sid}).

init(Args) ->
    ?DO_INIT(Args).
handle_call(Request, From, State) ->
    ?DO_HANDLE_CALL(Request, From, State).
handle_cast(Msg, State) ->
    ?DO_HANDLE_CAST(Msg, State).
handle_info(Info, State) ->
    ?DO_HANDLE_INFO(Info, State).
terminate(Reason, State) ->
    ?DO_TERMINATE(Reason, State).
code_change(OldVsn, State, Extra) ->
    ?DO_CODE_CHANGE(OldVsn, State, Extra).

do_init([]) ->
    ClsType = config:get_cls_type(),
    EnableAcId = lib_activitycalen_api:get_first_enabled_ac_id(?MOD_TOPPK, 0),
    case {ClsType, EnableAcId} of
        {?NODE_GAME, {ok, ActId}} ->
            State = init_act(#match_state{}, ActId),
            {ok, State#match_state{cls_type = ?ACT_CLS_LOCAL}};
        {?NODE_GAME, _} ->
            {ok, #match_state{cls_type = ?ACT_CLS_LOCAL}};
        _ ->
            {ok, #match_state{cls_type = ?ACT_CLS_CENTER}}
    end.

do_handle_call(_Request, _From, State) ->
    {reply, ok, State}.

% do_handle_cast({get_act_info, Sid}, State) ->
%     NowTime = utime:unixtime(),
%     #match_state{start_time = StartTime, end_time = EndTime} = State,
%     if
%         StartTime =< NowTime andalso NowTime =< EndTime -> IsOpen = 1;
%         true -> IsOpen = 0
%     end,
%     lib_server_send:send_to_sid(Sid, pt_281, 28107, [IsOpen, StartTime, EndTime]),
%     {noreply, State};

do_handle_cast({act_start, Args}, State) ->
    NowTime = utime:unixtime(),
    if
        State#match_state.start_time =< NowTime andalso NowTime =< State#match_state.end_time ->
            LastState = State;
        true ->
            NewState = clear_old_state(State),
            LastState = init_act(NewState, Args)
    end,
    {noreply, LastState};

do_handle_cast({gm_start, Time}, State) ->
    Now = utime:unixtime(),
    if
        State#match_state.start_time =< Now andalso Now =< State#match_state.end_time ->
            LastState = State;
        true ->
            NewState = clear_old_state(State),
            LastState = start(NewState, Now, Now, Now + Time)
    end,
    {noreply, LastState};

do_handle_cast({gm_end}, State) ->
    Now = utime:unixtime(),
    if
        State#match_state.start_time =< Now andalso Now =< State#match_state.end_time ->
            NewState = act_end(State);
        true -> NewState = State
    end,
    {noreply, NewState};

do_handle_cast({enter_match, Node, RoleId, Args}, State) ->
    NowTime = utime:unixtime(),
    if
        State#match_state.start_time =< NowTime andalso NowTime < State#match_state.end_time ->
            enter_match(State, Node, RoleId, Args);
        true ->
            send_error(Node, RoleId, ?ERRCODE(err157_act_not_open)),
            {noreply, State}
    end;

do_handle_cast({cancel_match, Node, RoleId}, State) ->
    #match_state{match_queue = MatchQueue} = State,
    NewMatchQueue = lists:keydelete({Node, RoleId}, #match_info.role_key, MatchQueue),
    {ok, BinData} = pt_281:write(28114, [?SUCCESS]),
    send_msg(Node, RoleId, BinData),
    {noreply, State#match_state{match_queue = NewMatchQueue}};

% do_handle_cast({create_battle, Role1, Role2}, State) ->
%     lib_top_pk:create_battle(Role1, Role2),
%     {noreply, State};

do_handle_cast(_Msg, State) ->
    {noreply, State}.

do_handle_info({timeout, TimerRef, act_end}, #match_state{end_ref = TimerRef} = State) ->
    NewState = act_end(State),
    {noreply, NewState};
do_handle_info({timeout, TimerRef, match}, #match_state{match_ref = TimerRef} = State) ->
    #match_state{match_queue = MatchQueue} = NewState = do_match(State),
    case MatchQueue of
        [] -> {noreply, NewState#match_state{match_ref = undefined}};
        _ ->
            Ref = erlang:start_timer(?TOP_PK_KV_MATCH_TIME*1000, self(), match),
            {noreply, NewState#match_state{match_ref = Ref}}
    end;
do_handle_info(_Info, State) ->
    {noreply, State}.
do_terminate(_Reason, _State) ->
    ok.
do_code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%% --------------------------------------------------------------------------
%%% Internal functions
%% --------------------------------------------------------------------------
init_act(State, ActId) ->
    Act = data_activitycalen:get_ac(?MOD_TOPPK, 0, ActId),
    #base_ac{time_region = TimeRegion} = Act,
    {_NowDay, {NowH,NowM,_}} = calendar:local_time(),
    Now = NowH * 60 + NowM,
    ZeroAclock = utime:unixdate(),
    NowTime = utime:unixtime(),
    case ulists:find(
        fun ({{SH, SM}, {EH, EM}}) ->
            SH * 60 + SM =< Now andalso Now =< EH * 60 + EM
        end, TimeRegion) of
        {ok, {{SH, SM}, {EH, EM}}} ->
            StartTime = ZeroAclock + SH * 3600 + SM * 60,
            EndTime = ZeroAclock + EH * 3600 + EM * 60,
            start(State, NowTime, StartTime, EndTime);
        _ -> State
    end.

start(State, NowTime, StartTime, EndTime) ->
    EndDelay = max(EndTime - NowTime, 1),
    EndRef = erlang:start_timer(EndDelay * 1000, self(), act_end),
    % {ok, BinData} = pt_281:write(28107, [1, StartTime, EndTime]),
    % lib_server_send:send_to_all(all_lv, {?TOP_PK_KV_OPEN_LV, 65535}, BinData),
    NewState = State#match_state{start_time = StartTime, end_time = EndTime, end_ref = EndRef},
    %% 同步活动状态到单服进程
    case State#match_state.cls_type of
        ?ACT_CLS_LOCAL -> mod_top_pk_local:apply_cast({'local_sync_act_status', ?ACT_STATUS_START});
        _ -> clusters_center:apply_to_all_node(mod_top_pk_local, apply_cast, [{'center_sync_act_status', ?ACT_STATUS_START}], 100)
    end,
    NewState.

act_end(State) ->
    % {ok, BinData} = pt_281:write(28107, [0, 0, 0]),
    % lib_server_send:send_to_all(all_lv, {?TOP_PK_KV_OPEN_LV, 65535}, BinData),
    NewState = clear_old_state(State),
    %% 同步活动状态到单服进程
    case NewState#match_state.cls_type of
        ?ACT_CLS_LOCAL -> mod_top_pk_local:apply_cast({'local_sync_act_status', ?ACT_STATUS_STOP});
        _ ->
            mod_top_pk_rank_center:apply_cast('act_end'),
            clusters_center:apply_to_all_node(mod_top_pk_local, apply_cast, [{'center_sync_act_status', ?ACT_STATUS_STOP}], 100)
    end,
    NewState.

clear_old_state(State) ->
    #match_state{match_ref = MatchRef, end_ref = EndRef, match_queue = TimeOutList} = State,
    util:cancel_timer(MatchRef),
    util:cancel_timer(EndRef),
    cancel_matching(TimeOutList),
    State#match_state{start_time = 0, end_time = 0, match_ref = undefined, match_queue = []}.

%% 活动结束，取消当前正在参与匹配的玩家匹配状态
cancel_matching([]) -> ignore;
cancel_matching([#match_info{role_key = {Node, RoleId}} |T]) ->
    player_apply_cast(Node, RoleId, lib_top_pk, cancel_match, []),
    {ok, BinData} = pt_281:write(28114, [?SUCCESS]),
    send_msg(Node, RoleId, BinData),
    cancel_matching(T);
cancel_matching([_|T]) ->
    cancel_matching(T).

enter_match(State, Node, RoleId, Args) ->
    #match_state{match_queue = MatchQueue, match_ref = MatchRef} = State,
    RoleKey = {Node, RoleId},
    case lists:keyfind(RoleKey, #match_info.role_key, MatchQueue) of
        false ->
            [RoleName, Lv, Platform, ServerNum, Power, Score, ContinuedFail] = Args,
            Info = #match_info{
                role_key = RoleKey, name = RoleName, lv = Lv, platform = Platform, server_num = ServerNum,
                power = Power, score = Score, continued_fail = ContinuedFail,
                match_count = 1, match_time = utime:unixtime()},
            NewState = State#match_state{match_queue = [Info|MatchQueue]},
            player_apply_cast(Node, RoleId, lib_top_pk, set_player_match_start, [node()]),
            {ok, BinData} = pt_281:write(28110, [?SUCCESS]),
            send_msg(Node, RoleId, BinData),
            if
                is_reference(MatchRef) =:= false -> do_handle_info({timeout, MatchRef, match}, NewState);
                true ->  {noreply, NewState}
            end;
        _ ->
            player_apply_cast(Node, RoleId, lib_top_pk, set_player_match_start, [node()]),
            % send_error(Node, RoleId, ?ERRCODE(err281_match_repeat)),
            {ok, BinData} = pt_281:write(28110, [?SUCCESS]),
            send_msg(Node, RoleId, BinData),
            {noreply, State}
    end.

do_match(State) ->
    NowTime = utime:unixtime(),
    NeedMatchTime = ?TOP_PK_KV_MATCH_TIME,
    #match_state{match_queue = MatchQueue} = State,
    F1 = fun(A, B) -> queue_sort(A, B) end,
    SortMatchQueue = lists:sort(F1, MatchQueue),
    F2 = fun(Matching, {Queue, RankId}) ->
        NewMatching = Matching#match_info{rank_id = RankId},
        {[NewMatching|Queue], RankId + 1}
    end,
    {MatchQueueAfter, _RankId} = lists:foldl(F2, {[], 1}, SortMatchQueue),
    {Pairs, NewMatchQueue} = calc_match(MatchQueueAfter, [], [], NowTime, NeedMatchTime),
    create_pairs(Pairs),
    % print_id(old, SortMatchQueue),
    % print_id(new, NewMatchQueue),
    State#match_state{match_queue = NewMatchQueue}.

queue_sort(#match_info{score = ScoreA, power = PowerA},
           #match_info{score = ScoreB, power = PowerB}) ->
    ScoreA < ScoreB orelse (ScoreA =:= ScoreB andalso PowerA < PowerB).

% print_id(Tag, MatchQueue) ->
%     Ids = [Key||#match_info{role_key = Key}<-MatchQueue],
%     ?INFO("print_idprint_id:~p, ~p~n", [Tag, Ids]),
%     ok.

% get_match_area(1) -> 10;
% get_match_area(2) -> 25;
% get_match_area(3) -> 50;
% get_match_area(4) -> 100;
get_match_area(MatchCount) ->
    AreaList = ?TOP_PK_KV_MATCH_AREA_LIST,
    case lists:keyfind(MatchCount, 1, AreaList) of
        {MatchCount, Area} -> Area;
        _ -> 100
    end.

calc_match([], Pairs, FailMatchQueue, _NowTime, _NeedMatchTime) -> {Pairs, lists:reverse(FailMatchQueue)};
calc_match([One|Others], Pairs, FailMatchQueue, NowTime, NeedMatchTime) ->
    #match_info{match_count = MatchCount, continued_fail = FailCount, match_time = MatchTime} = One,
    case MatchCount >= ?TOP_PK_KV_MATCH_MAX_NUN orelse FailCount>=?TOP_PK_KV_FAIL_COUNT_TO_MATCH_DUMMY of
        true when NowTime >= (MatchTime + NeedMatchTime) ->
            calc_match(Others, [{One, ai}|Pairs], FailMatchQueue, NowTime, NeedMatchTime);
        true ->
            calc_match(Others, Pairs, [One|FailMatchQueue], NowTime, NeedMatchTime);
        false when NowTime < (MatchTime + NeedMatchTime) ->
            calc_match(Others, Pairs, [One|FailMatchQueue], NowTime, NeedMatchTime);
        false ->
            case calc_match_helper(One, Others, Others, NowTime, NeedMatchTime) of
                {true, NewPair, NreOthers} ->
                    calc_match(NreOthers, [NewPair|Pairs], FailMatchQueue, NowTime, NeedMatchTime);
                _ ->
                    NewOne = One#match_info{match_count = MatchCount + 1},
                    calc_match(Others, Pairs, [NewOne|FailMatchQueue], NowTime, NeedMatchTime)
            end
    end.

calc_match_helper(_One, [], _OldOthers, _NowTime, _NeedMatchTime) -> false;
calc_match_helper(One, [Two|Others], OldOthers, NowTime, NeedMatchTime) ->
    #match_info{match_count = MatchCountB, continued_fail = FailCountB, match_time = MatchTimeB} = Two,
    case MatchCountB >= ?TOP_PK_KV_MATCH_MAX_NUN orelse FailCountB>=?TOP_PK_KV_FAIL_COUNT_TO_MATCH_DUMMY of
        true ->
            calc_match_helper(One, Others, OldOthers, NowTime, NeedMatchTime);
        false when NowTime < (MatchTimeB + NeedMatchTime) ->
            calc_match_helper(One, Others, OldOthers, NowTime, NeedMatchTime);
        false ->
            #match_info{match_count = MatchCountA, rank_id = RankIdA} = One,
            #match_info{role_key = RoleKeyB, match_count = MatchCountB, rank_id = RankIdB} = Two,
            Diff = abs(RankIdA - RankIdB),
            case Diff=<get_match_area(MatchCountA) andalso Diff=<get_match_area(MatchCountB) of
                true -> {true, {One, Two}, lists:keydelete(RoleKeyB, #match_info.role_key, OldOthers)};
                false -> calc_match_helper(One, Others, OldOthers, NowTime, NeedMatchTime)
            end
    end.

create_pairs([]) -> ok;
create_pairs([{One, ai}|Pairs]) ->
    create_fake_battle_room(One),
    create_pairs(Pairs);
create_pairs([{One, Two}|Pairs]) ->
    #match_info{role_key = {_Node1, _} = Key1, score = Score1, platform = Platform1,
            server_num = ServerNum1, name = RoleName1, power = Power1} = One,
    #match_info{role_key = {_Node2, _} = Key2, score = Score2, platform = Platform2,
            server_num = ServerNum2, name = RoleName2, power = Power2} = Two,
    create_battle_room(
        {Key1, Score1, [Platform1, ServerNum1, RoleName1], Power1},
        {Key2, Score2, [Platform2, ServerNum2, RoleName2], Power2}),
    create_pairs(Pairs).

create_battle_room({Key1, _Score1, _PlatInfo1, _Power1} = Args1, {Key2, _Score2, _PlatInfo2, _Power2} = Args2) ->
    BattleNode = node(),
    {Node1, RoleId1} = Key1, {Node2, RoleId2} = Key2,
    case catch mod_battle_field:start(lib_top_pk_battle, [Args1, Args2]) of
        BattlePid when is_pid(BattlePid) ->
            apply_cast(Node1, lib_player, apply_cast, [RoleId1, ?APPLY_CAST_STATUS, lib_top_pk, battle_field_create_done, [BattleNode, BattlePid, Key1]]),
            apply_cast(Node2, lib_player, apply_cast, [RoleId2, ?APPLY_CAST_STATUS, lib_top_pk, battle_field_create_done, [BattleNode, BattlePid, Key2]]);
        _R ->
            ?ERR("create_battle_room:~p~n", [_R]),
            apply_cast(Node1, lib_player, apply_cast, [RoleId1, ?APPLY_CAST_STATUS, lib_top_pk, battle_field_close, [init_error]]),
            apply_cast(Node2, lib_player, apply_cast, [RoleId2, ?APPLY_CAST_STATUS, lib_top_pk, battle_field_close, [init_error]])
    end.

create_fake_battle_room(One) ->
    #match_info{role_key = {MyNode, RoleId} = RoleKey, lv = Lv, power = Power, score = Score} = One,
    FakeManData = generate_fake_man_attr(Lv, Power),
    BattleNode = node(),
    case catch mod_battle_field:start(lib_top_pk_battle, [RoleKey, Score, Power, fake_man, FakeManData]) of
        BattlePid when is_pid(BattlePid) ->
            apply_cast(MyNode, lib_player, apply_cast, [RoleId, ?APPLY_CAST_STATUS, lib_top_pk, battle_field_create_done, [BattleNode, BattlePid, RoleKey]]);
        _R ->
            ?ERR("create_fake_battle_room:~p~n", [_R]),
            apply_cast(MyNode, lib_player, apply_cast, [RoleId, ?APPLY_CAST_STATUS, lib_top_pk, battle_field_close, [init_error]])
   end.

generate_fake_man_attr(OldLv, OldPower) ->
    case data_top_pk:get_fake_man_cfg(OldLv) of
        #base_top_pk_fake_man{min_lv = MinLv, max_lv = MaxLv} ->
            Lv = urand:rand(MinLv, MaxLv),
            DefalutPower = max(OldPower, 9999),
            BattleAttr = lib_fake_role:get_faker_role_attr(Lv, DefalutPower, ?MOD_TOPPK, 0),
            Power = max(BattleAttr#battle_attr.combat_power, DefalutPower);
        _ ->
            Lv = OldLv,
            OldPower2 = max(OldPower, 9999),
            Power = round((urand:rand(60, 120) / 100) * OldPower2),
            BattleAttr = make_default_battle_attr(Power)
    end,
    Career = urand:list_rand(?ALL_CAREER_LIST),
    Turn = urand:list_rand(lists:seq(0, 5)),
    #career{sex = Sex} = data_career:get(Career),
    LvModel = lib_player:get_model_list(Career, Sex, Turn, Lv),
    InitFigure = #figure{sex = Sex, turn = Turn, career = Career, lv = Lv, lv_model = LvModel},
    Figure = lib_dummy_api:change_figure(InitFigure, [create_name]),
    SkillList = lib_skill_api:get_career_active_skill_ids(Career, Sex),
    LastStarSoulList = [],
    [Figure, BattleAttr, SkillList, Power, LastStarSoulList].

%% 生成默认BA
make_default_battle_attr(CombatPower) ->
    #battle_attr{
        hp = round(CombatPower * 0.6),
        hp_lim = round(CombatPower * 0.6),
        attr = #attr{hp = round(CombatPower * 0.6), att = round(CombatPower * 0.025)}
    }.

player_apply_cast(Node, RoleId, Mod, Fun, Args) ->
    if
        Node =:= node() ->
            lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, Mod, Fun, Args);
        true ->
            rpc:cast(Node, lib_player, apply_cast, [RoleId, ?APPLY_CAST_STATUS, Mod, Fun, Args])
    end.

apply_cast(Node, Mod, Fun, Args) ->
    if
        Node =:= node() -> erlang:apply(Mod, Fun, Args);
        true -> rpc:cast(Node, Mod, Fun, Args)
    end.

send_msg(Node, RoleId, BinData) ->
    if
        Node =:= node() ->
            lib_server_send:send_to_uid(RoleId, BinData);
        true ->
            rpc:cast(Node, lib_server_send, send_to_uid, [RoleId, BinData])
    end.

send_error(Node, RoleId, Code) ->
    {ErrInt, ErrArgs} = util:parse_error_code(Code),
    {ok, BinData} = pt_281:write(28100, [ErrInt, ErrArgs]),
    send_msg(Node, RoleId, BinData).

