%%<copyright>
%% <year>1997-2008</year>
%% <holder>Ericsson AB, All Rights Reserved</holder>
%%</copyright>
%%<legalnotice>
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% The Initial Developer of the Original Code is Ericsson AB.
%%</legalnotice>
%%

-module(httpd).

-behaviour(inets_service).

-include("httpd.hrl").

-deprecated({start, 0, next_major_release}).
-deprecated({start, 1, next_major_release}).
-deprecated({start_link, 1, next_major_release}).
-deprecated({start_child, 0, next_major_release}).
-deprecated({start_child, 1, next_major_release}).
-deprecated({stop, 0, next_major_release}).
-deprecated({stop, 1, next_major_release}).
-deprecated({stop, 2, next_major_release}).
-deprecated({stop_child, 0, next_major_release}).
-deprecated({stop_child, 1, next_major_release}).
-deprecated({stop_child, 2, next_major_release}).
-deprecated({restart, 0, next_major_release}).
-deprecated({restart, 1, next_major_release}).
-deprecated({restart, 2, next_major_release}).
-deprecated({block, 0, next_major_release}).
-deprecated({block, 1, next_major_release}).
-deprecated({block, 2, next_major_release}).
-deprecated({block, 3, next_major_release}).
-deprecated({block, 4, next_major_release}).
-deprecated({unblock, 0, next_major_release}).
-deprecated({unblock, 1, next_major_release}).
-deprecated({unblock, 2, next_major_release}).

%% Behavior callbacks
-export([start_standalone/1, start_service/1, stop_service/1, services/0, 
	 service_info/1]).

%% API
-export([parse_query/1, reload_config/2, info/1, info/2, info/3]).

%% Deprecated
-export([start/0, start/1, 
	 start_link/0, start_link/1, 
	 start_child/0,start_child/1,
	 stop/0,stop/1,stop/2,
	 stop_child/0,stop_child/1,stop_child/2,
	 restart/0,restart/1,restart/2]).

%% Management stuff should be internal functions 
%% Will be from r13
-export([block/0,block/1,block/2,block/3,block/4,
	 unblock/0,unblock/1,unblock/2]).

%% Internal Debugging and status info stuff...
%% Keep for now should probably be moved to test catalog
-export([get_status/1,get_status/2,get_status/3,
	 get_admin_state/0,get_admin_state/1,get_admin_state/2,
	 get_usage_state/0,get_usage_state/1,get_usage_state/2]).

%%%========================================================================
%%% API
%%%========================================================================

parse_query(String) ->
  {ok, SplitString} = regexp:split(String,"[&;]"),
  foreach(SplitString).

reload_config(Config = [Value| _], Mode) when is_tuple(Value) ->
    do_reload_config(Config, Mode);
reload_config(ConfigFile, Mode) ->
    case httpd_conf:load(ConfigFile) of
	{ok, ConfigList} ->
	    do_reload_config(ConfigList, Mode);
	Error ->
	    Error
    end.

info(Pid) when is_pid(Pid) ->
    info(Pid, []).

info(Pid, Properties) when is_pid(Pid), is_list(Properties) ->
    {ok, ServiceInfo} = service_info(Pid), 
    Address = proplists:get_value(bind_address, ServiceInfo),
    Port = proplists:get_value(port, ServiceInfo),
    case Properties of
	[] ->
	    info(Address, Port);
	_ ->
	    info(Address, Port, Properties)
    end; 
info(Address, Port) when is_integer(Port) ->    
    httpd_conf:get_config(Address, Port).

info(Address, Port, Properties) when is_integer(Port), 
				     is_list(Properties) ->    
    httpd_conf:get_config(Address, Port, Properties).

%%%========================================================================
%%% Behavior callbacks
%%%========================================================================

start_standalone(Config) ->
    httpd_sup:start_link([{httpd, Config}], stand_alone).

start_service(Conf) ->
    httpd_sup:start_child(Conf).

