%% Copyright (C) 2001, Joe Armstrong
%% File    : tcp_server.erl
%% Author  : Joe Armstrong (joe.armstrong@telia.com)
%% Purpose : Keeps track of a number of TCP sessions

-module(tcp_server).

-export([start_raw_server/3, start_raw_server/4]).
-export([stop/1]).
-export([children/1]).
-export([start_child/3]).

%% start_raw_server(Port, Fun, Max)
%%   This server accepts up to Max connections on Port
%%   The *first* time a connection is made to Port
%%   Then Fun(Socket) is called. 
%%   Thereafter messages to the socket result in messsages to the handler.

%% tcp_is typically used as follows:

%% To setup a lister

%% start_agent(Port) ->    
%%     S = self(),
%%     process_flag(trap_exit, true),
%%     tcp_server:start_raw_server(Port, 
%% 				fun(Socket) -> input_handler(Socket, S) end, 
%% 				15)
%%     loop().

%% The loop() process is a central controller that all
%% processes can use to synchronize amongst themselfves if necessary
%% It ends up as the variable "Controller" in the input_handler


%% input_handler(Socket, Controller) ->
%%     receive
%% 	{tcp, Socket, Bin} ->
%% 	    ...
%% 	    gen_tcp:send(Socket, ...)
%% 
%% 	{tcp_closed, Socket} ->
%%
%% 
%% 	Any ->
%% 	    ...
%% 
%%     end.

%% Note when start_raw_server retruns it should be ready to
%% Immediately accept connections

start_raw_server(Port, Fun, Max) ->
    start_raw_server(Port, Fun, Max, 0).

start_raw_server(Port, Fun, Max, PacketLength) ->
    Name = port_name(Port),
    case whereis(Name) of
	undefined ->
	    Self = self(),
	    Pid = spawn_link(fun() ->
				     cold_start(Self, Port, Fun, Max, 
						PacketLength)
			     end),
	    register(Name, Pid),
	    receive
		{Pid, ack} ->
		    started
	    end;
	Pid ->
	    already_started
    end.

stop(Port) when integer(Port) ->
    Name = port_name(Port),
    case whereis(Name) of
	undefined ->
	    not_started;
	Pid ->
	    exit(Pid, kill),
	    (catch unregister(Name)),
	    stopped
    end.

children(Port) when integer(Port) ->
    port_name(Port) ! {children, self()},
    receive
	{session_server, Reply} -> Reply
    end.

port_name(Port) when integer(Port) ->
    list_to_atom("portServer" ++ integer_to_list(Port)).

cold_start(Master, Port, Fun, Max, PacketLength) ->
    process_flag(trap_exit, true),
    io:format("Starting a port server on ~p...~n",[Port]),
    {ok, Listen} = gen_tcp:listen(Port, [binary,
						% {dontroute, true},
					 {nodelay,true},
					 {reuseaddr, true}, 
					 {packet, PacketLength},
					 {active, false}]),
    %% io:format("Listening on:~p~n",[Listen]),
    Master ! {self(), ack},
    New = start_accept(Listen, Fun),
    %% Now we're ready to run
    socket_loop(Listen, New, [], Fun, Max).

%% Don't mess with the following code uless you really know what you're 
%% doing (and Thanks to Magnus for heping me get it right)

socket_loop(Listen, New, Active, Fun, Max) ->
    receive
	{istarted, New} ->
	    Active1 = [New|Active],
	    possibly_start_another(false, Listen, Active1, Fun, Max);
	{'EXIT', New, Why} ->
	    %% io:format("Child exit=~p~n",[Why]),
	    possibly_start_another(false, Listen, Active, Fun, Max);
	{'EXIT', Pid, Why} ->
	    %% io:format("Child exit=~p~n",[Why]),
	    Active1 = lists:delete(Pid, Active),
	    possibly_start_another(New, Listen, Active1, Fun, Max);
	{children, From} ->
	    From ! {session_server, Active},
	    socket_loop(Listen, New, Active, Fun, Max);
	Other ->
	    io:format("Here in loop:~p~n",[Other])
    end.

possibly_start_another(New, Listen, Active, Fun, Max) when pid(New) ->
    socket_loop(Listen, New, Active, Fun, Max);
possibly_start_another(false, Listen, Active, Fun, Max) ->
    case length(Active) of
	N when N < Max ->
	    New = start_accept(Listen, Fun),
	    socket_loop(Listen, New, Active, Fun, Max);
	_ ->
	    socket_loop(Listen, false, Active, Fun, Max)
    end.

start_accept(Listen, Fun) ->
    spawn_link(?MODULE, start_child, [self(), Listen, Fun]).
    %% S = self(),
    %% spawn_link(fun() -> start_child(S, Listen, Fun) end).

start_child(Parent, Listen, Fun) ->
    case gen_tcp:accept(Listen) of
	{ok, Socket} ->
	    Parent ! {istarted,self()},		    % tell the controller
	    inet:setopts(Socket, [{nodelay,true},
				  {active, true}]), % before we activate socket
	    Fun(Socket);
	Other ->
	    exit(oops)
    end.









