-module(echo).

%% API functions
-export([start/0, start/1, stop/0]).
-export([do_echo/1]).


%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------

start() ->
    start("./luaport").
    
start(PortName) ->
	spawn(	fun() ->
				register(echo, self()),
				process_flag(trap_exit, true),
				Port = open_port({spawn, PortName}, [stream, {line, 100}]),
				loop(Port)
	  		end).

stop() ->
    echo ! stop.    

do_echo(Msg) ->
    ValidMsg1 = case is_newline_terminated(Msg) of
                    true  -> Msg;
                    false -> Msg ++ [$\n]
                end,
    ValidMsg2 = case count_chars(ValidMsg1, $\n) of
                    1     -> ValidMsg1;
                    _     -> erlang:error(badarg)
                end,
    call_port(ValidMsg2).


call_port(Msg) ->
    echo ! {call, self(), Msg},
    receive
	{echo, Result} ->
	    Result
    end.
    
    
loop(Port) ->
    receive
	{call, Caller, Msg} ->
		io:format("~p",[Msg]),
	    port_command(Port, Msg),
	    case collect_response(Port) of
	        {response, Response} -> 
	        	Caller ! {echo, Response};
	        timeout -> 
	        	io:format("timeout")
	    end;
	stop ->
	    port_close(Port),
	    receive
		{Port, closed} ->
		    exit(normal)
	    end;
	{'EXIT', Port, Reason} ->
	    exit({port_terminated,Reason})
    end.


%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
collect_response(Port) ->
    collect_response(Port, [], []).

collect_response(Port, RespAcc, LineAcc) ->
    receive
        {Port, {data, {eol, "OK"}}} ->
            {response, lists:reverse(RespAcc)};

        {Port, {data, {eol, Result}}} ->
            Line = lists:reverse([Result | LineAcc]),
            collect_response(Port, [Line | RespAcc], []);

        {Port, {data, {noeol, Result}}} ->
            collect_response(Port, RespAcc, [Result | LineAcc])
            
    after 3000 -> 
            timeout
    end.

is_newline_terminated([])    -> false;
is_newline_terminated([$\n]) -> true;
is_newline_terminated([_|T]) -> is_newline_terminated(T).
    
count_chars(String, Char) ->
    length([X || X <- String, X == Char]).