stop_service({Address, Port}) ->
    httpd_sup:stop_child(Address, Port);

stop_service(Pid) when is_pid(Pid) ->
    case service_info(Pid)  of
	{ok, Info} ->	   
	    Address = proplists:get_value(bind_address, Info),
	    Port = proplists:get_value(port, Info),
	    stop_service({Address, Port});
	Error ->
	    Error
    end.
	    
services() ->
    [{httpd, ChildPid} || {_, ChildPid, _, _} <- 
			      supervisor:which_children(httpd_sup)].

service_info(Pid) ->
    try
	[{ChildName, ChildPid} || 
	    {ChildName, ChildPid, _, _} <- 
		supervisor:which_children(httpd_sup)] of
	Children ->
	    child_name2info(child_name(Pid, Children))
    catch
	exit:{noproc, _} ->
	    {error, service_not_available} 
    end.
%%%--------------------------------------------------------------
%%% Internal functions
%%%--------------------------------------------------------------------

child_name(_, []) ->
    undefined;
child_name(Pid, [{Name, Pid} | _]) ->
    Name;
child_name(Pid, [_ | Children]) ->
    child_name(Pid, Children).

child_name2info(undefined) ->
    {error, no_such_service};
child_name2info({httpd_instance_sup, any, Port}) ->
    {ok, Host} = inet:gethostname(),
    Info = info(any, Port, [server_name]),
    {ok, [{bind_address,  any}, {host, Host}, {port, Port} | Info]};
child_name2info({httpd_instance_sup, Address, Port}) ->
    Info = info(Address, Port, [server_name]),
    case inet:gethostbyaddr(Address) of
	{ok, {_, Host, _, _,_, _}} ->
	    {ok, [{bind_address, Address}, 
		  {host, Host}, {port, Port} | Info]};
	_  ->
	    {ok, [{bind_address, Address}, {port, Port} | Info]}
    end.

reload(Config, Address, Port) ->
    Name = make_name(Address,Port),
    case whereis(Name) of
	Pid when pid(Pid) ->
	    httpd_manager:reload(Pid, Config);
	_ ->
	    {error,not_started}
    end.

reload(Addr, Port) when is_integer(Port) ->
    Name = make_name(Addr,Port),
    case whereis(Name) of
	Pid when pid(Pid) ->
	    httpd_manager:reload(Pid, undefined);
	_ ->
	    {error,not_started}
    end.
    
%%% =========================================================
%%% Function:    block/0, block/1, block/2, block/3, block/4
%%%              block()
%%%              block(Port)
%%%              block(ConfigFile)
%%%              block(Addr,Port)
%%%              block(Port,Mode)
%%%              block(ConfigFile,Mode)
%%%              block(Addr,Port,Mode)
%%%              block(ConfigFile,Mode,Timeout)
%%%              block(Addr,Port,Mode,Timeout)
%%% 
%%% Returns:     ok | {error,Reason}
%%%              
%%% Description: This function is used to block an HTTP server.
%%%              The blocking can be done in two ways, 
%%%              disturbing or non-disturbing. Default is disturbing.
%%%              When a HTTP server is blocked, all requests are rejected
%%%              (status code 503).
%%% 
%%%              disturbing:
%%%              By performing a disturbing block, the server
%%%              is blocked forcefully and all ongoing requests
%%%              are terminated. No new connections are accepted.
%%%              If a timeout time is given then, on-going requests
%%%              are given this much time to complete before the
%%%              server is forcefully blocked. In this case no new 
%%%              connections is accepted.
%%% 
%%%              non-disturbing:
%%%              A non-disturbing block is more gracefull. No
%%%              new connections are accepted, but the ongoing 
%%%              requests are allowed to complete.
%%%              If a timeout time is given, it waits this long before
%%%              giving up (the block operation is aborted and the 
%%%              server state is once more not-blocked).
%%%
%%% Types:       Port       -> integer()             
%%%              Addr       -> {A,B,C,D} | string() | undefined
%%%              ConfigFile -> string()
%%%              Mode       -> disturbing | non_disturbing
%%%              Timeout    -> integer()
%%%
block() -> block(undefined,8888,disturbing).

