%%%-------------------------------------------------------------------
%%% @author liushl
%%% @doc
%%%     活动：节奏大师，一方邀请，另一方同意则开始。
%%% 随机指定一张曲谱，倒计时10秒开始
%%% 随机确定下一拍点组操作玩家，开始后循环启动拍点切换定时器。
%%% 播放期间接收前端点击反馈(是否连击)，并计算分数。
%%% 该组拍点全部播放完毕，则交换玩家，进入下一拍点组。
%%% 全部拍点组播放完毕，结束，进入结算界面。
%%% @end
%%%-------------------------------------------------------------------
-module(mate_rhythm_master).
-author("liushl").

-include("mate.hrl").
-include("common.hrl").
-include("def_act_label.hrl").
-include("def_module.hrl").
-include("errcode.hrl").

%% 节奏大师 玩法事件
-define(MATE_RHYTHM_MASTER_BEGIN,       1). % 游戏开始
-define(MATE_RHYTHM_MASTER_BIT_SHOW,    2). % 拍点出现
-define(MATE_RHYTHM_MASTER_ROLE_CHANGE, 3). % 玩家切换
-define(MATE_RHYTHM_MASTER_TAP,         4). % 玩家点击
-define(MATE_RHYTHM_MASTER_STA,         5). % 分数/连击变化
-define(MATE_RHYTHM_MASTER_END,         6). % 游戏结算

%% API
-export([handle_event/3]).

