-module(freeswitch_monitor).
-author('fig@marketelsystems.com').

-behaviour(gen_server).

-define(PID_ANNOUNCE, "run_freeswitch.pid=").
-define(PREFIX, "freeswitch< ").
-define(PROG, "./run_freeswitch").
-define(TIMEOUT, 5000).

-record(state, {port, prefix=?PREFIX, pid, fspid, sync=[], trigger, node}).

-export([start_link/0, terminate/2, code_change/3, init/1,
	 handle_call/3, handle_cast/2, handle_info/2]).
-export([send/1]).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% gen_server methods
start_link() ->
    process_flag(trap_exit, true),
    SName = lists:takewhile(fun ($.) -> false;
				(_C) -> true
			    end,
			    net_adm:localhost()),
    FsNode = list_to_atom("freeswitch@" ++ SName),
	syslog:debug("FsNode = ~w calling freeswitch:start_link()~n", [FsNode]),
    gen_server:start_link({global, freeswitch}, ?MODULE, FsNode, []).

init(FsNode) ->
    %% Have our terminate/2 function run when our supervisor shuts us down.
	syslog:debug("init(~w) called~n",[FsNode]),
    State = #state{port=starconf:start_ext(?MODULE, ?PROG), node=FsNode},
	syslog:debug("State = ~w~n", [State]),
    {ok, try_getpid(1000, State)}.

