src/qdate_srv.erl

% vim: ts=4 sw=4 et
% Copyright (c) 2013-2021 Jesse Gumm
% See LICENSE for licensing information.

-module(qdate_srv).
-behaviour(gen_server).

-export([
    set_timezone/1,
    set_timezone/2,
    get_timezone/0,
    get_timezone/1,
    clear_timezone/0,
    clear_timezone/1,

    register_parser/1,
    register_parser/2,
    get_parsers/0,
    deregister_parsers/0,
    deregister_parser/1,

    register_format/2,
    get_format/1,
    deregister_format/1,
    get_formats/0
]).


%% API
-export([start_link/0]).

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


%% Simple wrappers for unique keys
-define(BASETAG, qdate_var).
-define(KEY(Name), {?BASETAG, Name}).

-define(TZTAG, qdate_tz).
-define(TZKEY(Name), {?TZTAG, Name}).
-define(PARSERTAG, qdate_parser).
-define(PARSERKEY(Name), {?PARSERTAG, Name}).
-define(FORMATTAG, qdate_format).
-define(FORMATKEY(Name), {?FORMATTAG, Name}).

-define(SERVER, ?MODULE).
-define(TABLE, ?MODULE).

-record(state, {}).

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

init([]) ->
    error_logger:info_msg("Creating qdate ETS Table: ~p",[?TABLE]),
    ?TABLE = ets:new(?TABLE, [public, {read_concurrency, true}, named_table]),
    {ok, #state{}}.

handle_call({set, Key, Val}, _From, State) ->
    ets:insert(?TABLE, {Key, Val}),
    {reply, ok, State};
handle_call({unset, Key}, _From, State) ->
    ets:delete(?TABLE, Key),
    {reply, ok, State};
handle_call(_, _From, State) ->
    {reply, invalid_request, State}.

handle_cast(_Msg, State) ->
        {noreply, State}.

handle_info(_Info, State) ->
        {noreply, State}.

terminate(_Reason, _State) ->
        ok.

code_change(_OldVsn, State, _Extra) ->
        {ok, State}.



%% PUBLIC API FUNCTIONS

set_timezone(TZ) ->
    put_pd(?TZTAG, TZ).

set_timezone(Key, TZ) ->
    set_env(?TZKEY(Key), TZ).

get_timezone() ->
    get_pd(?TZTAG).

get_timezone(Key) ->
    get_env(?TZKEY(Key)).

clear_timezone() ->
    unset_pd(?TZTAG).

clear_timezone(Key) ->
    unset_env(?TZKEY(Key)).

register_parser(Parser) when is_function(Parser,1) ->
    register_parser(erlang:make_ref(),Parser).

register_parser(Key,Parser) when is_function(Parser,1) ->
    set_env(?PARSERKEY(Key), Parser).

deregister_parser(Key) ->
    unset_env(?PARSERKEY(Key)).

deregister_parsers() ->
    [deregister_parser(Key) || {Key, _} <- get_parsers()].

get_parsers() ->
    get_all_env(?PARSERTAG). 

register_format(Key, Format) ->
    set_env(?FORMATKEY(Key), Format).

get_format(Key) ->
    get_env(?FORMATKEY(Key)).

deregister_format(Key) ->
    unset_env(?FORMATKEY(Key)).
   
get_formats() ->
    get_all_env(?FORMATTAG).

%% PRIVATE TOOLS

%% App Vars

set_env(Key, Val) ->
    gen_server:call(?SERVER, {set, ?KEY(Key), Val}).

get_env(Key) ->
    get_env(Key, undefined).

get_env(Key, Default) ->
    case ets:lookup(?TABLE, ?KEY(Key)) of
        [{__Key, Val}] -> Val;
        [] -> Default
    end.

unset_env(Key) ->
    gen_server:call(?SERVER, {unset, ?KEY(Key)}).

get_all_env(FilterTag) ->
    try ets:tab2list(?TABLE) of
        All ->
            [{Key, V} || {{?BASETAG, {Tag, Key}}, V} <- All, Tag==FilterTag]
    catch
        error:badarg:S ->
            Msg = maybe_start_msg(),
            logger:error(Msg),
          
            error({qdate_get_all_env_failed, #{
                original_error => {error, badarg, S}
            }})
    end.

maybe_start_msg() ->
    "Attempting to read qdate environment failed (qdate_srv:get_all_env/1).\n"
    "qdate may not have been started properly.\n"
    "Please ensure qdate is started properly by either:\n"
    "* Putting qdate in the 'applications' key in your .app.src or .app file, or\n"
    "* Starting it manually with application:ensure_all_started(qdate) or qdate:start().".


%% ProcDic Vars

get_pd(Key) ->
    erlang:get(?KEY(Key)).

put_pd(Key, Val) ->
    erlang:put(?KEY(Key), Val),
    ok.

unset_pd(Key) ->
    put_pd(Key, undefined).