%%%----------------------------------------------------------------------
%%% desc    :社团跑环协助
%%% module  :guild_run_assist.erl
%%% author  :hh
%%% time    :21/04/14
%%%----------------------------------------------------------------------

-module(guild_run_assist).
-behaviour(gen_server).

-include("guild_run_assist.hrl").
-include("common.hrl").
-include ("server.hrl").
-include ("def_module.hrl").
-include ("predefine.hrl").
-include ("figure.hrl").
-include ("errcode.hrl").
-include ("def_event.hrl").
-include ("rec_event.hrl").

%% API
-export([
    start_link/0
    ,add_run_assist/5           %% 有玩家增加新的协助请求
    ,take_run_assist/4          %% 协助者接取了协助
    ,fin_run_assist/5           %% 协助者结束协助请求
    ,quiz_expire_account/5      %% 答题超时结算
    ,handle_run_assist/3        %% 处理跑环协助
    ]).


-export ([
    role_handle_run_assist/4
    ,check_run_assist_trigger/1
    ,role_add_req_run_assist/4
    ,expire_cancel_run_asist/3
    ,role_trigger_cancel_assist/3
    ,role_finish_support/3
    ,role_login_get_req_assist/4
    ,handle_event/2
    ,get_guild_fail_role/1
]).


-export ([
    gm_add_run_assist/2
    ]).

%% 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, [], []).

%% 跑环协助&模块映射
assist_type_mod_map(?GUILD_RUN_ASSIST_QUIZ) -> guild_run_assist_quiz;
assist_type_mod_map(?GUILD_RUN_ASSIST_BOSS) -> lib_dungeon_help;
assist_type_mod_map(_) -> undefined.

%% 处理跑环协助相关
-spec handle_run_assist(MsgType, AssistType, MsgContent) -> term() when
    MsgType :: cast | info,         %% 通知方式
    AssistType :: 
        ?GUILD_RUN_ASSIST_QUIZ  |   %% 答题
        ?GUILD_RUN_ASSIST_BOSS  |   %% 击杀boss
        ?GUILD_RUN_ASSIST_CONVOY|   %% 护送
        ?GUILD_RUN_ASSIST_PATCH |   %% 拼图
        ?GUILD_RUN_ASSIST_MORA  |   %% 石头剪刀布
        ?GUILD_RUN_ASSIST_ANGRY_BIT,%% 愤怒比特
    MsgContent :: tuple().   %% 通知消息
handle_run_assist(cast, AssistType, Msg) ->
    gen_server:cast(?MODULE, {handle_run_assist, AssistType, Msg});
handle_run_assist(info, AssistType, Msg) ->
    erlang:send(?MODULE, {handle_run_assist, AssistType, Msg}).

%% 记录新的跑环协助请求
-spec add_run_assist(RoleId, GuildId, Sid, AssistType, ExtraData) -> term() when
    RoleId  :: integer(),
    GuildId :: integer(),
    Sid     :: integer(),           %% 协助唯一id
    AssistType :: 
        ?GUILD_RUN_ASSIST_QUIZ  |   %% 答题
        ?GUILD_RUN_ASSIST_BOSS  |   %% 击杀boss
        ?GUILD_RUN_ASSIST_CONVOY|   %% 护送
        ?GUILD_RUN_ASSIST_PATCH |   %% 拼图
        ?GUILD_RUN_ASSIST_MORA  |   %% 石头剪刀布
        ?GUILD_RUN_ASSIST_ANGRY_BIT,%% 愤怒比特
    ExtraData :: map().             %% 额外数据
add_run_assist(RoleId, GuildId, Sid, AssistType, ExtraData) ->
   gen_server:cast(?MODULE, {add_run_assist, [RoleId, GuildId, Sid, AssistType, ExtraData]}).

%% 有人接取协助
take_run_assist(RoleId, GuildId, Sid, AssistType) ->
    gen_server:cast(?MODULE, {take_run_assist, [RoleId, GuildId, Sid, AssistType]}).

%% 协助者下线取消协助
logout_save_run_assist(RoleId, GuildId, Sid, RoleAssistData) ->
    gen_server:cast(?MODULE, {logout_save_run_assist, RoleId, GuildId, Sid, RoleAssistData}).

%% 被协助者上线查询是否有协助请求
login_get_req_assist(RoleId, GuildId) ->
    gen_server:cast(?MODULE, {login_get_req_assist, RoleId, GuildId}).

%% 结束跑环协助请求
-spec fin_run_assist(RoleId, GuildId, Sid, AssistType, FinType) -> term() when
    RoleId :: integer(),
    GuildId :: integer(),
    Sid :: integer(),               %% 协助唯一id
    AssistType :: 
        ?GUILD_RUN_ASSIST_QUIZ  |   %% 答题
        ?GUILD_RUN_ASSIST_BOSS  |   %% 击杀boss
        ?GUILD_RUN_ASSIST_CONVOY|   %% 护送
        ?GUILD_RUN_ASSIST_PATCH |   %% 拼图
        ?GUILD_RUN_ASSIST_MORA  |   %% 石头剪刀布
        ?GUILD_RUN_ASSIST_ANGRY_BIT,%% 愤怒比特
    FinType :: 0|1.                 %% 0正常结束|1终止或失败
