%% ``Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%%
%%     $Id: httpd_manager.erl,v 1.1 2008/12/17 09:53:34 mikpe Exp $

-module(httpd_manager).

-include("httpd.hrl").
-include("httpd_verbosity.hrl").

-behaviour(gen_server).

%% External API
-export([start/2, start/3, start_link/2, start_link/3, stop/1, restart/1]).

%% Internal API
-export([new_connection/1, done_connection/1]).

%% Module API
-export([config_lookup/2, config_lookup/3,
	 config_multi_lookup/2, config_multi_lookup/3,
	 config_match/2, config_match/3]).

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


%% Management exports
-export([block/2, block/3, unblock/1]).
-export([get_admin_state/1, get_usage_state/1]).
-export([is_busy/1,is_busy/2,is_busy_or_blocked/1,is_blocked/1]). %% ???????
-export([get_status/1, get_status/2]).
-export([verbosity/2, verbosity/3]).


-export([c/1]).

-record(state,{socket_type  = ip_comm,
	       config_file,
	       config_db    = null,
	       connections, %% Current request handlers
	       admin_state  = unblocked,
	       blocker_ref  = undefined,
	       blocking_tmr = undefined,
	       status       = []}).


c(Port) ->
    Ref = httpd_util:make_name("httpd",undefined,Port),
    gen_server:call(Ref, fake_close).


%%
%% External API
%%

start(ConfigFile, ConfigList) ->
    start(ConfigFile, ConfigList, []).

start(ConfigFile, ConfigList, Verbosity) ->
    Port = httpd_util:key1search(ConfigList,port,80),
    Addr = httpd_util:key1search(ConfigList,bind_address),
    Name = make_name(Addr,Port),
    ?LOG("start -> Name = ~p",[Name]),
    gen_server:start({local,Name},?MODULE,
		     [ConfigFile, ConfigList, Addr, Port, Verbosity],[]).

start_link(ConfigFile, ConfigList) ->
    start_link(ConfigFile, ConfigList, []).

start_link(ConfigFile, ConfigList, Verbosity) ->
    Port = httpd_util:key1search(ConfigList,port,80),
    Addr = httpd_util:key1search(ConfigList,bind_address),
    Name = make_name(Addr,Port),
    ?LOG("start_link -> Name = ~p",[Name]),
    gen_server:start_link({local, Name},?MODULE,
			  [ConfigFile, ConfigList, Addr, Port, Verbosity],[]).

%% stop

stop(ServerRef) ->
    gen_server:call(ServerRef, stop).

%% restart

restart(ServerRef) ->
    gen_server:call(ServerRef, restart).


%%%----------------------------------------------------------------

block(ServerRef, disturbing) ->
    call(ServerRef,block);

block(ServerRef, non_disturbing) ->
    do_block(ServerRef, non_disturbing, infinity).

block(ServerRef, Method, Timeout) ->
    do_block(ServerRef, Method, Timeout).


%% The reason for not using call here, is that the manager cannot
%% _wait_ for completion of the requests. It must be able to do
%% do other things at the same time as the blocking goes on.
do_block(ServerRef, Method, infinity) ->
    Ref = make_ref(),
    cast(ServerRef, {block, Method, infinity, self(), Ref}),
    receive
	{block_reply, Reply, Ref} ->
	    Reply
    end;
do_block(ServerRef,Method,Timeout) when Timeout > 0 ->
    Ref = make_ref(),
    cast(ServerRef,{block,Method,Timeout,self(),Ref}),
    receive
	{block_reply,Reply,Ref} ->
	    Reply
    end.


%%%----------------------------------------------------------------

%% unblock

unblock(ServerRef) ->
    call(ServerRef,unblock).

%% get admin/usage state

get_admin_state(ServerRef) ->
    call(ServerRef,get_admin_state).

get_usage_state(ServerRef) ->
    call(ServerRef,get_usage_state).


%% get_status

get_status(ServerRef) ->
    gen_server:call(ServerRef,get_status).

get_status(ServerRef,Timeout) ->
    gen_server:call(ServerRef,get_status,Timeout).


verbosity(ServerRef,Verbosity) ->
    verbosity(ServerRef,all,Verbosity).

verbosity(ServerRef,all,Verbosity) ->
    gen_server:call(ServerRef,{verbosity,all,Verbosity});
