%% ---------------------------------------------------------------------------
%% @doc lib_mail.erl

%% @author  hjh
%% email  hejiahua@163.com
%% @since  2017-01-13
%% @deprecated 邮件
%% ---------------------------------------------------------------------------
-module(lib_mail).
-export([]).

-compile(export_all).

-include("server.hrl").
-include("unite.hrl").
-include("common.hrl").
-include("predefine.hrl").
-include("def_id_create.hrl").
-include("errcode.hrl").
-include("goods.hrl").
-include("mail.hrl").
-include("guild.hrl").
-include("def_module.hrl").
-include("def_daily.hrl").
-include("def_goods.hrl").
-include("def_fun.hrl").
-include("language.hrl").
-include("figure.hrl").

%% 此处邮件正文content不加载，待阅读邮件内容调用
%% get_mail时再加载，避免内存中存大量字符串
make_record(mail_from_db, [MailId, Type, State, Locked, SId, RId, Title, CmAttachmentBin, Timestamp, EffectSt, ModuleId, SubModuleId, Important]) ->
    Time = utime:microsec_to_sec(Timestamp),
    case util:bitstring_to_term(CmAttachmentBin) of
        CmAttachment when is_list(CmAttachment) -> ok;
        _ -> CmAttachment = []
    end,
    #mail{
        id = MailId
        , type = Type
        , state = State
        , locked = Locked
        , sid = SId
        , rid = RId
        , title = Title
        , content = <<>>
        , cm_attachment = CmAttachment
        , timestamp = Timestamp
        , time = Time
        , effect_st = EffectSt
        , effect_et = get_effect_et(CmAttachment, State, EffectSt)
        , module_id = ModuleId
        , sub_module_id = SubModuleId
        , important = Important
    }.

%% 配置
get_config(T) ->
    case T of
        % 有附件的邮件有效时间 15天
        attachment_effect_time -> 1296000;
        % 未读取且无附件的邮件有效时间 3天
        not_read_and_no_attachment_effect_time -> 259200;
        % 读取后且没有附件的邮件有效时间 1天
        read_and_no_attachment_effect_time -> 86400;
        % 最大发放的邮件数量(防止客户端过多)
        max_send_mail_len -> 100
    end.

%% ----------------------------------------------------
%% 内存邮件数据操作(主要是玩家进程操作)
%% ----------------------------------------------------

%% 初始化邮件的Dict到玩家进程中(玩家进程执行)
init_mail_dict(RoleId) ->
    Sql = io_lib:format(?sql_mail_select, [RoleId]),
    List = db:get_all(Sql),
    MailList = [make_record(mail_from_db, T)||T<-List],
    save_mail_dict(RoleId, MailList),
    MailList.

%% 保存MailList到玩家进程字典中
save_mail_dict(RoleId, MailList) ->
    case put(?P_MAIL_KEY(RoleId), MailList) of
        undefined -> MailList;
        _ -> MailList
    end.

%% 获取进程字典内容
get_mail_list_on_dict(RoleId) ->
    case get(?P_MAIL_KEY(RoleId)) of
        undefined -> init_mail_dict(RoleId);
        MailList -> MailList
    end.

