%% ----------------------------------
%% desc         :合成机公共管理
%% author       :hh
%% module       :printer_draw.erl
%% time         :20/12/22
%% ----------------------------------

-module(printer_draw).

-behaviour (gen_statem).

-include ("printer_draw.hrl").
-include ("common.hrl").
-include ("custom_act.hrl").
-include ("errcode.hrl").

%% callback
-export ([
    init/1
    ,handle_event/4
    ,callback_mode/0
    ,terminate/3
    ]).

%% api
-export ([
    start_link/0
    ,act_start/1            %% 活动开始
    ,act_end/2              %% 活动结束
    ,chat/5                 %% 提问反馈判断
    ,collect/4              %% 收藏
    ,achieve/5              %% 发现新配方
    ,get_formula/4          %% 获取配方信息
    ,get_achieve/2          %% 获取收藏信息
    ,get_chat_cd/2          %% 获取提问反馈CD
    ]).

-define (DELAY_INIT, 10000).

-define (QUIZ,      0).     %% 提问判断
-define (ANSWER,    1).     %% 回答判断

-define (CD_TIME,   300).   %% 冷却时间

%% ---------------------------
%% @doc 
%% basic state type:start close
%% ---------------------------


start_link() ->
    gen_statem:start_link({local, ?MODULE}, ?MODULE, [], []).

