%%-----------------------------------------------------------------------------
%% @Module  :       lib_top_pk_battle.erl
%% @Author  :       J
%% @Email   :       j-som@foxmail.com
%% @Created :       2017-12-06
%% @Description:    巅峰竞技战斗
%%-----------------------------------------------------------------------------

-module(lib_top_pk_battle).
-behaviour (lib_battle_field).
-include("battle_field.hrl").
-include("def_fun.hrl").
-include("predefine.hrl").
-include("server.hrl").
-include("top_pk.hrl").
-include("scene.hrl").
-include("attr.hrl").
-include("common.hrl").
-include("errcode.hrl").
-include("figure.hrl").
-include("battle.hrl").

%% 行为接口
-export([
    %% 战场进程
    init/2
    ,player_enter/3
    ,player_quit/3
    ,player_extra_quit/3
    ,player_logout/3
    ,player_finish_change_scene/3
    ,evt_enter_scene_begin/2
    ,player_die/3
    ,player_revive/3
    ,player_reconnect/3
    ,player_disconnect/3
    ,terminate/2
    %% 玩家进程
    ,evt_out_of_battle/2
    % % ,mon_hp_change/3
    ,mon_die/3
]).

%% 其它接口
-export([
    fake_man_die_handler/5
    ,battle_time_out/2
    ,adjudge_battle/2
    ,get_scene_obj_hp/2
    ,battle_start/2
    ,dummy_start_battle/2
    ,late_force_end/2
]).

-compile(export_all).

-define(SELF_GROUP, 1).
-define(RIVAL_GROUP,2).
-define(COPY(Id), {?MODULE, Id}).
-define(STOP_TIME_AF_RESULT, 10). %% 结算多久后关闭战场进程(s)