fin_run_assist(RoleId, GuildId, Sid, AssistType, FinType) ->
    gen_server:cast(?MODULE, {fin_run_assist, [RoleId, GuildId, Sid, AssistType, FinType]}).

quiz_expire_account(AssistType, GuildId, RoleId, Sid, FinType) ->
    gen_server:cast(?MODULE, {quiz_expire_account, AssistType, GuildId, RoleId, Sid, FinType}).

%% 玩家进程分发
-spec role_handle_run_assist(Player, Event, SubModule, Data) -> {ok, NewPlayer} when
    Player :: #player_status{},
    Event  :: atom(),                   %% 自定义事件/消息
    SubModule :: 
        ?GUILD_RUN_ASSIST_QUIZ  |       %% 答题
        ?GUILD_RUN_ASSIST_BOSS  |       %% 击杀boss
        ?GUILD_RUN_ASSIST_CONVOY|       %% 护送
        ?GUILD_RUN_ASSIST_PATCH |       %% 拼图
        ?GUILD_RUN_ASSIST_MORA  |       %% 石头剪刀布
        ?GUILD_RUN_ASSIST_ANGRY_BIT,    %% 愤怒比特
    Data :: list(),                     %% 自定义数据列表
    NewPlayer :: #player_status{}.
role_handle_run_assist(Player, Event, SubModule, Data) ->
    Module = assist_type_mod_map(SubModule),
    NewPlayer =
    if Module =/= undefined ->
            Module:role_handle_assist_event(Player, Event, Data);
        true ->
            Player
    end,
    {ok, NewPlayer}.

%% 玩家进程-检查协助触发
check_run_assist_trigger(RoleId) ->
    check_run_assist_trigger(RoleId, []).

check_run_assist_trigger(RoleId, Args) ->
    GetCountLimit = data_guild_run_assist:get_kv(get_times),
    Weight   = data_guild_run_assist:get_kv(weight),
    GetCount = lib_daily:get_count(RoleId, ?MOD_GUILD_RUN_ASSIST, ?TRIGGER_ASSIST_COUNT_TYPE),
    if GetCount < GetCountLimit -> %% 次数未达到,检查触发
            RandTaskId =
            case Args of
                [{gm, GmTaskId}|_] -> GmTaskId;
                _ ->
                    Rand = urand:rand(1, 100),
                    if Rand =< Weight -> %% 触发
                            TaskWeights = data_guild_run_assist:get_kv(assist_tasks_weight),
                            urand:rand_with_weight(TaskWeights);
                        true -> false
                    end
            end,
            case lists:member(RandTaskId, [
                ?GUILD_RUN_ASSIST_QUIZ, ?GUILD_RUN_ASSIST_BOSS, ?GUILD_RUN_ASSIST_CONVOY,
                ?GUILD_RUN_ASSIST_PATCH, ?GUILD_RUN_ASSIST_MORA, ?GUILD_RUN_ASSIST_ANGRY_BIT
            ]) of
                true ->
                    Content = {0, 0},
                    lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, lib_guild_support_mod, ask_for_support,
                        [?MOD_GUILD_RUN_ASSIST, RandTaskId, Content]);
                _ ->
                    skip
            end;
        true ->
            skip
    end.

