%% ---
%%  Excerpted from "Programming Erlang",
%%  published by The Pragmatic Bookshelf.
%%  Copyrights apply to this code. It may not be used to create training material,
%%  courses, books, articles, and the like. Contact us if you are in doubt.
%%  We make no guarantees that this code is fit for any purpose.
%%  Visit http://www.pragmaticprogrammer.com/titles/jaerlang for more book information.
%%  
%%  日本語版の『プログラミングErlang』、およびサンプルコードについては、
%%   http://ssl.ohmsha.co.jp/cgi-bin/menu.cgi?ISBN=978-4-274-06714-3 を確認してください。
%% ---
%% Copyright (C) 2002, Joe Armstrong
%% File    : lib_tcp_server.erl
%% Author  : Joe Armstrong (joe@sics.se)
%% Purpose : 複数のTCPセッションを管理する
%% Time-stamp: <2006-09-18 14:53:15 ejoearm>

-module(lib_tcp_server).

-export([start_server/4, start_client/4]).
-export([stop/1]).
-export([children/1]).

%% -export([start_child/3]).

%% start_raw_server(Port, Fun, Max)
%%   このサーバは最大Max個の接続をPortにて受け付ける
%%   Portに*最初の*接続要求が来たときに
%%   Fun(Socket)が呼び出される。
%%   以降はソケットへのメッセージはハンドラへのメッセージとなる。

%% tcpは以下のように使うのが普通だ：

%% 接続を受け付ける準備をする

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

%% loop()プロセスは全プロセスが必要に応じて互いに同期を取るために使える
%% 中央コントローラだ
%% これがinput_handlerの変数"Controller"になる


%% サーバのコードは以下のように使う
%%
%% start_server(Port, StartSpec) -> true.
%% stop_server(Port) -> true.
%% StartSpec = {Mod, Func, Args}
%%    ModはFunc/2をエクスポートしていなければならない
%%    startはCという引数を1つ与えて生成される
%%    典型的なサーバのテンプレートは以下の通り
%%
%% start() -> start_server(3030, mod_handle)
%%
%% これはmod_handleにある：
%%
%% start(C) ->
%%     receive
%%        {msg, M} -> ...
%%            C ! {msg, T}
%%            C ! close
%%        closed ->
%%     end

start_client(Host, Port, StartSpec, Trace) ->
    S = self(),
    Pid = spawn_link(fun() -> start_client(Host,Port,S,StartSpec,Trace) end),
    receive
	{Pid, Reply} ->
	    Reply
    end.

start_client(Host, Port, S, StartSpec, Trace) ->
    process_flag(trap_exit, true),
    case gen_tcp:connect(Host, Port,
			 [binary, {active, true}, {packet, 4}]) of
	{ok, Socket} ->
	    {Mod, Func, Args} = StartSpec,
	    S ! {self(), {ok, self()}},
	    Pid = spawn_link(Mod, Func, [self(), Args]),
	    loop(Socket, Pid, Trace);
	Error ->
	    S ! {self(), Error}
    end.

					 
%% start_raw_serverが戻るときには即座に接続を受け付ける準備が
%% できていなければならない

start_server(Port, StartSpec, Trace, Max) ->
    Name = port_name(Port),
    case whereis(Name) of
	undefined ->
	    Self = self(),
	    Pid = spawn_link(fun() ->
				     cold_start(Self, Port, StartSpec, Trace,
						Max)
			     end),
	    receive
		{Pid, ok} ->
		    register(Name, Pid),
		    {ok, self()};
		{Pid, Error} ->
		    Error
	    end;
	_Pid ->
	    {error, 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, StartSpec, Trace, Max) ->
    process_flag(trap_exit, true),
    io:format("Starting a port server on ~p...~n",[Port]),
    case gen_tcp:listen(Port, [binary,
			       %% {dontroute, true},
			       {nodelay,true},
			       {packet, 4},
			       {reuseaddr, true}, 
			       {active, true}]) of
	{ok, Listen} ->
	    io:format("Listening on:~p~n",[Listen]),
	    Master ! {self(), ok},
	    New = start_accept(Listen, StartSpec, Trace),
	    %% 実行準備ができた
	    socket_loop(Listen, New, [], StartSpec, Trace, Max);
	Error ->
	    Master ! {self(), Error}
    end.

%% 何をやっているかちゃんと理解せずに以下のコードをいじらないこと
%% （うまく動くようにするのを手伝ってくれたMagnusに感謝）

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

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

start_accept(Listen, StartSpec, Trace) ->
    S = self(),
    spawn_link(fun() -> start_child(S, Listen, StartSpec, Trace) end).

start_child(Parent, Listen, StartSpec, Trace) ->
    case gen_tcp:accept(Listen) of
	{ok, Socket} ->
	    Parent ! {istarted,self()},		    % コントローラに通知する
	    inet:setopts(Socket, [{packet,4},
				  binary,
				  {nodelay,true},
				  {active, true}]), % ソケットをアクティベートする前
	    %% io:format("running the child:~p StartSpec=~p~n",
	    %%  [Socket, StartSpec]),
	    %% StartSpecをアンパックする
	    {Mod, Start, Args} = StartSpec,
	    Pid = spawn_link(Mod, Start, [self(), Args]),
	    process_flag(trap_exit, true),
	    loop(Socket, Pid, Trace);
	_Other ->
	    exit(oops)
    end.

%% ループはアクティブモード :-)

loop(Socket, Pid, Trace) ->
    receive
	{tcp, Socket, Bin} ->
	    Term = bin_with_length_to_term(Bin),
	    trace_msg(Trace, {received, Term}),
	    Pid ! {msg, Term},
	    loop(Socket, Pid, Trace);
	{tcp_closed, Socket} ->
	    trace_msg(Trace, socket_closed),
	    Pid ! closed;
	{msg, Term} ->
	    trace_msg(Trace, {sent, Term}),
	    send_term_with_length_check(Socket, Term),
	    loop(Socket, Pid, Trace);
	close ->
	    trace_msg(Trace, closed_by_application),
	    trace_msg(Trace, server_terminating),
	    gen_tcp:close(Socket);
	{'EXIT', Pid, Why} ->
	    trace_msg(Trace, {application_died_with_exit, Why}),
	    trace_msg(Trace, server_terminating),
	    gen_tcp:close(Socket);
	Other ->
	    trace_msg(Trace, {unexpected_message, Other}),
	    trace_msg(Trace, server_terminating),
	    exit({protocolError, Other})
    end.

trace_msg(no, _) -> true;
trace_msg({yes, Tag}, Msg) ->
    io:format("trace: ~p ~p~n",[Tag, Msg]).

send_term_with_length_check(Socket, Term) ->
    B = term_to_binary(Term),
    Len = size(B),
    B1 = <<Len:32>>,
    gen_tcp:send(Socket, [B1, B]).

bin_with_length_to_term(<<Length:32,Bin/binary>>) ->
    case size(Bin) of
	Length ->
	    %% io:format("packet length ok~n",[]),
	    binary_to_term(Bin);
	Len1 ->
	    io:format("Length error:~p ~p ~n",[Length, Len1]),
	    error1231312423235
    end.



    