block(Port) when is_integer(Port) -> 
    block(undefined,Port,disturbing);

block(ConfigFile) when is_list(ConfigFile) ->
    case get_addr_and_port(ConfigFile) of
	{ok,Addr,Port} ->
	    block(Addr,Port,disturbing);
	Error ->
	    Error
    end.

block(Addr,Port) when is_integer(Port) -> 
    block(Addr,Port,disturbing);

block(Port,Mode) when is_integer(Port), is_atom(Mode) ->
    block(undefined,Port,Mode);

block(ConfigFile,Mode) when is_list(ConfigFile), is_atom(Mode) ->
    case get_addr_and_port(ConfigFile) of
	{ok,Addr,Port} ->
	    block(Addr,Port,Mode);
	Error ->
	    Error
    end.


block(Addr,Port,disturbing) when is_integer(Port) ->
    do_block(Addr,Port,disturbing);
block(Addr,Port,non_disturbing) when is_integer(Port) ->
    do_block(Addr,Port,non_disturbing);

block(ConfigFile,Mode,Timeout) when is_list(ConfigFile), is_atom(Mode), 
				    is_integer(Timeout) ->
    case get_addr_and_port(ConfigFile) of
	{ok,Addr,Port} ->
	    block(Addr,Port,Mode,Timeout);
	Error ->
	    Error
    end.


block(Addr,Port,non_disturbing,Timeout) when 
  is_integer(Port), is_integer(Timeout) ->
    do_block(Addr,Port,non_disturbing,Timeout);
block(Addr,Port,disturbing,Timeout) when is_integer(Port), 
					 is_integer(Timeout) ->
    do_block(Addr,Port,disturbing,Timeout).

do_block(Addr,Port,Mode) when is_integer(Port), is_atom(Mode) -> 
    Name = make_name(Addr,Port),
    case whereis(Name) of
	Pid when pid(Pid) ->
	    httpd_manager:block(Pid,Mode);
	_ ->
	    {error,not_started}
    end.
    

do_block(Addr,Port,Mode,Timeout) when is_integer(Port), is_atom(Mode) -> 
    Name = make_name(Addr,Port),
    case whereis(Name) of
	Pid when pid(Pid) ->
	    httpd_manager:block(Pid,Mode,Timeout);
	_ ->
	    {error,not_started}
    end.
    

%%% =========================================================
%%% Function:    unblock/0, unblock/1, unblock/2
%%%              unblock()
%%%              unblock(Port)
%%%              unblock(ConfigFile)
%%%              unblock(Addr,Port)
%%%              
%%% Description: This function is used to reverse a previous block 
%%%              operation on the HTTP server.
%%%
%%% Types:       Port       -> integer()             
%%%              Addr       -> {A,B,C,D} | string() | undefined
%%%              ConfigFile -> string()
%%%
unblock()                        -> unblock(undefined,8888).
unblock(Port) when is_integer(Port) -> unblock(undefined,Port);

unblock(ConfigFile) when is_list(ConfigFile) ->
    case get_addr_and_port(ConfigFile) of
	{ok,Addr,Port} ->
	    unblock(Addr,Port);
	Error ->
	    Error
    end.

unblock(Addr,Port) when is_integer(Port) -> 
    Name = make_name(Addr,Port),
    case whereis(Name) of
	Pid when pid(Pid) ->
	    httpd_manager:unblock(Pid);
	_ ->
	    {error,not_started}
    end.

foreach([]) ->
  [];