%% 玩家回应同意
handle_event({mate_act_response_yes, DailyScores}, #mate_act{key = Key, data = undefined} = ActInfo, State) ->
    case urand:list_rand(data_mate:get_mate_all_music()) of
        MusicId when is_integer(MusicId) ->
            case data_mate:get_mate_music(MusicId) of
                #base_mate_rhythm_master_music{bit_group = [BitGroup|_] = BitGroups} ->
                    case data_mate:get_mate_bit_by_group(BitGroup) of
                        [_|_] = BitIds ->
                            %% 计算曲谱拍点数和单点积分
                            TotalBits = lists:sum([
                                length(data_mate:get_mate_bit_by_group(GroupId))
                                || GroupId <- BitGroups
                            ]),
                            BitScore = data_mate:get_mate_key(music_score) / TotalBits,
                            DoubleHitScore = data_mate:get_mate_key(combo_rate) / (TotalBits*(TotalBits+1)/2),
                            ActData = #mate_rhythm_master{
                                music_id = MusicId,
                                bit_group = BitGroups,
                                bit_id = BitIds,
                                base_bit_score = BitScore,
                                base_double_hit_score = DoubleHitScore
                            },
                            NewMateAct = lists:keyreplace(
                                ActInfo#mate_act.key,
                                #mate_act.key,
                                State#mate_mgr.mate_act,
                                ActInfo#mate_act{data = ActData, daily_score = DailyScores}),
                            broadcast_event(Key, ?MATE_RHYTHM_MASTER_BEGIN, ActData, Key),
                            State#mate_mgr{mate_act = NewMateAct};
                        _ ->
                            %% 拍点组没有配置拍点
                            State
                    end;
                _ ->
                    %% 曲谱没有配置拍点组
                    State
            end;
        _ ->
            %% 不存在曲谱id
            State
    end;
%% 全部准备好
handle_event(all_get_ready, #mate_act{key = Key, data = #mate_rhythm_master{} = ActData} = ActInfo, State) ->
    %% 准备时间后开始
    ReadTime = 1,
    BeginTime = utime:unixtime()+ReadTime,
    Ref = mate_mgr:set_mate_act_timer(ReadTime*1000, self(), Key, mate_rhythm_master_begin),
    NewActData = ActData#mate_rhythm_master{ref_bit = Ref},
    NewMateAct = lists:keyreplace(
        ActInfo#mate_act.key,
        #mate_act.key,
        State#mate_mgr.mate_act,
        ActInfo#mate_act{data = NewActData, time = BeginTime}),
    State#mate_mgr{mate_act = NewMateAct};
%% 游戏开始 确定第一个操作玩家
handle_event(mate_rhythm_master_begin, #mate_act{key = Key, data = ActData} = ActInfo, State) ->
    #mate_rhythm_master{
        bit_group = [BitGroup|_]
        , bit_id = [BitId|_]
        , music_id = MusicId
    } = ActData,
    #base_mate_rhythm_master_bit{pre_time = PreTime} = data_mate:get_mate_bit(BitGroup, BitId),
    %% 前置时间 后生成第一个点
    Ref = mate_mgr:set_mate_act_timer(PreTime, self(), Key, mate_rhythm_master_bit_show),
    %% 歌曲长度后结束游戏
    #base_mate_rhythm_master_music{time = Time} = data_mate:get_mate_music(MusicId),
    RefEnd = mate_mgr:set_mate_act_timer(Time, self(), Key, mate_rhythm_master_end),
    NewActData = ActData#mate_rhythm_master{
        tap_role = mate_mgr:rand_role(Key),   %% 确定第一个操作玩家
        ref_bit = Ref,
        ref_end = RefEnd
    },
    NewMateAct = lists:keyreplace(ActInfo#mate_act.key, #mate_act.key, State#mate_mgr.mate_act, ActInfo#mate_act{data = NewActData}),
    broadcast_event(Key, ?MATE_RHYTHM_MASTER_ROLE_CHANGE, NewActData, []),
    State#mate_mgr{mate_act = NewMateAct};
%% 玩家点击：只有点击玩家和规定的一样才有效|拍点id存在才有效
handle_event({mate_rhythm_master_tap, RoleId, BitId}, #mate_act{key = Key, data = ActData} = ActInfo, State) ->
    #mate_rhythm_master{
        tap_role = TapRole,
        double_hit = DoubleHit,
        max_double_hit = Max,
        exist_bit = ExistBit,
        base_bit_score = BitScore,
        score = Score,
        base_double_hit_score = DoubleHitScore
    } = ActData,
    case RoleId =:= TapRole andalso lists:member(BitId, ExistBit) of
        true ->
            %% 计算分数/连击数+1/将拍点设为已点击
            NewDoubleHit = DoubleHit + 1,
            NewScore = BitScore + DoubleHitScore*NewDoubleHit + Score,
            case NewDoubleHit > Max of
                true  -> NewMaX = NewDoubleHit;
                false -> NewMaX = Max
            end,
            NewActData = ActData#mate_rhythm_master{
                exist_bit = lists:delete(BitId, ExistBit),
                double_hit = NewDoubleHit,
                max_double_hit = NewMaX,
                score = NewScore
            },
            NewMateAct = lists:keyreplace(Key, #mate_act.key, State#mate_mgr.mate_act, ActInfo#mate_act{data = NewActData}),
            broadcast_event(Key, ?MATE_RHYTHM_MASTER_TAP, NewActData, BitId),
            broadcast_event(Key, ?MATE_RHYTHM_MASTER_STA, NewActData, []),
            State#mate_mgr{mate_act = NewMateAct};
        _ ->
            State
    end;
%% 生成拍点
handle_event(mate_rhythm_master_bit_show, #mate_act{key = Key, data = ActData} = ActInfo, State) ->
    #mate_rhythm_master{
        bit_id = [Id|T],
        exist_bit = ExistBit
    } = ActData,
    ContinueTime = data_mate:get_mate_key(note_last),
    NewExistBit = [Id|ExistBit],
    NewActData = ActData#mate_rhythm_master{
        exist_bit = NewExistBit
    },
    %% 定时检测连击落空
    IsExchangeRole =
        case T of
            [] -> true;
            _  -> false
        end,
    mate_mgr:set_mate_act_timer(ContinueTime, self(), Key, {mate_rhythm_master_check_double_hit, Id, IsExchangeRole}),
    broadcast_event(Key, ?MATE_RHYTHM_MASTER_BIT_SHOW, NewActData, []),
    %% 预定下一个点
    handle_event(mate_rhythm_master_bit_change, ActInfo#mate_act{data = NewActData}, State);
%% 产生生成下一个点的定时器
%% 三种情况：最后一组的最后一点已经生成|当前组的最后一点已经生成|当前组的中间点生成
handle_event(mate_rhythm_master_bit_change, #mate_act{key = Key, data = ActData} = ActInfo, State) ->
    case {ActData#mate_rhythm_master.bit_group, ActData#mate_rhythm_master.bit_id} of
        {[_], [_]} ->
            %% 最后一组的最后一点已经生成 等待结束
            NewMateAct = lists:keyreplace(Key, #mate_act.key, State#mate_mgr.mate_act, ActInfo),
            State#mate_mgr{mate_act = NewMateAct};
        {[_|BitGroups], [_]} ->
            %% 当前组的最后一点已经生成 预定下一组的第一个拍点
            [NewGroup|_] = BitGroups,
            case data_mate:get_mate_bit_by_group(NewGroup) of
                [NextId|_] = NewBitIds ->
                    #base_mate_rhythm_master_bit{pre_time = PreTime} = data_mate:get_mate_bit(NewGroup, NextId),
                    Ref = mate_mgr:set_mate_act_timer(PreTime, self(), Key, mate_rhythm_master_bit_show),
                    NewActData = ActData#mate_rhythm_master{
                        ref_bit = Ref,
                        bit_id = NewBitIds,
                        bit_group = BitGroups
                    },
                    NewMateAct = lists:keyreplace(Key, #mate_act.key, State#mate_mgr.mate_act, ActInfo#mate_act{data = NewActData}),
                    State#mate_mgr{mate_act = NewMateAct};
                _ ->
                    %% 配置错误了
                    State
            end;
        {[BitGroupNow|_], [_|BitIds]} ->
            %% 当前组的中间点生成 预定当前组的下一点
            [NextId|_] = BitIds,
            #base_mate_rhythm_master_bit{pre_time = PreTime} = data_mate:get_mate_bit(BitGroupNow, NextId),
            Ref = mate_mgr:set_mate_act_timer(PreTime, self(), Key, mate_rhythm_master_bit_show),
            NewActData = ActData#mate_rhythm_master{
                ref_bit = Ref,
                bit_id = BitIds
            },
            NewMateAct = lists:keyreplace(Key, #mate_act.key, State#mate_mgr.mate_act, ActInfo#mate_act{data = NewActData}),
            State#mate_mgr{mate_act = NewMateAct}
    end;
%% 判断拍点是否被点击到 最后一个点的时候切换对象
handle_event({mate_rhythm_master_check_double_hit, Id, IsExchangeRole}, #mate_act{key = Key, data = ActData} = ActInfo, State) ->
    #mate_rhythm_master{
        tap_role = TapRole
        , exist_bit = ExistBit
        , bit_group = BitGroup
        , bit_id = BitIds
    } = ActData,
    case lists:member(Id, ExistBit) of
        true ->
            %% 没被点到，连击数清0
            %% 通知前端
            ActData1 = ActData#mate_rhythm_master{
                double_hit = 0,
                exist_bit = lists:delete(Id, ExistBit)
            },
            broadcast_event(Key, ?MATE_RHYTHM_MASTER_STA, ActData1, []);
        _ ->
            %% 不存在，说明被点击了 pass
            ActData1 = ActData
    end,
    %% 判断是否切换玩家
    NewActData = case IsExchangeRole of
                     true ->
                         case {BitGroup, BitIds} of
                             {[_], [_]} ->
                                 ActData1;
                             _ ->
                                 %% 一组结束 通知双方切换操作对象
                                 NewTapRole = mate_mgr:get_target_role(Key, TapRole),
                                 ActData2 = ActData1#mate_rhythm_master{tap_role = NewTapRole},
                                 broadcast_event(Key, ?MATE_RHYTHM_MASTER_ROLE_CHANGE, ActData2, []),
                                 ActData2
                         end;
                     false ->
                         ActData1
                 end,
    NewMateAct = lists:keyreplace(Key, #mate_act.key, State#mate_mgr.mate_act, ActInfo#mate_act{data = NewActData}),
    State#mate_mgr{mate_act = NewMateAct};
%% 游戏结束
handle_event(mate_rhythm_master_end, #mate_act{key = {RoleA, RoleB} = Key, data = ActData, daily_score = DailyScores, time = Time} = _ActInfo, State) ->
    #mate_rhythm_master{
        score = Score
    } = ActData,
    settlement(RoleA, round(Score), lists:keyfind(RoleA, 1, DailyScores), Time),
    settlement(RoleB, round(Score), lists:keyfind(RoleB, 1, DailyScores), Time),
    free_role(RoleA),
    free_role(RoleB),
    NewMateAct = lists:keydelete(Key, #mate_act.key, State#mate_mgr.mate_act),
    broadcast_event(Key, ?MATE_RHYTHM_MASTER_END, ActData, []),
    State#mate_mgr{mate_act = NewMateAct};

%% 重新上线：游戏正在玩/可以继续玩
handle_event({login, RoleId}, #mate_act{data = ActData, key = Key}, State) ->
    %% 给玩家发一些数据：当前可点击玩家/当前成绩
    case ActData of
        #mate_rhythm_master{} ->
            Bin1 = pack_data(?MATE_RHYTHM_MASTER_BEGIN, ActData, Key),
            Bin2 = pack_data(?MATE_RHYTHM_MASTER_ROLE_CHANGE, ActData, []),
            Bin3 = pack_data(?MATE_RHYTHM_MASTER_STA, ActData, []),
            lib_server_send:send_to_uid(RoleId, <<Bin1/binary, Bin2/binary, Bin3/binary>>),
            lib_scene:change_act_label(RoleId, ?ACT_LABEL_MATE_RHYTHM_MASTER, 1),
            lib_player:setup_action_lock(RoleId, ?ERRCODE(err149_on_game));
        _ ->
            ok
    end,
    State;

%% 玩家退出/直接结算
handle_event(mate_act_quit, #mate_act{data = #mate_rhythm_master{ref_bit = Ref, ref_end = RefEnd, score = Score} = ActData, time = Time, key = {RoleA, RoleB}, daily_score = DailyScores}, State) ->
    util:cancel_timer(Ref),
    util:cancel_timer(RefEnd),
    settlement(RoleA, round(Score), lists:keyfind(RoleA, 1, DailyScores), Time),
    settlement(RoleB, round(Score), lists:keyfind(RoleB, 1, DailyScores), Time),
    broadcast_event({RoleA, RoleB}, ?MATE_RHYTHM_MASTER_END, ActData, []),
    State;

handle_event(_Msg, _ActInfo, State) ->
    %io:format("~p, ~p: ~p, ~p", [?MODULE, ?LINE, _Msg, _ActInfo]),
    State.

%%%===================================================================
%%% Internal functions
%%%===================================================================
%% 事件广播
broadcast_event({RoleA, RoleB}, Event, Record, Other) ->
    Bin = pack_data(Event, Record, Other),
    lib_server_send:send_to_uid(RoleA, Bin),
    lib_server_send:send_to_uid(RoleB, Bin).

pack_data(Event, Record, Other) ->
    #mate_rhythm_master{
        tap_role = TapRole,
        music_id = MusicId,
        bit_group = [BitGroup|_],
        bit_id = [BitId|_],
        score = Score,
        double_hit = DoubleHit,
        max_double_hit = MaxDoubleHit
    } = Record,
    case Event of
        ?MATE_RHYTHM_MASTER_BEGIN ->
            {RoleA, RoleB} = Other,
            {ok, Bin} = pt_149:write(14924, [MusicId, 0, [lib_role:get_role_figure(RoleA), lib_role:get_role_figure(RoleB)]]);
        ?MATE_RHYTHM_MASTER_BIT_SHOW ->
            {ok, Bin} = pt_149:write(14925, [MusicId, BitGroup, BitId]);
        ?MATE_RHYTHM_MASTER_ROLE_CHANGE ->
            {ok, Bin} = pt_149:write(14926, [TapRole]);
        ?MATE_RHYTHM_MASTER_TAP ->
            {ok, Bin} = pt_149:write(14928, [Other]);
        ?MATE_RHYTHM_MASTER_STA ->
            {ok, Bin} = pt_149:write(14929, [round(Score), DoubleHit]);
        ?MATE_RHYTHM_MASTER_END ->
            {ok, Bin} = pt_149:write(14930, [MusicId, round(Score), MaxDoubleHit])
    end,
    Bin.

%% 结算
settlement(RoleId, Score, {RoleId, DailyScore}, Time) ->
    MateScoreLv = data_mate:get_mate_score_lv(Score),
    DailyLv = data_mate:get_mate_score_lv(DailyScore),
    case MateScoreLv > DailyLv of
        true  ->
            case utime:is_today(Time) of
                true  ->
                    lib_daily:set_count_offline(RoleId, ?MOD_MATE, ?MATE_ACT_RHYTHM_MASTER, Score);
                false ->
                    ok
            end,
            Award = calc_award(DailyLv, MateScoreLv, []),

            lib_goods_api:send_reward_with_mail(RoleId, Award, mate_rhythm_master, "");
        false ->
            ok
    end;
settlement(_, _, _, _) -> ok.

free_role(RoleId) ->
    lib_scene:change_act_label(RoleId, ?ACT_LABEL_MATE_RHYTHM_MASTER, 0),
    lib_player:break_action_lock(RoleId, ?ERRCODE(err149_on_game)).
calc_award(MaxLv, MaxLv, Award) -> Award;
calc_award(Lv, MaxLv, Award) ->
    calc_award(Lv+1, MaxLv, data_mate:get_mate_score_award(Lv+1) ++ Award).
