%%%-----------------------------------
%%% @Module  : lib_counter
%%% @Author  : hek
%%% @Created : 2016.10.18
%%% @Description: 终生次数记录器
%%%-----------------------------------
-module(lib_counter).

-include("counter.hrl").
-include("common.hrl").
-include("predefine.hrl").
-export(
    [
        online/1,
        get/4,
        get_all/1,
        get_count/2,
        get_count/3,
        get_count/4,
        set_count/2,
        set_count/4,
        set_count/5,
        sync_set_count/5,
        increment/3,
        increment/4,
        plus_count/2,
        plus_count/4,
        plus_count/5,
        cut_count/5,
        new/1,
        get_refresh_time/4,
        set_refresh_time/4,
        lessthan_limit/4,
        get_count_limit/2,
        get_count_limit/3,
        update_count_db/7,
        get_count_and_other_from_db/4,
        get_count_and_refresh_time_from_db/4,
        get_count_info_from_db/4,
        get_count_from_db/4,

        get_count_offline/3,
        get_count_offline/4,
        set_count_offline/5,
        increment_offline/3,
        increment_offline/4,
        plus_count_offline/5,
    
        gm_clean/1
    ]
).

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

%% 获取整个记录器
get(RoleId, Module, SubModule, Type) ->
    {Res, ModuleId, TypeId} = get_id(Module, SubModule, Type),
    if
        Res=/=true ->
            #ets_counter{id={ModuleId, SubModule, TypeId}, count = 99999};
        true ->
            Data = get_all(RoleId),
            lists:keyfind({ModuleId, SubModule, TypeId}, #ets_counter.id, Data)
    end.

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

get_count(RoleId, List) ->
    lists:map(
      fun({Module, SubModule, Type} = Id) ->
              Count = get_count(RoleId, Module, SubModule, Type),
              {Id, Count}
      end, List).

%% 获取数量
get_count(RoleId, Module, Type) ->
    get_count(RoleId, Module, ?DEFAULT_SUB_MODULE, Type).

%% 获取数量
get_count(RoleId, Module, SubModule, TypeList) when is_list(TypeList) ->
    % F = fun(Type, Acc) -> Acc ++ [{Type, get_count(RoleId, Module, SubModule, Type)}] end,
    [{Type, get_count(RoleId, Module, SubModule, Type)} || Type <- TypeList];
    % lists:foldl(F, [], TypeList);

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

%% 设置数量
set_count(RoleId, List) ->
    F = fun
        ({ {Module, SubModule, Type}, Count}, L) ->
            D
            = case lib_counter:get(RoleId, Module, SubModule, Type) of
                false ->
                    new([Module, SubModule, Type, Count]);
                RD ->
                    RD#ets_counter{count = Count}
            end,
            [D|L]
    end,
    CounterList = lists:foldl(F, [], List),
    save_batch(RoleId, CounterList).

%% 设置数量
set_count(RoleId, Module, Type, Count) ->
    set_count(RoleId, Module, ?DEFAULT_SUB_MODULE, Type, Count).

%% 设置数量
set_count(RoleId, Module, SubModule, Type, Count) ->
    case lib_counter:get(RoleId, Module, SubModule, Type) of
        false -> save(RoleId, new([Module, SubModule, Type, Count]));
        RD -> save(RoleId, RD#ets_counter{count = Count})
    end.

%% 同步式设置数量
sync_set_count(RoleId, Module, SubModule, Type, Count) ->
    set_count(RoleId, Module, SubModule, Type, Count),
    get_count(RoleId, Module, SubModule, Type).

%% 加一操作
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, List) ->
    F = fun
        ({ {Module, SubModule, Type}, Count}, L) ->
            D
            = case lib_counter:get(RoleId, Module, SubModule, Type) of
                false ->
                    new([Module, SubModule, Type, Count]);
                RD ->
                    RD#ets_counter{count = Count + RD#ets_counter.count}
            end,
            [D|L]
    end,
    CounterList = lists:foldl(F, [], List),
    save_batch(RoleId, CounterList).

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


%% 追加数量
plus_count(RoleId, Module, SubModule, Type, Count) ->
    case lib_counter:get(RoleId, Module, SubModule, Type) of
        false -> save(RoleId, new([Module, SubModule, Type, Count]));
        RD -> save(RoleId, RD#ets_counter{count = RD#ets_counter.count + Count})
    end.

%% 扣除数量
cut_count(RoleId, Module, SubModule, Type, Count) ->
    case lib_counter:get(RoleId, Module, SubModule, Type) of
        false -> save(RoleId, new([Module, SubModule, Type, Count]));
        RD -> save(RoleId, RD#ets_counter{count = RD#ets_counter.count - Count})
    end.

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

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

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

save(RoleId, RoleDaily) ->
    NowTime = utime:unixtime(),
    NewRoleDaily = RoleDaily#ets_counter{refresh_time=NowTime},
    {ModuleId, SubModuleId, TypeId} = NewRoleDaily#ets_counter.id,
    Data = get_all(RoleId),
    Data1 = lists:keydelete(NewRoleDaily#ets_counter.id, #ets_counter.id, Data) ++ [NewRoleDaily],
    put(?COUNTER_KEY, Data1),
    db:execute(io_lib:format(?sql_counter_role_upd, [RoleId, ModuleId, SubModuleId, TypeId, NewRoleDaily#ets_counter.count,
        util:term_to_bitstring(NewRoleDaily#ets_counter.other), NowTime])).

save_batch(_RoleId, []) -> ok;
save_batch(RoleId, CounterList) ->
    NowTime = utime:unixtime(),
    F = fun
        (#ets_counter{id = Id}) ->
            lists:keyfind(Id, #ets_counter.id, CounterList) =:= false
    end,
    Data = get_all(RoleId),
    Data1 = CounterList ++ lists:filter(F, Data),
    put(?COUNTER_KEY, Data1),
    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), NowTime]) || #ets_counter{id = {ModuleId, SubModuleId, TypeId}, count = Count, other = Other} <- CounterList], ","),
    SQL = io_lib:format(?sql_counter_role_upd_batch, [ValueStr]),
    db:execute(SQL).

%% 所有数据重载
reload(RoleId) ->
    erase(?COUNTER_KEY),
    List = db:get_all(io_lib:format(?sql_counter_role_sel_all, [RoleId])),
    D = to_dict(List, []),
    put(?COUNTER_KEY, D),
    D.

%% 获取次数上限
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_counter{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_counter:get(RoleId, Module, SubModule, Type) of
        false -> 0;
        RD -> RD#ets_counter.count
    end,
    Count<Limit.

%% 直接操作数据库，适合在玩家不在线的时候调用
update_count_db(RoleId, ModuleId, SubModuleId, TypeId, Count, Other, Time) ->
    db:execute(io_lib:format(?sql_counter_role_upd, [RoleId, ModuleId, SubModuleId, TypeId, Count, util:term_to_bitstring(Other), Time])).

%% 获取次数
%% @return Count|[{TypeId, Count}]
get_count_from_db(RoleId, ModuleId, SubModuleId, TypeIds) when is_list(TypeIds) ->
    do_get_count_from_db(RoleId, ModuleId, SubModuleId, TypeIds);
get_count_from_db(RoleId, ModuleId, SubModuleId, TypeId) ->
    case do_get_count_from_db(RoleId, ModuleId, SubModuleId, [TypeId]) of
        [{TypeId, Count}] -> Count;
        _ -> 0
    end.

do_get_count_from_db(RoleId, ModuleId, SubModuleId, TypeIds) ->
    NewList = case TypeIds =/= [] of
        true ->
            Sql = io_lib:format(?sql_counter_role_sel_count, [RoleId, ModuleId, SubModuleId, util:link_list(TypeIds)]),
            List = db:get_all(Sql),
            [{TType, TCount} || [TType, TCount] <- List];
        false -> []
    end,
    F = fun(Key) ->
        {_Res, _ModuleId, TypeId} = get_id(ModuleId, SubModuleId, Key),
        case lists:keyfind(TypeId, 1, NewList) of
            {TypeId, Val} -> {TypeId, Val};
            _ -> {TypeId, 0}
        end
    end,
    lists:map(F, TypeIds).

    % F = fun(T, {Acc1, Acc2}) ->
    %     case get_config(ModuleId, SubModuleId, T) of
    %         #base_counter{} -> {Acc1, [T|Acc2]};
    %         _ -> {[{T, 0}|Acc1], [T|Acc2]}
    %     end
    % end,
    % {CounterList, SelL} = lists:foldl(F, {[], []}, TypeIds),
    % List1 = case SelL =/= [] of
    %     true ->
    %         Sql = io_lib:format(?sql_counter_role_sel_count, [RoleId, ModuleId, SubModuleId, util:link_list(SelL)]),
    %         List = db:get_all(Sql),
    %         [{TType, TCount} || [TType, TCount] <- List];
    %     false -> []
    % end,
    % F1 = fun(Key) ->
    %     case lists:keyfind(Key, 1, List1) of
    %         {Key, Val} ->
    %             {Key, Val};
    %         _ ->
    %             {Key, 0}
    %     end
    % end,
    % List2 = lists:map(F1, SelL),
    % CounterList ++ List2.

%% 获取次数，扩展数据
%% @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_counter{} ->
            Sql = io_lib:format(?sql_counter_role_sel, [RoleId, ModuleId, SubModuleId, TypeId]),
            case db:get_row(Sql) of
                [] -> {0, 0, []};
                [Count, RefreshTime, Other] -> {Count, RefreshTime, util:bitstring_to_term(Other)}
            end;
        _ -> {0, 0, []}
    end.

%% ------------------------- local function -------------------------
to_dict([], D) ->
    D;
to_dict([[Module, SubModule, Type, Count, Other, Time] | T], D) ->
    to_dict(T, D ++ [#ets_counter{
            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, Module, Type};
        #base_counter{} ->
            {true, Module, Type}
    end.

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


%% 获取数量
%% 在线时：  同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 = lib_counter:get_count_from_db(RoleId, Module, SubModule, Type),
            Count
    end.


%% 设置数量，不在线则处理离线
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_counter:get_count_and_other_from_db(RoleId, Module, SubModule, Type),
                lib_counter: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.

%% 加一操作
%% 在线时：  同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_counter:get_count_and_other_from_db(RoleId, Module, SubModule, Type),
                lib_counter: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.

gm_clean(RoleId) ->
    db:execute(io_lib:format(<<"DELETE FROM counter WHERE role_id=~p">>, [RoleId])),
    reload(RoleId).
