%%%-----------------------------------
%%% @Module  : lib_week
%%% @Author  : HHL
%%% @Email   : 1942007864@qq.com
%%% @Created : 2014.07.17
%%% @Description: 每周记录器
%%%-----------------------------------
-module(lib_week).

-include("week.hrl").
-include("common.hrl").
-include("predefine.hrl").
-export([
         online/1,                    %%上线加载数据
         get_all/2,

         get_count/2,                 %% 列表获取计数
         get_count/3,
         get_count/4,

         set_count/2,
         set_count/5,

         increment/3,                 %%加一操作
         increment/4,

         decrement/3,                 %%减一操作
         decrement/4,
         cut_count/5,

         plus_count/2,                %%列表追加
         plus_count/4,
         plus_count/5,

         get_refresh_time/4,
         set_refresh_time/4,
         update_count_db/7,
         lessthan_limit/4,
         get_count_limit/2,
         get_count_limit/3,
         get_count_and_other_from_db/4,
         get_count_and_refresh_time_from_db/4,
         get_count_info_from_db/4,
         week_dividing/2,

         get_count_offline/2,   %%第三方和离线操作
         get_count_offline/3,
         get_count_offline/4,
         increment_offline/3,
         increment_offline/4,
         plus_count_offline/5,
         set_count_offline/4,
         set_count_offline/5
        ]
       ).

-export([
    get/4,
    get/5,
    new/1
]).

%% 上线操作
online(RoleId) ->
    reload(RoleId).

%% 周分界时间
week_dividing(CounterType, NowTime) ->
    case CounterType =:= ?WEEK_TYPE_ZERO of
        true  -> utime:unixtime({week, NowTime});
        false -> utime:logic_week_start(NowTime)
    end.

%% 获取整个记录器
get(RoleId, Module, SubModule, Type) ->
    NowTime = utime:unixtime(),
    get(RoleId, Module, SubModule, Type, NowTime).
