%% ---------------------------------------------------------------------------
%% @doc 交易市场管理进程
%% @author zhaoyu
%% @since  2019-12-25
%% ---------------------------------------------------------------------------
-module(market_mgr).


-behaviour(gen_server).

%% API
-export([
        start_link/0
        ,call/1                     %% call本进程
        ,cast/1                     %% cast本进程
        ,info/1                     %% 给进程发送消息
    ]).

%% gen_server callbacks
-export([
        init/1
        ,handle_call/3
        ,handle_cast/2
        ,handle_info/2
        ,terminate/2
        ,code_change/3
]).

-include("common.hrl").
-include("market.hrl").
-include("errcode.hrl").
-include("def_goods.hrl").
-include("language.hrl").
-include("goods.hrl").
-include("predefine.hrl").
-include("def_module.hrl").
-include("def_id_create.hrl").
-include_lib("stdlib/include/ms_transform.hrl").


%% ---------------------------------------------------------------------------
%% @doc Starts the server
-spec start_link() -> {ok, Pid} | ignore | {error, Error} when
    Pid :: pid(),
    Error :: term().
%% ---------------------------------------------------------------------------
start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).


%% ---------------------------------------------------------------------------
%% @doc call进程
-spec call(Msg) -> term() | {error, Reason} when
    Msg     :: term(),
    Reason  :: any().
%% ---------------------------------------------------------------------------
call(Msg) ->
    try gen_server:call(?MODULE, Msg) of
    Reply -> Reply
    catch Class:Reason ->
        Error = {Class, Reason, erlang:get_stacktrace()},
        ?ERR("~w:handle_cast error:~p~n", [?MODULE, Error]),
        {error, Reason}
    end.


%% ---------------------------------------------------------------------------
%% @doc cast进程
-spec cast(Msg) -> ok when
    Msg     :: term().
%% ---------------------------------------------------------------------------
cast(Msg) ->
    gen_server:cast(?MODULE, Msg).


%% ---------------------------------------------------------------------------
%% @doc 给进程发送消息
-spec info(Msg) -> ok when
    Msg     :: term().
%% ---------------------------------------------------------------------------
info(Msg) ->
    erlang:send(?MODULE, Msg).


%% ---------------------------------------------------------------------------
%% @doc Initializes the server
-spec init(Args) ->
    {ok, State}
    | {ok, State, Timeout}
    | ignore
    | {stop, Reason} when
    Args    :: term() ,
    State   :: #market_mgr{},
    Timeout :: non_neg_integer() | infinity,
    Reason  :: term().
