%%%-----------------------------------------------------------------------------
%%% @copyright (C) 1999-2013, Erlang Solutions Ltd
%%% @author Oscar Hellström <oscar@hellstrom.st>
%%% @author Magnus Henoch <magnus@erlang-consulting.com>
%%% @author Diana Parra Corbacho <diana.corbacho@erlang-solutions.com>
%%% @doc Simple web server for testing purposes
%%% @end
%%%-----------------------------------------------------------------------------
-module(webserver).
-copyright("2013, Erlang Solutions Ltd.").

-export([start/2, start/3, stop/2, stop/3]).
-export([acceptor/3]).

start(Module, Responders) ->
    start(Module, Responders, inet).

start(Module, Responders, Family) ->
    case get_addr("localhost", Family) of
        {ok, Addr} ->
            LS = listen(Module, Addr, Family),
            Pid = spawn(?MODULE, acceptor, [Module, LS, Responders]),
            {ok, Pid, LS, port(Module, LS)};
        Error ->
            Error
    end.

stop(Listener, LS) ->
    stop(gen_tcp, Listener, LS).

stop(Module, Listener, LS) ->
    (catch exit(kill, Listener)),
    Module:close(LS).

acceptor(Module, ListenSocket, Responders) ->
    case accept(Module, ListenSocket) of
	error ->
	    ok;
	Socket ->
	    spawn_link(fun() ->
			       acceptor(Module, ListenSocket, Responders)
		       end),
	    server_loop(Module, Socket, nil, [], Responders)
    end.

server_loop(Module, Socket, _, _, []) ->
    Module:close(Socket);
server_loop(Module, Socket, Request, Headers, [H | T] = Responders) ->
    receive
	stop ->
	    Module:close(Socket)
    after 0 ->
	    case Module:recv(Socket, 0, 500) of
		{ok, {http_request, _, _, _} = NewRequest} ->
		    server_loop(Module, Socket, NewRequest, Headers, Responders);
		{ok, {http_header, _, Field, _, Value}} when is_atom(Field) ->
		    NewHeaders = [{atom_to_list(Field), Value} | Headers],
		    server_loop(Module, Socket, Request, NewHeaders, Responders);
		{ok, {http_header, _, Field, _, Value}} when is_list(Field) ->
		    NewHeaders = [{Field, Value} | Headers],
		    server_loop(Module, Socket, Request, NewHeaders, Responders);
		{ok, http_eoh} ->
		    RequestBody = case proplists:get_value("Content-Length", Headers) of
				      undefined ->
					  <<>>;
				      "0" ->
					  <<>>;
				      SLength ->
					  Length = list_to_integer(SLength),
					  setopts(Module, Socket, [{packet, raw}]),
					  {ok, Body} = Module:recv(Socket, Length),
					  setopts(Module, Socket, [{packet, http}]),
					  Body
				  end,
		    H(Module, Socket, Request, Headers, RequestBody),
		    case proplists:get_value("Connection", Headers) of
			"close" ->
			    Module:close(Socket);
			_ ->
			    server_loop(Module, Socket, none, [], T)
		    end;
		{error, timeout} ->
		    server_loop(Module, Socket, Request, Headers, Responders);
		{error, closed} ->
		    Module:close(Socket)
	    end
    end.

listen(ssl, Addr, Family) ->
    Root = code:lib_dir(fusco, test),
    Opts = [
        Family,
        {packet, http},
        binary,
        {active, false},
        {ip, Addr},
        {verify,0},
        {keyfile, filename:join(Root, "key.pem")},
        {certfile, filename:join(Root,"crt.pem")}
    ],
    {ok, LS} = ssl:listen(0, Opts),
    LS;
listen(Module, Addr, Family) ->
    {ok, LS} = Module:listen(0, [
            Family,
            {packet, http},
            binary,
            {active, false},
            {ip, Addr}
        ]),
    LS.

get_addr(Host, Family) ->
    case inet:getaddr(Host, Family) of
        {ok, Addr} ->
            {ok, Addr};
        _ ->
            {error, family_not_supported}
    end.

accept(ssl, ListenSocket) ->
    case ssl:transport_accept(ListenSocket, 1000000) of
	{ok, Socket} ->
	    ok = ssl:ssl_accept(Socket),
	    Socket;
	{error, _} ->
	    error
    end;
accept(Module, ListenSocket) ->
    case Module:accept(ListenSocket, 100000) of
	{ok, Socket} ->
	    Socket;
	{error, _} ->
	    error
    end.

setopts(ssl, Socket, Options) ->
    ssl:setopts(Socket, Options);
setopts(_, Socket, Options) ->
    inet:setopts(Socket, Options).

port(ssl, Socket) ->
    {ok, {_, Port}} = ssl:sockname(Socket),
    Port;
port(_, Socket) ->
    {ok, Port} = inet:port(Socket),
    Port.