get(RoleId, Module, SubModule, Type, NowTime) ->
    {Res, CounterType} = get_id(Module, SubModule, Type),
    if
        Res =/= true ->
            #ets_week{id={Module, SubModule, Type}, count = 99999};
        CounterType =:= ?WEEK_TYPE_ZERO orelse
        CounterType =:= ?WEEK_TYPE_FOUR ->
            Data = get_all(RoleId, CounterType),
            case lists:keyfind({Module, SubModule, Type}, #ets_week.id, Data) of
                #ets_week{refresh_time = RefreshTime} = EtsWeek ->
                    WeekDividing = week_dividing(CounterType, NowTime),
                    case RefreshTime >= WeekDividing of
                        true  -> EtsWeek;
                        false -> false
                    end;
                _ ->
                    false
            end;
        true ->
            #ets_week{id={Module, SubModule, Type}, count = 99999}
    end.

%% 取玩家的整个记录
get_all(RoleId,CounterType) ->
    Data =  get(?WEEK_KEY(CounterType)),
    case Data =:= undefined of
        true -> reload(RoleId,CounterType);
        false -> Data
    end.

%% 获取列表计数
get_count(RoleId, TypeList) when is_list(TypeList) ->
    NowTime = utime:unixtime(),
    F = fun({Module, SubModule, Type}=Id, Acc) ->
                [{Id, get_count(RoleId, Module, SubModule, Type,NowTime)}|Acc]
        end,
    lists:foldl(F, [], TypeList).

get_count(RoleId, Module, Type) ->
    get_count(RoleId, Module, ?DEFAULT_SUB_MODULE, Type).

%% 获取数量
get_count(RoleId, Module, SubModule, TypeList) when is_list(TypeList) ->
    NowTime = utime:unixtime(),
    [{Type, get_count(RoleId, Module, SubModule, Type, NowTime)} || Type <- TypeList];
%% 获取数量
get_count(RoleId, Module, SubModule, Type) ->
    NowTime = utime:unixtime(),
    get_count(RoleId, Module, SubModule, Type,NowTime).


%% 获取数量
get_count(RoleId, Module, SubModule, Type, NowTime) ->
    case lib_week:get(RoleId, Module, SubModule, Type, NowTime) of
        false -> 0;
        RD -> RD#ets_week.count
    end.

%% 设置数量
set_count(_, []) ->
    0;
set_count(RoleId, TypeList) ->
    NowTime = utime:unixtime(),
    F = fun
        ({{Module, SubModule, Type}, Count}, {ZeroList, FourList}) ->
            case get_config(Module, SubModule, Type) of
                #base_week{type = CounterType} ->
                    WeekData = case lib_week:get(RoleId, Module, SubModule, Type, NowTime) of
                        false ->
                            new([Module, SubModule, Type, Count]);
                        RD ->
                            RD#ets_week{count = Count}
                    end,
                    NewData = WeekData#ets_week{refresh_time = NowTime},
                    case CounterType of
                        ?WEEK_TYPE_ZERO ->
                            {[NewData | ZeroList], FourList};
                        _ ->
                            {ZeroList, [NewData | FourList]}
                    end;
                _ ->
                    {ZeroList, FourList}
            end
    end,
    {ZeroList, FourList} = lists:foldl(F, {[], []}, TypeList),
    save_batch(RoleId, ?WEEK_TYPE_ZERO, ZeroList),
    save_batch(RoleId, ?WEEK_TYPE_FOUR, FourList).


set_count(RoleId, Module, SubModule, Type, Count) ->
    NowTime = utime:unixtime(),
    case lib_week:get(RoleId, Module, SubModule, Type, NowTime) of
        false ->
            WeekData = new([Module, SubModule, Type, Count]),
            save(RoleId, WeekData#ets_week{refresh_time = NowTime});
        RD ->
            save(RoleId, RD#ets_week{count = Count,refresh_time = NowTime})
    end.

%% 减一操作
decrement(RoleId, Module, Type) ->
    decrement(RoleId, Module, ?DEFAULT_SUB_MODULE, Type).

decrement(RoleId, Module, SubModule, Type) ->
    cut_count(RoleId, Module, SubModule, Type, 1).

%% 扣除数量
cut_count(RoleId, Module, SubModule, Type, Count) ->
    case lib_week:get(RoleId, Module, SubModule, Type) of
        false ->
            error;
        RD = #ets_week{count = OldCount} ->
            NowTime = utime:unixtime(),
            save(RoleId, RD#ets_week{count = max(OldCount - Count,0), refresh_time = NowTime})
    end.


%% 加一操作
increment(RoleId, Module, Type) ->
    increment(RoleId, Module, ?DEFAULT_SUB_MODULE, Type).

increment(RoleId, Module, SubModule, Type) ->
    plus_count(RoleId, Module, SubModule, Type, 1).

%% 追加数量
plus_count(RoleId, Module, Type, Count) ->
    plus_count(RoleId, Module, ?DEFAULT_SUB_MODULE, Type, Count).

%% 追加数量
plus_count(RoleId, Module, SubModule, Type, Count) ->
    NowTime = utime:unixtime(),
    case lib_week:get(RoleId, Module, SubModule, Type, NowTime) of
        false ->
            WeekData = new([Module, SubModule, Type, Count]),
            save(RoleId, WeekData#ets_week{refresh_time = NowTime});
        RD = #ets_week{count = OldCount} ->
            save(RoleId, RD#ets_week{count = OldCount + Count, refresh_time = NowTime})
    end.

%% 列表追加
plus_count(_, []) ->
    0;
plus_count(RoleId, TypeList) ->
    NowTime = utime:unixtime(),
    F = fun
        ({{Module, SubModule, Type}, Count}, {ZeroList, FourList}) ->
            case get_config(Module, SubModule, Type) of
                #base_week{type = CounterType} ->
                    WeekData = case lib_week:get(RoleId, Module, SubModule, Type, NowTime) of
                        false ->
                            new([Module, SubModule, Type, Count]);
                        RD = #ets_week{count = OldCount} ->
                            RD#ets_week{count = Count + OldCount}
                    end,
                    NewData = WeekData#ets_week{refresh_time = NowTime},
                    case CounterType of
                        ?WEEK_TYPE_ZERO ->
                            {[NewData | ZeroList], FourList};
                        _ ->
                            {ZeroList, [NewData | FourList]}
                    end;
                _ ->
                    {ZeroList, FourList}
            end
    end,
    {ZeroList, FourList} = lists:foldl(F, {[], []}, TypeList),
    save_batch(RoleId, ?WEEK_TYPE_ZERO, ZeroList),
    save_batch(RoleId, ?WEEK_TYPE_FOUR, FourList).

%% 获取刷新时间
get_refresh_time(RoleId, Module, SubModule, Type) ->
    case lib_week:get(RoleId, Module, SubModule, Type) of
        false -> 0;
        RD -> RD#ets_week.refresh_time
    end.

%% 更新刷新时间
set_refresh_time(RoleId, Module, SubModule, Type) ->
    case lib_week:get(RoleId, Module, SubModule, Type) of
        false ->
            save(RoleId, new([Module, SubModule, Type, 0]));
        _RD ->
            ignore
    end.

new([Module, SubModule, Type, Count]) ->
    new([Module, SubModule, Type, Count, ?DEFAULT_OTHER]);
new([Module, SubModule, Type, Count, Other]) ->
    #ets_week{
       id              = {Module, SubModule, Type}
             ,count          = Count
             ,other          = Other
             ,refresh_time   = 0
      }.

save(RoleId, RoleData) ->
    #ets_week{id = DataId, count = Count, other = Other, refresh_time = RefreshTime} = RoleData,
    {ModuleId, SubModuleId, TypeId} = DataId,
    CounterType = get_counter_type(ModuleId, SubModuleId, TypeId),
    Data = get_all(RoleId, CounterType),
    NewData = [RoleData | lists:keydelete(DataId, #ets_week.id, Data)],
    put(?WEEK_KEY(CounterType), NewData),
    SqlFmt =
    case CounterType of
        ?WEEK_TYPE_ZERO -> ?SQL_WEEK_ZERO_ROLE_UPDATE;
        _               -> ?SQL_WEEK_FOUR_ROLE_UPDATE
    end,
    Sql = io_lib:format(SqlFmt,
        [RoleId, ModuleId, SubModuleId, TypeId, Count, util:term_to_bitstring(Other), RefreshTime]
    ),
    db:execute(Sql).

save_batch(_RoleId, _CounterType, []) -> ok;
save_batch(RoleId, CounterType, CounterList) ->
    F = fun(#ets_week{id = Id}) ->
            not lists:keymember(Id, #ets_week.id, CounterList)
    end,
    DataList = get_all(RoleId, CounterType),
    NewDataList = CounterList ++ lists:filter(F, DataList),
    put(?WEEK_KEY(CounterType), NewDataList),
    ValueStr = ulists:list_to_string([
            io_lib:format("(~p,~p,~p,~p,~p,'~ts',~p)", [
                    RoleId, ModuleId, SubModuleId, TypeId, Count, util:term_to_bitstring(Other), RefreshTime
                ]) ||
            #ets_week{id = {ModuleId, SubModuleId, TypeId},
                count = Count, other = Other, refresh_time = RefreshTime
            } <- CounterList
        ], ","
    ),
    SqlFmt = case CounterType of
        ?WEEK_TYPE_ZERO -> ?SQL_WEEK_ZERO_ROLE_UPDATE_BATCH;
        _               -> ?SQL_WEEK_FOUR_ROLE_UPDATE_BATCH
    end,
    SQL = io_lib:format(SqlFmt, [ValueStr]),
    db:execute(SQL).

%% 所有数据重载
reload(RoleId) ->
    DataZero = reload(RoleId, ?WEEK_TYPE_ZERO),
    DataFour = reload(RoleId, ?WEEK_TYPE_FOUR),
    {DataZero, DataFour}.

reload(RoleId, CounterType) ->
    erase(?WEEK_KEY(CounterType)),
    Sql = case CounterType of
        ?WEEK_TYPE_ZERO -> util:fmt_list(?SQL_WEEK_ZERO_ROLE_ALL, [RoleId]);
        _               -> util:fmt_list(?SQL_WEEK_FOUR_ROLE_ALL, [RoleId])
    end,
    List = db:get_all(Sql),
    Data = fmt_data_list(List),
    put(?WEEK_KEY(CounterType), Data),
    Data.

%% 获取次数上限
get_count_limit(Module, Type) ->
    get_count_limit(Module, ?DEFAULT_SUB_MODULE, Type).

%% 获取次数上限
get_count_limit(Module, SubModule, Type) ->
    case get_config(Module, SubModule, Type) of
        [] -> 0;
        #base_week{limit = Limit} -> Limit
    end.

%% 未超过限制次数
%% @return [{type, boolean}]
lessthan_limit(RoleId, Module, SubModule, TypeList) when is_list(TypeList) ->
    F = fun(Type, Acc) -> Acc ++ [{Type, lessthan_limit(RoleId, Module, SubModule, Type)}] end,
    lists:foldl(F, [], TypeList);

%% 未超过限制次数
%% @return true未超过|false超过
lessthan_limit(RoleId, Module, SubModule, Type) ->
    %% 后台缺失配置的情况下，直接返回超过次数
    Limit = get_count_limit(Module, SubModule, Type),
    Count = case lib_week:get(RoleId, Module, SubModule, Type) of
                false -> 0;
                RD -> RD#ets_week.count
            end,
    Count<Limit.

%% 直接操作数据库，适合在玩家不在线的时候调用
update_count_db(RoleId, ModuleId, SubModuleId, TypeId, Count, Other, Time) ->
    CounterType = get_id(ModuleId, SubModuleId, TypeId),
    SqlFmt =
    case CounterType of
        {_,?WEEK_TYPE_ZERO} -> ?SQL_WEEK_ZERO_ROLE_UPDATE;
        _               -> ?SQL_WEEK_ZERO_ROLE_UPDATE
    end,
    Sql = io_lib:format(SqlFmt, [RoleId, ModuleId, SubModuleId, TypeId, Count, util:term_to_bitstring(Other), Time]),
    db:execute(Sql).

%% 获取次数，扩展数据
%% @return {0, 0}
get_count_and_other_from_db(RoleId, ModuleId, SubModuleId, TypeId) ->
    {Count, _Time, Other} = get_count_info_from_db(RoleId, ModuleId, SubModuleId, TypeId),
    {Count, Other}.

%% 获取次数，刷新时间
%% @return {0, 0}
get_count_and_refresh_time_from_db(RoleId, ModuleId, SubModuleId, TypeId) ->
    {Count, Time, _Other} = get_count_info_from_db(RoleId, ModuleId, SubModuleId, TypeId),
    {Count, Time}.

get_count_info_from_db(RoleId, ModuleId, SubModuleId, TypeId) ->
    case get_config(ModuleId, SubModuleId, TypeId) of
        #base_week{type = CounterType} ->
            SqlFmt = case CounterType of
                ?WEEK_TYPE_ZERO -> ?SQL_WEEK_ZERO_ROLE;
                _               -> ?SQL_WEEK_FOUR_ROLE
            end,
            Sql = io_lib:format(SqlFmt, [RoleId, ModuleId, SubModuleId, TypeId]),
            case db:get_row(Sql) of
                [] -> {0, 0, []};
                [Count, RefreshTime, Other] ->
                    WeekDividing = week_dividing(CounterType, utime:unixtime()),
                    case RefreshTime >= WeekDividing of
                        true  -> {Count, RefreshTime, util:bitstring_to_term(Other)};
                        false -> {0, 0, []}
                    end
            end;
        _ -> {0, 0, []}
    end.

%% ------------------------- local function -------------------------
fmt_data_list(DataList) ->
    [fmt_data(Data) || Data = [_|_] <- DataList].

fmt_data([Module, SubModule, Type, Count, Other, Time]) ->
    #ets_week{id       = {Module, SubModule, Type},
        count          = Count,
        other          = util:bitstring_to_term(Other),
        refresh_time   = Time
    }.

get_id(Module, SubModule, Type) ->
    Config = get_config(Module, SubModule, Type),
    case Config of
        [] ->
            {error, ?WEEK_TYPE_ZERO};
        #base_week{type = CounterType} ->
            {true, CounterType}
    end.

get_config(Module, SubModule, Type) ->
    case SubModule ==?DEFAULT_SUB_MODULE of
        true -> Config = data_week:get_id(Module, Type);
        false -> Config = data_week_extra:get_id(Module, SubModule)
    end,
    case Config of
        #base_week{} -> ignore;
        _ -> ?ERR(">>>>> important >>>>> missing config:~p~n", [{Module, SubModule, Type}])
    end,
    Config.

get_counter_type(Module, SubModule, Type) ->
    Config =
    case SubModule ==?DEFAULT_SUB_MODULE of
        true  -> data_week:get_id(Module, Type);
        false -> data_week_extra:get_id(Module, SubModule)
    end,
    case Config of
        #base_week{type = CounterType} -> CounterType;
        _ -> ?WEEK_TYPE_ZERO
    end.

%%---------------------离线操作/非玩家进程操作------------------------

%% 加一操作
%% 在线时：  同increment一样
%% 不在线时：不在线则直接修改db
increment_offline(RoleId, Module, Type) ->
    increment_offline(RoleId, Module, ?DEFAULT_SUB_MODULE, Type).

%% 加一操作
%% 在线时：  同increment一样
%% 不在线时：不在线则直接修改db
increment_offline(RoleId, Module, SubModule, Type) ->
    plus_count_offline(RoleId, Module, SubModule, Type, 1).


%% 追加数量，不在线则处理离线
plus_count_offline(RoleId,  Module, SubModule, Type, Count) ->
    Pid = misc:get_player_process(RoleId),
    case misc:is_process_alive(Pid) of
        true  when Pid =:= self() ->
            plus_count(RoleId, Module, SubModule, Type, Count);
        true->
            lib_player:apply_cast(Pid,?APPLY_CAST,?MODULE,plus_count,[RoleId, Module, SubModule, Type, Count]);
        _  ->
            Time = utime:unixtime(),
            F = fun() ->
                {OldCount, Other} = lib_week:get_count_and_other_from_db(RoleId, Module, SubModule, Type),
                lib_week:update_count_db(RoleId, Module, SubModule, Type, Count + OldCount, Other, Time),
                ok
            end,
            case catch db:transaction(F) of
                ok -> ok;
                Error ->
                    ?ERR("plus_count_offline:~p~n", [Error]),
                    error
            end
    end.

set_count_offline(RoleId, Module, Type, Count) ->
    set_count_offline(RoleId, Module, ?DEFAULT_SUB_MODULE, Type, Count).

%% 设置数量，不在线则处理离线
set_count_offline(RoleId,  Module, SubModule, Type, Count) ->
    Pid = misc:get_player_process(RoleId),
    case misc:is_process_alive(Pid) of
        true  when Pid =:= self() ->
            set_count(RoleId, Module, SubModule, Type, Count);
        true->
            lib_player:apply_cast(Pid,?APPLY_CAST,?MODULE,set_count,[RoleId, Module, SubModule, Type, Count]);
        _  ->
            Time = utime:unixtime(),
            F = fun() ->
                {_OldCount, Other} = lib_week:get_count_and_other_from_db(RoleId, Module, SubModule, Type),
                lib_week:update_count_db(RoleId, Module, SubModule, Type, Count, Other, Time),
                ok
            end,
            case catch db:transaction(F) of
                ok -> ok;
                Error ->
                    ?ERR("set_count_offline:~p~n", [Error]),
                    error
            end
    end.


%% 获取数量
%% 在线时：  同get_count一样
%% 不在线时：不在线则直接读取db
get_count_offline(RoleId, Module, Type) ->
    get_count_offline(RoleId, Module, ?DEFAULT_SUB_MODULE, Type).

%% 获取数量
%% 在线时：  同get_count一样
%% 不在线时：不在线则直接读取db
get_count_offline(RoleId, Module, SubModule, Type) ->
    Pid = misc:get_player_process(RoleId),
    case misc:is_process_alive(Pid) of
        true when Pid =:= self() ->
            get_count(RoleId, Module, SubModule, Type);
        true ->
            lib_player:apply_call(Pid,?APPLY_CALL,?MODULE,get_count,[RoleId, Module, SubModule, Type]);
        _  ->
            {Count, _Other} = get_count_and_other_from_db(RoleId, Module, SubModule, Type),
            Count
    end.

%% 获取数量(列表形式)
%% 在线时：  同get_count一样
%% 不在线时：不在线则直接读取db
%% RoleId    :: integer() 玩家id
%% List      :: [{Module::integer(), SubModule::integer(), Type::integer()}]
%% @return   :: [{ {Module::integer(), SubModule::integer(), Type::integer()}, Count::integer()}]
get_count_offline(RoleId, List) ->
    Pid = misc:get_player_process(RoleId),
    case misc:is_process_alive(Pid) of
        true when Pid =:= self() ->
            get_count(RoleId, List);
        true ->
            lib_player:apply_call(Pid,?APPLY_CALL,?MODULE,get_count,[RoleId, List]);
        _  ->
            lists:map(
                fun({Module, SubModule, Type} = Id) ->
                    {Count, _Other} = get_count_and_other_from_db(RoleId, Module, SubModule, Type),
                    {Id, Count}
                end, List)
    end.