%% Copyright 2015 Kolab Systems AG (http://www.kolabsys.com)
%%
%% Aaron Seigo (Kolab Systems) <seigo a kolabsys.com>
%%
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%%
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%% GNU General Public License for more details.
%%
%% You should have received a copy of the GNU General Public License
%% along with this program.  If not, see <http://www.gnu.org/licenses/>.

-module(kolab_guam_listener).

-behaviour(supervisor).
-define(DEFAULT_IMAP_PORT, 143).
-define(DEFAULT_LISTENER_POOL_SIZE, 10).

%% API
-export([start_link/2, create_initial_listeners/2, cleanup/1]).

%% gen_supervisor callbacks
-export([init/1]).

%% state record definition
%%TODO: support reconfiguration requests

%% public API
start_link(Name, Config) -> supervisor:start_link(?MODULE, [Name, Config]).

%% gen_server API
init([Name, Config]) ->
    Host = proplists:get_value(host, Config, none),
    NetIface = proplists:get_value(net_iface, Config, none),
    Port = proplists:get_value(port, Config, ?DEFAULT_IMAP_PORT),
    ImplicitTLS = proplists:get_value(implicit_tls, Config, false),
    TLSConfig = proplists:get_value(tls_config, Config, []),
    Rules = proplists:get_value(rules, Config, []),
    Options = listen_options(NetIface, Host),
    lager:info("Starting listener \"~p\" on port ~B (~p) with ~B rules", [Name, Port, Options, length(Rules)]),
    { ok, ListenSocket } = listen(ImplicitTLS, Port, Options),
    spawn_link(?MODULE, cleanup, [ListenSocket]),
    %% setting up the initial listeners must be done async to allow the init to be done and the supervisor to be setup
    ListenerPoolSize = proplists:get_value(listener_pool_size, Config, ?DEFAULT_LISTENER_POOL_SIZE),
    spawn_link(kolab_guam_listener, create_initial_listeners, [ListenerPoolSize, self()]),
    ImapConfig = imap_config(proplists:get_value(imap_server, Config, none)),
    lager:debug("ImapConfig is ~p", [ImapConfig]),
    {ok, { { simple_one_for_one, 60, 3600 },
         [ { session, { kolab_guam_session, start_link, [self(), ListenSocket, ImapConfig, ImplicitTLS, TLSConfig, Rules] }, temporary, 1000, worker, [kolab_guam_session] } ]
         }
    }.

imap_config(none) -> kolab_guam_sup:default_imap_server_config();
imap_config(Backend) -> kolab_guam_sup:imap_server_config(Backend).

-spec listen_options(Iface :: string(), Hostname :: string()) -> list().
listen_options(none, none) -> default_listen_options();
listen_options(none, Hostname) ->
    case inet:gethostbyname(Hostname) of
        { ok, { hostent, _HostName, _Unused, inet, _Ver, [IP] } } ->
            [ { ip, IP } | default_listen_options() ];
        _ ->
            listen_options(none, none)
    end;
listen_options(Iface, Hostname) ->
    { ok, Ifaces } = inet:getifaddrs(),
    case proplists:get_value(Iface, Ifaces) of
        undefined -> listen_options(none, Hostname);
        Info -> Addr = proplists:get_value(addr, Info, none),
                listen_options(none, Addr)
    end.

default_listen_options() -> [ { keepalive, true }, { reuseaddr, true }, {active, false}, inet6 ].

create_initial_listeners(ListenerPoolSize, PID) when is_pid(PID) ->
    lager:debug("Creating session pool of size ~p for listener ~p", [ListenerPoolSize, PID]),
    [ supervisor:start_child(PID, []) || _ <- lists:seq(1, ListenerPoolSize) ].

cleanup(Socket) ->
    process_flag(trap_exit, true),
    receive
        { 'EXIT', _PID, _  } -> ok;
        _ -> cleanup(Socket)
    end,
    gen_tcp:close(Socket).

listen(true, Port, Options) -> gen_tcp:listen(Port, Options);
listen(_ImplicitTLS, Port, Options) -> gen_tcp:listen(Port, Options).

%% private API