%% 玩家增加跑环任务协助请求
role_add_req_run_assist(Player, SupportId, SubModule, ExtraData) ->
    #player_status{id = RoleId, figure = #figure{guild_id = GuildId}, role_guild_run_assist = RoleGuildRunAssist} = Player,
    #role_guild_run_assist{req_sid = ReqSids} = RoleGuildRunAssist,
    NewReqSids = maps:put(SupportId, #{assist_type => SubModule}, ReqSids),
    NewRoleGuildRunAssist = RoleGuildRunAssist#role_guild_run_assist{req_sid = NewReqSids},
    NewPlayer = Player#player_status{role_guild_run_assist = NewRoleGuildRunAssist},
    guild_run_assist:add_run_assist(RoleId, GuildId, SupportId, SubModule, ExtraData),
    lib_daily:increment(RoleId, ?MOD_GUILD_RUN_ASSIST, ?TRIGGER_ASSIST_COUNT_TYPE),
    {ok, NewPlayer1} = guild_run_assist:role_handle_run_assist(NewPlayer, role_add_req_run_assist, SubModule, [SupportId]),
    {ok, NewPlayer1}.

%% 超时系统取消协助
expire_cancel_run_asist(Player, SubModule, SupportSid) ->
    #player_status{role_guild_run_assist = RoleGuildRunAssist} = Player,
    #role_guild_run_assist{req_sid = ReqSids, res_sid = ResSid} = RoleGuildRunAssist,
    NewReqSids = maps:remove(SupportSid, ReqSids),
    if ResSid == SupportSid ->
            NewRoleGuildRunAssist = RoleGuildRunAssist#role_guild_run_assist{res_sid = 0, req_sid = NewReqSids};
        true ->
            NewRoleGuildRunAssist = RoleGuildRunAssist#role_guild_run_assist{req_sid = NewReqSids}
    end,
    NewPlayer = Player#player_status{role_guild_run_assist = NewRoleGuildRunAssist},
    {ok, NewPlayer1} = role_handle_run_assist(NewPlayer, expire_cancel_run_asist, SubModule, [SupportSid]),
    {ok, NewPlayer1}.

%% 协助者任务失败|主动取消|下线 触发协助取消
role_trigger_cancel_assist(Player, SubModule, SupportSid) ->
    #player_status{role_guild_run_assist = RoleGuildRunAssist} = Player,
    #role_guild_run_assist{res_sid = ResSid} = RoleGuildRunAssist,
    if ResSid == SupportSid ->
            NewRoleGuildRunAssist = RoleGuildRunAssist#role_guild_run_assist{res_sid = 0},
            NewPlayer = Player#player_status{role_guild_run_assist = NewRoleGuildRunAssist},
            {ok, NewPlayer1} = role_handle_run_assist(NewPlayer, role_trigger_cancel_assist, SubModule, [SupportSid]),
            {ok, NewPlayer1};
        true ->
            {ok, Player}
    end.

%% 请求者协助单已被其他人完成
role_finish_support(Player, AssistType, SupportSid) ->
    #player_status{role_guild_run_assist = RoleGuildRunAssist} = Player,
    #role_guild_run_assist{req_sid = ReqSids} = RoleGuildRunAssist,
    case maps:get(SupportSid, ReqSids, false) of
        false ->
            {ok, Player};
        _Map ->
            NewReqSids = maps:remove(SupportSid, ReqSids),
            NewRoleGuildRunAssist = RoleGuildRunAssist#role_guild_run_assist{req_sid = NewReqSids},
            NewPlayer = Player#player_status{role_guild_run_assist = NewRoleGuildRunAssist},
            {ok, NewPlayer1} = role_handle_run_assist(NewPlayer, role_finish_support, AssistType, [SupportSid]),
            {ok, NewPlayer1}
    end.

%% 上线获取已有的协助请求
role_login_get_req_assist(Player, ReqSids, ResSid, RoleAssistData) ->
    #player_status{id = RoleId, role_guild_run_assist = RoleGuildRunAssist} = Player,
    NewPlayer = Player#player_status{role_guild_run_assist = 
        RoleGuildRunAssist#role_guild_run_assist{
            req_sid = ReqSids,
            res_sid = ResSid,
            extra_data = RoleAssistData
        }
    },
    AssistL = maps:to_list(RoleAssistData),
    lists:map(fun({AssistType, _Data}) -> 
        case AssistType of
            ?GUILD_RUN_ASSIST_QUIZ ->
                guild_run_assist_quiz:relogin_notice(RoleId, ResSid, _Data);
            _ ->
                skip
        end
    end, AssistL),
    {ok, NewPlayer}.