foreach([KeyValue|Rest]) ->
  {ok, Plus2Space, _} = regexp:gsub(KeyValue,"[\+]"," "),
  case regexp:split(Plus2Space,"=") of
    {ok,[Key|Value]} ->
      [{httpd_util:decode_hex(Key),
	httpd_util:decode_hex(lists:flatten(Value))}|foreach(Rest)];
    {ok,_} ->
      foreach(Rest)
  end.

get_addr_and_port(ConfigFile) ->
    case httpd_conf:load(ConfigFile) of
	{ok,ConfigList} ->
	    Port = proplists:get_value(port,ConfigList,80),
	    Address = 
		case proplists:get_value(bind_address, ConfigList) of
		    any  ->
			any;
		    Host -> 
			{ok, Addr} = httpd_util:ip_address(Host),
			Addr
		end,
	    {ok,Address,Port};
	Error ->
	    Error
    end.


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

%%%--------------------------------------------------------------
%%% Internal debug functions - Do we want these functions here!?
%%%--------------------------------------------------------------------

%%% =========================================================
%%% Function:    get_admin_state/0, get_admin_state/1, get_admin_state/2
%%%              get_admin_state()
%%%              get_admin_state(Port)
%%%              get_admin_state(Addr,Port)
%%%              
%%% Returns:     {ok,State} | {error,Reason}
%%%              
%%% Description: This function is used to retrieve the administrative 
%%%              state of the HTTP server.
%%%
%%% Types:       Port    -> integer()             
%%%              Addr    -> {A,B,C,D} | string() | undefined
%%%              State   -> unblocked | shutting_down | blocked
%%%              Reason  -> term()
%%%
get_admin_state()                        -> get_admin_state(undefined,8888).
get_admin_state(Port) when is_integer(Port) -> get_admin_state(undefined,Port);

get_admin_state(ConfigFile) when is_list(ConfigFile) ->
    case get_addr_and_port(ConfigFile) of
	{ok,Addr,Port} ->
	    unblock(Addr,Port);
	Error ->
	    Error
    end.

get_admin_state(Addr,Port) when is_integer(Port) -> 
    Name = make_name(Addr,Port),
    case whereis(Name) of
	Pid when is_pid(Pid) ->
	    httpd_manager:get_admin_state(Pid);
	_ ->
	    {error,not_started}
    end.



%%% =========================================================
%%% Function:    get_usage_state/0, get_usage_state/1, get_usage_state/2
%%%              get_usage_state()
%%%              get_usage_state(Port)
%%%              get_usage_state(Addr,Port)
%%%              
%%% Returns:     {ok,State} | {error,Reason}
%%%              
%%% Description: This function is used to retrieve the usage 
%%%              state of the HTTP server.
%%%
%%% Types:       Port    -> integer()             
%%%              Addr    -> {A,B,C,D} | string() | undefined
%%%              State   -> idle | active | busy
%%%              Reason  -> term()
%%%
get_usage_state()                        -> get_usage_state(undefined,8888).
get_usage_state(Port) when is_integer(Port) -> get_usage_state(undefined,Port);

get_usage_state(ConfigFile) when is_list(ConfigFile) ->
    case get_addr_and_port(ConfigFile) of
	{ok,Addr,Port} ->
	    unblock(Addr,Port);
	Error ->
	    Error
    end.

get_usage_state(Addr,Port) when is_integer(Port) -> 
    Name = make_name(Addr,Port),
    case whereis(Name) of
	Pid when is_pid(Pid) ->
	    httpd_manager:get_usage_state(Pid);
	_ ->
	    {error,not_started}
    end.



%%% =========================================================
%% Function:    get_status(ConfigFile)        -> Status
%%              get_status(Port)              -> Status
%%              get_status(Addr,Port)         -> Status
%%              get_status(Port,Timeout)      -> Status
%%              get_status(Addr,Port,Timeout) -> Status
%%
%% Arguments:   ConfigFile -> string()  
%%                            Configuration file from which Port and 
%%                            BindAddress will be extracted.
%%              Addr       -> {A,B,C,D} | string()
%%                            Bind Address of the http server
%%              Port       -> integer()
%%                            Port number of the http server
%%              Timeout    -> integer()
%%                            Timeout time for the call
%%
%% Returns:     Status -> list()
%%
%% Description: This function is used when the caller runs in the 
%%              same node as the http server or if calling with a 
%%              program such as erl_call (see erl_interface).
%% 