init(State, Args) ->
    {SceneId, PosList} = case util:is_cls() of
        true -> {?TOP_PK_KV_CENTER_SCENE, ?TOP_PK_KV_CENTER_BORN_COORD};
        _ -> {?TOP_PK_KV_LOCAL_SCENE, ?TOP_PK_KV_LOCAL_BORN_COORD}
    end,
    ScenePoolId = 0,
    CopyId = ?COPY(State#battle_state.self),
    % [{X1, Y1}, {X2, Y2}|_] = ?IF(urand:rand(1,2) == 1, PosList, lists:reverse(PosList)),
    [{X1, Y1}, {X2, Y2}|_] = PosList,
    case Args of
        [
            { {_Node1, _RoleId1} = RoleKey1, Score1, [Platform1, Serv1, RoleName1], Power1},
            { {_Node2, _RoleId2} = RoleKey2, Score2, [Platform2, Serv2, RoleName2], Power2}
        ] ->
            BattleList1 = [{?BATTLE_SIGN_PLAYER, 1}],
            BattleList2 = [{?BATTLE_SIGN_PLAYER, 1}],
            RoleMap = #{
                RoleKey1 => #battle_role{key = RoleKey1, in_info = #{x => X1, y => Y1},
                    data = #{platform => Platform1, server => Serv1, name => RoleName1, self_score => Score1,  rival_score => Score2,
                                group => ?SELF_GROUP, battle_list => BattleList1, combat_power => Power1} },
                RoleKey2 => #battle_role{key = RoleKey2, in_info = #{x => X2, y => Y2},
                    data = #{platform => Platform2, server => Serv2, name => RoleName2, self_score => Score2,  rival_score => Score1,
                                group => ?RIVAL_GROUP, battle_list => BattleList2, combat_power => Power2} }
            },
            Data = #{role_key1 => RoleKey1, role_key2 => RoleKey2};
        [ {_Node1, _RoleId1} = RoleKey1, Score1, _StarSoulList1, Power1, fake_man, FakeMan] ->
            [Figure, BattleAttr, Skills, Power2, _StarSoulList2] = FakeMan,
            RoleKey2 = {none, fake_man},
            BattleList1 = [{?BATTLE_SIGN_PLAYER, 1}],
            BattleList2 = [{?BATTLE_SIGN_DUMMY, 1}],
            FakeScore = round((urand:rand(80, 120)/100)*Score1),
            RoleMap = #{
                RoleKey1 => #battle_role{key = RoleKey1, in_info = #{x => X1, y => Y1},
                    data = #{self_score => Score1,  rival_score => FakeScore, group => ?SELF_GROUP, battle_list => BattleList1, combat_power => Power1}},
                RoleKey2 => #battle_role{key = RoleKey2, state = ?ROLE_STATE_IN,
                    data = #{self_score => FakeScore, rival_score => Score1, group => ?RIVAL_GROUP, battle_list => BattleList2, combat_power => Power2}}
            },
            MyArgs = [{figure, Figure}, {battle_attr, BattleAttr}, {skill, Skills},
                {die_handler, {?MODULE, fake_man_die_handler, [State#battle_state.self, RoleKey2]}}, {group, ?RIVAL_GROUP}, {combat_power, Power2}],
            Id = lib_scene_object:sync_create_object(?BATTLE_SIGN_DUMMY, 0, SceneId, ScenePoolId, X2, Y2, 1, CopyId, 1, MyArgs),

            Data = #{fake_man => #{data => FakeMan, id => Id}, role_key1 => RoleKey1, role_key2 => RoleKey2}
    end,
    NewState = State#battle_state{cur_scene = SceneId, scene_pool_id = ScenePoolId, copy_id = CopyId, roles = RoleMap, data = Data},
    {ok, NewState}.

player_enter(RoleKey, Args, State) ->
    #battle_state{roles = RoleMap} = State,
    case maps:find(RoleKey, RoleMap) of
        {ok, #battle_role{out_info = OutInfo, in_info = InInfo, data = #{group:=Group}} = Role} ->
            [OSceneId, OPoolId, OCopyId, X, Y, Hp, HpLim] = Args,
            NewOutInfo
            = OutInfo#{
                scene => OSceneId,
                scene_pool_id => OPoolId,
                copy_id => OCopyId,
                x => X,
                y => Y,
                hp => max(1, Hp),
                hp_lim => HpLim,
                scene_args => []
            },
            if
                Hp > 0 ->
                    InSceneArgs = [{hp_lim, HpLim}, {hp, HpLim}, {group, Group}];
                true ->
                    InSceneArgs = [{change_scene_hp, 1}, {group, Group}]
            end,
            {ok, Role#battle_role{out_info = NewOutInfo, in_info = InInfo#{scene_args => InSceneArgs}}, State};
        _ ->
            skip
    end.

%% 退出算输
player_quit(#battle_role{key = RoleKey} = Role, _Args, State) ->
    if
        State#battle_state.is_end =:= false ->
            #battle_state{roles = RoleMap} = State,
            NewRoleMap = maps:map(
                fun
                    (K, #battle_role{data = D} = R) ->
                        if
                            K =/= RoleKey ->
                                R#battle_role{data = D#{res => 1}};
                            true ->
                                R#battle_role{data = D#{res => 0}}
                        end
                end, RoleMap),
            #battle_state{roles = NewRoleMap2} = NewState = handle_result(State#battle_state{roles = NewRoleMap}),
            NewRole = maps:get(RoleKey, NewRoleMap2),
            {ok, NewRole, NewState};
        true -> {ok, Role, State}
    end.

player_extra_quit(Role, Args, State) ->
    player_quit(Role, Args, State).

player_disconnect(_Role, _Args, _State) ->
    skip.

player_logout(_Role, _Args, _State) ->
    skip.

player_die(#battle_role{} = Role, _Args, #battle_state{is_end = false} = State) ->
    {NewState, NewRole, CanHandleResult} = handle_battle_list_af_die(State, Role, ?BATTLE_SIGN_PLAYER),
    case CanHandleResult of
        true -> do_player_die(NewRole, _Args, NewState);
        false -> {ok, NewState}
    end;

player_die(_Role, _Args, _State) ->
    skip.

do_player_die(#battle_role{key = RoleKey}, _Args, State) ->
    #battle_state{roles = RoleMap} = State,
    NewRoleMap = handle_winer(RoleMap, RoleKey),
    #battle_state{roles = NewRoleMap2} = NewState = handle_result(State#battle_state{roles = NewRoleMap}),
    NewRole = maps:get(RoleKey, NewRoleMap2),
    #battle_role{out_info = #{hp := Hp} = OutInfo} = NewRole,
    SceneAttr = maps:get(scene_args, OutInfo, []),
    ReviveSceneAttr
    = case lists:keyfind(change_scene_hp, 1, SceneAttr) of
        false->
            [{change_scene_hp, Hp}|SceneAttr];
        _ ->
            SceneAttr
    end,
    {ok, NewState#battle_state{roles = NewRoleMap2#{RoleKey => NewRole#battle_role{out_info = OutInfo#{scene_args => ReviveSceneAttr}}}}}.

player_revive(_Role, _Args, _State) ->
    skip.

player_reconnect(#battle_role{key = {Node, RoleId}}, _, #battle_state{data = #{battle_end_time := BattleEndTime}}) ->
    {ok, BinData} = pt_281:write(28112, [2, BattleEndTime]),
    lib_top_pk_util:apply_cast(Node, lib_server_send, send_to_uid, [RoleId, BinData]);
player_reconnect(_, _, _) ->
    skip.

%% 进入场景回调逻辑：主要用于断线重连状态
evt_enter_scene_begin(PS, _) ->
    TopPkStatus = PS#player_status.top_pk,
    case ets:lookup(?ETS_ROLE_BATTLE_PID, PS#player_status.id) of
        [#role_battle_pid{pid = BattlePid}] -> skip;
        _ -> BattlePid = none
    end,
    case misc:is_process_alive(BattlePid) of
        true ->
            BattleNode = node(BattlePid),
            Lock = ?ERRCODE(err281_on_battle_state),
            NewTopPkStatus = TopPkStatus#top_pk_status{pk_state = {battle, BattleNode, BattlePid}},
            NewPS = PS#player_status{top_pk = NewTopPkStatus},
            LastPS = lib_player:setup_action_lock(NewPS, Lock),
            LastPS;
        _ -> PS
    end.

player_finish_change_scene(Role, _Args, #battle_state{data = #{battle_end_time := BattleEndTime}} = State) when BattleEndTime>0 ->
    {ok, Role, State};
player_finish_change_scene(Role, _Args, State) ->
    % ?PRINT("player_finish_change_scene res = ~p~n", [ok]),
    #battle_state{roles = RoleMap, self = Self, data = Data, cur_scene = SceneId, scene_pool_id = ScenePoolId} = State,
    RoleList = maps:to_list(RoleMap),
    case lists:all(fun
        ({_key, R}) ->
            R#battle_role.state =:= ?ROLE_STATE_IN orelse R#battle_role.key =:= Role#battle_role.key
    end, RoleList) of
        true ->
            BeforeStartTime = ?TOP_PK_KV_READY_TIME,
            BatleTime = ?TOP_PK_KV_BATTLE_TIME,
            BattleStartTime = utime:unixtime() + BeforeStartTime,
            {ok, BinData} = pt_281:write(28112, [1, BattleStartTime]),

            lists:map(fun
                ({{Node, RoleId}, _}) ->
                    lib_top_pk_util:apply_cast(Node, lib_server_send, send_to_uid, [RoleId, BinData])
            end, RoleList),
            erlang:send_after(BeforeStartTime * 1000, Self, {apply, ?MODULE, battle_start, [BattleStartTime + BatleTime]}),
            erlang:send_after((BeforeStartTime + BatleTime) * 1000, Self, {apply, ?MODULE, battle_time_out, []}),
            % case Data of
            %     #{fake_man := #{id := DummyId}} ->
            %         mod_scene_agent:apply_cast(SceneId, ScenePoolId, ?MODULE, dummy_start_battle, [DummyId, BeforeStartTime]);
            %     _ ->
            %         ok
            % end,
            CopyId = ?COPY(State#battle_state.self),
            mod_scene_agent:apply_cast(SceneId, ScenePoolId, ?MODULE, dummy_start_battle, [CopyId, BeforeStartTime]),
            {ok, Role, State#battle_state{data = Data#{late_force_end => false}}};
        _ ->
            erlang:send_after(30 * 1000, Self, {apply, ?MODULE, late_force_end, Role#battle_role.key}),
            {ok, Role, State}
     end.

dummy_start_battle(CopyId, Time) ->
    ObjectList = lib_scene_object_agent:get_scene_object(CopyId),
    % old: [ Aid ! {'change_attr', [{find_target, Time*1000}]} ||#scene_object{aid = Aid}<-ObjectList].
    [
        mon_mgr:send(Aid, MonId, {'change_attr', [{find_target, Time*1000}]}) ||
        #scene_object{aid = Aid, id = MonId} <- ObjectList
    ],
    ok.
    % case lib_scene_object_agent:get_object(Id) of
    %     #scene_object{aid = Aid} ->
    %         Aid ! {'change_attr', [{find_target, Time*1000}]};
    %     _ ->
    %         skip
    % end.

mon_die(MonKey, [RoleKey, BattleSign] = Args, #battle_state{roles = RoleMap, is_end = false} = State) ->
    case maps:get(RoleKey, RoleMap, false) of
        #battle_role{} = Role ->
            {NewState, _NewRole, CanHandleResult} = handle_battle_list_af_die(State, Role, BattleSign),
            case CanHandleResult of
                true -> do_mon_die(MonKey, Args, NewState);
                false -> {ok, NewState}
            end;
        _ -> {ok, State}
    end;
mon_die(_, _, _) -> ok.

do_mon_die(_MonKey, [RoleKey, _BattleSign], #battle_state{is_end = false} = State) ->
    #battle_state{roles = RoleMap} = State,
    NewRoleMap = handle_winer(RoleMap, RoleKey),
    NewState = handle_result(State#battle_state{roles = NewRoleMap}),
    {ok, NewState}.

terminate(_Reason, #battle_state{cur_scene = SceneId, scene_pool_id = ScenePoolId, copy_id = CopyId}) ->
    lib_scene:clear_scene_room(SceneId, ScenePoolId, CopyId).

battle_start(#battle_state{data = #{battle_end_time := OldBattleEndTime}} = State, [_BattleEndTime]) when OldBattleEndTime>0 ->
    State;
battle_start(#battle_state{roles = RoleMap, data = Data} = State, [BattleEndTime]) ->
    {ok, BinData} = pt_281:write(28112, [2, BattleEndTime]),
    maps:map(fun
                ({Node, RoleId}, _) ->
                    lib_top_pk_util:apply_cast(Node, lib_server_send, send_to_uid, [RoleId, BinData])
            end, RoleMap),

    State#battle_state{data = Data#{battle_end_time => BattleEndTime}}.

late_force_end(#battle_state{roles = RoleMap, data = StateData} = State, {Node, RoleId}) ->
    case StateData of
        #{late_force_end := false} ->
            skip;
        _ ->
            NewRoleMap = maps:map(fun
                (_, #battle_role{state = S, data = Data} = Role) ->
                    case S of
                        ?ROLE_STATE_IN ->
                            Role#battle_role{data = Data#{res => 1}};
                        _ ->
                            Role#battle_role{data = Data#{res => 0}}
                    end
            end, RoleMap),
            {ok, BinData} = pt_281:write(28100, [?ERRCODE(err281_enemy_is_late), []]),
            lib_top_pk_util:apply_cast(Node, lib_server_send, send_to_uid, [RoleId, BinData]),
            handle_result(State#battle_state{roles = NewRoleMap})
    end.


battle_time_out(#battle_state{is_end = false} = State, []) ->
    #battle_state{cur_scene = SceneId, scene_pool_id = ScenePoolId, self = Self} = State,
    CopyId = ?COPY(State#battle_state.self),
    mod_scene_agent:apply_cast(SceneId, ScenePoolId, ?MODULE, get_scene_obj_hp, [Self, CopyId]);
battle_time_out(_State, _) -> ok.

get_scene_obj_hp(ReqPid, CopyId) ->
    Objects = lib_scene_object_agent:get_scene_object(CopyId),
    NewObjects = [Object||#scene_object{sign = Sign} = Object <- Objects, Sign == ?BATTLE_SIGN_DUMMY],
    Users = lib_scene_agent:get_scene_user(CopyId),
    TotalList = NewObjects ++ Users,
    F = fun(Object, {TargetGroup, AccHp}) ->
        case Object of
            #scene_object{battle_attr = #battle_attr{group = TargetGroup, hp = Hp}} ->
                {TargetGroup, AccHp + Hp};
            #ets_scene_user{battle_attr = #battle_attr{group = TargetGroup, hp = Hp}} ->
                {TargetGroup, AccHp + Hp};
            _ -> {TargetGroup, AccHp}
        end
    end,
    {_SelfGroup, Hp1} = lists:foldl(F, {?SELF_GROUP, 0}, TotalList),
    {_RivalGroup, Hp2} = lists:foldl(F, {?RIVAL_GROUP, 0}, TotalList),
    mod_battle_field:apply_cast(ReqPid, ?MODULE, adjudge_battle, [Hp1, Hp2]).

adjudge_battle(#battle_state{is_end = false} = State, [Hp1, Hp2]) ->
    #battle_state{roles = RoleMap,
        data = #{role_key1 := RoleKey1, role_key2 := RoleKey2}} = State,
    Role1 = maps:get(RoleKey1, RoleMap, false),
    Role2 = maps:get(RoleKey2, RoleMap, false),
    NewRoleMap = if
        Role1=/=false andalso Role2 =/=false ->
            #battle_role{data = #{combat_power:=Power1}} = Role1,
            #battle_role{data = #{combat_power:=Power2}} = Role2,
            WinerKey = if
                Hp1 == Hp2 andalso Power1 > Power2 -> RoleKey1;
                Hp1 == Hp2 -> RoleKey2;
                Hp1 >= Hp2 -> RoleKey1;
                true -> RoleKey2
            end,
            maps:map(fun
                (Key, #battle_role{data = D} = R) ->
                    if
                        WinerKey =:= Key ->
                            R#battle_role{data = D#{res => 1}};
                        true ->
                            R#battle_role{data = D#{res => 0}}
                    end
            end, RoleMap);
        true ->
            maps:map(fun
                (_, #battle_role{data = D} = R) ->
                    R#battle_role{data = D#{res => 0}}
                end, RoleMap)
    end,
    handle_result(State#battle_state{roles = NewRoleMap});

adjudge_battle(_, _) ->
    ok.

handle_battle_list_af_die(#battle_state{roles = RoleMap} = State,
        #battle_role{key = RoleKey, data = #{battle_list := BattleList} = Data} = Role, DieSign) ->
    NewAliveNum = case lists:keyfind(DieSign, 1, BattleList) of
        {DieSign, AliveNum} -> max(AliveNum - 1, 0);
        _ -> 0
    end,
    NewBattleList = lists:keystore(DieSign, 1, BattleList, {DieSign, NewAliveNum}),
    % ?PRINT("####### handle_battle_list_af_die:~p~n", [{RoleKey, NewBattleList}]),
    CanHandleResult = lists:all(fun({_Sign, AliveNum}) -> AliveNum =< 0 end, NewBattleList),
    NewRole = Role#battle_role{data = Data#{battle_list => NewBattleList}},
    NewRoleMap = RoleMap#{RoleKey => NewRole},
    NewState = State#battle_state{roles = NewRoleMap},
    {NewState, NewRole, CanHandleResult}.

handle_result(State) ->
    handle_result(State, ?STOP_TIME_AF_RESULT).

handle_result(#battle_state{roles = RoleMap, self = Self, data = Data} = State, StopTime) ->
    RoleList = maps:to_list(RoleMap),
    %% 计算胜利方积分奖励
    % Winer = get_winer(RoleList),
    % #battle_role{data = #{self_score:= WinScore, rival_score := LoseScore}} = Winer,
    % WinerAddScore = calc_score(WinScore - LoseScore),
    erlang:send_after(StopTime*1000, Self, stop),
    QuitTime = utime:unixtime() + 10,
    [begin
        % AddScore = ?IF(Res==1, WinerAddScore, util:floor(WinerAddScore*1/3)),
        EnemyInfo = calc_enemy_info({Node, RoleId}, RoleList, Data),
        lib_top_pk_util:apply_cast(Node, lib_top_pk, calc_battle_result, [RoleId, Res, [EnemyInfo, QuitTime]])
     end || {{Node, RoleId}, #battle_role{data = #{res := Res}}} <- RoleList, Node =/= none],
    State#battle_state{is_end = true}.

handle_winer(RoleMap, LoserKey) ->
    maps:map(fun
        (K, #battle_role{data = D} = R) ->
            if
                K =/= LoserKey ->
                    R#battle_role{data = D#{res => 1}};
                true ->
                    R#battle_role{data = D#{res => 0}}
            end
    end, RoleMap).

get_winer([Default|_] = RoleList) ->
    case [Role||{_Key, #battle_role{data = #{res := Res}} = Role} <- RoleList, Res == 1] of
        [Role|_] -> Role;
        _ -> {_Key, Role} = Default, Role
    end.

%% DiffScore  积分差
calc_score(DiffScore) ->
    K = max(?TOP_PK_KV_ELO_K_FACTOR, 32),
    S = max(?TOP_PK_KV_ELO_S_FACTOR, 1),
    E = 1/(1+math:pow(10, DiffScore/400)),
    util:floor(K*(S - E)).

evt_out_of_battle(Player, _Reason) ->
    % ?PRINT("evt_out_of_battle res = ~p~n", [_Reason]),
    #player_status{top_pk = PKStatus} = Player,
    lib_player:break_action_lock(Player#player_status{top_pk = PKStatus#top_pk_status{pk_state = []}}, ?ERRCODE(err281_on_battle_state)).

%% private
calc_enemy_info(RoleKey, [{RoleKey, _}|T], Data) ->
    calc_enemy_info(RoleKey, T, Data);

calc_enemy_info(_, [{{_, RoleId}, #battle_role{data = #{platform := Platform, server := Serv, name := RoleName}}}|_], _) ->
    [RoleId, Platform, Serv, RoleName];

calc_enemy_info(_, [_|_], _) ->
    [].

fake_man_die_handler(#ob_act{object = #scene_object{id = Id}}, _Klist, _Atter, _AtterSign, [BattleFieldPid, RoleKey]) ->
    mod_battle_field:mon_die(BattleFieldPid, Id, [RoleKey, ?BATTLE_SIGN_DUMMY]).