handle_event(Player, #event_callback{type_id = ?EVENT_DISCONNECT}) ->
    #player_status{id = RoleId, figure = #figure{guild_id = GuildId}, role_guild_run_assist = RoleGuildRunAssist} = Player,
    #role_guild_run_assist{res_sid = ResSid, extra_data = ExtraData} = RoleGuildRunAssist,
    if ResSid =/= 0 ->
            logout_save_run_assist(RoleId, GuildId, ResSid, ExtraData);
        true ->
            skip
    end,
    {ok, Player};

handle_event(Player, #event_callback{type_id = ?EVENT_LOGIN_CAST}) ->
    #player_status{id = RoleId, figure = #figure{guild_id = GuildId}} = Player,
    login_get_req_assist(RoleId, GuildId),
    {ok, Player};


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

get_guild_fail_role(GuildId) ->
    case ets:lookup(?ETS_GUILD_RUN_ASSIST, GuildId) of
        [#guild_run_assist_data{data = Data}] ->
            maps:fold(fun(Sid, #assist_instance{fail_role = FailRoles}, TempList) -> 
                [{Sid, FailRoles}|TempList]
            end, [], Data);
        _ ->
            []
    end.

%% ----------------------------------------------------------------------------------------------
%% Server Callback functions
init([]) ->
    ets:new(?ETS_GUILD_RUN_ASSIST, [{keypos, #guild_run_assist_data.guild_id}, named_table, public, set]),
    {ok, #run_assist_state{}}.

handle_call(Request, From, State) ->
    case catch do_call(Request, From, State) of
        {'EXIT', Reason} ->
            ?ERR("handle_call error:~p, request:~p", [Reason, Request]),
            {reply, error, State};
        {reply, Reply, NewState} ->
            {reply, Reply, NewState};
        _ ->
            {reply, ok, State}
    end.

handle_cast(Msg, State) ->
    case catch do_cast(Msg, State) of
        {'EXIT', Reason} ->
            ?ERR("handle_cast error:~p, msg:~p", [Reason, Msg]),
            {noreply, State};
        {noreply, NewState} ->
            {noreply, NewState};
        {stop, normal, NewState} ->
            {stop, normal, NewState};
        _ ->
            {noreply, State}
    end.

handle_info(Info, State) ->
    case catch do_info(Info, State) of
        {'EXIT', Reason} ->
            ?ERR("handle_info error:~p, info:~p", [Reason, Info]),
            {noreply, State};
        {noreply, NewState} ->
            {noreply, NewState};
        _ ->
            {noreply, State}
    end.

terminate(_Reason, _State) ->
    ok.

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

%% ----------------------------------------------------------------------------------------------------------------
%% Internal functions

do_call(Info, _, State) ->
    ?INFO("guild_run_assist call is not match:~w", [Info]),
    {reply, ok, State}.

do_cast({handle_run_assist, AssistType, Msg}, State) ->
    NewState = mgr_handle_assist_event(AssistType, Msg, State),
    {noreply, NewState};

do_cast({add_run_assist, [RoleId, GuildId, Sid, AssistType, ExtraData]}, State) ->
    #run_assist_state{timer_data = TimerData, extra_data = ExtraMap} = State,
    TimerRef = maps:get(interval_timer, ExtraMap, undefined),
    {NewTimerData, NewTimerRef} = add_run_assist_logic(RoleId, GuildId, Sid, AssistType, TimerData, TimerRef, ExtraData),
    NewExtraMap  = maps:put(interval_timer, NewTimerRef, ExtraMap),
    NewState     = State#run_assist_state{timer_data = NewTimerData, extra_data = NewExtraMap},
    NewState1    = mgr_handle_assist_event(AssistType, {after_add_run_assist, [RoleId, GuildId, Sid]}, NewState),
    SupportCfgId = guild_support_run_assist:get_support_cfg_id_by_assist_type(AssistType),
    {ok, BinData1} = pt_417:write(41700, [SupportCfgId, 0]),
    lib_server_send:send_to_uid(RoleId, BinData1),
    {noreply, NewState1};

do_cast({take_run_assist, [RoleId, GuildId, Sid, AssistType]}, State) ->
    #run_assist_state{timer_data = TimerData, extra_data = ExtraMap} = State,
    TimerRef = maps:get(interval_timer, ExtraMap, undefined),
    {NewTimerData, NewTimerRef} = take_run_assist_logic(RoleId, GuildId, Sid, TimerData, TimerRef),
    NewExtraMap = maps:put(interval_timer, NewTimerRef, ExtraMap),
    NewState = State#run_assist_state{timer_data = NewTimerData, extra_data = NewExtraMap},
    NewState1 = mgr_handle_assist_event(AssistType, {after_take_run_assist, [RoleId, GuildId, Sid]}, NewState),
    % SupportCfgId = guild_support_run_assist:get_support_cfg_id_by_assist_type(AssistType),
    % {ok, BinData2} = pt_400:write(40052, [?SUCCESS, Sid, SupportCfgId]),
    % lib_server_send:send_to_uid(RoleId, BinData2),
    {noreply, NewState1};

do_cast({fin_run_assist, [RoleId, GuildId, Sid, AssistType, FinType]}, State) ->
    #run_assist_state{timer_data = TimerData, extra_data = ExtraMap} = State,
    TimerRef = maps:get(interval_timer, ExtraMap, undefined),
    if FinType == 0 -> %% 正常结束
            {NewTimerData, NewTimerRef} = fin_run_assist_logic(RoleId, GuildId, Sid, TimerData, TimerRef),
            NewExtraMap  = maps:put(interval_timer, NewTimerRef, ExtraMap),
            NewState = State#run_assist_state{timer_data = NewTimerData, extra_data = NewExtraMap},
            NewState1 = mgr_handle_assist_event(AssistType, {after_fin_run_assist, [RoleId, GuildId, Sid, FinType]}, NewState),
            {noreply, NewState1};
        FinType == 1 -> %% 终止/取消
            {NewTimerData, NewTimerRef} = res_role_cancel_assist_logic(active_cancel, RoleId, GuildId, Sid, TimerData, TimerRef),
            NewExtraMap  = maps:put(interval_timer, NewTimerRef, ExtraMap),
            NewState = State#run_assist_state{timer_data = NewTimerData, extra_data = NewExtraMap},
            NewState1 = mgr_handle_assist_event(AssistType, {after_fin_run_assist, [RoleId, GuildId, Sid, FinType]}, NewState),
            {noreply, NewState1}
    end;

do_cast({quiz_expire_account, AssistType, GuildId, RoleId, Sid, FinType}, State) ->
    case ets:lookup(?ETS_GUILD_RUN_ASSIST, GuildId) of
        [#guild_run_assist_data{data = Data}] ->
            case maps:get(Sid, Data, false) of
                #assist_instance{} ->
                    do_cast({fin_run_assist, [RoleId, GuildId, Sid, AssistType, FinType]}, State);
                _ ->
                    {noreply, State}
            end;
        _ ->
            {noreply, State}
    end;

do_cast({logout_save_run_assist, RoleId, GuildId, ResSid, RoleAssistData}, State) ->
    erlang:put(?DICT_GUILD_RUN_ASSIST(RoleId), {GuildId, ResSid, RoleAssistData}),
    % TimerRef = maps:get(interval_timer, ExtraMap, undefined),
    % {NewTimerData, NewTimerRef} = res_role_cancel_assist_logic(logout_cancel, RoleId, GuildId, Sid, TimerData, TimerRef),
    % NewExtraMap  = maps:put(interval_timer, NewTimerRef, ExtraMap),
    % NewState = State#run_assist_state{timer_data = NewTimerData, extra_data = NewExtraMap},
    % NewState1 = mgr_handle_assist_event(AssistType, {after_fin_run_assist, [RoleId, GuildId, Sid, 1]}, NewState),
    {noreply, State};

do_cast({login_get_req_assist, RoleId, GuildId}, State) ->
    case erlang:erase(?DICT_GUILD_RUN_ASSIST(RoleId)) of
        {GuildId, ResSid, RoleAssistData} -> ok;
        _ -> ResSid = 0, RoleAssistData = #{}
    end,
    case ets:lookup(?ETS_GUILD_RUN_ASSIST, GuildId) of
        [#guild_run_assist_data{data = Data}] ->
            RoleSids = maps:fold(fun(SupportSid, Instance, TempSids) -> 
                if Instance#assist_instance.req_role == RoleId ->
                        maps:put(SupportSid, #{assist_type => Instance#assist_instance.assist_type}, TempSids);
                    true ->
                        TempSids
                end
            end, #{}, Data),
            lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?MODULE, role_login_get_req_assist, [RoleSids, ResSid, RoleAssistData]);
        _ ->
            skip
    end,
    {noreply, State};

do_cast(Info, State) ->
    ?INFO("guild_run_assist cast is not match:~w", [Info]),
    {noreply, State}.

do_info({handle_run_assist, AssistType, Msg}, State) ->
    NewState = mgr_handle_assist_event(AssistType, Msg, State),
    {noreply, NewState};

do_info({interval_refresh_timer}, State) ->
    #run_assist_state{timer_data = TimerData, extra_data = ExtraMap} = State,
    TimerRef = maps:get(interval_timer, ExtraMap, undefined),
    NowTime = utime:unixtime(),
    ExpireTimerData = calc_old_expired_timer(NowTime, TimerData),
    NewTimerData = run_assist_expire_logic(NowTime, ExpireTimerData, TimerData),
    SortedTimerData = lists:keysort(2, NewTimerData),
    MinEndTime  = calc_timer_data(NowTime, SortedTimerData),
    NewTimerRef = util:send_after(TimerRef, max(1000, (MinEndTime - NowTime)), self(), {interval_refresh_timer}),
    NewExtraMap  = maps:put(interval_timer, NewTimerRef, ExtraMap),   
    NewState = State#run_assist_state{timer_data = SortedTimerData, extra_data = NewExtraMap},
    {noreply, NewState};

do_info(Info, State) ->
    ?INFO("guild_run_assist info is not match:~w", [Info]),
    {noreply, State}.

%% --------------------------------------------------------------------------------------------------------------------
add_run_assist_logic(RoleId, GuildId, Sid, AssistType, TimerData, TimerRef, ExtraData) ->
    NowTime = utime:unixtime(),
    ExistTime = data_guild_run_assist:get_kv(exist_time),
    Instance = #assist_instance{
        sid         = Sid,
        assist_type = AssistType,
        module      = assist_type_mod_map(AssistType),
        start_time  = NowTime,
        req_role    = RoleId,
        extra_data  = ExtraData
    },
    if
        AssistType == ?GUILD_RUN_ASSIST_BOSS ->
            BossPower = maps:get(mon_power, ExtraData, 0),
            BossId = maps:get(mon_id, ExtraData, 0),
            DunId = maps:get(dun_id, ExtraData, 0),
            guild_support_run_assist:update_support_status(GuildId, Sid,
                [{?END_TIME, NowTime+ExistTime}, {?BOSS_POWER, BossPower}, {?BOSS_ID, BossId}, {?DUN_ID, DunId}, {?SUPPORT_STATUS, 0}]);
        true ->
            guild_support_run_assist:update_support_status(GuildId, Sid, [{?END_TIME, NowTime+ExistTime}, {?SUPPORT_STATUS, 0}])
    end,
    case ets:lookup(?ETS_GUILD_RUN_ASSIST, GuildId) of
        [GuildAssistData = #guild_run_assist_data{data = Data}] ->
            NewData = maps:put(Sid, Instance, Data),
            NewGuildAssistData = GuildAssistData#guild_run_assist_data{data = NewData},
            ets:insert(?ETS_GUILD_RUN_ASSIST, NewGuildAssistData),
            {MinEndTime, NewTimerData} = update_timer_data(NowTime, {GuildId, Sid, NowTime+ExistTime, add_run_assist}, TimerData),
            NewTimerRef = util:send_after(TimerRef, max(1000, (MinEndTime - NowTime)*1000), self(), {interval_refresh_timer}),
            {NewTimerData, NewTimerRef};
        _ ->
            NewData = #{Sid => Instance},
            NewGuildAssistData = #guild_run_assist_data{guild_id = GuildId, data = NewData},
            ets:insert(?ETS_GUILD_RUN_ASSIST, NewGuildAssistData),
            {MinEndTime, NewTimerData} = update_timer_data(NowTime, {GuildId, Sid, NowTime+ExistTime, add_run_assist}, TimerData),
            NewTimerRef = util:send_after(TimerRef, max(1000, (MinEndTime - NowTime)*1000), self(), {interval_refresh_timer}),
            {NewTimerData, NewTimerRef}
    end.

take_run_assist_logic(RoleId, GuildId, Sid, TimerData, TimerRef) ->
    NowTime = utime:unixtime(),
    DoneTime = data_guild_run_assist:get_kv(done_time),
    case ets:lookup(?ETS_GUILD_RUN_ASSIST, GuildId) of
        [GuildAssistData = #guild_run_assist_data{data = Data}] ->
            case maps:get(Sid, Data, false) of
                Instance = #assist_instance{res_role = ResRoles} ->
                    NewResRoles =
                    case lists:member(RoleId, ResRoles) of
                        true -> ResRoles;
                        _    -> [RoleId|ResRoles]
                    end,
                    NewInstance = Instance#assist_instance{res_role = NewResRoles},
                    NewData = maps:put(Sid, NewInstance, Data),
                    NewGuildAssistData = GuildAssistData#guild_run_assist_data{data = NewData},
                    ets:insert(?ETS_GUILD_RUN_ASSIST, NewGuildAssistData),
                    guild_support_run_assist:update_support_status(GuildId, Sid, [{?SUPPORT_STATUS, 1},{?END_TIME, NowTime+DoneTime}]),
                    {MinEndTime, NewTimerData} = update_timer_data(NowTime, {GuildId, Sid, NowTime+DoneTime, take_run_assist}, TimerData),
                    NewTimerRef = util:send_after(TimerRef, max(1000, (MinEndTime - NowTime)*1000), self(), {interval_refresh_timer}),
                    {NewTimerData, NewTimerRef};
                _ ->
                    {TimerData, TimerRef}
            end;
        _ ->
            {TimerData, TimerRef}
    end.

fin_run_assist_logic(RoleId, GuildId, Sid, TimerData, TimerRef) ->
    NowTime = utime:unixtime(),
    case ets:lookup(?ETS_GUILD_RUN_ASSIST, GuildId) of
        [GuildAssistData = #guild_run_assist_data{data = Data}] ->
            #assist_instance{req_role = ReqRole, assist_type = AssistType} = maps:get(Sid, Data, #assist_instance{}),
            NewData = maps:remove(Sid, Data),
            %% 完成协助
            ReqRole =/= 0 andalso mod_guild_support:apply_cast(guild_support_run_assist, finish_support, [Sid, GuildId, AssistType, ReqRole]),
            NewGuildAssistData = GuildAssistData#guild_run_assist_data{data = NewData},
            {MinEndTime, NewTimerData} = del_timer_data(NowTime, {GuildId, Sid}, TimerData),
            NewTimerRef = util:send_after(TimerRef, max(1000, (MinEndTime - NowTime)*1000), self(), {interval_refresh_timer}),
            ets:insert(?ETS_GUILD_RUN_ASSIST, NewGuildAssistData),
            lib_achievement_api:async_event(RoleId, lib_achievement_api, support_event, 1),
            {NewTimerData, NewTimerRef};
        _ ->
            {TimerData, TimerRef}
    end.

res_role_cancel_assist_logic(CancelType, RoleId, GuildId, Sid, TimerData, TimerRef) ->
    case ets:lookup(?ETS_GUILD_RUN_ASSIST, GuildId) of
        [GuildAssistData = #guild_run_assist_data{data = Data}|_] ->
            case maps:get(Sid, Data, false) of
                Instance = #assist_instance{start_time = StartTime, res_role = ResRoles, assist_type = SubModule, fail_role = FailRoles} ->
                    %% 取消/终止协助
                    case lists:member(RoleId, ResRoles) of
                        true ->
                            guild_support_run_assist:cancel_support(CancelType, SubModule, RoleId, Sid),
                            NewResRoles = lists:delete(RoleId, ResRoles),
                            NewFailRoles = 
                            case lists:member(RoleId, FailRoles) of
                                true -> FailRoles;
                                _    -> [RoleId|FailRoles]
                            end,
                            NewInstance = Instance#assist_instance{res_role = NewResRoles, fail_role = NewFailRoles},
                            NewData = maps:put(Sid, NewInstance, Data),
                            ExistTime = data_guild_run_assist:get_kv(exist_time),
                            NowTime = utime:unixtime(),
                            NewGuildAssistData = GuildAssistData#guild_run_assist_data{data = NewData},
                            ets:insert(?ETS_GUILD_RUN_ASSIST, NewGuildAssistData),
                            guild_support_run_assist:update_support_status(GuildId, Sid, [{?END_TIME, StartTime+ExistTime}, {?SUPPORT_STATUS, 0}]),
                            {MinEndTime, NewTimerData} = cancel_timer_data(NowTime, StartTime+ExistTime, {GuildId, Sid}, TimerData),
                            NewTimerRef = util:send_after(TimerRef, max(1000, (MinEndTime - NowTime)*1000), self(), {interval_refresh_timer}),
                            {NewTimerData, NewTimerRef};
                        _ ->
                            {TimerData, TimerRef}
                    end;
                _ ->
                    {TimerData, TimerRef}
            end;
        _ ->
            {TimerData, TimerRef}
    end.    

run_assist_expire_logic(_, [], TimerData) -> TimerData;
run_assist_expire_logic(NowTime, [{{GuildId, Sid}, _EndTime, Event}|Rest], TimerData) ->
    case ets:lookup(?ETS_GUILD_RUN_ASSIST, GuildId) of
        [GuildAssistData = #guild_run_assist_data{data = Data}] ->
            case maps:get(Sid, Data, false) of
                Instance = #assist_instance{start_time = StartTime, assist_type = AssistType, fail_role = FailRoles, req_role = ReqRole, res_role = ResRoles} ->
                    run_assist_expire_logic_do(AssistType, Event, [GuildId, Sid, ReqRole, ResRoles]),
                    {NewData1, NewTimerData1} = 
                    if 
                        Event == take_run_assist andalso AssistType == ?GUILD_RUN_ASSIST_QUIZ -> %% 接单到期了
                            %% 答题超时帮协助者结算,通用默认清除离线临时数据
                            guild_run_assist_quiz:role_expire_check(ResRoles, AssistType, GuildId, Sid),
                            {Data, TimerData};
                        Event == take_run_assist ->
                            [erlang:erase(?DICT_GUILD_RUN_ASSIST(RoleIdX)) || RoleIdX <- ResRoles],
                            ExistTime = data_guild_run_assist:get_kv(exist_time),
                            NewEndTime = StartTime + ExistTime,
                            {IsExpire, NewData, NewTimerData} =
                            if NowTime >= NewEndTime ->
                                    {true, maps:remove(Sid, Data), TimerData};
                                true ->
                                    guild_support_run_assist:update_support_status(GuildId, Sid, [{?END_TIME, NewEndTime}, {?SUPPORT_STATUS, 0}]),
                                    {
                                        false, 
                                        maps:put(Sid, Instance#assist_instance{fail_role = lists:append(ResRoles, FailRoles), res_role = []}, Data),
                                        lists:keystore({GuildId, Sid}, 1, TimerData, {{GuildId, Sid}, NewEndTime, add_run_assist})
                                    }
                            end,
                            mod_guild_support:apply_cast(guild_support_run_assist, expire_support, [GuildId, Sid, IsExpire]),
                            {NewData, NewTimerData};
                        true -> %% 无人的单子超时了
                            mod_guild_support:apply_cast(guild_support_run_assist, expire_support, [GuildId, Sid, true]),
                            {maps:remove(Sid, Data), TimerData}
                    end,
                    NewGuildAssistData = GuildAssistData#guild_run_assist_data{data = NewData1},
                    ets:insert(?ETS_GUILD_RUN_ASSIST, NewGuildAssistData),
                    run_assist_expire_logic(NowTime, Rest, NewTimerData1);
                _ ->
                    run_assist_expire_logic(NowTime, Rest, TimerData)
            end;
        _ ->
            run_assist_expire_logic(NowTime, Rest, TimerData)
    end.

calc_old_expired_timer(NowTime, TimerData) ->
    calc_old_expired_timer_1(NowTime, TimerData, []).

calc_old_expired_timer_1(_NowTime, [], ExpireTimerData) ->
    ExpireTimerData;
calc_old_expired_timer_1(NowTime, [{{GuildId, Sid}, EndTime, Event}|Rest], ExpireTimerData) ->
    if
        NowTime >= EndTime -> %% 过期了
            calc_old_expired_timer_1(NowTime, Rest, [{{GuildId, Sid}, EndTime, Event}|ExpireTimerData]);
        true ->
            calc_old_expired_timer_1(NowTime, Rest, ExpireTimerData)
    end.

%% TODO 协助任务过期的处理
%% AssistType   ::
% ?GUILD_RUN_ASSIST_QUIZ  |   %% 答题
% ?GUILD_RUN_ASSIST_BOSS  |   %% 击杀boss
% ?GUILD_RUN_ASSIST_CONVOY|   %% 护送
% ?GUILD_RUN_ASSIST_PATCH |   %% 拼图
% ?GUILD_RUN_ASSIST_MORA  |   %% 石头剪刀布
% ?GUILD_RUN_ASSIST_ANGRY_BIT,%% 愤怒比特
%% Event        :: add_run_assist | take_run_assist ...
%% Data         :: [GuildId, Sid, ReqRole, ResRoles]
%% 无人接取而协助过期
run_assist_expire_logic_do(_AssistType, add_run_assist, _Data) ->
    skip;
%% 接取任务后超时
run_assist_expire_logic_do(_AssistType, take_run_assist, _Data) ->
    skip;
run_assist_expire_logic_do(_AssistType, _Event, _Data) ->
    ok.

%% TODO 待优化
update_timer_data(NowTime, {GuildId, Sid, EndTime, Event}, TimerData) ->
    NewTimerData    = lists:keystore({GuildId, Sid}, 1, TimerData, {{GuildId, Sid}, EndTime, Event}),
    SortedTimerData = lists:keysort(2, NewTimerData),
    MinEndTime = calc_timer_data(NowTime, SortedTimerData),
    {MinEndTime, SortedTimerData}.

del_timer_data(NowTime, {GuildId, Sid}, TimerData) ->
    NewTimerData    = lists:keydelete({GuildId, Sid}, 1, TimerData),
    SortedTimerData = lists:keysort(2, NewTimerData),
    MinEndTime = calc_timer_data(NowTime, SortedTimerData),
    {MinEndTime, SortedTimerData}.

%% 取消任务时重新设置过期时间
cancel_timer_data(NowTime, NewEndTime, {GuildId, Sid}, TimerData) ->
    NewTimerData = lists:keystore({GuildId, Sid}, 1, TimerData, {{GuildId, Sid}, NewEndTime, add_run_assist}),
    SortedTimerData = lists:keysort(2, NewTimerData),
    MinEndTime = calc_timer_data(NowTime, SortedTimerData),
    {MinEndTime, SortedTimerData}.

calc_timer_data(NowTime, []) -> NowTime;
calc_timer_data(NowTime, [{{_GuildId, _Sid}, EndTime, _Event}|Rest]) ->
    if NowTime > EndTime ->
            calc_timer_data(NowTime, Rest);
        true ->
            EndTime
    end.

%% 社团跑环协助公共服务分发
-spec mgr_handle_assist_event(AssistType, Msg, State) -> NewState when
    AssistType :: 
        ?GUILD_RUN_ASSIST_QUIZ      |   %% 答题
        ?GUILD_RUN_ASSIST_BOSS      |   %% 击杀boss
        ?GUILD_RUN_ASSIST_CONVOY    |   %% 护送
        ?GUILD_RUN_ASSIST_PATCH     |   %% 拼图
        ?GUILD_RUN_ASSIST_MORA      |   %% 石头剪刀布
        ?GUILD_RUN_ASSIST_ANGRY_BIT,    %% 愤怒比特
    Msg    :: tuple(),      %% 消息/事件
    State  :: #run_assist_state{},
    NewState :: #run_assist_state{}.
mgr_handle_assist_event(AssistType, Msg, State) ->
    Module = assist_type_mod_map(AssistType),
    case Module:mgr_handle_assist_event(Msg, State) of
        NewState = #run_assist_state{} ->
            NewState;
        {ok, NewState} ->
            NewState;
        {noreply, NewState} ->
            NewState;
        _A ->
            State
    end.

gm_add_run_assist(RoleId, TaskType) ->
    case lists:member(TaskType, [
        ?GUILD_RUN_ASSIST_QUIZ, ?GUILD_RUN_ASSIST_BOSS, ?GUILD_RUN_ASSIST_CONVOY,
        ?GUILD_RUN_ASSIST_PATCH, ?GUILD_RUN_ASSIST_MORA, ?GUILD_RUN_ASSIST_ANGRY_BIT
        ]) of
        true ->
            check_run_assist_trigger(RoleId, [{gm, TaskType}]);
        false ->
            skip
    end,
    ok.