%% 插入到进程字典中
add_mail_on_dict(RoleId, Mail) ->
    MailList = get_mail_list_on_dict(RoleId),
    case lists:keymember(Mail#mail.id, #mail.id, MailList) of
        true -> MailList;
        false -> save_mail_dict(RoleId, [Mail|MailList])
    end.

%% 更新邮件到进程字典中
update_mail_on_dict(RoleId, Mail) ->
    MailList = get_mail_list_on_dict(RoleId),
    NewMailList = lists:keyreplace(Mail#mail.id, #mail.id, MailList, Mail),
    save_mail_dict(RoleId, NewMailList).

%% 根据邮件id获取邮件
%% @return false | #mail{}
get_mail_by_mail_id_on_dict(RoleId, MailId) ->
    MailList = get_mail_list_on_dict(RoleId),
    lists:keyfind(MailId, #mail.id, MailList).

%% 根据邮件ids获取邮件
%% @return false | #mail{}
get_mails_by_ids_on_dict(RoleId, MailIds) ->
    MailList = get_mail_list_on_dict(RoleId),
    lists:filter(fun(OneMail) ->
        lists:member(OneMail#mail.id, MailIds)
                 end, MailList).

%% 根据邮件id删除邮件
delete_mail_by_mail_id_on_dict(RoleId, MailId) ->
    MailList = get_mail_list_on_dict(RoleId),
    NewMailList = lists:keydelete(MailId, #mail.id, MailList),
    save_mail_dict(RoleId, NewMailList).

%% 根据邮件ids批量删除邮件
delete_by_mail_ids_on_dict(RoleId, DelMailIds) ->
    MailList = get_mail_list_on_dict(RoleId),
    NewMailList = do_delete_by_mail_ids_on_dict(MailList, DelMailIds, []),
    save_mail_dict(RoleId, NewMailList).

do_delete_by_mail_ids_on_dict([], _DelMailIds, Result) -> Result;
do_delete_by_mail_ids_on_dict([Mail|MailList], DelMailIds, Result) ->
    case lists:member(Mail#mail.id, DelMailIds) of
        false ->
            do_delete_by_mail_ids_on_dict(MailList, DelMailIds, Result ++ [Mail]);
        true ->
            do_delete_by_mail_ids_on_dict(MailList, DelMailIds, Result)
    end.

%% ----------------------------------------------------
%% player
%% ----------------------------------------------------

add_mail(#player_status{id = RoleId, sid = Sid}, AddMailList) ->
    F = fun(Mail) -> add_mail_on_dict(RoleId, Mail) end,
    lists:foreach(F, AddMailList),
    F2 = fun(Mail) ->
        #mail{id = MailId, type = Type, state = State, title = Title, time = Time, effect_et = EffectEt, important = Important} = Mail,
        IsAttach = is_attach(Mail),
        {MailId, Type, State, Title, IsAttach, Time, EffectEt, Important}
         end,
    List = [F2(T)||T<-AddMailList],
    {ok, BinData} = pt_190:write(19004, [List]),
    lib_server_send:send_to_sid(Sid, BinData),
    % 邮件通知
    {ok, BinData2} = pt_190:write(19008, [?SUCCESS]),
    lib_server_send:send_to_sid(Sid, BinData2),
    ok.

%% 是否有附件
is_attach(#mail{cm_attachment = []}) -> 0;
is_attach(#mail{state = ?MAIL_STATE_HAS_RECEIVE}) -> 0;
is_attach(_) -> 1.

%% 获取信件
get_mail(RoleId, MailId) ->
    Mail = get_mail_by_mail_id_on_dict(RoleId, MailId),
    case Mail of
        false -> {false, ?ERRCODE(err190_no_mail)};
        % 信件内容尚未加载
        #mail{
            type = MailType, sid = Sid, sname = CurSName, content = <<>>
            ,module_id = ModuleId
        } ->
            case db:get_row(io_lib:format(?sql_mail_select_content, [MailId])) of
                [] -> {false, ?ERRCODE(err190_read_mail_content_fail)};
                [Content] ->
                    SName = case CurSName == undefined of
                                true ->
                                    case MailType of
                                        ?MAIL_TYPE_PRIV -> lib_role:get_role_name(Sid);
                                        ?MAIL_TYPE_GUILD -> utext:get(4000004);
                                        ?MAIL_TYPE_GUILD_MEMBER -> utext:get(4000004);
                                        ?MAIL_TYPE_SYS when ModuleId =:= ?MOD_MARKET -> data_module:get(ModuleId);
                                        _ -> utext:get(?SYSTEM)
                                    end;
                                false -> CurSName
                            end,
                    NewMail = Mail#mail{sname = SName, content = Content},
                    {ok, NewMail2} = change_mail_read_state(RoleId, NewMail),
                    {ok, NewMail2}
            end;
        % 信件内容已经加载
        Mail ->
            {ok, NewMail} = change_mail_read_state(RoleId, Mail),
            {ok, NewMail}
    end.

%% 改变邮件的阅读状态
change_mail_read_state(RoleId, #mail{id = MailId, state = OldState, cm_attachment = CmAttachment, effect_st = OldEffectSt, effect_et = OldEffectEt} = Mail) ->
    if
    % % 查看带附件的邮件不改变信件状态
    % CmAttachment =/= [] -> State = OldState;
    % 更新未读信件状态为已读
        OldState == ?MAIL_STATE_NO_READ ->
            State = ?MAIL_STATE_READ,
            case CmAttachment == [] of
                true -> %% 查看没有附件的邮件要把有效期变为1天
                    EffectSt = utime:unixtime(),
                    EffectEt = get_effect_et(CmAttachment, State, EffectSt);
                false -> %% 查看附件的邮件不更新有效时间
                    EffectSt = OldEffectSt,
                    EffectEt = OldEffectEt
            end,
            db:execute(io_lib:format(?sql_mail_update_state_and_effect_st, [State, EffectSt, MailId]));
        true ->
            State = OldState,
            EffectSt = OldEffectSt,
            EffectEt = OldEffectEt
    end,
    NewMail = Mail#mail{state = State, effect_st = EffectSt, effect_et = EffectEt},
    update_mail_on_dict(RoleId, NewMail),
    {ok, NewMail}.

%% 获得截断的有效邮件列表
%% 新项目补充分类--系统邮件和拍卖邮件
get_sub_effect_mail_list(RoleId, _Type) ->
    MailList = get_effect_mail_list(RoleId),
    MaxLen = get_config(max_send_mail_len),
    % Filter  = fun(M) -> M#mail.type == ?MAIL_TYPE_AUCTION end,
    % Filter1 = fun(M) -> M#mail.type =/= ?MAIL_TYPE_AUCTION end,
    % case Type of
    %    ?MAIL_TYPE_AUCTION ->
    %        MailList1 = lists:filter(Filter, MailList);
    %    _ ->
    %        MailList1 = lists:filter(Filter1, MailList)
    % end,
    lists:sublist(MailList, MaxLen).

%% 获得有效的邮件列表
get_effect_mail_list(RoleId) ->
    MailList = get_mail_list_on_dict(RoleId),
    NowTime = utime:unixtime(),
    F = fun(#mail{effect_et = EffectEt}) -> NowTime =< EffectEt end,
    lists:filter(F, MailList).

%% 批量删除邮件检测函数
%% 检测邮件是否有附件
check_delete_mails(RoleId, DelMailIds) ->
    NowTime = utime:unixtime(),
    MailList = get_mail_list_on_dict(RoleId),
    do_check_delete_mails(MailList, DelMailIds, NowTime, []).

%% 检测要批量删除的邮件是否有效
do_check_delete_mails([], _, _, Result) -> {ok, Result};
do_check_delete_mails([OneMail|Mails], DelMailIds, NowTime, Result) ->
    case lists:member(OneMail#mail.id, DelMailIds) of
        true ->
            IsAttach = ?IF(OneMail#mail.cm_attachment =/= [] andalso OneMail#mail.state =/= ?MAIL_STATE_HAS_RECEIVE, true, false),
            IsNotVaild = ?IF(OneMail#mail.effect_et < NowTime, true, false),
            if
                IsAttach -> {false, ?ERRCODE(err190_have_attachment_not_to_delete)};
                IsNotVaild -> {false, ?ERRCODE(err190_mail_timeout_to_delete)};
                true ->
                    do_check_delete_mails(Mails, DelMailIds, NowTime, [OneMail#mail.id|Result])
            end;
        false ->
            do_check_delete_mails(Mails, DelMailIds, NowTime, Result)
    end.

%% 根据邮件id批量删除邮件
delete_mail_by_mail_ids(RoleId, DelMailIds) ->
    case check_delete_mails(RoleId, DelMailIds) of
        {ok, RealDelIds} when RealDelIds =/= [] ->
            DelArgs = util:link_list(RealDelIds),
            F = fun() ->
                Sql = io_lib:format(?sql_mail_delete_more, [DelArgs]),
                db:execute(Sql),
                ok
                end,
            case F() of
                ok ->
                    log_mail_clear_when_manual_delete(RoleId, RealDelIds),
                    delete_by_mail_ids_on_dict(RoleId, RealDelIds),
                    {?SUCCESS, RealDelIds};
                Err ->
                    ?ERR("Del Mails Fail:~p~n", [Err]),
                    {?FAIL, []}
            end;
        {false, Code} -> {Code, []};
        _ -> {?SUCCESS, []}
    end.

log_mail_clear_when_manual_delete(RoleId, MailIds) ->
    MailList = get_mails_by_ids_on_dict(RoleId, MailIds),
    spawn(fun() -> log_mail_clear_when_manual_delete_do(RoleId, MailList, 1) end),
    ok.

log_mail_clear_when_manual_delete_do(_, [], _) -> ok;
log_mail_clear_when_manual_delete_do(RoleId, [Mail|Rest], Index) ->
    if Index rem 10 == 0 ->
        timer:sleep(500);
        true ->
            skip
    end,
    #mail{title = Title, cm_attachment = CmAttachment, effect_st = EffectSt} = Mail,
    lib_log_api:log_mail_clear(RoleId, Title, util:term_to_bitstring(CmAttachment), 4, EffectSt),
    log_mail_clear_when_manual_delete_do(RoleId, Rest, Index+1).

%% 删除邮件
delete_mail_by_mail_id(RoleId, MailId) ->
    case get_mail_by_mail_id_on_dict(RoleId, MailId) of
        false -> ErrorCode = ?ERRCODE(err190_no_mail_to_delete);
        Mail ->
            case delete_mail(RoleId, Mail) of
                {ok, ErrorCode} -> ok;
                {false, ErrorCode} -> ok
            end
    end,
    {ok, BinData} = pt_190:write(19003, [ErrorCode, MailId]),
    lib_server_send:send_to_uid(RoleId, BinData).

%% 删除邮件
delete_mail(RoleId, #mail{id = MailId, effect_et = EffectEt} = Mail) ->
    IsAttach = is_attach(Mail),
    NowTime = utime:unixtime(),
    if
        IsAttach == 1 -> {false, ?ERRCODE(err190_have_attachment_not_to_delete)};
        NowTime > EffectEt -> {false, ?ERRCODE(err190_mail_timeout_to_delete)};
        true ->
            F = fun() ->
                Sql = io_lib:format(?sql_mail_delete, [MailId]),
                db:execute(Sql),
                ok
                end,
            case F() of
                ok ->
                    delete_mail_by_mail_id_on_dict(RoleId, MailId),
                    {ok, ?SUCCESS};
                _ ->
                    {false, ?FAIL}
            end
    end.

%% 批量提取附件
extract_attachment_by_mail_ids(Player, MailIds) ->
    {ErrorCode, NewPlayer, HasExtractIds} = do_extract_attachment_by_mail_ids(MailIds, Player, []),
    {ok, BinData} = pt_190:write(19005, [ErrorCode, HasExtractIds]),
    lib_server_send:send_to_sid(Player#player_status.sid, BinData),
    {ok, NewPlayer}.

%% 批量提取附件
do_extract_attachment_by_mail_ids([], Player, []) -> {?ERRCODE(err190_no_mail_attachment), Player, []};
do_extract_attachment_by_mail_ids([], Player, HasExtractIds) -> {?SUCCESS, Player, HasExtractIds};
do_extract_attachment_by_mail_ids([MailId|MailIds], Player, HasExtractIds) ->
    case extract_attachment_by_mail_id(Player, MailId) of
        {1, NewPlayer} ->
            do_extract_attachment_by_mail_ids(MailIds, NewPlayer, [MailId|HasExtractIds]);
        {ErrorCode, NewPlayer} -> {ErrorCode, NewPlayer, HasExtractIds}
    end.

%% 提取附件
%% 分三部分取邮件 1:金钱 2:通用 3:物品
extract_attachment_by_mail_id(Player, MailId) ->
    case check_extract_attachment_by_mail_id(Player, MailId) of
        {false, ErrorCode} -> NewPlayer = Player;
        {ok, Mail} ->
            %% 分离出市场交易竞价失败的邮件(不作为成就触发)
            %% Type 0-其他邮件 1-交易失败邮件
            #mail{module_id = Module, sub_module_id = SubModule} = Mail,
            case Module == ?MOD_MARKET andalso SubModule == 1 of
                true -> Type = 1;
                _ -> Type = 0
            end,
            case extract_attachment(Player, Mail, Type) of
                {false, ErrorCode, NewPlayer, NewMail} -> skip;
                {ok, NewPlayer, NewMail} -> ErrorCode = ?SUCCESS
            end,
            #mail{cm_attachment = CmAttachment} = Mail,
            #mail{cm_attachment = NewCmAttachment} = NewMail,
            List = CmAttachment -- NewCmAttachment,
            case List =/= [] of
                true ->
                    %% 获得物品提示
                    {ok, BinData2} = pt_110:write(11061, [List]),
                    lib_server_send:send_to_sid(Player#player_status.sid, BinData2);
                false ->
                    skip
            end
    end,
    {ErrorCode, NewPlayer}.

check_extract_attachment_by_mail_id(Player, MailId) ->
    #player_status{id = RoleId} = Player,
    case get_mail_by_mail_id_on_dict(RoleId, MailId) of
        false -> {false, ?ERRCODE(err190_no_mail)};
        #mail{cm_attachment = []} -> {false, ?ERRCODE(err190_no_mail_attachment)};
        #mail{state = ?MAIL_STATE_HAS_RECEIVE} -> {false, ?ERRCODE(err190_have_receive_attachment)};
        #mail{effect_et = EffectEt, cm_attachment = CmAttachment} = Mail ->
            NowTime = utime:unixtime(),
            case NowTime > EffectEt of
                true ->
                    {false, ?ERRCODE(err190_mail_timeout_to_extract_attachment)};
                false ->
                    case lib_goods_api:can_give_goods(CmAttachment) of
                        true ->
                            {ok, Mail};
                        {false, Error} ->
                            {false, Error}
                    end
            end
    end.

extract_attachment(Player, Mail, Type) ->
    #mail{id = MailId, cm_attachment = CmAttachment} = Mail,
    UqCmAttachment = make_cm_attachment_unique(CmAttachment),
    Map = spilt_attachment(UqCmAttachment),
    #{money_list := MoneyList, common_list := CommonList, goods_list := GoodsList} = Map,
    List = [{money_list, MoneyList}, {common_list, CommonList}, {goods_list, GoodsList}],
    case extract_attachment_help(List, Player, Mail, Type) of
        {false, ErrorCode, NewPlayer, NewMail2} -> {false, ErrorCode, NewPlayer, NewMail2};
        {ok, NewPlayer, NewMail} ->
            % 领取附件后重新计算有效结束时间
            % 领取后附件不清空,设置状态为已领取状态 by czc 2018/1/25
            NowTime = utime:unixtime(),
            NewState = ?MAIL_STATE_HAS_RECEIVE,
            Sql = io_lib:format(?sql_mail_update_receive_state, [NewState, util:term_to_bitstring(CmAttachment), NowTime, MailId]),
            db:execute(Sql),
            EffectEt = get_effect_et(CmAttachment, NewState, NowTime),
            NewMail2 = NewMail#mail{state = NewState, cm_attachment = CmAttachment, effect_st = NowTime, effect_et = EffectEt},
            update_mail_on_dict(Player#player_status.id, NewMail2),
            lib_goods_api:send_tv_tip(Player#player_status.id, MoneyList++lib_gift_new:merge_helper(GoodsList,[],[])),
            {ok, NewPlayer, NewMail2}
    end.

extract_attachment_help([], Player, Mail, _Type) -> {ok, Player, Mail};

extract_attachment_help([{money_list, MoneyList}|L], Player, Mail, Type) ->
    case recv_money_list(Player, Mail, MoneyList, Type) of
        {false, ErrorCode} -> {false, ErrorCode, Player, Mail};
        {ok, NewPlayer, NewMail} -> extract_attachment_help(L, NewPlayer, NewMail, Type)
    end;
extract_attachment_help([{common_list, CommonList}|L], Player, Mail, Type) ->
    {ok, NewPlayer, NewMail} = recv_common_list(Player, Mail, CommonList, Type),
    extract_attachment_help(L, NewPlayer, NewMail, Type);

extract_attachment_help([{goods_list, []}|L], Player, Mail, Type) ->
    extract_attachment_help(L, Player, Mail, Type);

extract_attachment_help([{goods_list, GoodsList}|L], Player, Mail, Type) ->
    #player_status{id = RoleId} = Player,
    case lib_goods_api:can_give_goods(Player, GoodsList) of
        true ->
            % 邮件处理
            #mail{id = MailId, cm_attachment = CmAttachment} = Mail,
            NewCmAttachment = CmAttachment -- GoodsList,
            db_mail_update_cm_attachment(MailId, NewCmAttachment),
            log(log_mail_get, [Mail, GoodsList]),
            NewMail = Mail#mail{cm_attachment = NewCmAttachment},
            update_mail_on_dict(RoleId, NewMail),
            % Produce
            F = fun
                    ({?TYPE_GOODS, GoodsTypeId, GoodsNum}) -> {goods, GoodsTypeId, GoodsNum, lib_goods_api:get_goods_bind(GoodsTypeId)};
                    ({?TYPE_BIND_GOODS, GoodsTypeId, GoodsNum}) -> {goods, GoodsTypeId, GoodsNum, ?BIND};
                    ({?TYPE_ATTR_GOODS, GoodsTypeId, GoodsNum, Attr}) -> {goods_attr, GoodsTypeId, GoodsNum, Attr}
                end,
            GiveGoodsList = lists:map(F, GoodsList),
            % ?INFO("extract_attachment_help GoodsList:~w GiveGoodsList:~w ~n", [GoodsList, GiveGoodsList]),
            Produce = #produce{type = mail, remark = lists:concat(["MailId:", MailId])},
            case catch goods_add:give_goods_by_pile(Player, GiveGoodsList, Produce) of
                1 ->
                    %% 有分解需求的整件物品需要及时分解
                    lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, lib_goods_api, after_send_reward, [GoodsList]),
                    lib_vie_rank:update_role_vie(GoodsList,Player),
                    %% 在线/离线拍卖获得
                    Mail#mail.module_id =:= ?MOD_MARKET andalso
                        lib_achievement_api:async_event(RoleId, lib_achievement_api, shop_buy_equip_event, 1),
                    %% 触发任务收集
                    lib_task_api:collect_goods(RoleId, GoodsList);
                Error ->
                    ?ERR("RoleId:~p~n MailId:~p~nSenderId:~p / ReceiverId:~p~nModuleId:~p / SubModuleId:~p~nGoodsList:~w~n Error:~p~n",
                        [Player#player_status.id, Mail#mail.id, Mail#mail.sid, Mail#mail.rid, Mail#mail.module_id, Mail#mail.sub_module_id, GoodsList, Error])
            end,
            extract_attachment_help(L, Player, NewMail, Type);
        false ->
            {false, ?ERRCODE(err190_bag_full), Player, Mail}
    end.

%% 唯一处理(不要修改格式)
%% CmAttachment :: [{Type, GoodsId, Num}]
make_cm_attachment_unique(CmAttachment) ->
    List = make_cm_attachment_unique(CmAttachment, []),
    F = fun(T, Acc) ->
        case T of
            {{Type, GoodsTypeId}, GoodsNum} ->
                [{Type, GoodsTypeId, GoodsNum}|Acc];
            {?TYPE_ATTR_GOODS, GoodsTypeId, GoodsNum, Attr} ->
                [{?TYPE_ATTR_GOODS, GoodsTypeId, GoodsNum, Attr}|Acc];
            _ -> Acc
        end
        end,
    lists:foldl(F, [], List).

make_cm_attachment_unique([], ObjectList) -> ObjectList;
make_cm_attachment_unique([H|T], ObjectList) ->
    case H of
        {Type, GoodsTypeId, GoodsNum} when is_integer(Type) ->
            NewObjectList = case lists:keyfind({Type, GoodsTypeId}, 1, ObjectList) of
                                {_, Num} -> [{ {Type, GoodsTypeId}, GoodsNum+Num}| lists:keydelete({Type, GoodsTypeId}, 1, ObjectList)];
                                false    -> [{ {Type, GoodsTypeId}, GoodsNum} | ObjectList]
                            end,
            make_cm_attachment_unique(T, NewObjectList);
        {?TYPE_ATTR_GOODS, _GoodsTypeId, _GoodsNum, _Attr} ->
            make_cm_attachment_unique(T, [H|ObjectList]);
        _ ->
            make_cm_attachment_unique(T, ObjectList)
    end.

%% 分离附件(不要修改格式)
%% @return #{money_list = MoneyList, goods_list = GoodsList, common_list = CommonList}
%%  MoneyList 金钱列表 [{?TYPE_GOLD|?TYPE_BGOLD|?TYPE_COIN, GoodsTypeId, Num}|...]
%%  GoodsList 物品列表 [{?TYPE_GOODS|?TYPE_BIND_GOODS, GoodsTypeId, Num}|...]
%%  CommonList 通用列表
spilt_attachment(CmAttachment) ->
    Map = #{money_list => [], goods_list => [], common_list => []},
    spilt_attachment(CmAttachment, Map).

spilt_attachment([], Map) -> Map;
spilt_attachment([{Type, _GoodsTypeId, _GoodsNum}=T|L], Map) when
    Type == ?TYPE_GOLD;
    Type == ?TYPE_BGOLD;
    Type == ?TYPE_HONOUR;
%%    Type == ?TYPE_FGOLD;
    Type == ?TYPE_COIN ->
    MoneyList = maps:get(money_list, Map, []),
    NewMap = maps:put(money_list, [T|MoneyList], Map),
    spilt_attachment(L, NewMap);
spilt_attachment([{Type, _GoodsTypeId, _GoodsNum}=T|L], Map) when
    Type == ?TYPE_GOODS orelse Type == ?TYPE_BIND_GOODS ->
    GoodsList = maps:get(goods_list, Map, []),
    NewMap = maps:put(goods_list, [T|GoodsList], Map),
    spilt_attachment(L, NewMap);
spilt_attachment([{Type, _GoodsTypeId, _GoodsNum}=T|L], Map) when
    Type == ?TYPE_EXP;
    Type == ?TYPE_CHARM;
    Type == ?TYPE_FAME;
    Type == ?TYPE_GFUNDS;
    Type == ?TYPE_GFAME;
    Type == ?TYPE_CURRENCY;
    Type == ?TYPE_GUILD_GROWTH;
    Type == ?TYPE_HI_POINT;
    Type == ?TYPE_WORKROOM;
    Type == ?TYPE_VIP_EXP ->
    CommonList = maps:get(common_list, Map, []),
    NewMap = maps:put(common_list, [T|CommonList], Map),
    spilt_attachment(L, NewMap);
spilt_attachment([{?TYPE_ATTR_GOODS, _GoodsTypeId, _GoodsNum, _Attr}=T|L], Map) ->
    GoodsList = maps:get(goods_list, Map, []),
    NewMap = maps:put(goods_list, [T|GoodsList], Map),
    spilt_attachment(L, NewMap);
spilt_attachment([_|L], Map) ->
    spilt_attachment(L, Map).

%% 收取金钱
recv_money_list(Player, Mail, [], _MarketRType) -> {ok, Player, Mail};
recv_money_list(#player_status{id = RoleId, figure = #figure{vip = Vip}} = Player, Mail, MoneyList, MarketRType) ->
    F = fun() ->
        % 邮件附件处理
        #mail{id = MailId, cm_attachment = CmAttachment} = Mail,
        NewCmAttachment = CmAttachment -- MoneyList,
        db_mail_update_cm_attachment(MailId, NewCmAttachment),
        NewMail = Mail#mail{cm_attachment = NewCmAttachment},
        log(log_mail_get, [Mail, MoneyList]),
        {ok, NewMail}
        end,
    case F() of
        {ok, NewMail} ->
            % 金钱增加
            Map = #{?TYPE_COIN => 0, ?TYPE_BGOLD => 0, ?TYPE_GOLD => 0, ?TYPE_HONOUR => 0},
            F2 = fun({Type, _GoodsTypeId, GoodsNum}, TmpMap) -> maps:put(Type, GoodsNum, TmpMap) end,
            NewMap = lists:foldl(F2, Map, MoneyList),
            #{?TYPE_COIN := AddCoin, ?TYPE_BGOLD := AddBgold, ?TYPE_GOLD := AddGold, ?TYPE_HONOUR := AddHonour} = NewMap,
            #player_status{coin = Coin, bgold = Bgold, gold = Gold, honour = Honour} = Player,
            IsLimit = lib_vip_api:check_mail_vip_gold_limit(RoleId, Vip, {AddGold, AddBgold}),
            if IsLimit =/= true ->
                %% 名望券每日上限
                case lib_player:check_daily_can_get_honour(Player, Honour) of
                    {true, AddNum} ->
                        NewPlayer = Player#player_status{coin = Coin+AddCoin, honour = Honour+AddNum};
                    _ ->
                        AddNum    = 0,
                        NewPlayer = Player#player_status{coin = Coin+AddCoin}
                end;
                true ->
                    case lib_player:check_daily_can_get_honour(Player, AddHonour) of
                        {true, AddNum} ->
                            NewPlayer = Player#player_status{coin = Coin+AddCoin, bgold = Bgold+AddBgold, gold = Gold+AddGold, honour = Honour+AddNum};
                        _ ->
                            AddNum    = 0,
                            NewPlayer = Player#player_status{coin = Coin+AddCoin, bgold = Bgold+AddBgold, gold = Gold+AddGold}
                    end
            end,
            lib_player:update_player_money(Player, NewPlayer),
            case AddNum >0 of
                true->
                    lib_daily:plus_count(RoleId, ?MOD_GOODS, 2, AddNum),
                    %% 推送最新日名望券
                    TodayNum = lib_daily:get_count(Player#player_status.id, ?MOD_GOODS, 2),
                    {ok, BinData} = pt_150:write(15088, [TodayNum]),
                    lib_server_send:send_to_sid(Player#player_status.sid, BinData),
                    lib_activitycalen_api:honour_get_event(Player, AddNum);
                _->
                    skip
            end,
            %% 触发成就
            MarketRType == 0 andalso
                begin
                    lib_achievement_api:coin_get_event(Player#player_status.id, AddCoin),
                    lib_activitycalen_api:coin_get_event(Player, AddCoin)
                end,
            update_mail_on_dict(RoleId, NewMail),
            lib_player:send_attribute_change_notify(NewPlayer, ?NOTIFY_MONEY),
            log_money_produce(MoneyList, Player, NewPlayer, mail, lists:concat(["MailId:", NewMail#mail.id])),
            % lib_goods_api:send_tv_tip(RoleId, MoneyList),
            {ok, NewPlayer, NewMail};
        Error ->
            ?ERR("Error:~p RoleId:~p Mail:~w", [Error, RoleId, Mail]),
            {false, ?FAIL}
    end.

log_money_produce([], _Player, _NewPlayer, _ProduceType, _About) -> skip;
log_money_produce([{Type, _GoodsTypeId, _GoodsNum}|L], Player, NewPlayer, ProduceType, About) ->
    lib_log_api:log_produce(ProduceType, Type, Player, NewPlayer, About),
    log_money_produce(L, Player, NewPlayer, ProduceType, About).

%% 收取普通物品
recv_common_list(Player, Mail, [], _Type) -> {ok, Player, Mail};
recv_common_list(Player, Mail, CommonList, _Type) ->
    #player_status{id = RoleId} = Player,
    #mail{id = MailId, title = MailTitle, cm_attachment = CmAttachment} = Mail,
    NewCmAttachment = CmAttachment -- CommonList,
    db_mail_update_cm_attachment(MailId, NewCmAttachment),
    NewMail = Mail#mail{cm_attachment = NewCmAttachment},
    update_mail_on_dict(RoleId, NewMail),
    log(log_mail_get, [Mail, CommonList]),
    NewPlayer = do_recv_common_list(CommonList, MailTitle, Player),
    {ok, NewPlayer, NewMail}.

do_recv_common_list([], _MailTitle, Player) -> Player;
do_recv_common_list([T|L], MailTitle, Player) ->
    NewPlayer = goods_add:send_reward_currency(Player, T, mail, ""),
    do_recv_common_list(L, MailTitle, NewPlayer).

%% 发送公会邮件
send_guild_mail_on_server(Player, Title, Content) ->
    case check_send_guild_mail_on_server(Player, Title, Content) of
        {false, ErrorCode} -> {false, ErrorCode};
        true ->
            lib_daily:increment(Player#player_status.id, ?MOD_MAIL, ?DAILY_SEND_GUILD_MAIL),
            mod_guild:send_guild_mail_by_role_id(Player#player_status.id, Title, Content, [], [Player#player_status.id]),
            {ok, Player}
    end.

%% 检查
check_send_guild_mail_on_server(Player, Title, Content) ->
    CheckTitleLen = util:check_length(Title, 50),
    CheckTitleKeyWord = check_illegal_word(Title),
    CheckContentLen = util:check_length(Content, 100),
    CheckContentKeyWord = check_illegal_word(Content),
    #player_status{id = RoleId, guild = StatusGuild} = Player,
    IsLessthanLimit = lib_daily:lessthan_limit(RoleId, ?MOD_MAIL, ?DAILY_SEND_GUILD_MAIL),
    if
        CheckTitleLen == false -> {false, ?ERRCODE(err190_mail_title_len)};
        CheckTitleKeyWord -> {false, ?ERRCODE(err190_mail_title_sensitive)};
        CheckContentLen == false -> {false, ?ERRCODE(err190_mail_content_len)};
        CheckContentKeyWord -> {false, ?ERRCODE(err190_mail_content_sensitive)};
        IsLessthanLimit == false -> {false, ?ERRCODE(err190_send_guild_mail_count_not_enough)};
        StatusGuild#status_guild.id =< 0 -> {false, ?ERRCODE(err190_must_on_guild_to_send_guild_mail)};
        true -> true
    % case mod_guild:is_have_permission(RoleId, ?PERMISSION_SEND_GUILD_MAIL) of
    %     true -> true;
    %     false -> {false, ?ERRCODE(err190_not_permisssion_to_send_guild_mail)}
    % end
    end.

%% 邮件通知
send_mail_notice(RoleId) ->
    MailList = get_mail_list_on_dict(RoleId),
    NowTime = utime:unixtime(),
    F = fun(#mail{effect_et = EffectEt, state = State} = Mail) ->
        case NowTime =< EffectEt of
            true ->
                case State of
                    ?MAIL_STATE_NO_READ -> true;
                    ?MAIL_STATE_HAS_RECEIVE -> false;
                    _ -> is_attach(Mail) == 1
                end;
            false ->
                false
        end
        end,
    MailListFilter = lists:filter(F, MailList),
    case MailListFilter == [] of
        true -> ErrorCode = ?FAIL;
        false -> ErrorCode = ?SUCCESS
    end,
    {ok, BinData} = pt_190:write(19008, [ErrorCode]),
    lib_server_send:send_to_uid(RoleId, BinData),
    ok.

%% 邮件的错误码
send_error_code(Sid, ErrorCode) ->
    {ok, BinData} = pt_190:write(19000, [ErrorCode]),
    lib_server_send:send_to_sid(Sid, BinData).

%% ----------------------------------------------------
%% 接口/其他
%% ----------------------------------------------------

%% 发送系统邮件
send_sys_mail(PlayerInfoList, ModuleId, SubModuleId, Title, Content, CmAttachment) ->
    SendLogType = get_send_log_type(CmAttachment),
    Important = ?NOT_IMPORTANT_MAIL,
    send_mail(PlayerInfoList, ModuleId, SubModuleId, Title, Content, CmAttachment, ?MAIL_TYPE_SYS, SendLogType, Important).

send_sys_mail(PlayerInfoList, ModuleId, SubModuleId, Title, Content, CmAttachment, SendLogType) ->
    Important = ?NOT_IMPORTANT_MAIL,
    send_mail(PlayerInfoList, ModuleId, SubModuleId, Title, Content, CmAttachment, ?MAIL_TYPE_SYS, SendLogType, Important).

send_sys_mail(PlayerInfoList, ModuleId, SubModuleId, Title, Content, CmAttachment, SendLogType, Important) ->
    send_mail(PlayerInfoList, ModuleId, SubModuleId, Title, Content, CmAttachment, ?MAIL_TYPE_SYS, SendLogType, Important).


%% 发送拍卖会邮件
send_auction_mail(PlayerInfoList, ModuleId, SubModuleId, Title, Content, CmAttachment) ->
    SendLogType = get_send_log_type(CmAttachment),
    Important = ?NOT_IMPORTANT_MAIL,
    send_mail(PlayerInfoList, ModuleId, SubModuleId, Title, Content, CmAttachment, ?MAIL_TYPE_AUCTION, SendLogType, Important).

send_auction_mail(PlayerInfoList, ModuleId, SubModuleId, Title, Content, CmAttachment, SendLogType) ->
    Important = ?NOT_IMPORTANT_MAIL,
    send_mail(PlayerInfoList, ModuleId, SubModuleId, Title, Content, CmAttachment, ?MAIL_TYPE_AUCTION, SendLogType, Important).


%% 发送公会邮件
send_guild_mail(PlayerInfoList, ModuleId, SubModuleId, Title, Content, Type) ->
    send_guild_mail(PlayerInfoList, ModuleId, SubModuleId, Title, Content, Type, []).

%% 发送公会邮件
send_guild_mail(PlayerInfoList, ModuleId, SubModuleId, Title, Content, Type, CmAttachment) ->
    SendLogType = get_send_log_type(CmAttachment),
    Important = ?NOT_IMPORTANT_MAIL,
    send_mail(PlayerInfoList, ModuleId, SubModuleId, Title, Content, CmAttachment, Type, SendLogType, Important).

%% 是否记录日志
get_send_log_type(CmAttachment) ->
    case CmAttachment of
        [] -> ?SEND_LOG_TYPE_NO;
        _ -> ?SEND_LOG_TYPE_YES
    end.

%% 发送系统邮件
%% @param PlayerInfoList : 名字(string() | binary())列表、 角色Id列表
%%      后台使用:包含角色名字或Id的二进制字符串如：<<"[12,34]">>、<<"[\"80\",\"测试\"]">>、<<"[12, \"测试\"]">>
%% @param UknTitle、UknContent: string() | binary()
%% @param ModuleId、SubModuleId: integer()
%% @param CmAttachment: list()
%% @param Type 1:系统邮件;2:私人邮件;3:公会邮件
%% @param SendLogType 是否需要记录邮件的发送日志
%% @param SaveType 是否立即发送、保存邮件 0 否|1 是
send_mail(UknPlayerInfoList, ModuleId, SubModuleId, UknTitle, UknContent, UnkCmAttachment, Type, SendLogType, Important) ->
    send_mail(UknPlayerInfoList, ModuleId, SubModuleId, UknTitle, UknContent, UnkCmAttachment, Type, SendLogType, ?SCHEDULE_SAVE_MAIL, Important).
send_mail(UknPlayerInfoList, ModuleId, SubModuleId, UknTitle, UknContent, UnkCmAttachment, Type, SendLogType, SaveType, Important) ->
    Title   = text_enc:get_text(UknTitle),
    Content = text_enc:get_text(UknContent, list),
    PlayerInfoList = util:to_term(UknPlayerInfoList),
    RoleIdList = lists:map(fun get_role_id/1, PlayerInfoList),
    CmAttachment = util:make_sure_list(UnkCmAttachment),
    {CanOverlapList, CannotOverlapList} = lib_goods_util:split_can_overlap(CmAttachment),
    CmAttachment1 = lib_goods_util:merge_same_goods(CanOverlapList) ++ CannotOverlapList,
    [NewCmAttachment, CmAttachmentD, CmAttachmentF] = lists:foldl(fun(Award,[ArgsA, ArgsB, ArgsC])->
        case Award of
            {?TYPE_DESIGN,_DesID,_Num}->
                [ArgsA, [Award|ArgsB], ArgsC];
            {?TYPE_DESIGN,_DesID}->
                [ArgsA, [Award|ArgsB], ArgsC];
            {?TYPE_FASHION, _DesID,_Num}->
                [ArgsA, ArgsB, [Award|ArgsC]];
            {?TYPE_FASHION,_DesID} ->
                [ArgsA, ArgsB, [Award|ArgsC]];
            _->
                [[Award|ArgsA], ArgsB, ArgsC]
        end
                                                                  end,[[], [], []],CmAttachment1),
    %% 私人类型邮件限制字符数量短一些；其他系统或功能模块发送的邮件限制长一些
    LenContent = length(Content),
    NewContent = if
                     Type =:= 2 ->
                         case LenContent >= 500 of
                             true  -> lists:sublist(500, Content);
                             false -> Content
                         end;
                     true ->
                         case LenContent >= 1500 of
                             true  -> lists:sublist(1500, Content);
                             false -> Content
                         end
                 end,
    F = fun
            (0) -> skip;
            (RId) ->
                lib_designation_api:batch_active_dsgt_common(RId,CmAttachmentD),
                lib_fashion:batch_active_fashion(RId,CmAttachmentF)
        end,
    lists:foreach(fun(Rid) -> F(Rid) end, RoleIdList),
    BinContent = unicode:characters_to_binary(NewContent),
    if
        SaveType =:= ?SCHEDULE_SAVE_MAIL -> mod_mail:schedule_send_mail([RoleIdList, SendLogType, Type, Title, BinContent, NewCmAttachment, ModuleId, SubModuleId, Important]);
        true -> mod_mail:direct_send_mail([RoleIdList, SendLogType, Type, Title, BinContent, NewCmAttachment, ModuleId, SubModuleId, Important])
    end,
    ok.

%% 输入角色Id或角色名，获得角色Id
get_role_id(PlayerInfo) when is_integer(PlayerInfo) ->
    PlayerInfo;
get_role_id(PlayerName) when is_binary(PlayerName) ->
    case mod_chat_agent:match(match_name, [ PlayerName ]) of
        [Player] ->
            Player#ets_unite.id;
        _ ->
            case lib_player:get_role_id_by_name(PlayerName) of
                null -> 0;
                PlayerId -> PlayerId
            end
    end;
get_role_id(PlayerName) when is_list(PlayerName) ->
    Name = unicode:characters_to_binary(PlayerName),
    get_role_id(Name);
get_role_id(_) ->
    0.

get_effect_et(_, ?MAIL_STATE_HAS_RECEIVE, EffectSt) ->
    EffectTime = get_config(read_and_no_attachment_effect_time),
    EffectSt + EffectTime;
get_effect_et([], ?MAIL_STATE_READ, EffectSt) ->
    EffectTime = get_config(read_and_no_attachment_effect_time),
    EffectSt + EffectTime;
get_effect_et([], ?MAIL_STATE_NO_READ, EffectSt) ->
    EffectTime = get_config(not_read_and_no_attachment_effect_time),
    EffectSt + EffectTime;
get_effect_et(_, _, EffectSt) ->
    EffectTime = get_config(attachment_effect_time),
    EffectSt + EffectTime.

%% 是否记录邮件发送记录
log_mail_send(?SEND_LOG_TYPE_YES, Mail) -> log(log_mail_send, Mail);
log_mail_send(_, _Mail) -> skip.

%% 日志
log(log_mail_get, [Mail, CmAttachment]) ->
    #mail{id = Id, sid = Sid, rid = RId, title = Title, timestamp = Timestamp, module_id = ModuleId, sub_module_id = SubModuleId} = Mail,
    mod_log:add_log(log_mail_get, [Id, Sid, RId, Title, ModuleId, SubModuleId, util:term_to_string(CmAttachment), Timestamp, utime:unixtime()]);
log(log_mail_send, Mail) ->
    #mail{id = Id, sid = SId, rid = RId, title = Title, cm_attachment = CmAttachment, module_id = ModuleId, sub_module_id = SubModuleId} = Mail,
    mod_log:add_log(log_mail_send, [Id, SId, RId, Title, util:term_to_string(CmAttachment), ModuleId, SubModuleId, utime:unixtime()]).

%% 清理邮件的规则(由PHP处理)
clean_mail() ->
    NowTime = utime:unixtime(),
    NoEffectTime = get_config(read_and_no_attachment_effect_time), %% 没有附件且已读的邮件或者已领取附件的邮件
    Sql = io_lib:format(<<"SELECT id, rid, title, cm_attachment, state, effect_st FROM mail WHERE effect_st <= ~p and (state = 3 or (state = 1 and cm_attachment = '[]'))">>, [NowTime-NoEffectTime]),
    List = db:get_all(Sql),
    NoEffectTime1 = get_config(not_read_and_no_attachment_effect_time), %% 没有附件也没有读取的过期邮件
    Sql1 = io_lib:format(<<"SELECT id, rid, title, cm_attachment, state, effect_st FROM mail WHERE state = 2 and effect_st <= ~p and cm_attachment = '[]'">>, [NowTime-NoEffectTime1]),
    List1 = db:get_all(Sql1),
    EffectTime = get_config(attachment_effect_time), %% 过期的未领取附件的邮件
    Sql2 = io_lib:format(<<"SELECT id, rid, title, cm_attachment, state, effect_st FROM mail WHERE state <> 3 and effect_st <= ~p and cm_attachment <> '[]'">>, [NowTime-EffectTime]),
    List2 = db:get_all(Sql2),
    TotalList = List ++ List1 ++ List2,
    DelMailIds = [MailId || [MailId, _RoleId, _Title, _CmAttachment, _State, _EffectSt] <- TotalList],
    case DelMailIds =/= [] of
        true ->
            %% 一次只清理100封邮件
            clean_db(DelMailIds, [], 0),
            F2 = fun(T) ->
                case T of
                    [_MailId, RoleId, Title, CmAttachment, State, EffectSt] ->
                        lib_log_api:log_mail_clear(RoleId, Title, CmAttachment, State, EffectSt);
                    _ -> skip
                end end,
            lists:foreach(F2, TotalList);
        false -> skip
    end,
    spawn(fun() -> clean_mail_on_role() end),
    ok.

clean_db([], DelMailIds, _N) ->
    clean_db_do(DelMailIds),
    ok;
clean_db(MailIds, DelMailIds, N) when N >= 100 ->
    clean_db_do(DelMailIds),
    clean_db(MailIds, [], 0);
clean_db([Id|MailIds], DelMailIds, N) ->
    clean_db(MailIds, [Id|DelMailIds], N + 1).

clean_db_do([]) -> ignore;
clean_db_do(DelMailIds) ->
    DelMailIdsStr = util:link_list(DelMailIds),
    db:execute(io_lib:format(<<"DELETE FROM mail WHERE id in (~ts)">>, [DelMailIdsStr])),
    ok.
    
%% 删除玩家内存里面的邮件
clean_mail_on_role() ->
    PIds = lib_player:get_online_pids(),
    lists:map(fun(PId) ->
        lib_player:apply_cast(PId, ?APPLY_CAST_STATUS, ?MODULE, clean_mail_on_role_core, [])
              end, PIds),
    ok.

clean_mail_on_role_core(Player) ->
    RoleId = Player#player_status.id,
    MailList = get_effect_mail_list(RoleId),
    save_mail_dict(RoleId, MailList),
    ok.

%% ----------------------------------------------------
%% util
%% ----------------------------------------------------

%% 检查非法字符
check_illegal_word(Text) ->
    check_illegal_word(Text, ?ESC_ILLEGAL_SQL_CHARS).

%% 检查关键字，存在非法字符返回true，否则false
%% @spec check_illegal_word(Text, Words) -> false | true
%% @param Text : 需要检查的字符串（或字符串的二进制形式）
%% @param Words: 非法字符列表
check_illegal_word(_, []) -> false;
check_illegal_word(Text, [Word | Words]) ->
    case re:run(Text, Word, [{capture, none}]) of
        match -> true;
        nomatch -> check_illegal_word(Text, Words)
    end.

%% 替换非法字符
replace_illegal_word(Text) ->
    NewText = util:make_sure_binary(Text),
    List = util:link_list(?ESC_ILLEGAL_SQL_CHARS, "|"),
    re:replace(NewText, List, "*", [global, {return, binary}]).

%% ----------------------------------------------------
%% 数据库
%% ----------------------------------------------------

%% 更新邮件附件
db_mail_update_cm_attachment(MailId, CmAttachment) ->
    Sql = io_lib:format(?sql_mail_update_cm_attachment, [util:term_to_bitstring(CmAttachment), MailId]),
    db:execute(Sql).

%% 错误码
%% err190_no_mail:
%% err190_read_mail_content_fail:
%% err190_no_mail_attachment:
%% err190_no_mail_to_delete:
%% err190_have_attachment_not_to_delete:
%% err190_bag_full
%% err190_mail_timeout_to_delete
%% err190_mail_timeout_to_extract_attachment