terminate(Reason, State) ->
    case Reason of
	{process_exited, _} ->
	    ok;
	_ ->
	    Port = State#state.port,
	    catch port_command(Port, "shutdown\n"),
	    receive
		{Port, {exit_status, _}} ->
		    ok
	    after 5000 ->
		    starconf:stop_ext(State#state.port, State#state.pid)
	    end
    end,
    ok.

code_change(OldVsn, State, _Extra) ->
    starconf:stop_ext(State#state.port, State#state.pid),
    {ok, #state{port=starconf:start_ext(?MODULE, OldVsn, ?PROG)}}.

handle_call(sync, From, #state{fspid=undefined}=State) ->
	syslog:debug("freeswitch_monitor reached to handle sync call 1.~n"),
    {foo, State#state.node} ! getpid,
    {noreply, State#state{sync=[From | State#state.sync]}};
handle_call(sync, _From, State) ->
    %% We are synced, so return our Pid to link to.
	syslog:debug("freeswitch_monitor reached to handle sync call 2 and is successful.~n"),
    {reply, {ok, self()}, State};
handle_call(_, _From, #state{fspid=undefined}=State) ->
	syslog:error("Freeswitch_monitor is not ready and throw error msg.~n"),
	{reply, {error, not_ready}, State};
handle_call({pbx_api, Pid, Tag, Cmd, Args}, _From, State) ->
    %% We background the call so we can continue, but reply to
    %% the original caller from our child process.
    CPid = spawn(fun () ->
			 link(State#state.fspid),
			 {api, State#state.node} ! {api, Cmd, Args},
			 receive
			     {error, Reason} ->
				 Pid ! {Tag, {error, Reason}};
			     {ok, Reply} ->
				 Pid ! {Tag, {ok, Reply}}
			 end
		 end),
    {reply, {ok, CPid}, State};
handle_call({pbx_bgapi, Pid, Tag, Cmd, Args}, _From, State) ->
    %% We background the call so we can continue, but reply to
    %% the original caller from our child process.
    CPid = spawn(fun () ->
			 link(State#state.fspid),
			 {bgapi, State#state.node} ! {bgapi, Cmd, Args},
			 receive
			     {error, Reason} ->
				 	Pid ! {Tag, {error, Reason}};
			     {ok, Reply} ->
				 	Pid ! {Tag, {ok, Reply}}
			 end
		 end),
    {reply, {ok, CPid}, State};
handle_call({pbx_sendmsg, Pid, Tag, UUID, Headers}, _From, State) ->
    %% We background the call so we can continue, but reply to
    %% the original caller from our child process.
    CPid = spawn(fun () ->
			link(State#state.fspid),
			syslog:debug( "freeswitch_monitor:handle_call() pbx_sendmsg invoking sendmsg, UUID=~p, Headers=~p~n",
						  [ UUID, Headers ] ),
			{sendmsg, State#state.node} ! {sendmsg, UUID, Headers},
			receive
				ok ->
					Pid ! { Tag, { ok } };
				{error, Reason} ->
					syslog:debug( "freeswitch_monitor:handle_call() pbx_sendmsg received {error, ~p}~n", [Reason] ),
					Pid ! { Tag, {error, Reason} }
			after ?TIMEOUT ->
				timeout
			end
		 end),
    {reply, {ok, CPid}, State};
handle_call({pbx_bind, Section, Pid}, _From, State) ->
    Status = freeswitch_bind:start(State#state.node, Section, Pid),
    {reply, Status, State};
handle_call(Request, _From, State) ->
    error_logger:error_msg("~p:~p received unrecognized request ~p~n",
			   [?MODULE, self(), Request]),
    {reply, {error, unrecognized_request}, State}.

handle_cast({pbx_api, _Pid, _Tag, _Cmd, _Args}=Request, State) ->
    handle_call(Request, undefined, State),
    {noreply, State};
handle_cast({send, Cmd}, State) ->
    catch port_command(State#state.port, [Cmd, "\n"]),
    {noreply, State};
handle_cast(Message, State) ->
    error_logger:error_msg("~p:~p received unrecognized cast ~p~n",
			   [?MODULE, self(), Message]),
    {noreply, State}.

handle_info({Port, {exit_status, Status}}, #state{port=Port}=State) ->
    case Status of
	0 ->
	    {stop, normal, State};
	_N ->
	    {stop, {process_exited, Status}, State}
    end;
handle_info({try_getpid, Timeout}, State) ->
    {noreply, try_getpid(Timeout, State)};
handle_info({ok, Pid}, #state{sync=Sync}=State) when is_pid(Pid) ->
    syslog:info("~p:~p got ~p pid ~p~n",
		[?MODULE, self(), State#state.node, Pid]),
    case State#state.trigger of
	undefined -> ok;
	Trigger ->
	    timer:cancel(Trigger),
	    ok
    end,
    lists:foreach(fun (From) ->
			  gen_server:reply(From, {ok, self()})
		  end,
		  Sync),
    {noreply, State#state{trigger=undefined, fspid=Pid, sync=[]}};
handle_info({Port, {data, {noeol, Output}}}, #state{port=Port}=State) ->
    syslog:info("~s~s", [State#state.prefix, Output]),
    {noreply, State#state{prefix=""}};
handle_info({Port, {data, {eol, Output}}}, #state{port=Port}=State) ->
    syslog:info("~s~s~n", [State#state.prefix, Output]),
    Len = string:len(?PID_ANNOUNCE),
    Pid = case string:substr(Output, 1, Len) of
	      ?PID_ANNOUNCE ->
		  string:substr(Output, Len + 1);
	      _ ->
		  State#state.pid
	  end,
    {noreply, State#state{prefix=?PREFIX, pid=Pid}};
handle_info(Message, State) ->
    syslog:info("~p:handle_info() Got info ~p~n", [?MODULE, Message]),
    {noreply, State}.

try_getpid(Timeout, #state{fspid=undefined}=State) ->
    syslog:debug("~p:try_getpid() trying to get ~p pid~n",
			   [?MODULE, State#state.node]),
    {foo, State#state.node} ! getpid,
    {ok, Trigger} = timer:send_after(Timeout, {try_getpid, Timeout}),
    State#state{trigger=Trigger};
try_getpid(_Timeout, State) ->
    State.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% API

send(Cmd) ->
    gen_server:cast({global, freeswitch}, {send, Cmd}).