%% ---------------------------------------------------------------------------
init(_) ->
    process_flag(trap_exit, true),
    %% 保存交易市场物品信息
    EtsArgsG = [{keypos, #market_goods.id}, named_table, public, set],
    ets:new(?MARKET_GOODS_ETS, EtsArgsG),
    %% 保存交易市场玩家出价信息
    EtsArgsB = [{keypos, #market_bid.key}, named_table, public, set],
    ets:new(?MARKET_BID_ETS, EtsArgsB),
    %% 保存角色入库提醒订阅信息
    EtsArgsR = [{keypos, #market_reminder.key}, named_table, public, set],
    ets:new(?MARKET_REMINDER_ETS, EtsArgsR),
    %% 保存系统补给商品数据信息
    EtsArgsS = [{keypos, #market_supply.goods_type_id}, named_table, public, set],
    ets:new(?MARKET_SUPPLY_ETS, EtsArgsS),
    erlang:send_after(0, self(), {market_mgr_init}),
    {ok, #market_mgr{}}.


%% ---------------------------------------------------------------------------
%% @doc Handling call messages
-spec handle_call(Request, From, State) ->
    {reply, Reply, State}
    | {reply, Reply, State, Timeout}
    | {noreply, State}
    | {noreply, State, Timeout}
    | {stop, Reason, Reply, State}
    | {stop, Reason, State} when
    Request :: term(),
    From :: pid(),
    State :: #market_mgr{},
    Reply :: term(),
    State :: #market_mgr{},
    Timeout :: non_neg_integer() | infinity.
%% ---------------------------------------------------------------------------
handle_call(Request, From, State) ->
    try handle_call2(Request, From, State) of
        Result ->
            Result
    catch Class:Reason ->
        Error = {Class, Reason, erlang:get_stacktrace()},
        ?ERR("~w:handle_cast error:~p~n", [?MODULE, Error]),
        {reply, error, State}
    end.


%% ---------------------------------------------------------------------------
%% @doc Handling cast messages
-spec handle_cast(Msg, State) ->
    {noreply, State}
    | {noreply, State, Timeout}
    | {stop, Reason, State} when
    Msg :: term(),
    State :: #market_mgr{},
    Timeout :: non_neg_integer() | infinity,
    Reason :: term().
%% ---------------------------------------------------------------------------
handle_cast(Msg, State) ->
    try handle_cast2(Msg, State) of
        Result ->
            Result
    catch Class:Reason ->
        Error = {Class, Reason, erlang:get_stacktrace()},
        ?ERR("~w:handle_cast error:~p~n", [?MODULE, Error]),
        {noreply, State}
    end.


%% ---------------------------------------------------------------------------
%% @doc Handling all non call/cast messages
-spec handle_info(Info, State) ->
    {noreply, State}
    | {noreply, State, Timeout}
    | {stop, Reason, State} when
    Info :: term(),
    State :: #market_mgr{},
    Timeout :: non_neg_integer() | infinity,
    Reason :: term().
%% ---------------------------------------------------------------------------
handle_info(Info, State) ->
    try handle_info2(Info, State) of
        Result ->
            Result
    catch Class:Reason ->
        Error = {Class, Reason, erlang:get_stacktrace()},
        ?ERR("~w:handle_cast error:~p~n", [?MODULE, Error]),
        {noreply, State}
    end.


%% ---------------------------------------------------------------------------
%% @doc Handling call messages
-spec handle_call2(Request, From, State) ->
    {reply, Reply, State}
    | {reply, Reply, State, Timeout}
    | {noreply, State}
    | {noreply, State, Timeout}
    | {stop, Reason, Reply, State}
    | {stop, Reason, State} when
    Request :: term(),
    From :: pid(),
    State :: #market_mgr{},
    Reply :: term(),
    State :: #market_mgr{},
    Timeout :: non_neg_integer() | infinity.
%% ---------------------------------------------------------------------------
handle_call2(_Request, _From, State) ->
    {reply, ok, State}.


%% ---------------------------------------------------------------------------
%% @doc Handling cast messages
-spec handle_cast2(Msg, State) ->
    {noreply, State}
    | {noreply, State, Timeout}
    | {stop, Reason, State} when
    Msg :: term(),
    State :: #market_mgr{},
    Timeout :: non_neg_integer() | infinity,
    Reason :: term().
%% ---------------------------------------------------------------------------
%% 物品上架
handle_cast2({mgr_put_on_goods, MarketGoods}, State) ->
    NewState = mgr_put_on_goods_logic(State, MarketGoods),
    {noreply, NewState};
%% 批量物品上架
handle_cast2({mgr_put_on_goods_list, MarketGoodsList}, State) ->
    Fun = fun(MarketGoods, MgrState) ->
        MgrState2 = mgr_put_on_goods_logic(MgrState, MarketGoods),
        MgrState2
    end,
    NewState = lists:foldl(Fun, State, MarketGoodsList),
    {noreply, NewState};
%% 竞拍物品
handle_cast2({mgr_bid_for_goods, RoleId, RoleName, RolePid, MarketId, Price}, State) ->
    NewState = handle_bid_for_goods(State, RoleId, RoleName, RolePid, MarketId, Price),
    {noreply, NewState};
%% 物品下架
handle_cast2({mrg_put_goods_down, RoleId, RolePid, MarketId}, State) ->
    NewState = handle_put_goods_down(State, RoleId, RolePid, MarketId),
    {noreply, NewState};
%% 发送交易市场成交记录
handle_cast2({mgr_send_deal_log, LogType, RoleSid}, State) ->
    handle_send_deal_log(State, LogType, RoleSid),
    {noreply, State};
%% 装备进化升星购买装备
handle_cast2({mgr_equipment_rise_star_buy, MarketIds, RoleId, Name}, State) ->
    NewState = handle_buy_for_equipment_rise_star(State, MarketIds, #market_bid{role_id = RoleId, role_name = Name}),
    {noreply, NewState};
%% 定时清理过期商品
handle_cast2(mgr_clean_offtime_goods, State) ->
    spawn(fun() -> clean_offtime_goods() end),
    {noreply, State};
handle_cast2(_Msg, State) ->
    {noreply, State}.


%% ---------------------------------------------------------------------------
%% @doc Handling all non call/cast messages
-spec handle_info2(Info, State) ->
    {noreply, State}
    | {noreply, State, Timeout}
    | {stop, Reason, State} when
    Info :: term(),
    State :: #market_mgr{},
    Timeout :: non_neg_integer() | infinity,
    Reason :: term().
%% ---------------------------------------------------------------------------
handle_info2({market_mgr_init}, State) ->
    NewState = market_db:load_market_goods(State),
    market_db:load_market_bid_infos(),
    #market_mgr{off_time_list = OffTimeList, buyback_time_list = BackList} = NewState,
    Now = utime:unixtime(),
    OffTimeList2 = init_off_timer(OffTimeList, [], Now),
    BackList2 = init_buyback_timer(BackList, [], Now),
    NewState2 = NewState#market_mgr{
        off_time_list = OffTimeList2, buyback_time_list = BackList2
    },
    NewState3 = market_db:load_deal_log(NewState2),
    market_db:del_deal_log(),
    market_db:load_market_supply(),
    {noreply, NewState3};
%% 拍卖倒计时结束
handle_info2({goods_off_time, Type, SubType}, State) ->
    NewState =
    try goods_off_time_check(State, Type, SubType) of
        State2 = #market_mgr{} ->
            State2
    catch Class:Reason -> %% 超时之类的
        Error = {Class, Reason, erlang:get_stacktrace()},
        ?ERR("~w:goods_off_time error:~p~n", [?MODULE, Error]),
        %% 定时器报错，30秒后再试一次。
        erlang:send_after(30 * 1000, self(), {goods_off_time, Type, SubType}),
        State
    end,
    {noreply, NewState};
%% 系统回购倒计时结束
handle_info2({sys_buyback_time, GoodsTypeId}, State) ->
    NewState =
    try sys_buyback_time_check(State, GoodsTypeId) of
        State2 = #market_mgr{} ->
            State2
    catch Class:Reason -> %% 超时之类的
        Error = {Class, Reason, erlang:get_stacktrace()},
        ?ERR("~w:sys_buyback_time error:~p~n", [?MODULE, Error]),
        %% 定时器报错，30秒后再试一次。
        erlang:send_after(30 * 1000, self(), {sys_buyback_time, GoodsTypeId}),
        State
    end,
    {noreply, NewState};
handle_info2(_Info, State) ->
    {noreply, State}.


%% ---------------------------------------------------------------------------
%% @doc called by a gen_server when it is about to terminate
-spec terminate(Reason, State) -> ok when
    Reason :: term(),
    State  :: #market_mgr{}.
%% ---------------------------------------------------------------------------
terminate(_Reason, State) ->
    save_market_log(State),
    ok.


%% ---------------------------------------------------------------------------
%% @doc Convert process state when code is changed
-spec code_change(OldVsn, State, Extra) -> {ok, NewState} when
    OldVsn :: term(),
    State :: #market_mgr{},
    Extra :: term(),
    NewState :: #market_mgr{}.
%% ---------------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.


%% ---------------------------------------------------------------------------
%% @doc 启动拍卖倒计时计时器
-spec init_off_timer(OffTimeList, OffTimeList, Now) -> NewOffTimeList when
    OffTimeList     :: [#m_off_time_info{}],
    Now             :: integer(),
    NewOffTimeList  :: [#m_off_time_info{}].
%% ---------------------------------------------------------------------------
init_off_timer([], OffTimeList, _Now) ->
    OffTimeList;
init_off_timer([OffTimeInfo | T], OffTimeList, Now) ->
    #m_off_time_info{key = {Type, SubType}, min_off_time = OffTime} = OffTimeInfo,
    LessTime = min(86400, max(0, OffTime - Now)) * 1000,
    Ref = erlang:send_after(LessTime, self(), {goods_off_time, Type, SubType}),
    OffTimeInfo2 = OffTimeInfo#m_off_time_info{timer_ref = Ref},
    OffTimeList2 = [OffTimeInfo2 | OffTimeList],
    init_off_timer(T, OffTimeList2, Now).


%% ---------------------------------------------------------------------------
%% @doc 启动系统回购倒计时计时器
-spec init_buyback_timer(TimeList, TimeList, Now) -> NewTimeList when
    TimeList    :: [#m_buyback_time_info{}],
    Now         :: integer(),
    NewTimeList :: [#m_buyback_time_info{}].
%% ---------------------------------------------------------------------------
init_buyback_timer([], TimeList, _Now) ->
    TimeList;
init_buyback_timer([TimeInfo | T], TimeList, Now) ->
    #m_buyback_time_info{goods_type_id = GoodsTypeId, min_time = Time} = TimeInfo,
    LessTime = max(0, Time - Now) * 1000,
    Ref = erlang:send_after(LessTime, self(), {sys_buyback_time, GoodsTypeId}),
    TimeInfo2 = TimeInfo#m_buyback_time_info{timer_ref = Ref},
    TimeList2 = [TimeInfo2 | TimeList],
    init_buyback_timer(T, TimeList2, Now).


%% ---------------------------------------------------------------------------
%% @doc 更新拍卖倒计时计时器和回购定时器
-spec update_timer(MarketGoods, State) -> NewState when
    MarketGoods :: #market_goods{},
    State       :: #market_mgr{},
    NewState    :: #market_mgr{}.
%% ---------------------------------------------------------------------------
update_timer(MarketGoods, State) ->
    #market_mgr{
        off_time_list = OffTimeList, buyback_time_list = BuyBackTimeList
    } = State,
    #market_goods{
        off_time = OffTime, type = Type, sub_type = SubType
        ,goods_type_id = GoodsTypeId, sys_buyback_time = BuyBackTime
    } = MarketGoods,
    Now = utime:unixtime(),
    %% 更新拍卖倒计时计时器
    OffTimeInfo = #m_off_time_info{key = {Type, SubType}, min_off_time = OffTime},
    OffTimeList2 = update_off_time_timer(OffTimeInfo, OffTimeList, Now),
    %% 回购定时器
    BuyBackTimeList2 = update_buy_back_timer(GoodsTypeId, BuyBackTime, BuyBackTimeList, Now),
    State#market_mgr{off_time_list = OffTimeList2, buyback_time_list = BuyBackTimeList2}.

%% ---------------------------------------------------------------------------
%% @doc 更新拍卖倒计时定时器
-spec update_off_time_timer(OffTimeInfo, OffTimeList, Now) -> NewOffTimeList when
    OffTimeInfo     :: #m_off_time_info{},
    OffTimeList     :: [#m_off_time_info{}],
    Now             :: integer(),
    NewOffTimeList  :: [#m_off_time_info{}].
%% ---------------------------------------------------------------------------
update_off_time_timer(OffTimeInfo, OffTimeList, Now) ->
    #m_off_time_info{key = Key = {Type, SubType}, min_off_time = OffTime} = OffTimeInfo,
    case lists:keyfind(Key, #m_off_time_info.key, OffTimeList) of
        Info = #m_off_time_info{
            min_off_time = MinTime, timer_ref = OldRef
        } when OffTime < MinTime ->
            util:cancel_timer(OldRef),
            LessTime = min(86400, max(0, OffTime - Now)) * 1000,
            Ref = erlang:send_after(LessTime, self(), {goods_off_time, Type, SubType}),
            Info2 = Info#m_off_time_info{min_off_time = OffTime, timer_ref = Ref},
            lists:keystore(Key, #m_off_time_info.key, OffTimeList, Info2);
        false ->
            LessTime = min(86400, max(0, OffTime - Now)) * 1000,
            Ref = erlang:send_after(LessTime, self(), {goods_off_time, Type, SubType}),
            OffInfo = #m_off_time_info{key = Key, min_off_time = OffTime, timer_ref = Ref},
            [OffInfo | OffTimeList];
        _ ->
            OffTimeList
    end.

%% @doc 更新回购定时器
update_buy_back_timer(GoodsTypeId, BuyBackTime, BuyBackTimeList, Now) ->
    case lists:keyfind(GoodsTypeId, #m_buyback_time_info.goods_type_id, BuyBackTimeList) of
        BackInfo = #m_buyback_time_info{
            min_time = BackMinTime, timer_ref = OldBackRef
        } when BuyBackTime < BackMinTime andalso BuyBackTime > 0 ->
            util:cancel_timer(OldBackRef),
            BackLessTime = max(0, BuyBackTime - Now) * 1000,
            BackRef = erlang:send_after(
                BackLessTime, self(), {sys_buyback_time, GoodsTypeId}
            ),
            BackInfo2 = BackInfo#m_buyback_time_info{
                min_time = BuyBackTime, timer_ref = BackRef
            },
            lists:keystore(GoodsTypeId, #m_buyback_time_info.goods_type_id, BuyBackTimeList, BackInfo2);
        false when BuyBackTime > 0 ->
            BackLessTime = max(0, BuyBackTime - Now) * 1000,
            BackRef = erlang:send_after(
                BackLessTime, self(), {sys_buyback_time, GoodsTypeId}
            ),
            BackInfo = #m_buyback_time_info{
                goods_type_id = GoodsTypeId, min_time = BuyBackTime, timer_ref = BackRef
            },
            [BackInfo | BuyBackTimeList];
        _ ->
            BuyBackTimeList
    end.
%% ---------------------------------------------------------------------------
%% @doc 发送入库提醒
-spec send_reminder_msg(MarketGoods) -> ok when
    MarketGoods :: #market_goods{}.
%% ---------------------------------------------------------------------------
send_reminder_msg(MarketGoods) ->
    #market_goods{goods_type_id = GoodsTypeId, sell_type = SellType, guild_id = GuildId} = MarketGoods,
    Now = utime:unixtime(),
    MatchSpec = ets:fun2ms(
        fun(
            #market_goods{goods_type_id = Id, off_time = OffTime}
        ) when Id =:= GoodsTypeId andalso OffTime > Now -> Id end
    ),
    case ets:select(?MARKET_GOODS_ETS, MatchSpec, 2) of
        '$end_of_table' ->
            ok;
        {MatchList, _Continuation} when length(MatchList) =< 1 ->
            MatchSpecReminder = ets:fun2ms(
                fun(
                    #market_reminder{goods_type_id = Id, role_id = RoleId}
                ) when Id =:= GoodsTypeId -> RoleId end
            ),
            RoleIdList = ets:select(?MARKET_REMINDER_ETS, MatchSpecReminder),
            [
                market:reminder_others(SendId, GoodsTypeId, SellType, GuildId)
                || SendId <- RoleIdList
            ];
        _ ->
            ok
    end.

%% ---------------------------------------------------------------------------
%% @doc 竞拍物品
-spec handle_bid_for_goods(State, RoleId, RoleName, RolePid, MarketId, Price) ->
    NewState when
    State       :: #market_mgr{},
    RoleId      :: integer(),
    RoleName    :: string(),
    RolePid     :: pid(),
    MarketId    :: integer(),
    Price       :: integer(),
    NewState    :: #market_mgr{}.
%% ---------------------------------------------------------------------------
handle_bid_for_goods(State, RoleId, RoleName, RolePid, MarketId, Price) ->
    MarketGoods = case ets:lookup(?MARKET_GOODS_ETS, MarketId) of
        [MG = #market_goods{} | _] -> MG;
        _ -> false
    end,
    Now = utime:unixtime(),
    MarketBid = case ets:lookup(?MARKET_BID_ETS, {RoleId, MarketId}) of
        [MB = #market_bid{} | _] ->
            MB;
        _ ->
            #market_bid{
                key = {RoleId, MarketId}, id = MarketId, role_id = RoleId
                ,role_name = RoleName, time = Now
            }
    end,
    GoodsCfg = case MarketGoods of
        #market_goods{goods_type_id = GTypeId} -> data_goods_type:get(GTypeId);
        _ -> false
    end,
    Result = if
        not (is_record(MarketGoods, market_goods)) ->
            {false, ?ERRCODE(err155_market_goods_not_exist)};
        not (is_record(GoodsCfg, ets_goods_type)) ->
            {false, ?ERRCODE(err155_goods_not_exist)};
        MarketGoods#market_goods.off_time =< Now ->
            {false, ?ERRCODE(err155_market_goods_off)};
        MarketBid#market_bid.price >= Price ->
            {false, ?ERRCODE(err155_market_goods_price_error)};
        true ->
            #market_bid{price = RolePrice} = MarketBid,
            #market_goods{buyout_price = BuyoutPrice} = MarketGoods,
            CostGold = case BuyoutPrice > 0 of
                true -> min(Price - RolePrice, BuyoutPrice - RolePrice);
                _ -> Price - RolePrice
            end,
            Args = [MarketId, CostGold, MarketBid, MarketGoods],
            MFA = {market, bid_for_goods_callback, Args},
            %% 扣除消耗，只等待2s，超时算失败
            try gen_server:call(RolePid, {fast_call, MFA}, 2000) of
                {false, Rea} -> %% 消耗不足
                    {false, Rea};
                {ok} ->
                    handle_bid_for_goods_do(
                        State, RoleId, CostGold, MarketBid, MarketGoods
                    );
                _Err ->
                    ?ERR("~w:handle_bid_for_goods error:~p~n", [?MODULE, _Err]),
                    {false, ?ERRCODE(fail)}
            catch Class:Reason -> %% 超时之类的
                Error = {Class, Reason, erlang:get_stacktrace()},
                ?ERR("~w:handle_bid_for_goods error:~p~n", [?MODULE, Error]),
                {false, ?ERRCODE(fail)}
            end
    end,
    case Result of
        {false, ErrReason} ->
            {ok, Bin} = pt_155:write(15507, [ErrReason, MarketId, Price]),
            lib_server_send:send_to_uid(RoleId, Bin),
            State;
        {true, NewState} ->
            {ok, Bin} = pt_155:write(15507, [?SUCCESS, MarketId, Price]),
            lib_server_send:send_to_uid(RoleId, Bin),
            NewState
    end.

%% 竞拍物品逻辑
handle_bid_for_goods_do(State, RoleId, Price, MarketBid, MarketGoods) ->
    #market_goods{
        price = MaxPrice, off_time = OffTime, bidder_id = OldBidderId
        ,id = MarketId, buyout_price = BuyoutPrice
    } = MarketGoods,
    #market_bid{price = RolePrice} = MarketBid,
    RolePrice2 = RolePrice + Price,
    IsHighestPrice = RolePrice2 > MaxPrice orelse OldBidderId =:= 0,
    %% 出价次数+1
    MarketGoods1 = MarketGoods#market_goods{bid_count = MarketGoods#market_goods.bid_count + 1},
    MarketGoods2 = case IsHighestPrice of
        true -> %% 角色竞价成为最高竞价
            MarketGoods1#market_goods{price = RolePrice2, bidder_id = RoleId};
        _ ->
            MarketGoods1
    end,
    MarketBid2 = MarketBid#market_bid{price = RolePrice2},
    %% 先更新?MARKET_BID_ETS再结算
    ets:insert(?MARKET_BID_ETS, MarketBid2),
    %% 是否成交
    IsSettle = RolePrice2 >= BuyoutPrice andalso BuyoutPrice > 0,
    %% 给上一个最高竞价者退钱
    case OldBidderId =:= 0 orelse OldBidderId =:= RoleId of
        true  ->
            ok;
        false ->
            %% 将上一个竞拍者出价设置为0
            case ets:lookup(?MARKET_BID_ETS, {OldBidderId, MarketId}) of
                [MB = #market_bid{} | _] ->
                    ResetMB = MB#market_bid{price = 0},
                    ets:insert(?MARKET_BID_ETS, ResetMB),
                    market_db:save_market_bid(ResetMB),
                    send_loser_money([MB], MarketGoods, IsSettle);
                _ ->
                    ok
            end
    end,
    market:log_market_race(MarketGoods2, MarketGoods),
    case IsSettle of
        true -> %% 达到一口成交价。结算竞拍。
            NewState = market_goods_settlement(State, MarketGoods2),
            {true, NewState};
        false when IsHighestPrice -> %% 角色竞价成为最高竞价
            market_db:save_market_bid(MarketBid2),
            Now = utime:unixtime(),
            %% 更新物品off_time
            RestTimeCfg = case data_market:get_config(buy_time) of
                BTime when is_integer(BTime) -> BTime;
                _ -> 60
            end,
            OffTime2 = case OffTime - Now =< RestTimeCfg of
                true -> OffTime + RestTimeCfg;
                false -> OffTime
            end,
            MarketGoods3 = MarketGoods2#market_goods{off_time = OffTime2},
            case MarketGoods3 of
                MarketGoods ->
                    ok;
                _ ->
                    ets:insert(?MARKET_GOODS_ETS, MarketGoods3),
                    market_db:save_market_goods(MarketGoods3)
            end,
            %% 通知竞拍者，商品变化
            notice_goods_change(RoleId, MarketGoods3, 0),
            {true, State};
        _ ->
            market_db:save_market_bid(MarketBid2),
            {true, State}
    end.


%% ---------------------------------------------------------------------------
%% @doc  装备进化升星购买装备
%% ---------------------------------------------------------------------------
handle_buy_for_equipment_rise_star(State, [], _Buyer) -> State;
handle_buy_for_equipment_rise_star(State, [MarketId|T], Buyer) ->
    case ets:lookup(?MARKET_GOODS_ETS, MarketId) of
        [MarketGoods = #market_goods{id = MarketId, buyout_price = Price} | _] ->
            MarketGoodsInfo = MarketGoods#market_goods{price = Price},
            State1 = do_something_necessary(State, MarketGoodsInfo, [], Buyer, ?MARKET_BUY_TYPE_EQUIP_AUTO),
            %% 竞拍物品结算后的处理
            State2 = after_market_goods_settlement(State1, MarketGoodsInfo, Buyer),
            handle_buy_for_equipment_rise_star(State2, T, Buyer);
        _ ->
            handle_buy_for_equipment_rise_star(State, T, Buyer)
    end.


%% ---------------------------------------------------------------------------
%% @doc 竞拍物品结算
-spec market_goods_settlement(State, MarketGoods) -> NewState when
    State       :: #market_mgr{},
    MarketGoods :: #market_goods{},
    NewState    :: #market_mgr{}.
%% ---------------------------------------------------------------------------
market_goods_settlement(State, MarketGoods) ->
    #market_goods{id = MarketId, role_id = Mod} = MarketGoods,
    IsModule = is_guild_auto(Mod),
    {NewState, GetRole} = case get_buyer_list_sorted(MarketId) of
        [] when IsModule->
            %% 物品竞拍，系统删除
            del_market_goods_data(0, MarketGoods, ?MARKET_OPERATION_SYS_DEL),
            {State, false};
        [] ->
            %% 没人竞拍，进行下架处理(无需处理)
            %% 如果是系统上架的商品，则判断是否需要继续回购
            {State, false};
        [GetGoodsRole | RestList] ->
            %% 出售者处理/通知卖家和买家
            State2 = do_something_necessary(State, MarketGoods, RestList, GetGoodsRole, ?MARKET_BUY_TYPE_NORMAL),
            %% 邮件给竞拍胜出者物品
            send_winner_goods(GetGoodsRole, MarketGoods),
            %% 通知竞拍者，商品变化
            notice_goods_change(GetGoodsRole#market_bid.role_id, MarketGoods, 1),
            {State2, GetGoodsRole}
    end,
    %% 竞拍物品结算后的处理
    NewState2 = after_market_goods_settlement(NewState, MarketGoods, GetRole),
    NewState2.


get_buyer_list_sorted(MarketId) ->
    MatchSpec = ets:fun2ms(
        fun(#market_bid{id = Id} = BidInfo) when Id =:= MarketId -> BidInfo end
    ),
    BuyerList = ets:select(?MARKET_BID_ETS, MatchSpec),
    lists:reverse(lists:keysort(#market_bid.price, BuyerList)).

del_market_goods_data(GetRoleId, MarketGoods, Operation)->
    #market_goods{id = MarketId} = MarketGoods,
    %% 清除数据
    ets:delete(?MARKET_GOODS_ETS, MarketId),
    market_db:del_market_goods(MarketGoods),
    market_db:del_market_bid_infos(MarketId),
    market:log_market_operation(Operation, MarketGoods, GetRoleId),
    %% 删除角色关注
    mgr_del_like_info(MarketId),
    ok.

%% ---------------------------------------------------------------------------
%% @doc 竞拍成功后处理：包括普通竞拍和装备升星自动购买
do_something_necessary(State, MarketGoods, _BuyerList, GetGoodsRole, BuyType) ->
    #market_goods{id = MarketId} = MarketGoods,
    MatchSpecDel = ets:fun2ms(
        fun(#market_bid{id = Id}) when Id =:= MarketId -> true end
    ),
    %% 清理竞拍记录
    ets:select_delete(?MARKET_BID_ETS, MatchSpecDel),
    #market_bid{role_id = GetRoleId} = GetGoodsRole,
    %% 清理商品信息
    del_market_goods_data(GetRoleId,MarketGoods, ?MARKET_OPERATION_SETTLEMENT),
    %% 给出售者货币
    send_seller_money(MarketGoods),
    %% 更新双方买卖记录
    State2 = market_settlement_log(State, MarketGoods, GetGoodsRole, BuyType),
    State2.

%% 给竞拍胜出者物品, 游戏系统出价拍得
send_winner_goods(#market_bid{role_id = RoleId}, _) when RoleId =< ?MARKET_SYS_MAX_ROLE_ID ->
    ok;
%% 给竞拍胜出者物品
send_winner_goods(GetGoodsRole, MarketGoods) ->
    #market_bid{role_id = RoleId} = GetGoodsRole,
    #market_goods{
        goods_type_id = GoodsTypeId, num = Num
        ,extra_attr = ExtraAttr, buyout_price = _BuyoutPrice
    } = MarketGoods,
    Award = [{?TYPE_ATTR_GOODS, GoodsTypeId, Num, [{bind, ?BIND} | ExtraAttr]}],
    Title = ?LAN_MSG(1550001),
    Content = ?LAN_MSG(1550002),
    lib_mail_api:send_auction_mail([RoleId], ?MOD_MARKET, Title, Content, Award),
    distribute_sys_cashback(1,MarketGoods),
    ok.

%% 给出售者货币
send_seller_money(MarketGoods) ->
    #market_goods{
        role_id = RoleId, price = Price, guild_id = _GuildId
    } = MarketGoods,
    case RoleId =< ?MARKET_SYS_MAX_ROLE_ID of
        true  -> %% 系统上架的
            ok;
        false ->
            TaxRates = market:get_market_tex(Price),
            GetGold  = Price - TaxRates,
            Award = [{?TYPE_COIN, 0, GetGold}],
            Title = ?LAN_MSG(1550001),
            Content = uio:format(?LAN_MSG(1550003), [GetGold]),
            lib_mail_api:send_auction_mail([RoleId], ?MOD_MARKET, Title, Content, Award)
    end,
    ok.

%% 给没中竞拍的玩家退钱
send_loser_money([], _MarketGoods, _IsSettle) ->
    ok;
send_loser_money([Role | T], MarketGoods, IsSettle) ->
    #market_bid{role_id = RoleId, price = Price} = Role,
    Award = [{?TYPE_COIN, 0, Price}],
    case IsSettle of
        true  ->
            %% 成交的时候给玩家发一封邮件
            Produce = #produce{
                type = auction_fail,
                reward = Award,
                module = ?MOD_MARKET,
                module_sub = 1,
                off_title   = ?LAN_MSG(1550001),
                off_content = uio:format(?LAN_MSG(1550009), [MarketGoods#market_goods.goods_type_id, Price])
            },
            case lib_player:is_online_global(RoleId) of
                true  ->
                    lib_mail:send_auction_mail([RoleId], ?MOD_MARKET, 1, Produce#produce.off_title, Produce#produce.off_content, []),
                    lib_goods_api:send_reward_by_id(Produce, RoleId);
                false ->
                    lib_goods_api:send_reward_by_id(Produce, RoleId)
            end;
        false ->
            %% 直接给玩家退钱
            Produce = #produce{
                type = auction_fail,
                reward = Award,
                module = ?MOD_MARKET,
                module_sub = 1,
                off_title   = ?LAN_MSG(1550001),
                off_content = uio:format(?LAN_MSG(1550004), [Price])
            },
            lib_goods_api:send_reward_by_id(Produce, RoleId),
            {ok, Bin} = pt_155:write(15517, [MarketGoods#market_goods.goods_type_id, market:extra_data_pack(MarketGoods#market_goods.extra_attr)]),
            lib_server_send:send_to_uid(RoleId, Bin)
    end,
    send_loser_money(T, MarketGoods, IsSettle).


%% ---------------------------------------------------------------------------
%% @doc 成交记录
-spec market_settlement_log(State, MarketGoods, GetGoodsRole, BuyType) -> NewState when
    State       :: #market_mgr{},
    MarketGoods :: #market_goods{},
    GetGoodsRole:: #market_bid{},
    BuyType     :: integer(),
    NewState    :: #market_mgr{}.
%% ---------------------------------------------------------------------------
market_settlement_log(State, MarketGoods, GetGoodsRole, BuyType) ->
    #market_goods{
        id = Id, role_id = RoleId, goods_id = GoodsId, goods_type_id = GoodsTypeId
        ,num = Num, lev = Lev, color = Color, price = Price, sell_type = SellType
        ,guild_id = GuildId, time = Time, extra_attr = ExtraAttr
        ,type = Type, sub_type = SubType, buyout_price = BuyoutPrice
    } = MarketGoods,
    #market_bid{role_id = GetRoleId, role_name = GetRoleName} = GetGoodsRole,
    case GetRoleId > ?MARKET_SYS_MAX_ROLE_ID of
        true  ->
            %% 买家是人
            lib_player:apply_cast(GetRoleId, ?APPLY_CAST_STATUS, ?HAND_OFFLINE, ta_agent_fire, auction_state, [{MarketGoods, 1, BuyType}]);
        false ->
            %% 买家不是人，系统回购
            lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?HAND_OFFLINE, ta_agent_fire, auction_state, [{MarketGoods, 4, BuyType}])
    end,
    case data_market:get_goods_log_type(Type, SubType, Color) of
        LogTypeId when is_integer(LogTypeId) ->
            DealType = case Price >= BuyoutPrice of
                true -> 1;
                _ -> 0
            end,
            DealLog = #market_deal_log{
                id = Id, role_id = RoleId, get_role_id = GetRoleId
                ,get_role_name = GetRoleName, goods_id = GoodsId
                ,goods_type_id = GoodsTypeId, num = Num, lev = Lev, color = Color
                ,price = Price, sell_type = SellType, guild_id = GuildId
                ,time = Time, deal_time = utime:unixtime(), log_type = LogTypeId
                ,extra_attr = ExtraAttr, type = Type, sub_type = SubType
                ,deal_type = DealType
            },
            LogKey = {SellType, LogTypeId},
            #market_mgr{all_deal_log_list = AllDealLogList} = State,
            MaxLog = data_market:get_log_type_cfg(SellType, LogTypeId),
            KeyPos = #deal_log_type.log_key,
            AllDealLogList2 = case lists:keytake(LogKey, KeyPos, AllDealLogList) of
                {value, TypeLog = #deal_log_type{log_list = LogList}, RestALLList} ->
                    LogList2 = lists:sublist([DealLog | LogList], MaxLog),
                    LogType2 = TypeLog#deal_log_type{log_list = LogList2},
                    [LogType2 | RestALLList];
                _ ->
                    TypeLog = #deal_log_type{log_key = LogKey, log_list = [DealLog]},
                    [TypeLog | AllDealLogList]
            end,
            %% 通知角色更新成交记录
            cast_to_roles(DealLog, BuyType),
            %% 成交记录在进程退出才保存
            State#market_mgr{all_deal_log_list = AllDealLogList2};
        _ ->
            State
    end.

%% 通知角色更新成交记录
cast_to_roles(DealLog, BuyType) ->
    #market_deal_log{
        id = Id, role_id = RoleId, get_role_id = GetRoleId, goods_type_id = GoodsTypeId
        ,num = Num, price = Price, sell_type = SellType, guild_id = GuildId
        ,time = Time, deal_time = DealTime, extra_attr = ExtraAttr, deal_type = DealType
    } = DealLog,
    %% 出售者记录
    RoleDealLog = #market_role_deal_log{
        id = Id, role_id = RoleId, rela_role_id = GetRoleId, goods_type_id = GoodsTypeId
        ,num = Num, price = Price, sell_type = SellType, guild_id = GuildId, time = Time
        ,deal_time = DealTime, deal_type = 2, extra_attr = ExtraAttr
    },
    %% 购买者记录
    RoleDealLog2 = RoleDealLog#market_role_deal_log{
        role_id = GetRoleId, rela_role_id = RoleId, deal_type = DealType
    },
    market_db:save_role_deal_log([RoleDealLog, RoleDealLog2]),
    
    %% 卖家是玩家才处理
    RoleId > ?MARKET_SYS_MAX_ROLE_ID andalso market:update_role_deal(RoleId, RoleDealLog, BuyType, ?MARKET_SELLER),
    %% 买家是玩家才处理
    GetRoleId > ?MARKET_SYS_MAX_ROLE_ID andalso market:update_role_deal(GetRoleId, RoleDealLog2, BuyType, ?MARKET_BUYER),
    ok.

%% 删除角色关注 商品过期/未过期手动下架/成交
mgr_del_like_info(Id) ->
    spawn(
        fun() ->
            market_db:del_market_like_info(Id),
            Module = market,
            Fun = del_like_market_goods_callback,
            Args = [Id],
            lib_player:cast_online_role_apply(?APPLY_CAST_STATUS, Module, Fun, Args),
            ok
        end
    ),
    ok.


%% ---------------------------------------------------------------------------
%% @doc 拍卖倒计时结束
-spec goods_off_time_check(State, Type, SubType) -> NewState when
    State       :: #market_mgr{},
    Type        :: integer(),
    SubType     :: integer(),
    NewState    :: #market_mgr{}.
%% ---------------------------------------------------------------------------
goods_off_time_check(State, Type, SubType) ->
    %% 取出该商品未下架的商品
    Now = utime:unixtime(),
    MatchSpec = ets:fun2ms(
        fun(
            #market_goods{type = GType, sub_type = GSubType, off_shelf = OffShelf} = MG
        ) when GType =:= Type
            andalso GSubType =:= SubType
            andalso OffShelf == 0 -> MG
        end
    ),
    GoodsList = ets:select(?MARKET_GOODS_ETS, MatchSpec),
    Key = {Type, SubType},
    OffTimeInfo = #m_off_time_info{key = Key},
    %% 先删除旧的定时器，off_time_check/4中可能会需要更新定时器
    #market_mgr{off_time_list = OldOffTimeList} = State,
    OldOffTimeList2 = lists:keydelete(Key, #m_off_time_info.key, OldOffTimeList),
    State2 = State#market_mgr{off_time_list = OldOffTimeList2},
    {State3, OffTimeInfo2} =
        off_time_check(GoodsList, State2, OffTimeInfo, Now),
    #m_off_time_info{min_off_time = MinTime} = OffTimeInfo2,
    #market_mgr{off_time_list = OffTimeList} = State3,
    OffTimeList2 = case MinTime > 0 of
        true -> %% 还有物品需要定时
            update_off_time_timer(OffTimeInfo2, OffTimeList, Now);
        false ->
            OffTimeList
    end,
    State3#market_mgr{off_time_list = OffTimeList2}.

%% 检查物品是否需要结算
off_time_check([], State, OffTimeInfo, _Now) ->
    {State, OffTimeInfo};
off_time_check([MarketGoods | T], State, OffTimeInfo, Now) ->
    #market_goods{
        off_time = OffTime, bidder_id = BidderId, id = MarketId
        ,sell_type = SellType, role_id = Module
    } = MarketGoods,
    IsModule = is_guild_auto(Module),
    NewState = case Now >= OffTime of
        true when BidderId =/= 0 -> %% 有人参与竞拍
            State2 = market_goods_settlement(State, MarketGoods),
            State2;
        true when SellType == ?MARKET_SELL_TYPE_WORLD, IsModule == true ->
            %% 流拍到世界的的公会活动拍卖需要分红
            State2 = market_goods_settlement(State, MarketGoods),
            distribute_sys_cashback(0,MarketGoods),
            State2;
        true ->
            %% 定时器结束，但是没有人参与竞拍
            %% 没有人参与竞拍，如果是公会上架的，判断是否自动世界上架
            %% 如果是系统世界上架的物品需要删除
            State2 = to_world_or_delete(MarketGoods, State),
            %% 删除角色关注
            mgr_del_like_info(MarketId),
            %% 系统根据情况补充上架物品
            State3 = sys_supply_market_goods(State2, MarketGoods),
            State3;
        _ -> %% 定时还没到
            State
    end,
    %% 更新最近检查时间
    #m_off_time_info{min_off_time = MinTime} = OffTimeInfo,
    MinTime2 = case OffTime > Now of
        true ->
            case MinTime of
                0 -> OffTime;
                _ -> min(OffTime, MinTime)
            end;
        false ->
            MinTime
    end,
    OffTimeInfo2 = OffTimeInfo#m_off_time_info{min_off_time = MinTime2},
    off_time_check(T, NewState, OffTimeInfo2, Now).


%% ---------------------------------------------------------------------------
%% @doc 倒计时结束, 如果是公会上架的物品，判断是否自动世界上架
%% 如果是系统世界上架的物品需要删除
-spec to_world_or_delete(MarketGoods,State) -> NewState when
    State           :: #market_mgr{},
    MarketGoods     :: #market_goods{},
    NewState        :: #market_mgr{}.
%% ---------------------------------------------------------------------------
to_world_or_delete(MarketGoods = #market_goods{sell_type = ?MARKET_SELL_TYPE_GUILD}, State) ->
    #market_goods{
        goods_type_id = GoodsTypeId, is_to_world = IsToWorld, role_id = Module
    } = MarketGoods,
    IsModule = is_guild_auto(Module),
    case data_goods_type:get(GoodsTypeId) of
        #ets_goods_type{
            selling_time = SellingTime
        } when IsToWorld =:= 1 -> %% 自动世界上架
            Now = utime:unixtime(),
            MarketGoods2 = MarketGoods#market_goods{
                sell_type = ?MARKET_SELL_TYPE_WORLD, time = Now
                ,off_time = Now + SellingTime, sys_buyback_time = 0
            },
            ets:insert(?MARKET_GOODS_ETS, MarketGoods2),
            market:log_market_operation(?MARKET_OPERATION_OFF_TO_WORLD, MarketGoods2, 0),
            market_db:save_market_goods(MarketGoods2),
            %% 更新倒计时
            NewState = update_timer(MarketGoods2, State),
            NewState;
        _ when IsModule -> %% 社团分红
            State2 = market_goods_settlement(State, MarketGoods),
            distribute_sys_cashback(0,MarketGoods),
            State2;
        _ ->
            %% 通知玩家，有物品下架了
            notice_goods_overdue(MarketGoods),
            State
    end;
%% 如果是系统在世界上架的物品没有卖出去，需要删除
to_world_or_delete(MarketGoods, State) ->
    #market_goods{
        sell_type = SellType, role_id = RoleId, id = MarketId
    } = MarketGoods,
    case {SellType, RoleId =< ?MARKET_SYS_MAX_ROLE_ID} of
        {?MARKET_SELL_TYPE_WORLD, true} ->
            %% 系统在世界上架的物品，需要删除或者重新上架
            case do_buyback_then_sell(State, MarketGoods) of
                {true, NewState} ->
                    NewState;
                _ ->
                    %% 清除数据
                    ets:delete(?MARKET_GOODS_ETS, MarketId),
                    market_db:del_market_goods(MarketGoods),
                    market:log_market_operation(?MARKET_OPERATION_SYS_DEL, MarketGoods, 0),
                    State
            end;
        _ ->
            notice_goods_overdue(MarketGoods),
            State
    end.

%% 超时下架：流拍了
notice_goods_overdue(#market_goods{role_id = RoleId} = MarketGoods) ->
    OffMarketGoods = MarketGoods#market_goods{off_shelf = 1},
    ets:insert(?MARKET_GOODS_ETS, OffMarketGoods),
    market_db:save_market_goods(OffMarketGoods),
    %% 超时下架
    lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, ?HAND_OFFLINE, ta_agent_fire, auction_state, [{OffMarketGoods, 3, 0}]),
    lib_player:apply_cast(RoleId, ?APPLY_CAST_STATUS, market, notice_goods_overdue, []).
%% ---------------------------------------------------------------------------
%% @doc 系统回购倒计时结束
-spec sys_buyback_time_check(State, GoodsTypeId) -> NewState when
    State       :: #market_mgr{},
    GoodsTypeId :: integer(),
    NewState    :: #market_mgr{}.
%% ---------------------------------------------------------------------------
sys_buyback_time_check(State, GoodsTypeId) ->
    MatchSpec = ets:fun2ms(
        fun(
            #market_goods{
                goods_type_id = GTypeId, sys_buyback_time = SysBTime
                ,sell_type = SellType, off_shelf = OffShelf
            } = MG
        ) when GTypeId =:= GoodsTypeId andalso SysBTime > 0
            andalso SellType =:= ?MARKET_SELL_TYPE_WORLD
            andalso OffShelf == 0 -> MG
        end
    ),
    GoodsList = ets:select(?MARKET_GOODS_ETS, MatchSpec),
    Now = utime:unixtime(),
    TimeInfo = #m_buyback_time_info{goods_type_id = GoodsTypeId},
    %% 先剔除旧定时器
    OldTimeList =  State#market_mgr.buyback_time_list,
    ThenTimeList =
        case lists:keytake(GoodsTypeId, #m_buyback_time_info.goods_type_id, OldTimeList) of
            {value, _, Left} ->
                Left;
            _ ->
                OldTimeList
        end,
    {State2, TimeInfo2} = buyback_time_check(GoodsList, State#market_mgr{buyback_time_list = ThenTimeList}, TimeInfo, Now),
    #m_buyback_time_info{min_time = MinTime} = TimeInfo2,
    #market_mgr{buyback_time_list = TimeList} = State2,
    TimeList2 = update_buy_back_timer(GoodsTypeId, MinTime, TimeList, Now),
    State2#market_mgr{buyback_time_list = TimeList2}.

%% 检查物品是否需要回购
buyback_time_check([], State, TimeInfo, _Now) ->
    {State, TimeInfo};
buyback_time_check([MarketGoods | T], State, TimeInfo, Now) ->
    #market_goods{
        sys_buyback_time = BackTime, bidder_id = BidderId
    } = MarketGoods,
    NewState = case Now >= BackTime of
        true when BidderId =/= 0 -> %% 有人参与竞拍
            State;
        true -> %% 定时器结束，但是没有人参与竞拍
            %% 没有人参与竞拍，系统出价竞拍
            State2 = sys_bid_for_goods(State, MarketGoods),
            State2;
        _ -> %% 定时还没到
            State
    end,
    %% 更新最近检查时间
    #m_buyback_time_info{min_time = MinTime} = TimeInfo,
    MinTime2 = case BackTime > Now of
        true ->
            case MinTime of
                0 -> BackTime;
                _ -> min(BackTime, MinTime)
            end;
        false ->
            MinTime
    end,
    TimeInfo2 = TimeInfo#m_buyback_time_info{min_time = MinTime2},
    buyback_time_check(T, NewState, TimeInfo2, Now).


%% ---------------------------------------------------------------------------
%% @doc 没有人参与竞拍，系统出价竞拍
-spec sys_bid_for_goods(State, MarketGoods) -> NewState when
    State       :: #market_mgr{},
    MarketGoods :: #market_goods{},
    NewState    :: #market_mgr{}.
%% ---------------------------------------------------------------------------
sys_bid_for_goods(State, MarketGoods) ->
    #market_goods{goods_type_id = GoodsTypeId, id = MarketId, price = TradePrice, buyout_price = BuyoutPrice} = MarketGoods,
    GoodsCfg = data_goods_type:get(GoodsTypeId),
    if
        not (is_record(GoodsCfg, ets_goods_type)) ->
            {State, MarketGoods};
        true ->
            Price = case TradePrice =:= 0 of
                true  -> BuyoutPrice;
                false -> TradePrice
            end,
            RoleId = ?MARKET_SYS_ROLE_ID,
            RoleName = <<"sys"/utf8>>,
            Now = utime:unixtime(),
            MarketBid = #market_bid{
                key = {RoleId, MarketId}, id = MarketId, role_id = RoleId
                ,role_name = RoleName, time = Now
            },
            {true, NewState} = handle_bid_for_goods_do(
                State, RoleId, Price, MarketBid, MarketGoods
            ),
            NewState
    end.


%% ---------------------------------------------------------------------------
%% @doc 物品下架
-spec handle_put_goods_down(State, RoleId, RolePid, MarketId) -> NewState when
    State       :: #market_mgr{},
    RoleId      :: integer(),
    RolePid     :: pid(),
    MarketId    :: integer(),
    NewState    :: #market_mgr{}.
%% ---------------------------------------------------------------------------
handle_put_goods_down(State, RoleId, RolePid, MarketId) ->
    MarketGoods = case ets:lookup(?MARKET_GOODS_ETS, MarketId) of
        [MG = #market_goods{} | _] -> MG;
        _ -> false
    end,
    Result = if
        not (is_record(MarketGoods, market_goods)) ->
            {false, ?ERRCODE(err155_market_goods_not_exist)};
        MarketGoods#market_goods.bidder_id =/= 0 ->
            {false, ?ERRCODE(err155_market_goods_is_offer)};
        true ->
            Module = market,
            Fun = put_goods_down_callback,
            Args = [MarketGoods],
            lib_player:apply_cast(RolePid, ?APPLY_CAST_STATUS, Module, Fun, Args),
            ets:delete(?MARKET_GOODS_ETS, MarketId),
            %% 删除角色关注
            (MarketGoods#market_goods.off_time > utime:unixtime()) andalso
                mgr_del_like_info(MarketId),
            true
    end,
    case Result of
        {false, ErrReason} ->
            {ok, Bin} = pt_155:write(15508, [ErrReason]),
            lib_server_send:send_to_uid(RoleId, Bin),
            State;
        true ->
            %% 系统根据情况补充上架物品
            NewState = sys_supply_market_goods(State, MarketGoods),
            NewState
    end.


%% ---------------------------------------------------------------------------
%% @doc 保存成交记录
-spec save_market_log(State) -> ok when
    State       :: #market_mgr{}.
%% ---------------------------------------------------------------------------
save_market_log(State) ->
    #market_mgr{all_deal_log_list = AllDealLogList} = State,
    DealLogList = market_deal_log_list(AllDealLogList,  []),
    market_db:save_deal_log(DealLogList),
    ok.

market_deal_log_list([], DealLogList) ->
    DealLogList;
market_deal_log_list([DealTypeList | T], DealLogList) ->
    #deal_log_type{log_list = LogList} = DealTypeList,
    market_deal_log_list(T, LogList ++ DealLogList).



%% ---------------------------------------------------------------------------
%% @doc 发送交易市场成交记录
-spec handle_send_deal_log(State, LogType, RoleSid) -> ok when
    State       :: #market_mgr{},
    LogType     :: integer(),
    RoleSid     :: pid().
%% ---------------------------------------------------------------------------
handle_send_deal_log(State, LogType, RoleSid) ->
    #market_mgr{all_deal_log_list = AllDealLogList} = State,
    LogList = pack_deal_log_by_type(AllDealLogList, LogType, []),
    {ok, Bin} = pt_155:write(15504, [LogType, LogList]),
    lib_server_send:send_to_sid(RoleSid, Bin),
    ok.

%% 筛选指定类型的交易记录
pack_deal_log_by_type([], _LogType, InfoList) ->
    InfoList;
pack_deal_log_by_type([DealTypeList | T], LogType, InfoList) ->
    InfoList2 = case DealTypeList of
        #deal_log_type{log_key = {LogType, _}, log_list = LogList} ->
            pack_deal_log(LogList, InfoList);
        _ ->
            InfoList
    end,
    pack_deal_log_by_type(T, LogType, InfoList2).

pack_deal_log([], InfoList) ->
    InfoList;
pack_deal_log([DealLog | T], InfoList) ->
    #market_deal_log{
        id = Id, goods_type_id = GoodsTypeId, num = Num, price = Price
        ,deal_time = DealTime, deal_type = DealType, extra_attr = ExtraAttr
        ,get_role_name = GetRoleName
    } = DealLog,
    LogInfo = {
        Id, GoodsTypeId, DealTime, Price, DealType, Num, GetRoleName
        ,market:extra_data_pack(ExtraAttr)
    },
    pack_deal_log(T,  [LogInfo | InfoList]).


%%社团拍卖分红
distribute_sys_cashback(OpTy,MarketGoods)->
    #market_goods{
        sell_type = _SellType,
        role_id  = Module,
        price = Price,
        % time  = Time,
        guild_id = GuildID
    }=MarketGoods,
    case Module of
        ?MOD_GUILD_BOSS->
            mod_guild_boss_mgr:market_cashback(GuildID,util:ceil(Price*OpTy));
        _->
            ok
    end.

is_guild_auto(Module)->
    lists:member(Module,[?MOD_GUILD_BOSS]).


%% ---------------------------------------------------------------------------
%% @doc 统计物品累积上架数量
-spec update_goods_statistics(MarketGoods) -> ok when
    MarketGoods :: #market_goods{}.
%% ---------------------------------------------------------------------------
update_goods_statistics(MarketGoods) ->
    #market_goods{
        goods_type_id = GoodsTypeId, sell_type = SellType, num = GoodsNum
        ,role_id = RoleId
    } = MarketGoods,
    case {SellType, RoleId > ?MARKET_SYS_MAX_ROLE_ID} of
        {?MARKET_SELL_TYPE_WORLD, true} ->
            case ets:lookup(?MARKET_SUPPLY_ETS, GoodsTypeId) of
                [Supply = #market_supply{num = Num} | _] ->
                    NewSupply = Supply#market_supply{num = Num + GoodsNum},
                    ets:insert(?MARKET_SUPPLY_ETS, NewSupply),
                    market_db:save_market_supply(NewSupply);
                _ ->
                    NewSupply = #market_supply{
                        goods_type_id = GoodsTypeId, num = GoodsNum
                    },
                    ets:insert(?MARKET_SUPPLY_ETS, NewSupply),
                    market_db:save_market_supply(NewSupply)
            end;
        _ ->
            ok
    end,
    ok.



%% ---------------------------------------------------------------------------
%% @doc 竞拍物品结算后的处理
-spec after_market_goods_settlement(State, MarketGoods, GetRole) -> NewState when
    State       :: #market_mgr{},
    MarketGoods :: #market_goods{},
    GetRole     :: #market_bid{},
    NewState    :: #market_mgr{}.
%% ---------------------------------------------------------------------------
after_market_goods_settlement(State, MarketGoods, GetRole) ->
    %% 系统根据情况补充上架物品
    State2 = sys_supply_market_goods(State, MarketGoods),
    %% 如果是系统回购，需要判断是否需要再次上架
    NewState = sys_buyback_then_sell(State2, MarketGoods, GetRole),
    NewState.
%% ---------------------------------------------------------------------------
%% @doc 系统回购物品后
-spec sys_buyback_then_sell(State, MarketGoods, GetRole) -> NewState when
    State       :: #market_mgr{},
    MarketGoods :: #market_goods{},
    GetRole     :: #market_bid{},
    NewState    :: #market_mgr{}.
%% ---------------------------------------------------------------------------
sys_buyback_then_sell(State, MarketGoods, #market_bid{role_id = ?MARKET_SYS_ROLE_ID}) ->
    case do_buyback_then_sell(State, MarketGoods) of
        {true, NewState} ->
            NewState;
        _ ->
            State
    end;
sys_buyback_then_sell(State, _MarketGoods, _GetRole) ->
    State.

%% 系统回购
do_buyback_then_sell(State, MarketGoods) ->
    #market_goods{goods_type_id = GoodsTypeId, sys_buyback_count = Count} = MarketGoods,
    Max = data_market:get_config(sys_buyback_max),
    case erlang:is_integer(Max) andalso Count < Max of
        true  ->
            OpenDay = util:get_open_day(),
            case data_market:get_market_sys_buyback(GoodsTypeId, OpenDay) of
                #market_buyback_cfg{sell_again = 1} -> %% 再上架
                    NewMarketGoods = market:make_market_goods(
                        ?MARKET_SYS_ROLE_ID, MarketGoods#market_goods.goods_id, MarketGoods#market_goods.goods_type_id,
                        MarketGoods#market_goods.num, ?MARKET_SELL_TYPE_WORLD, 0, 0, false
                    ),
                    NewMarketGoods2 = NewMarketGoods#market_goods{
                        id = MarketGoods#market_goods.id,
                        extra_attr = MarketGoods#market_goods.extra_attr,
                        sys_buyback_count = Count + 1
                    },
                    ets:insert(?MARKET_GOODS_ETS, NewMarketGoods2),
                    market_db:save_market_goods(NewMarketGoods2),
                    market:log_market_operation(?MARKET_OPERATION_SYS_ON, NewMarketGoods2, 0),
                    %% 更新倒计时
                    NewState = update_timer(NewMarketGoods2, State),
                    {true, NewState};
                _ ->
                    ignore
            end;
        false ->
            ignore
    end.
%% ---------------------------------------------------------------------------
%% @doc 系统根据情况补充上架物品
-spec sys_supply_market_goods(State, MarketGoods) -> NewState when
    State       :: #market_mgr{},
    MarketGoods :: #market_goods{},
    NewState    :: #market_mgr{}.
%% ---------------------------------------------------------------------------
%% 1、定时器结束，但是没有人参与竞拍 2、物品下架 3、商品成交
sys_supply_market_goods(State, MarketGoods) ->
    case sys_supply_market_goods(MarketGoods) of
        NewMarketGoods = #market_goods{} -> %% 系统进行补给
            %% 更新倒计时
            NewState = update_timer(NewMarketGoods, State),
            NewState;
        _ -> %% 不补充
            State
    end.


%% ---------------------------------------------------------------------------
%% @doc 系统根据情况补充上架物品
-spec sys_supply_market_goods(MarketGoods) -> ok | NewMarketGoods when
    MarketGoods     :: #market_goods{},
    NewMarketGoods  :: #market_goods{}.
%% ---------------------------------------------------------------------------
sys_supply_market_goods(MarketGoods = #market_goods{sell_type = ?MARKET_SELL_TYPE_WORLD}) ->
    #market_goods{goods_type_id = GoodsTypeId} = MarketGoods,
    Now = utime:unixtime(),
    OpenDay = util:get_open_day(),
    case check_sys_supply_market_goods(GoodsTypeId, Now, OpenDay) of
        {true, GoodsCfg, SupplyInfo, SellNum} ->
            sys_supply_market_goods_do(MarketGoods, SupplyInfo, SellNum, GoodsCfg, Now);
        _ ->
            ok
    end;
sys_supply_market_goods(_MarketGoods) ->
    ok.

%% 系统补充上架物品
sys_supply_market_goods_do(MarketGoods, SupplyInfo, SellNum, _GoodsCfg, Now) ->
    %% 构造商品
    NewMarketGoods = market:make_market_goods(
        ?MARKET_SYS_ROLE_ID, 0, MarketGoods#market_goods.goods_type_id,
        1, ?MARKET_SELL_TYPE_WORLD, 0, 0, false
    ),
    %% 按照堆叠拆分商品
    MarketGoodsList = split_market_goods(NewMarketGoods#market_goods{num = SellNum, id = MarketGoods#market_goods.id}),
    
    SupplyNum = SupplyInfo#market_supply.supply_num,
    Supply2 = SupplyInfo#market_supply{supply_num = SupplyNum + SellNum, time = Now},
    ets:insert(?MARKET_SUPPLY_ETS, Supply2),
    ets:insert(?MARKET_GOODS_ETS, MarketGoodsList),
    market_db:batch_save_market_goods(MarketGoodsList),
    market_db:save_market_supply(Supply2),
    [market:log_market_operation(?MARKET_OPERATION_SYS_ON, Goods, 0) || Goods <- MarketGoodsList],
    %% 用于更新定时器
    NewMarketGoods.

%% 根据最大堆叠数分拆物品
split_market_goods(MarketGoods) ->
    #market_goods{goods_type_id = GoodsTypeId, num = Num} = MarketGoods,
    GoodsList = [{?TYPE_GOODS, GoodsTypeId, Num}],
    GoodsList2 = market:split_goods_list(GoodsList),
    %% 第一个物品复用之前的MarketId
    [{?TYPE_GOODS, GoodsTypeId, SplitNum} | T] = GoodsList2,
    MarketGoods2 = MarketGoods#market_goods{num = SplitNum},
    split_market_goods(MarketGoods, T, [MarketGoods2]).

split_market_goods(_MarketGoods, [], MarketGoodsList) ->
    MarketGoodsList;
split_market_goods(MarketGoods, [GoodsInfo | T], MarketGoodsList) ->
    {?TYPE_GOODS, _GoodTypeId, Num} = GoodsInfo,
    Id = mod_id_create:get_new_id(?SELL_ID_CREATE),
    NewMarketGoods = MarketGoods#market_goods{id = Id, num = Num},
    split_market_goods(MarketGoods, T, [NewMarketGoods | MarketGoodsList]).

%% ---------------------------------------------------------------------------
%% @doc 检查系统是否需要补充上架物品
-spec check_sys_supply_market_goods(GoodsTypeId, Now, OpenDay) ->
    {true, GoodsCfg, SupplyInfo, SellNum} | false when
    GoodsTypeId     :: integer(),
    Now             :: integer(),
    OpenDay         :: integer(),
    GoodsCfg        :: #ets_goods_type{},
    SupplyInfo      :: #market_supply{},
    SellNum         :: integer().
%% ---------------------------------------------------------------------------
check_sys_supply_market_goods(GoodsTypeId, Now, OpenDay) ->
    SysTriggerNum = data_market:get_trigger_sys_sell_num(GoodsTypeId),
    case data_market:get_market_sys_sell(GoodsTypeId, OpenDay) of
        #market_sys_sell_cfg{
            trigger_num = TriggerNum, sell_num = SellNum, sell_max_num = MaxSellNum, trigger_sys_sell_num = SysTriggerNum
        } when is_integer(SysTriggerNum) ->
            Supply = #market_supply{num = TotalNum} = get_goods_sys_supply(GoodsTypeId),
            GoodsCfg = data_goods_type:get(GoodsTypeId),
            if
                TotalNum < SysTriggerNum ->
                    %% 全服玩家累积上架数量没有达到激活系统自动补给系统的要求
                    false;
                Supply#market_supply.supply_num >= MaxSellNum ->
                    %% 系统自动补给达到今天的补给上限
                    false;
                not (is_record(GoodsCfg, ets_goods_type)) ->
                    false;
                true ->
                    MatchSpec = ets:fun2ms(
                        fun(
                            #market_goods{goods_type_id = Id, off_time = OffTime, num = Num}
                        ) when Id =:= GoodsTypeId andalso OffTime > Now -> Num end
                    ),
                    GoodsNumList = ets:select(?MARKET_GOODS_ETS, MatchSpec),
                    GoodsSumNum = lists:sum(GoodsNumList),
                    case GoodsSumNum >= TriggerNum of
                        true -> %% 数量还比较多，暂时不补给
                            false;
                        false ->
                            {true, GoodsCfg, Supply, SellNum}
                    end
            end;
        _ ->
            false
    end.


%% ---------------------------------------------------------------------------
%% @doc 获取物品系统补给数据信息
-spec get_goods_sys_supply(GoodsTypeId) -> MarketSupply when
    GoodsTypeId :: integer(),
    MarketSupply:: #market_supply{}.
%% ---------------------------------------------------------------------------
get_goods_sys_supply(GoodsTypeId) ->
    case ets:lookup(?MARKET_SUPPLY_ETS, GoodsTypeId) of
        [Supply = #market_supply{time = UpdateTime} | _] ->
            Now = utime:unixtime(),
            case utime:is_same_day(Now, UpdateTime) of
                true ->
                    Supply;
                false ->
                    Supply#market_supply{supply_num = 0, time = Now}
            end;
        _ ->
            #market_supply{goods_type_id = GoodsTypeId}
    end.


%% ---------------------------------------------------------------------------
%% @doc 物品上架
-spec mgr_put_on_goods_logic(State, MarketGoods) -> NewState when
    State       :: #market_mgr{},
    MarketGoods :: #market_goods{},
    NewState    :: #market_mgr{}.
%% ---------------------------------------------------------------------------
mgr_put_on_goods_logic(State, MarketGoods) ->
    %% 更新倒计时
    NewState = update_timer(MarketGoods, State),
    %% 入库提醒
    spawn(fun() -> send_reminder_msg(MarketGoods) end),
    %% 统计物品累积上架数量
    update_goods_statistics(MarketGoods),
    NewState.


%%   %% ---------------------------------------------------------------------------
%%   %% @doc 系统定时检查是否需要补充上架物品
%%   -spec supply_market_goods(State) -> NewState when
%%       State       :: #market_mgr{},
%%       NewState    :: #market_mgr{}.
%%   %% ---------------------------------------------------------------------------
%%   supply_market_goods(State) ->
%%       OpenDay = util:get_open_day(),
%%       MaxOpenDay = data_market:get_max_open_day(),
%%       case OpenDay > MaxOpenDay of
%%           true -> %% 过了系统补充上架物品的开服天数
%%               State;
%%           false ->
%%               Now = utime:unixtime(),
%%               SupplyGoodsList = data_market:get_sys_supply_goods_list(),
%%               supply_market_goods_do(State, OpenDay, Now, SupplyGoodsList)
%%       end.
%%
%%   supply_market_goods_do(State, _OpenDay, _Now, []) ->
%%       State;
%%   supply_market_goods_do(State, OpenDay, Now, [GoodsTypeId | T]) ->
%%       case check_sys_supply_market_goods(GoodsTypeId, Now, OpenDay) of
%%           {true, GoodsCfg, SupplyInfo, SellNum} ->
%%               supply_market_goods_do(State, OpenDay, Now, T);
%%           _ ->
%%               supply_market_goods_do(State, OpenDay, Now, T)
%%       end.

%% ---------------------------------------------------------------------------
%% @doc 定时清理过期的商品，邮件返还
%% ---------------------------------------------------------------------------
clean_offtime_goods() ->
    OfftimeGoods = select_clean_offtime_goods(),
    Title = ?LAN_MSG(1550007),
    Content = ?LAN_MSG(1550008),
    [
        begin
            #market_goods{id = Id, role_id = RoleId, goods_type_id = GoodsTypeId, num = Num, extra_attr = ExtraAttr} = MarketGoods,
            %% 清除数据
            ets:delete(?MARKET_GOODS_ETS, Id),
            market_db:del_market_goods(MarketGoods),
            market:log_market_operation(?MARKET_OPERATION_SYS_OFF, MarketGoods, 0),
            case RoleId > ?MARKET_SYS_MAX_ROLE_ID of
                true  ->
                    Award = case ExtraAttr =:= [] of
                                true  -> [{?TYPE_GOODS, GoodsTypeId, Num}];
                                false -> [{?TYPE_ATTR_GOODS, GoodsTypeId, Num, ExtraAttr}]
                            end,
                    lib_mail_api:send_sys_mail([RoleId], Title, Content, Award);
                false ->
                    ok
            end
        end || MarketGoods <- OfftimeGoods
    ],
    OfftimeGoods =/= [] andalso
    ?INFO("sys clean offtime market goods num:~p~n", [length(OfftimeGoods)]).

select_clean_offtime_goods() ->
    Now = utime:unixtime(),
    CheckTime = Now - 86400*7,
    MatchSpec = ets:fun2ms(
        fun(#market_goods{off_time = OffTime} = MarketGoods)
            when OffTime =< CheckTime -> MarketGoods
        end
    ),
    ets:select(?MARKET_GOODS_ETS, MatchSpec).

%% 通知竞拍过的玩家商品变化
notice_goods_change(RoleId, MarketGoods, IsDown) ->
    #market_goods{id = MarketId, price = Price} = MarketGoods,
    %% 告知其他所有竞拍者价格
    case get_buyer_list_sorted(MarketId) of
        []  ->
            ignore;
        AllBidRoles ->
            {ok, Bin} = pt_155:write(15518, [MarketId, Price, IsDown]),
            [
                lib_server_send:send_to_uid(Other, Bin)
                || #market_bid{role_id = Other} <- lists:keydelete(RoleId, #market_bid.role_id, AllBidRoles)
            ]
    end.