init([]) ->
    {ok, close, #printer_draw_state{}, {state_timeout, ?DELAY_INIT, {delay_init}}}.

act_start(ActInfo) ->
    gen_statem:cast(?MODULE, {act_start, ActInfo}).

act_end(ActInfo, ActStatus) ->
    gen_statem:cast(?MODULE, {act_end, ActInfo, ActStatus}).

chat(SubType, RoleId, RoleId1, ChatType, Id) ->
    gen_statem:cast(?MODULE, {chat, SubType, RoleId, RoleId1, ChatType, Id}).

collect(RoleId, SubType, Type, Id) ->
    gen_statem:cast(?MODULE, {collect, RoleId, SubType, Type, Id}).

achieve(RoleId, SubType, ItemSuit, Id, PoolId) ->
    gen_statem:cast(?MODULE, {achieve, RoleId, SubType, ItemSuit, Id, PoolId}).

get_formula(RoleId, SubType, ItemSuit, DrawList) ->
    gen_statem:cast(?MODULE, {get_formula, RoleId, SubType, ItemSuit, DrawList}).

get_achieve(RoleId, SubType) ->
    gen_statem:cast(?MODULE, {get_achieve, RoleId, SubType}).

get_chat_cd(RoleId, SubType) ->
    gen_statem:cast(?MODULE, {get_chat_cd, RoleId, SubType}).

%% 回调函数模式
callback_mode() ->
    handle_event_function.

handle_event(EventType, EventContent, State, Data) ->
    try do_handle_event(EventType, EventContent, State, Data) of
        Ret -> Ret
    catch Class:Reason ->
        Error = {Class, Reason, erlang:get_stacktrace()},
        ?ERR("printer draw error:[~p]", [Error]),
        keep_state_and_data
    end.

%% 初始化
do_handle_event(state_timeout, {delay_init}, _OldState, Data) ->
    case lib_custom_act_api:get_open_custom_act_infos(?CUSTOM_ACT_TYPE_PRINTER_DRAW) of
        ActInfos = [#act_info{}|_] ->
            % ?ERR("~p", [ActInfos]),
            DrawList = db_load_printer_data(),
            {NewDrawList, DeleteSubTypes} = update_printer_data(DrawList, ActInfos),
            db_delete_printer_data_with_type(DeleteSubTypes),
            NewData = Data#printer_draw_state{act_info = ActInfos, draw_list = NewDrawList},
            {next_state, start, NewData};
        _A ->
            % ?ERR("~p", [_A]),
            db_delete_printer_data(),
            keep_state_and_data
    end;

%% 活动开始.支持多开,匹配不判断state
do_handle_event(cast, {act_start, ActInfo}, State, Data) ->
    #printer_draw_state{act_info = ActInfos} = Data,
    #act_info{key = {_, SubType}} = ActInfo,
    case lists:filter(fun(#act_info{key = {_, SubTypeX}}) -> 
        SubType == SubTypeX end, ActInfos) of
        [MatchActInfo = #act_info{}|_] ->
            % ?ERR("~p", [MatchActInfo]),
            if ActInfo =/= MatchActInfo ->
                    NewActInfos = lists:keyreplace({?CUSTOM_ACT_TYPE_PRINTER_DRAW, SubType}, 1, ActInfos, ActInfo),
                    NewData     = Data#printer_draw_state{act_info = NewActInfos},
                    if State == close ->
                            {next_state, start, NewData};
                        true ->
                            {keep_state, NewData}
                    end;
                true ->
                    keep_state_and_data
            end;
        _A ->
            % ?ERR("~p", [_A]),
            NewActInfos = [ActInfo|ActInfos],
            NewData     = Data#printer_draw_state{act_info = NewActInfos},
            if State == close -> 
                    {next_state, start, NewData};
                true ->
                    {keep_state, NewData}
            end
    end;

%% 活动结束
do_handle_event(cast, {act_end, ActInfo, _ActStatus}, State, Data) ->
    #act_info{key = {_, SubType}} = ActInfo,
    #printer_draw_state{act_info = ActInfos} = Data,
    case lists:filter(fun(#act_info{key = {_, SubTypeX}}) -> SubTypeX == SubType end, ActInfos) of
        [#act_info{}|_] ->
            NewData    = act_end_logic(ActInfo, Data),
            OpenActLen = length(NewData#printer_draw_state.act_info),
            if OpenActLen =< 0 andalso State == start ->
                    {next_state, close, NewData};
                true ->
                    {keep_state, NewData}
            end;
        _A ->
            OpenActLen = length(ActInfos),
            if OpenActLen =< 0 andalso State == start ->
                    {next_state, close, Data};
                true ->
                    keep_state_and_data
            end
    end;

%% 准备聊天判断
do_handle_event(cast, {chat, SubType, RoleId, RoleId1, ChatType, Id}, start, Data) ->
    #printer_draw_state{draw_list = DrawList, cd_list = CdList} = Data,
    Now = utime:unixtime(),
    ChatRet = check_chat(CdList, SubType, RoleId, ChatType, Id, Now),
    case ChatRet of
        {true, TypeCdList} when ChatType == ?QUIZ ->
            case lists:keyfind(SubType, 1, DrawList) of
                {_, TypeDrawList} -> ok;
                _   -> TypeDrawList = []
            end,
            case lists:keymember(Id, 2, TypeDrawList) of
                true ->
                    NewTypeCdList = lists:keystore(RoleId, 1, TypeCdList, {RoleId, Id, Now, 0}),
                    NewCdList     = lists:keystore(SubType, 1, CdList, {SubType, NewTypeCdList}),
                    NewData = Data#printer_draw_state{cd_list = NewCdList},
                    {ok, BinData} = pt_332:write(33292, [?SUCCESS, SubType, ChatType, Id, RoleId]),
                    lib_server_send:send_to_uid(RoleId1, BinData),
                    {keep_state, NewData};
                _ ->
                    lib_server_send:send_to_uid(RoleId1, pt_332, 33292, [?ERRCODE(err331_not_discover_fomula), SubType, ChatType, Id, RoleId]),
                    {keep_state, Data}
            end;
        {true, {RoleId, Id, StartTime, _}, TypeCdList} when ChatType == ?ANSWER ->
            NewTypeCdList = lists:keystore(RoleId, 1, TypeCdList, {RoleId, Id, StartTime, Now}),
            NewCdList = lists:keystore(SubType, 1, CdList, {SubType, NewTypeCdList}),
            {ok, BinData} = pt_332:write(33292, [?SUCCESS, SubType, ChatType, Id, RoleId]),
            lib_server_send:send_to_uid(RoleId1, BinData),
            NewData = Data#printer_draw_state{cd_list = NewCdList},
            {keep_state, NewData};
        {false, Error} ->
            {ok, BinData} = pt_332:write(33292, [Error, SubType, ChatType, Id, RoleId]),
            lib_server_send:send_to_uid(RoleId1, BinData),
            keep_state_and_data;
        _Error ->
            {ok, BinData} = pt_332:write(33292, [?FAIL, SubType, ChatType, Id, RoleId]),
            lib_server_send:send_to_uid(RoleId1, BinData),            
            keep_state_and_data
    end;

%% 收藏&取消收藏操作判断
do_handle_event(cast, {collect, RoleId, SubType, Type, Id}, start, Data) ->
    #printer_draw_state{act_info = ActInfos, draw_list = DrawList} = Data,
    OpenRet = is_opening_act(SubType, ActInfos),
    if OpenRet ->
            DrawListRet = lists:keyfind(SubType, 1, DrawList),
            if not DrawListRet ->
                    lib_server_send:send_to_uid(RoleId, pt_332, 33293, [?FAIL, SubType, Type, Id]);
                true ->
                    {_, TypeDrawList} = DrawListRet,
                    case lists:keyfind(Id, 2, TypeDrawList) of
                        {_, _, _} ->
                            custom_act_printer_draw:collect(RoleId, SubType, Type, Id);
                        _ ->
                            lib_server_send:send_to_uid(RoleId, pt_332, 33293, [?ERRCODE(err331_formula_need_unlock), SubType, Type, Id])
                    end
            end;
        true ->
            lib_server_send:send_to_uid(RoleId, pt_332, 33293, [?ERRCODE(err331_act_closed), SubType, Type, Id])
    end,
    keep_state_and_data;

%% 达成新记录
do_handle_event(cast, {achieve, RoleId, SubType, ItemSuit, Id, PoolId}, start, Data) ->
    #printer_draw_state{act_info = ActInfos, draw_list = DrawList} = Data,
    OpenRet = is_opening_act(SubType, ActInfos),
    if OpenRet ->
            Now = utime:unixtime(),
            {IsNewAchieve, NewTypeDrawList} =
            case lists:keyfind(SubType, 1, DrawList) of
                {_, TypeDrawList} ->
                    if Id == 0 -> {false, TypeDrawList};
                        true ->
                            case lists:keyfind(Id, 2, TypeDrawList) of
                                {_, _, _} -> {false, TypeDrawList};
                                _         -> {true, lists:keystore(Id, 2, TypeDrawList, {RoleId, Id, Now})}
                            end
                    end;
                _ ->
                    if Id == 0 -> {false, []};
                        true ->  
                            {true, [{RoleId, Id, Now}]}
                    end
            end,
            if IsNewAchieve ->
                    RoleName = lib_role:get_role_name(RoleId),
                    {ok, BinData} = pt_332:write(33298, [SubType, RoleName, Id]),
                    lib_server_send:send_to_all(BinData),
                    NewDrawList = lists:keystore(SubType, 1, DrawList, {SubType, NewTypeDrawList});
                true ->
                    NewDrawList = DrawList
            end,
            custom_act_printer_draw:achieve_callback(RoleId, SubType, ItemSuit, Id, PoolId, IsNewAchieve),
            NewData = Data#printer_draw_state{draw_list = NewDrawList},
            IsNewAchieve andalso db_update_printer_data(RoleId, SubType, Id, Now),
            {keep_state, NewData};
        true ->
            keep_state_and_data
    end;

%% 获取配方信息
do_handle_event(cast, {get_formula, RoleId, SubType, ItemSuit, RoleDrawList}, start, Data) ->
    #printer_draw_state{act_info = ActInfos, draw_list = DrawList} = Data,
    OpenRet = is_opening_act(SubType, ActInfos),
    if OpenRet ->
            case lists:keyfind(SubType, 1, DrawList) of
                {_, TypeDrawList} -> ok;
                _   -> TypeDrawList = []
            end,
            FormulaList = calc_formula(RoleId, ItemSuit, RoleDrawList, TypeDrawList),
            lib_server_send:send_to_uid(RoleId, pt_332, 33296, [SubType, 0, FormulaList]);
        true ->
            skip
    end,
    keep_state_and_data;

do_handle_event(cast, {get_achieve, RoleId, SubType}, start, Data) ->
    #printer_draw_state{act_info = ActInfos, draw_list = DrawList} = Data,
    OpenRet = is_opening_act(SubType, ActInfos),
    if OpenRet ->
            case lists:keyfind(SubType, 1, DrawList) of
                {_, TypeDrawList} -> ok;
                _   -> TypeDrawList = []
            end,
            AchieveList = calc_achieve(TypeDrawList),
            lib_server_send:send_to_uid(RoleId, pt_332, 33297, [SubType, AchieveList]);
        true ->
            skip
    end,
    keep_state_and_data;

do_handle_event(cast, {get_chat_cd, RoleId, SubType}, start, Data) ->
    #printer_draw_state{act_info = ActInfos, cd_list = CdList} = Data,
    OpenRet = is_opening_act(SubType, ActInfos),
    if OpenRet ->
            Now = utime:unixtime(),
            case lists:keyfind(SubType, 1, CdList) of
                {_, TypeCdList} ->
                    case lists:keyfind(RoleId, 1, TypeCdList) of
                        {_, _, StartTime, _} -> ok;
                        _ -> StartTime = 0
                    end,
                    if Now - StartTime >= ?CD_TIME ->
                            lib_server_send:send_to_uid(RoleId, pt_332, 33299, [SubType, Now]);
                        true ->
                            lib_server_send:send_to_uid(RoleId, pt_332, 33299, [SubType, StartTime + ?CD_TIME])
                    end;
                _ ->
                    lib_server_send:send_to_uid(RoleId, pt_332, 33299, [SubType, Now])
            end;
        true ->
            lib_server_send:send_to_uid(RoleId, pt_332, 33299, [SubType, 0])
    end,
    keep_state_and_data;

do_handle_event(_EventType, _EventContent, State, _Data) ->
    State == start andalso ?ERR("printer_draw unkown event type [~w] event content [~w] state [~w]", [_EventType, _EventContent, State]),
    keep_state_and_data.

terminate(_Reason, _State, _Data) ->
    _Reason =/= normal andalso ?ERR("printer draw error:~w", [_Reason]),
    ok.

%% ------------------------------------------------------------------------------------------------------

db_load_printer_data() ->
    DrawList = db:get_all(?SQL_SELECT_PRINT_DRAW, []),
    DrawList. 

db_delete_printer_data() ->
    db:execute(?SQL_DELETE_PRINT_DRAW, []),
    ok.

db_update_printer_data(RoleId, SubType, Id, Now) ->
    db:execute(?SQL_REPLACE_PRINT_DRAW, [SubType, RoleId, Id, Now]),
    ok.

db_delete_printer_data_with_type(SubTypes) ->
    if SubTypes == [] -> ok;
        true -> 
            TypeString  = util:link_list(SubTypes),
            TypeString1 = lists:concat(["(", TypeString, ")"]),
            db:execute(io_lib:format(?SQL_DELETE_PRINT_DRAW_WITH_TYPE, [TypeString1])),
            ok
    end.

update_printer_data(DrawList, ActInfos) ->
    {DrawList1, DeleteSubTypes} = lists:foldl(fun([SubType, RoleId, Id, Time], {Acc1, Acc2}) ->
        OpenRet = is_opening_act(SubType, ActInfos),
        if OpenRet ->
                NewAcc1 =
                case lists:keyfind(SubType, 1, Acc1) of
                    {_, TypeList} -> lists:keystore(SubType, 1, Acc1, {SubType, [{RoleId, Id, Time}|TypeList]});
                    _             -> lists:keystore(SubType, 1, Acc1, {SubType, [{RoleId, Id, Time}]})
                end,
                {NewAcc1, Acc2};
            true ->
                {Acc1, [SubType|Acc2]}
        end
    end, {[], []}, DrawList),
    {DrawList1, DeleteSubTypes}.


%% 判断活动是否开启
is_opening_act(SubType, ActInfos) ->
    lists:any(fun(#act_info{key = {_, SubTypeX}}) -> 
        SubType == SubTypeX
    end, ActInfos).

%% 活动结束逻辑处理
act_end_logic(CloseAct, Data) ->
    #printer_draw_state{
        act_info = ActInfos, draw_list = DrawList
    } = Data,
    #act_info{key = {Type, SubType}} = CloseAct,
    NewActInfos     = lists:keydelete({Type, SubType}, #act_info.key, ActInfos),
    NewDrawList     = lists:keydelete(SubType, 1, DrawList),
    db_delete_printer_data_with_type([SubType]),
    Data#printer_draw_state{
        act_info = NewActInfos, 
        draw_list = NewDrawList
    }.

%%
calc_formula(_RoleId, ItemSuit, RoleDrawList, DrawList) ->
    PrinterIds = data_printer_draw:get_all_printers_1(),
    SubDrawList = lists:foldl(fun(Id, Acc) -> 
        case data_printer_draw:get_printer_draw(Id) of
            #base_printer_draw_list{item_suit = ItemSuit} ->
                case lists:keyfind(Id, 2, DrawList) of
                    {_, _, _} -> [{Id, ?FORMULA_LOCK}|Acc];
                    _         -> [{Id, 0}|Acc]
                end;
            _ ->
                Acc
        end
    end, [], PrinterIds),
    lists:foldl(fun(RolePrinterId, Acc) -> 
        case lists:keyfind(RolePrinterId, 1, Acc) of
            {_, _} -> lists:keystore(RolePrinterId, 1, Acc, {RolePrinterId, ?FORMULA_UNLOCK});
            _      -> Acc
        end
    end, SubDrawList, RoleDrawList).

calc_achieve(TypeDrawList) ->
    lists:foldl(fun({RoleId, Id, _}, Acc) -> 
        RoleName = lib_role:get_role_name(RoleId),
        [{RoleName, Id}|Acc]
    end, [], TypeDrawList).

check_chat(CdList, SubType, RoleId, ?QUIZ, _Id, Now) ->
    case lists:keyfind(SubType, 1, CdList) of
        {_, TypeCdList} ->
            case lists:keyfind(RoleId, 1, TypeCdList) of
                {RoleId, _, StartTime, _EndTime} ->
                    LastTime = Now - StartTime,
                    if LastTime >= ?CD_TIME -> {true, TypeCdList};
                        true -> {false, ?ERRCODE(err331_cd_chat)} %% 冷却时间范围内
                    end;
                _ -> {true, TypeCdList}
            end;
        _ -> {true, []}
    end;
check_chat(CdList, SubType, RoleId, ?ANSWER, Id, _Now) ->
    case lists:keyfind(SubType, 1, CdList) of
        {_, TypeCdList} ->
            case lists:keyfind(RoleId, 1, TypeCdList) of
                {_, Id, _StartTime, _EndTime} ->
                    if _EndTime == 0 ->
                            {true, {RoleId, Id, _StartTime, _EndTime}, TypeCdList};
                        true ->
                            {false, ?ERRCODE(err331_have_answered)} %% 已经有人回答过了
                    end;
                _ ->
                    {false, ?ERRCODE(err331_no_people_quiz)} %% 没人提问
            end;
        _ ->
            {false, ?ERRCODE(err331_no_people_quiz)}
    end.