verbosity(ServerRef,manager,Verbosity) ->
    gen_server:call(ServerRef,{verbosity,manager,Verbosity});
verbosity(ServerRef,request,Verbosity) ->
    gen_server:call(ServerRef,{verbosity,request,Verbosity});
verbosity(ServerRef,acceptor,Verbosity) ->
    gen_server:call(ServerRef,{verbosity,acceptor,Verbosity});
verbosity(ServerRef,security,Verbosity) ->
    gen_server:call(ServerRef,{verbosity,security,Verbosity});
verbosity(ServerRef,auth,Verbosity) ->
    gen_server:call(ServerRef,{verbosity,auth,Verbosity}).

%%
%% Internal API
%%


%% new_connection

new_connection(Manager) ->
    gen_server:call(Manager, {new_connection, self()}).

%% done

done_connection(Manager) ->
    gen_server:cast(Manager, {done_connection, self()}).


%% is_busy(ServerRef) -> true | false
%%
%% Tests if the server is (in usage state) busy,
%% i.e. has rached the heavy load limit.
%%

is_busy(ServerRef) ->
    gen_server:call(ServerRef,is_busy).

is_busy(ServerRef,Timeout) ->
    gen_server:call(ServerRef,is_busy,Timeout).


%% is_busy_or_blocked(ServerRef) -> busy | blocked | false
%%
%% Tests if the server is busy (usage state), i.e. has rached,
%% the heavy load limit, or blocked (admin state) .
%%

is_busy_or_blocked(ServerRef) ->
    gen_server:call(ServerRef,is_busy_or_blocked).


%% is_blocked(ServerRef) -> true | false
%%
%% Tests if the server is blocked (admin state) .
%%

is_blocked(ServerRef) ->
    gen_server:call(ServerRef,is_blocked).


%%
%% Module API. Theese functions are intended for use from modules only.
%%

config_lookup(Port, Query) ->
    config_lookup(undefined, Port, Query).
config_lookup(Addr, Port, Query) ->
    Name = httpd_util:make_name("httpd",Addr,Port),
    gen_server:call(whereis(Name), {config_lookup, Query}).

config_multi_lookup(Port, Query) ->
    config_multi_lookup(undefined,Port,Query).
config_multi_lookup(Addr,Port, Query) ->
    Name = httpd_util:make_name("httpd",Addr,Port),
    gen_server:call(whereis(Name), {config_multi_lookup, Query}).

config_match(Port, Pattern) ->
    config_match(undefined,Port,Pattern).
config_match(Addr, Port, Pattern) ->
    Name = httpd_util:make_name("httpd",Addr,Port),
    gen_server:call(whereis(Name), {config_match, Pattern}).


%%
%% Server call-back functions
%%

%% init

init([ConfigFile, ConfigList, Addr, Port, Verbosity]) ->
    process_flag(trap_exit, true),
    case (catch do_init(ConfigFile, ConfigList, Addr, Port, Verbosity)) of
	{error, Reason} ->
	    ?vlog("failed starting server: ~p", [Reason]),
	    {stop, Reason};
	{ok, State} ->
	    {ok, State}
    end.


do_init(ConfigFile, ConfigList, Addr, Port, Verbosity) ->
    put(sname,man),
    set_verbosity(Verbosity),
    ?vlog("starting",[]),
    ConfigDB   = do_initial_store(ConfigList),
    ?vtrace("config db: ~p", [ConfigDB]),
    SocketType = httpd_socket:config(ConfigDB),
    ?vtrace("socket type: ~p, now start acceptor", [SocketType]),
    case httpd_acceptor_sup:start_acceptor(SocketType, Addr, Port, ConfigDB) of
	{ok, Pid} ->
	    ?vtrace("acceptor started: ~p", [Pid]),
	    Status = [{max_conn,0}, {last_heavy_load,never},
		      {last_connection,never}],
	    State  = #state{socket_type = SocketType,
			    config_file = ConfigFile,
			    config_db   = ConfigDB,
			    connections = [],
			    status      = Status},
	    ?vdebug("started",[]),
	    {ok, State};
	Else ->
	    Else
    end.


do_initial_store(ConfigList) ->
    case httpd_conf:store(ConfigList) of
	{ok, ConfigDB} ->
	    ConfigDB;
	{error, Reason} ->
	    ?vinfo("failed storing configuration: ~p",[Reason]),
	    throw({error, Reason})
    end.