get_status(ConfigFile) when is_list(ConfigFile) ->
    case get_addr_and_port(ConfigFile) of
	{ok,Addr,Port} ->
	    get_status(Addr,Port);
	Error ->
	    Error
    end;

get_status(Port) when is_integer(Port) ->
    get_status(undefined,Port,5000).

get_status(Port,Timeout) when is_integer(Port), is_integer(Timeout) ->
    get_status(undefined,Port,Timeout);

get_status(Addr,Port) when is_list(Addr), is_integer(Port) ->
    get_status(Addr,Port,5000).

get_status(Addr,Port,Timeout) when is_integer(Port) ->
    Name = make_name(Addr,Port), 
    case whereis(Name) of
	Pid when is_pid(Pid) ->
	    httpd_manager:get_status(Pid,Timeout);
	_ ->
	    not_started
    end.

do_reload_config(ConfigList, Mode) ->
    Port = proplists:get_value(port,ConfigList,80),
    Address = 
	case proplists:get_value(bind_address, ConfigList) of
	    any  ->
		any;
	    Host -> 
		{ok, Addr} = httpd_util:ip_address(Host),
		Addr
	end,
    NewConfig = proplists:delete(bind_address, ConfigList),
    block(Address, Port, Mode),
    reload([{bind_address, Address}  | NewConfig], Address, Port),
    unblock(Address, Port).

%%%--------------------------------------------------------------
%%% Deprecated 
%%%--------------------------------------------------------------
start() ->
    start("/var/tmp/server_root/conf/8888.conf").

start(ConfigFile) ->
    {ok, Pid} = inets:start(httpd, ConfigFile, stand_alone), 
    unlink(Pid),
    {ok, Pid}.

start_link() ->
    start("/var/tmp/server_root/conf/8888.conf").

start_link(ConfigFile) when is_list(ConfigFile) ->
    inets:start(httpd, ConfigFile, stand_alone). 

stop() ->
  stop(8888).

stop(Port) when is_integer(Port) ->
    stop(undefined, Port);
stop(Pid) when is_pid(Pid) ->
    old_stop(Pid);
stop(ConfigFile) when is_list(ConfigFile) ->
    old_stop(ConfigFile).

stop(Addr, Port) when is_integer(Port) ->
    old_stop(Addr, Port).

start_child() ->
    start_child("/var/tmp/server_root/conf/8888.conf").

start_child(ConfigFile) ->
    httpd_sup:start_child(ConfigFile).

stop_child() ->
  stop_child(8888).

stop_child(Port) ->
    stop_child(undefined, Port).

stop_child(Addr, Port) when integer(Port) ->
    httpd_sup:stop_child(Addr, Port).

restart() -> reload(undefined, 8888).

restart(Port) when is_integer(Port) ->
    reload(undefined,  Port).
restart(Addr, Port) ->
    reload(Addr, Port).

old_stop(Pid) when pid(Pid) ->
    do_stop(Pid);
old_stop(ConfigFile) when list(ConfigFile) ->
    case get_addr_and_port(ConfigFile) of
	{ok, Addr, Port} ->
	    old_stop(Addr, Port);
	    
	Error ->
	    Error
    end;
old_stop(_StartArgs) ->
    ok.

old_stop(Addr, Port) when integer(Port) ->
    Name = old_make_name(Addr, Port), 
    case whereis(Name) of
	Pid when pid(Pid) ->
	    do_stop(Pid),
	    ok;
	_ ->
	    not_started
    end.
    
do_stop(Pid) ->
    exit(Pid, shutdown).

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