%% handle_call

handle_call(stop, _From, State) ->
    ?vlog("stop",[]),
    {stop, normal, ok, State};

handle_call({config_lookup, Query}, _From, State) ->
    ?vlog("config lookup: Query = ~p",[Query]),
    Res = httpd_util:lookup(State#state.config_db, Query),
    ?vdebug("config lookup result: ~p",[Res]),
    {reply, Res, State};

handle_call({config_multi_lookup, Query}, _From, State) ->
    ?vlog("multi config lookup: Query = ~p",[Query]),
    Res = httpd_util:multi_lookup(State#state.config_db, Query),
    ?vdebug("multi config lookup result: ~p",[Res]),
    {reply, Res, State};

handle_call({config_match, Query}, _From, State) ->
    ?vlog("config match: Query = ~p",[Query]),
    Res = ets:match_object(State#state.config_db, Query),
    ?vdebug("config match result: ~p",[Res]),
    {reply, Res, State};

handle_call(get_status, _From, State) ->
    ?vdebug("get status",[]),
    ManagerStatus  = manager_status(self()),
    %% AuthStatus     = auth_status(get(auth_server)),
    %% SecStatus      = sec_status(get(sec_server)),
    %% AccStatus      = sec_status(get(acceptor_server)),
    S1 = [{current_conn,length(State#state.connections)}|State#state.status]++
	[ManagerStatus],
    ?vtrace("status = ~p",[S1]),
    {reply,S1,State};

handle_call(is_busy, From, State) ->
    Reply = case get_ustate(State) of
		busy ->
		    true;
		_ ->
		    false
	  end,
    ?vlog("is busy: ~p",[Reply]),
    {reply,Reply,State};

handle_call(is_busy_or_blocked, From, State) ->
    Reply =
	case get_astate(State) of
	    unblocked ->
		case get_ustate(State) of
		    busy ->
			busy;
		    _ ->
			false
		end;
	    _ ->
		blocked
	  end,
    ?vlog("is busy or blocked: ~p",[Reply]),
    {reply,Reply,State};

handle_call(is_blocked, From, State) ->
    Reply =
	case get_astate(State) of
	    unblocked ->
		false;
	    _ ->
		true
	  end,
    ?vlog("is blocked: ~p",[Reply]),
    {reply,Reply,State};

handle_call(get_admin_state, From, State) ->
    Reply = get_astate(State),
    ?vlog("admin state: ~p",[Reply]),
    {reply,Reply,State};

handle_call(get_usage_state, From, State) ->
    Reply = get_ustate(State),
    ?vlog("usage state: ~p",[Reply]),
    {reply,Reply,State};

handle_call({verbosity,Who,Verbosity}, From, State) ->
    V = ?vvalidate(Verbosity),
    ?vlog("~n   Set new verbosity to ~p for ~p",[V,Who]),
    Reply = set_verbosity(Who,V,State),
    {reply,Reply,State};

handle_call(restart, From, State) when State#state.admin_state == blocked ->
    ?vlog("restart",[]),
    case handle_restart(State) of
	{stop, Reply,S1} ->
	    {stop, Reply, S1};
	{_, Reply, S1} ->
	    {reply,Reply,S1}
    end;

handle_call(restart, From, State) ->
    ?vlog("restart(~p)",[State#state.admin_state]),
    {reply,{error,{invalid_admin_state,State#state.admin_state}},State};

handle_call(block, From, State) ->
    ?vlog("block(disturbing)",[]),
    {Reply,S1} = handle_block(State),
    {reply,Reply,S1};

handle_call(unblock, {From,_Tag}, State) ->
    ?vlog("unblock",[]),
    {Reply,S1} = handle_unblock(State,From),
    {reply, Reply, S1};

handle_call({new_connection, Pid}, From, State) ->
    ?vlog("~n   New connection (~p) when connection count = ~p",
	  [Pid,length(State#state.connections)]),
    {S, S1} = handle_new_connection(State, Pid),
    Reply = {S, get(request_handler_verbosity)},
    {reply, Reply, S1};

handle_call(Request, From, State) ->
    ?vinfo("~n   unknown request '~p' from ~p", [Request,From]),
    String =
	lists:flatten(
	  io_lib:format("Unknown request "
			"~n   ~p"
			"~nto manager (~p)"
			"~nfrom ~p",
			[Request, self(), From])),
    report_error(State,String),
    {reply, ok, State}.


%% handle_cast

handle_cast({done_connection, Pid}, State) ->
    ?vlog("~n   Done connection (~p)", [Pid]),
    S1 = handle_done_connection(State, Pid),
    {noreply, S1};

handle_cast({block, disturbing, Timeout, From, Ref}, State) ->
    ?vlog("block(disturbing,~p)",[Timeout]),
    S1 = handle_block(State, Timeout, From, Ref),
    {noreply,S1};

handle_cast({block, non_disturbing, Timeout, From, Ref}, State) ->
    ?vlog("block(non-disturbing,~p)",[Timeout]),
    S1 = handle_nd_block(State, Timeout, From, Ref),
    {noreply,S1};

handle_cast(Message, State) ->
    ?vinfo("~n   received unknown message '~p'",[Message]),
    String =
	lists:flatten(
	  io_lib:format("Unknown message "
			"~n   ~p"
			"~nto manager (~p)",
			[Message, self()])),
    report_error(State, String),
    {noreply, State}.

%% handle_info

handle_info({block_timeout, Method}, State) ->
    ?vlog("received block_timeout event",[]),
    S1 = handle_block_timeout(State,Method),
    {noreply, S1};

handle_info({'DOWN', Ref, process, _Object, Info}, State) ->
    ?vlog("~n   down message for ~p",[Ref]),
    S1 =
	case State#state.blocker_ref of
	    Ref ->
		handle_blocker_exit(State);
	    _ ->
		%% Not our blocker, so ignore
		State
	end,
    {noreply, S1};

handle_info({'EXIT', Pid, normal}, State) ->
    ?vdebug("~n   Normal exit message from ~p", [Pid]),
    {noreply, State};

handle_info({'EXIT', Pid, blocked}, S) ->
    ?vdebug("blocked exit signal from request handler (~p)", [Pid]),
    {noreply, S};

handle_info({'EXIT', Pid, Reason}, State) ->
    ?vlog("~n   Exit message from ~p for reason ~p",[Pid, Reason]),
    S1 = check_connections(State, Pid, Reason),
    {noreply, S1};

handle_info(Info, State) ->
    ?vinfo("~n   received unknown info '~p'",[Info]),
    String =
	lists:flatten(
	  io_lib:format("Unknown info "
			"~n   ~p"
			"~nto manager (~p)",
			[Info, self()])),
    report_error(State, String),
    {noreply, State}.


%% terminate

terminate(R, #state{config_db = Db}) ->
    ?vlog("Terminating for reason: ~n   ~p", [R]),
    httpd_conf:remove_all(Db),
    ok.


%% code_change({down,ToVsn}, State, Extra)
%%
%% NOTE:
%% Actually upgrade from 2.5.1 to 2.5.3 and downgrade from
%% 2.5.3 to 2.5.1 is done with an application restart, so
%% these function is actually never used. The reason for keeping
%% this stuff is only for future use.
%%
code_change({down,ToVsn},State,Extra) ->
    {ok,State};

%% code_change(FromVsn, State, Extra)
%%
code_change(FromVsn,State,Extra) ->
    {ok,State}.



%% -------------------------------------------------------------------------
%% check_connection
%%
%%
%%
%%

check_connections(#state{connections = []} = State, _Pid, _Reason) ->
    State;
check_connections(#state{admin_state = shutting_down,
			 connections = Connections} = State, Pid, Reason) ->
    %% Could be a crashing request handler
    case lists:delete(Pid, Connections) of
	[] -> % Crashing request handler => block complete
	    String =
		lists:flatten(
		  io_lib:format("request handler (~p) crashed:"
				"~n   ~p", [Pid, Reason])),
	    report_error(State, String),
	    ?vlog("block complete",[]),
	    demonitor_blocker(State#state.blocker_ref),
	    {Tmr,From,Ref} = State#state.blocking_tmr,
	    ?vlog("(possibly) stop block timer",[]),
	    stop_block_tmr(Tmr),
	    ?vlog("and send the reply",[]),
	    From ! {block_reply,ok,Ref},
	    State#state{admin_state = blocked, connections = [],
			blocker_ref = undefined};
	Connections1 ->
	    State#state{connections = Connections1}
    end;
check_connections(#state{connections = Connections} = State, Pid, Reason) ->
    case lists:delete(Pid, Connections) of
	Connections -> % Not a request handler, so ignore
	    State;
	Connections1 ->
	    String =
		lists:flatten(
		  io_lib:format("request handler (~p) crashed:"
				"~n   ~p", [Pid, Reason])),
	    report_error(State, String),
	    State#state{connections = lists:delete(Pid, Connections)}
    end.


%% -------------------------------------------------------------------------
%% handle_[new | done]_connection
%%
%%
%%
%%

handle_new_connection(State, Handler) ->
    UsageState = get_ustate(State),
    AdminState = get_astate(State),
    handle_new_connection(UsageState, AdminState, State, Handler).

handle_new_connection(busy, unblocked, State, Handler) ->
    Status = update_heavy_load_status(State#state.status),
    {{reject, busy},
     State#state{status = Status}};

handle_new_connection(_UsageState, unblocked, State, Handler) ->
    Connections = State#state.connections,
    Status      = update_connection_status(State#state.status,
					   length(Connections)+1),
    link(Handler),
    {accept,
     State#state{connections = [Handler|Connections], status = Status}};

handle_new_connection(_UsageState, _AdminState, State, _Handler) ->
    {{reject, blocked},
     State}.


handle_done_connection(#state{admin_state = shutting_down,
			      connections = Connections} = State, Handler) ->
    unlink(Handler),
    case lists:delete(Handler, Connections) of
	[] -> % Ok, block complete
	    ?vlog("block complete",[]),
	    demonitor_blocker(State#state.blocker_ref),
	    {Tmr,From,Ref} = State#state.blocking_tmr,
	    ?vlog("(possibly) stop block timer",[]),
	    stop_block_tmr(Tmr),
	    ?vlog("and send the reply",[]),
	    From ! {block_reply,ok,Ref},
	    State#state{admin_state = blocked, connections = [],
			blocker_ref = undefined};
	Connections1 ->
	    State#state{connections = Connections1}
    end;

handle_done_connection(#state{connections = Connections} = State, Handler) ->
    State#state{connections = lists:delete(Handler, Connections)}.


%% -------------------------------------------------------------------------
%% handle_block
%%
%%
%%
%%
handle_block(#state{admin_state = AdminState} = S) ->
    handle_block(S, AdminState).

handle_block(S,unblocked) ->
    %% Kill all connections
    ?vtrace("handle_block(unblocked) -> kill all request handlers",[]),
%%    [exit(Pid,blocked) || Pid <- S#state.connections],
    [kill_handler(Pid) || Pid <- S#state.connections],
    {ok,S#state{connections = [], admin_state = blocked}};
handle_block(S,blocked) ->
    ?vtrace("handle_block(blocked) -> already blocked",[]),
    {ok,S};
handle_block(S,shutting_down) ->
    ?vtrace("handle_block(shutting_down) -> ongoing...",[]),
    {{error,shutting_down},S}.


kill_handler(Pid) ->
    ?vtrace("kill request handler: ~p",[Pid]),
    exit(Pid, blocked).
%%    exit(Pid, kill).

handle_block(S,Timeout,From,Ref) when Timeout >= 0 ->
    do_block(S,Timeout,From,Ref);

handle_block(S,Timeout,From,Ref) ->
    Reply = {error,{invalid_block_request,Timeout}},
    From ! {block_reply,Reply,Ref},
    S.

do_block(S,Timeout,From,Ref) ->
    case S#state.connections of
	[] ->
	    %% Already in idle usage state => go directly to blocked
	    ?vdebug("do_block -> already in idle usage state",[]),
	    From ! {block_reply,ok,Ref},
	    S#state{admin_state = blocked};
	_ ->
	    %% Active or Busy usage state => go to shutting_down
	    ?vdebug("do_block -> active or busy usage state",[]),
	    %% Make sure we get to know if blocker dies...
	    ?vtrace("do_block -> create blocker monitor",[]),
	    MonitorRef = monitor_blocker(From),
	    ?vtrace("do_block -> (possibly) start block timer",[]),
	    Tmr = {start_block_tmr(Timeout,disturbing),From,Ref},
	    S#state{admin_state = shutting_down,
		    blocker_ref = MonitorRef, blocking_tmr = Tmr}
    end.

handle_nd_block(S,infinity,From,Ref) ->
    do_nd_block(S,infinity,From,Ref);

handle_nd_block(S,Timeout,From,Ref) when Timeout >= 0 ->
    do_nd_block(S,Timeout,From,Ref);

handle_nd_block(S,Timeout,From,Ref) ->
    Reply = {error,{invalid_block_request,Timeout}},
    From ! {block_reply,Reply,Ref},
    S.

do_nd_block(S,Timeout,From,Ref) ->
    case S#state.connections of
	[] ->
	    %% Already in idle usage state => go directly to blocked
	    ?vdebug("do_nd_block -> already in idle usage state",[]),
	    From ! {block_reply,ok,Ref},
	    S#state{admin_state = blocked};
	_ ->
	    %% Active or Busy usage state => go to shutting_down
	    ?vdebug("do_nd_block -> active or busy usage state",[]),
	    %% Make sure we get to know if blocker dies...
	    ?vtrace("do_nd_block -> create blocker monitor",[]),
	    MonitorRef = monitor_blocker(From),
	    ?vtrace("do_nd_block -> (possibly) start block timer",[]),
	    Tmr = {start_block_tmr(Timeout,non_disturbing),From,Ref},
	    S#state{admin_state = shutting_down,
		    blocker_ref = MonitorRef, blocking_tmr = Tmr}
    end.

handle_block_timeout(S,Method) ->
    %% Time to take this to the road...
    demonitor_blocker(S#state.blocker_ref),
    handle_block_timeout1(S,Method,S#state.blocking_tmr).

handle_block_timeout1(S,non_disturbing,{_,From,Ref}) ->
    ?vdebug("handle_block_timeout1(non-disturbing) -> send reply: timeout",[]),
    From ! {block_reply,{error,timeout},Ref},
    S#state{admin_state = unblocked,
	    blocker_ref = undefined, blocking_tmr = undefined};

handle_block_timeout1(S,disturbing,{_,From,Ref}) ->
    ?vdebug("handle_block_timeout1(disturbing) -> kill all connections",[]),
    [exit(Pid,blocked) || Pid <- S#state.connections],

    ?vdebug("handle_block_timeout1 -> send reply: ok",[]),
    From ! {block_reply,ok,Ref},
    S#state{admin_state = blocked,    connections = [],
	    blocker_ref = undefined, blocking_tmr = undefined};

handle_block_timeout1(S,Method,{_,From,Ref}) ->
    ?vinfo("received block timeout with unknown block method:"
	   "~n   Method:  ~p",[Method]),
    From ! {block_reply,{error,{unknown_block_method,Method}},Ref},
    S#state{admin_state = blocked,    connections = [],
	    blocker_ref = undefined, blocking_tmr = undefined};

handle_block_timeout1(S,Method,TmrInfo) ->
    ?vinfo("received block timeout with erroneous timer info:"
	   "~n   Method:  ~p"
	   "~n   TmrInfo: ~p",[Method,TmrInfo]),
    S#state{admin_state = unblocked,
	    blocker_ref = undefined, blocking_tmr = undefined}.

handle_unblock(S,FromA) ->
    handle_unblock(S,FromA,S#state.admin_state).

handle_unblock(S,_FromA,unblocked) ->
    {ok,S};
handle_unblock(S,FromA,_AdminState) ->
    ?vtrace("handle_unblock -> (possibly) stop block timer",[]),
    stop_block_tmr(S#state.blocking_tmr),
    case S#state.blocking_tmr of
	{Tmr,FromB,Ref} ->
	    %% Another process is trying to unblock
	    %% Inform the blocker
	    FromB ! {block_reply, {error,{unblocked,FromA}},Ref};
	_ ->
	    ok
    end,
    {ok,S#state{admin_state = unblocked, blocking_tmr = undefined}}.

%% The blocker died so we give up on the block.
handle_blocker_exit(S) ->
    {Tmr,_From,_Ref} = S#state.blocking_tmr,
    ?vtrace("handle_blocker_exit -> (possibly) stop block timer",[]),
    stop_block_tmr(Tmr),
    S#state{admin_state = unblocked,
	    blocker_ref = undefined, blocking_tmr = undefined}.



%% -------------------------------------------------------------------------
%% handle_restart
%%
%%
%%
%%
handle_restart(#state{config_file = undefined} = State) ->
    {continue, {error, undefined_config_file}, State};
handle_restart(#state{config_db = Db, config_file = ConfigFile} = State) ->
    ?vtrace("load new configuration",[]),
    {ok, Config} = httpd_conf:load(ConfigFile),
    ?vtrace("check for illegal changes (addr, port and socket-type)",[]),
    case (catch check_constant_values(Db, Config)) of
	ok ->
	    %% If something goes wrong between the remove
	    %% and the store where fu-ed
	    ?vtrace("remove old configuration, now hold you breath...",[]),
	    httpd_conf:remove_all(Db),
	    ?vtrace("store new configuration",[]),
	    case httpd_conf:store(Config) of
		{ok, NewConfigDB} ->
		    ?vlog("restart done, puh!",[]),
		    {continue, ok, State#state{config_db = NewConfigDB}};
		Error ->
		    ?vlog("failed store new config: ~n   ~p",[Error]),
		    {stop, Error, State}
	    end;
	Error ->
	    ?vlog("restart NOT performed due to:"
		  "~n   ~p",[Error]),
	    {continue, Error, State}
    end.


check_constant_values(Db, Config) ->
    %% Check port number
    ?vtrace("check_constant_values -> check port number",[]),
    Port = httpd_util:lookup(Db,port),
    case httpd_util:key1search(Config,port) of  %% MUST be equal
	Port ->
	    ok;
	OtherPort ->
	    throw({error,{port_number_changed,Port,OtherPort}})
    end,

    %% Check bind address
    ?vtrace("check_constant_values -> check bind address",[]),
    Addr = httpd_util:lookup(Db,bind_address),
    case httpd_util:key1search(Config,bind_address) of  %% MUST be equal
	Addr ->
	    ok;
	OtherAddr ->
	    throw({error,{addr_changed,Addr,OtherAddr}})
    end,

    %% Check socket type
    ?vtrace("check_constant_values -> check socket type",[]),
    SockType = httpd_util:lookup(Db, com_type),
    case httpd_util:key1search(Config, com_type) of  %% MUST be equal
	SockType ->
	    ok;
	OtherSockType ->
	    throw({error,{sock_type_changed,SockType,OtherSockType}})
    end,
    ?vtrace("check_constant_values -> done",[]),
    ok.


%% get_ustate(State) -> idle | active | busy
%%
%% Retrieve the usage state of the HTTP server:
%%   0 active connection            -> idle
%%   max_clients active connections -> busy
%%   Otherwise                      -> active
%%
get_ustate(State) ->
    get_ustate(length(State#state.connections),State).

get_ustate(0,_State) ->
    idle;
get_ustate(ConnectionCnt,State) ->
    ConfigDB = State#state.config_db,
    case httpd_util:lookup(ConfigDB, max_clients, 150) of
	ConnectionCnt ->
	    busy;
	_ ->
	    active
    end.


get_astate(S) -> S#state.admin_state.


%% Timer handling functions
start_block_tmr(infinity,_) ->
    undefined;
start_block_tmr(T,M) ->
    erlang:send_after(T,self(),{block_timeout,M}).

stop_block_tmr(undefined) ->
    ok;
stop_block_tmr(Ref) ->
    erlang:cancel_timer(Ref).


%% Monitor blocker functions
monitor_blocker(Pid) when pid(Pid) ->
    case (catch erlang:monitor(process,Pid)) of
	MonitorRef ->
	    MonitorRef;
	{'EXIT',Reason} ->
	    undefined
    end;
monitor_blocker(_) ->
    undefined.

demonitor_blocker(undefined) ->
    ok;
demonitor_blocker(Ref) ->
    (catch erlang:demonitor(Ref)).


%% Some status utility functions

update_heavy_load_status(Status) ->
    update_status_with_time(Status,last_heavy_load).

update_connection_status(Status,ConnCount) ->
    S1 = case lists:keysearch(max_conn,1,Status) of
	     {value,{max_conn,C1}} when ConnCount > C1 ->
		 lists:keyreplace(max_conn,1,Status,{max_conn,ConnCount});
	     {value,{max_conn,C2}} ->
		 Status;
	     false ->
		 [{max_conn,ConnCount}|Status]
	 end,
    update_status_with_time(S1,last_connection).

update_status_with_time(Status,Key) ->
    lists:keyreplace(Key,1,Status,{Key,universal_time()}).

universal_time() -> calendar:universal_time().


auth_status(P) when pid(P) ->
    Items = [status, message_queue_len, reductions,
	     heap_size, stack_size, current_function],
    {auth_status, process_status(P,Items,[])};
auth_status(_) ->
    {auth_status, undefined}.

sec_status(P) when pid(P) ->
    Items = [status, message_queue_len, reductions,
	     heap_size, stack_size, current_function],
    {security_status, process_status(P,Items,[])};
sec_status(_) ->
    {security_status, undefined}.

acceptor_status(P) when pid(P) ->
    Items = [status, message_queue_len, reductions,
	     heap_size, stack_size, current_function],
    {acceptor_status, process_status(P,Items,[])};
acceptor_status(_) ->
    {acceptor_status, undefined}.


manager_status(P) ->
    Items = [status, message_queue_len, reductions,
	     heap_size, stack_size],
    {manager_status, process_status(P,Items,[])}.


process_status(P,[],L) ->
    [{pid,P}|lists:reverse(L)];
process_status(P,[H|T],L) ->
    case (catch process_info(P,H)) of
	{H, Value} ->
	    process_status(P,T,[{H,Value}|L]);
	_ ->
	    process_status(P,T,[{H,undefined}|L])
    end.

make_name(Addr,Port) ->
    httpd_util:make_name("httpd",Addr,Port).


report_error(State,String) ->
    Cdb = State#state.config_db,
    error_logger:error_report(String),
    mod_log:report_error(Cdb,String),
    mod_disk_log:report_error(Cdb,String).


set_verbosity(V) ->
    Units = [manager_verbosity,
	     acceptor_verbosity, request_handler_verbosity,
	     security_verbosity, auth_verbosity],
    case httpd_util:key1search(V, all) of
	undefined ->
	    set_verbosity(V, Units);
	Verbosity when atom(Verbosity) ->
	    V1 = [{Unit, Verbosity} || Unit <- Units],
	    set_verbosity(V1, Units)
    end.

set_verbosity(_V, []) ->
    ok;
set_verbosity(V, [manager_verbosity = Unit|Units]) ->
    Verbosity = httpd_util:key1search(V, Unit, ?default_verbosity),
    put(verbosity, ?vvalidate(Verbosity)),
    set_verbosity(V, Units);
set_verbosity(V, [Unit|Units]) ->
    Verbosity = httpd_util:key1search(V, Unit, ?default_verbosity),
    put(Unit, ?vvalidate(Verbosity)),
    set_verbosity(V, Units).


set_verbosity(manager,V,_S) ->
    put(verbosity,V);
set_verbosity(acceptor,V,_S) ->
    put(acceptor_verbosity,V);
set_verbosity(request,V,_S) ->
    put(request_handler_verbosity,V);
set_verbosity(security,V,S) ->
    OldVerbosity = put(security_verbosity,V),
    Addr = httpd_util:lookup(S#state.config_db, bind_address),
    Port = httpd_util:lookup(S#state.config_db, port),
    mod_security_server:verbosity(Addr,Port,V),
    OldVerbosity;
set_verbosity(auth,V,S) ->
    OldVerbosity = put(auth_verbosity,V),
    Addr = httpd_util:lookup(S#state.config_db, bind_address),
    Port = httpd_util:lookup(S#state.config_db, port),
    mod_auth_server:verbosity(Addr,Port,V),
    OldVerbosity;

set_verbosity(all,V,S) ->
    OldMv = put(verbosity,V),
    OldAv = put(acceptor_verbosity,V),
    OldRv = put(request_handler_verbosity,V),
    OldSv = put(security_verbosity,V),
    OldAv = put(auth_verbosity,V),
    Addr  = httpd_util:lookup(S#state.config_db, bind_address),
    Port  = httpd_util:lookup(S#state.config_db, port),
    mod_security_server:verbosity(Addr,Port,V),
    mod_auth_server:verbosity(Addr,Port,V),
    [{manager,OldMv}, {request,OldRv}, {security,OldSv}, {auth, OldAv}].


%%
call(ServerRef,Request) ->
    gen_server:call(ServerRef,Request).

cast(ServerRef,Message) ->
    gen_server:cast(ServerRef,Message).
