-module(gui).
-export([start_gui/0, init/1, terminate/2,  code_change/3,
	 handle_info/2, handle_call/3, handle_cast/2, handle_event/2]).
-behaviour(wx_object).
-include_lib("wx/include/wx.hrl").

%% File with records definitions to be used as state for the client
-include_lib("./defs.hrl").

-define(SYSTEM, 0).
-define(CMDLINE, 1).
-define(NOTEBOOK, 2).
-define(MAX_CONNECTIONS, 100000).

-define(SYSTEM_NAME, "System").

-spec do_join(_ClientPID, _ClientName, _ClientID, _Ref, _ChatName) -> _.
-spec start_gui() -> _.
-spec init(_Server) -> _.
-spec do_init(_Server) -> _.
-spec handle_event(_WX, _State) -> _.
-spec handle_info(_Msg, _State) -> _.
-spec do_send_msg(_Ref, _ClientName, _ClientPID, _Message) -> _.
-spec do_whoami(_St, _Ref, _ClientPID) -> _.
-spec do_new_nick(_St, _Ref, _ClientPID, _Nick) -> _.
-spec do_leave_chatroom(_ClientName, _ChatName, _Ref, _ClientPID) -> _.
-spec do_quit(_ClientPID, _Ref) -> _.

% This record defines the structure of the
% client process.
%
% It contains the fields:
%
% parent: it stores the top window (used with operations
%         related to display widgets in the GUI.
% gui: it stores the name the GUI process.
% client: it stores the name of the client process.
-record(state,
	{
	 parent,
	 client,
	 gui,
	 clientid % unique part of name, as integer
	}).

start_gui() ->
    Server = wx:new(),
    wx_object:start_link(?MODULE, Server, []).


init(Server) ->
    wx:batch(fun () -> do_init(Server) end).

font_size() -> %% font size
    24.

do_init(Server) ->

    % It creates a unique name for the client and gui processes
    {ClientName, ClientID} = find_unique_name("client_", ?MAX_CONNECTIONS),
    {GUIName,_ }           = find_unique_name("gui_", ?MAX_CONNECTIONS),

    % If any of the name choosen above are taken at this point, everything crashes!
    register(to_atom(GUIName), self()),
    {Nickname, _} = find_unique_name("user", ?MAX_CONNECTIONS),
%%    helper:start(to_atom(ClientName), client:initial_state(Nickname, GUIName) , fun client:main/1),
    %% Spawn and register client
    Pid = spawn(fun() -> apply(fun client:main/1, [client:initial_state(Nickname, GUIName)]) end),
    catch(unregister(to_atom(ClientName))),
    register(to_atom(ClientName), Pid),


    %% Starting GUI
    %% Frame = wxFrame:new(Server, -1, "Chat", []),
    Frame = wxFrame:new(Server, -1, "Chat",[{pos, {200,200}},{size,{500,500}}]),
    Parent = Frame,
    Panel = wxPanel:new(Parent, []),


    %% Widgets: command line and system tab
    Cmd  = wxTextCtrl:new(Panel, -1, [{value, ""}, {style, ?wxTE_PROCESS_ENTER}]),
    Font = wxFont:new(font_size(), ?wxFONTFAMILY_DEFAULT, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_BOLD),
    wxTextCtrl:setFont(Cmd, Font),


    label(ClientID, ?CMDLINE, Cmd),

    Ntbk = wxAuiNotebook:new(Panel,[{style,?wxAUI_NB_DEFAULT_STYLE}]),
    label(ClientID, ?NOTEBOOK, Ntbk),

    Tab = create_tab(ClientName, ?SYSTEM_NAME, "Welcome to CCHAT v. 0.2"),
    label(ClientID, ?SYSTEM, Tab),

    %% Sizers
    MainSizer = wxBoxSizer:new(?wxVERTICAL),
    wxSizer:add(MainSizer, Ntbk, [{flag, ?wxEXPAND}, {proportion,1}]),
    wxSizer:addSpacer(MainSizer,10),
    wxSizer:add(MainSizer, Cmd, [{flag, ?wxEXPAND}]),
    wxPanel:setSizer(Panel, MainSizer),

    wxFrame:show(Frame),

    focus(with_label(ClientName, ?CMDLINE)),

    wxTextCtrl:connect(Cmd, command_text_enter),

    wxAuiNotebook:connect(Ntbk, command_auinotebook_button),

    {Panel, #state{parent=Panel, client=ClientName, clientid=ClientID, gui=GUIName}}.

%% Join a chatroom
do_join(ClientPID, ClientName, ClientID, Ref, ChatName) ->
    %% tell client we want to join chatroom with name ChatName
    ClientPID!{request, self(), Ref, {join, ChatName}},
    receive
	{result, ClientPID, Ref, Response} ->
	    case Response of
		%% Cannot join, as client is already in chatroom
		err ->
		    %% tell system tab that client is already in chatroom
		    write_chatroom(with_label(ClientName, ?SYSTEM), "+ Already in chatroom " ++ ChatName);
		%% If not err, then response is the history of the chatroom
		History ->
		    %% We write in the system tabl that we have joined the chatroom
		    write_chatroom(with_label(ClientName, ?SYSTEM), "+ Joined " ++ ChatName),
		    %% Create a new tab, the name of which is ChatName
		    Tab = create_tab(ClientName, ChatName, "* Chatroom " ++ ChatName),
		    %% create a label for it? Not quite sure, this is a relic from Chalmers
		    label(ClientID, chatroom_id(ChatName), Tab),
		    %% goes through history, writing out each message
		    lists:foreach(
		      fun({Nickname, Message}) ->
			      write_chatroom(with_label(ClientName, chatroom_id(ChatName)), Nickname ++ ": " ++ Message)
		      end,
		      History
		     )
	    end
    end.

%% Leave a chatroom
do_leave_chatroom(ClientName, ChatName, Ref, ClientPID) ->
    %% tell client we want to leave chatroom with name ChatName
    ClientPID!{request, self(), Ref, {leave, ChatName}},
    receive
	{result, ClientPID, Ref, Response} ->
	    case Response of
		%% client has successfully left chat
		ok ->
		    %% close the tab of that chatroom
		    close_tab(with_label(ClientName, ?NOTEBOOK), ChatName),
		    %% write in the system tab that we left the chatroom
	            write_chatroom(with_label(ClientName, ?SYSTEM), "+ Left chatroom " ++ ChatName);
		%% client could not leave as client was not in that chatroom
		err ->
		    %% write in chatroom saying we cannot leave
		    write_chatroom(with_label(ClientName, ?SYSTEM), "+ Cannot leave chatroom " ++ ChatName ++ ". not in chatroom!")
	    end
    end.

%% executes quit protocol
do_quit(ClientPID, Ref) ->
    %% tells client we want to quit
    ClientPID!{request, self(), Ref, quit},
    %% waits for client to tell us we're welcome to quit
    receive
	{result,ClientPID, Ref, ack_quit} -> 
	    exit(normal)
    end.

%% executes send new message protocol
do_send_msg(Ref, ClientName, ClientPID, Message) ->
    %% get name of chatroom from the active tab
    ChatName = active_chatroom(with_label(ClientName, ?NOTEBOOK)),
    %% check if tab is system tab
    case ChatName of
	%% if it IS the system tab, then we error
	"System" -> write_chatroom(with_label(ClientName, ?SYSTEM), "+ Cannot send messages to " ++ ChatName);
	%% if not system tab, continue with protocol
	_ ->
	    %% tell client we'd like to send the following message to the name chatroom
	    ClientPID!{request, self(), Ref, {outgoing_msg, ChatName, Message}},
	    %% we wait to receive an ack from the client, including the
	    %% client's current nickname
	    receive
		{result, ClientPID, Ref, {_, Nickname}} ->
		    %% write to chatroom
		    write_chatroom(with_label(ClientName, chatroom_id(ChatName)), Nickname ++ ": " ++ Message)
	    end
    end.

%% executes whoami protocol
do_whoami(St, Ref, ClientPID) ->
    %% ask client what current nickname is
    ClientPID!{request, self (), Ref, whoami},
    %% wait for client to tell us
    receive
	{result, ClientPID, Ref, Nickname} ->
	    %% tell user through system tab what nickname is
	    write_chatroom(with_label(St#state.client, ?SYSTEM), "+ Nickname is \"" ++ Nickname ++ "\".")
    end.

%% executes nick protocol to change user nickname
do_new_nick(St, Ref, ClientPID, Nick) ->
    %% tell client we would like to change nickname
    ClientPID!{request, self (), Ref, {nick, Nick}},
    %% listen for client's response
    receive
	{result, ClientPID, Ref, Response} ->
	    %% checks type of response
	    case Response of
		%% nickname is same as current nickname
		err_same -> write_chatroom(with_label(St#state.client, ?SYSTEM), "+ Error: Could not change nickname. Requested nickname same as current nickname!");
		%% nickname is used by someone else
		err_nick_used -> write_chatroom(with_label(St#state.client, ?SYSTEM), "+ Error: Could not change nickname. Requested nickname already used by another client!");
		%% we successfully changed nickname
		ok_nick -> write_chatroom(with_label(St#state.client, ?SYSTEM), "+ Nickname updated! New nickname: \"" ++ Nick ++ "\".")
	    end
    end.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Async Events are handled in handle_event as in handle_info %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
handle_event(#wx{ event = #wxCommand{type = command_text_enter, cmdString = Item} },
             St = #state{ parent = _Panel, client = ClientName, clientid = _ClientID }) ->

    clear_text(with_label(ClientName, ?CMDLINE)),
    Cmd = lexgrm:parse_cmd(Item),
    Ref = make_ref(),
    ClientPID = whereis(list_to_atom(ClientName)),
    ClientID = St#state.clientid,
    %% _Panel = St#state.parent,
    case Cmd  of

	%% Disconnect from the server
	quit ->
	    do_quit(ClientPID, Ref);

	%% Joining a new chatroom
	{join, ChatName}   ->
	    do_join(ClientPID, ClientName, ClientID, Ref, ChatName);

        % /leave
	leave -> 
	    ChatName = active_chatroom(with_label(ClientName, ?NOTEBOOK)),
	    do_leave_chatroom(ClientName, ChatName, Ref, ClientPID);
	% /leave #chatroom
	{leave, ChatName} -> 
	    do_leave_chatroom(ClientName, ChatName, Ref, ClientPID);
	%% Sending a message
	{msg, Message}     ->
	    do_send_msg(Ref, ClientName, ClientPID, Message);

	%% /whoami
	whoami -> 
            do_whoami(St, Ref, ClientPID);

	%% /nick nickname
	{nick, Nick} -> 
            do_new_nick(St, Ref, ClientPID, Nick);

	%% The given command was wrong
	{ignore, Line}    ->
	    write_chatroom(with_label(St#state.client, ?SYSTEM), "+ Command not recognized: " ++ Line)
    end,
    focus(with_label(ClientName, ?CMDLINE)),
    {noreply, St};


% Clicking the X on a tab
handle_event(#wx{ event = #wxAuiNotebook{type = command_auinotebook_button, selection = _TabPos} },
             St = #state{ parent = _Panel, client = ClientName }) ->
    Ref = make_ref(),
    ChatName = active_chatroom(with_label(ClientName, ?NOTEBOOK)),
    ClientPID = whereis(list_to_atom(ClientName)),
    do_leave_chatroom(ClientName, ChatName, Ref, ClientPID),
    {noreply, St};

handle_event(WX = #wx{}, State = #state{}) ->
    io:format("#wx: ~p~n",[WX]),
    io:format("#state: ~p~n",[State]),
    {noreply, State}.

%% Callbacks handled as normal gen_server callbacks (not used)
handle_info(_Msg, State) ->
    {noreply, State}.

handle_call(shutdown, _From, State) ->
    {stop, normal, ok, State};

%% Here, the GUI receives a message from the client process!
handle_call({msg_to_GUI, Chatroom, CliNick, Msg}, _From, State = #state{ client = ClientName }) ->
    write_chatroom( with_label(ClientName, chatroom_id(Chatroom)), CliNick ++ ": " ++ Msg),
    {reply, ok, State};

%% Here, the GUI receives a message to the system tab
handle_call({msg_to_SYSTEM, Msg}, _From, State = #state{ client = ClientName }) ->
    write_chatroom(with_label(ClientName, ?SYSTEM), "* "++Msg),
    {reply, ok, State} ;

handle_call(_Msg, _From, State) ->
    {reply, {error,nyi}, State}.

handle_cast(_Msg, State) ->
    {noreply,State}.

code_change(_, _, State) ->
    {stop, ignore, State}.

terminate(_Reason, _State) ->
    ok.


%% Auxiliary functions

%% Finding a unique name
find_unique_name(Prefix,N) ->
    Num = rand:uniform(N),
    MStr = integer_to_list(Num),
    Name = Prefix++MStr,
    case whereis(to_atom(Name)) of
        undefined -> {Name, Num} ;
        _         -> find_unique_name(Prefix,N)
    end.

%% GUI
clear_text(ID) ->
    CmdLine = typed_search(ID, wxTextCtrl),
    wxTextCtrl:setValue(CmdLine, "").

create_tab(ClientName, Title, Init) ->
    Ntbk = typed_search(with_label(ClientName, ?NOTEBOOK), wxAuiNotebook),
    NtbkPanel = wxPanel:new(Ntbk, []),
    Msgs = wxTextCtrl:new(NtbkPanel, -1,
			  [{value, Init},
			   {style, ?wxTE_MULTILINE}]),
    Font = wxFont:new(font_size(), ?wxFONTFAMILY_DEFAULT, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_BOLD),
    wxTextCtrl:setFont(Msgs, Font),
    wxTextCtrl:setEditable(Msgs, false),
    wxTextCtrl:setInsertionPointEnd(Msgs),
    NtbkSizer  = wxBoxSizer:new(?wxVERTICAL),
    wxSizer:addSpacer(NtbkSizer, 10),
    wxSizer:add(NtbkSizer, Msgs, [{flag, ?wxEXPAND}, {proportion,1}]),
    wxPanel:setSizer(NtbkPanel, NtbkSizer),
    wxAuiNotebook:addPage(Ntbk,NtbkPanel,Title),
    wxWindow:setFocus(NtbkPanel),
    Msgs.

active_chatroom(ID) ->
    Ntbk = typed_search(ID, wxAuiNotebook),
    PageNumber = wxAuiNotebook:getSelection(Ntbk),
    Title = wxAuiNotebook:getPageText(Ntbk, PageNumber),
    Title.

close_tab(NotebookID, TabName) ->
    Ntbk = typed_search(NotebookID, wxAuiNotebook),
    Max  = wxAuiNotebook:getPageCount(Ntbk),
    Tabs = [ {wxAuiNotebook:getPageText(Ntbk,N), N} || N <- lists:seq(0,Max-1) ],
    {_, PageNumber} = lists:keyfind(TabName, 1, Tabs),
    Page = wxAuiNotebook:getPage(Ntbk,PageNumber),
    wxWindow:destroyChildren(Page),
    wxAuiNotebook:removePage(Ntbk, PageNumber).

write_chatroom(ID, String) ->
    DMesg = typed_search(ID,wxTextCtrl),
    wxTextCtrl:writeText(DMesg, "\n"++String).

%% Labels

hash(ID) ->
    erlang:phash2(ID,10000).

focus(ID) ->
    W = wxWindow:findWindowById(hash(ID)),
    wxWindow:setFocus(W).

typed_search(ID, no_cast) ->
    Result = wxWindow:findWindowById(hash(ID)),
    Result ;

typed_search(ID, Cast) ->
    Result = wxWindow:findWindowById(hash(ID)),
    {F1, F2, _, F3} = Result,
    {F1,F2,Cast,F3}.

label(ClientID, ID, Widget) ->
    Label = join_ids(ClientID, ID),
    _Result = wxWindow:setId(Widget, hash(Label)),
    ok.

with_label(ClientName, Id) ->
    CIds = lists:sublist(ClientName,8,5),
    S = lists:flatten(io_lib:format("~s~p", [CIds, Id])),
    {N, _} = string:to_integer(S),
    N.

join_ids(ClientId, Id) ->
    S = lists:flatten(io_lib:format("~p~p", [ClientId, Id])),
    {N, _} = string:to_integer(S),
    N.

chatroom_id(ChatroomName) ->
    S = lists:foldl(fun(S, Acc) -> io_lib:format("~p", [S])++Acc end, "", lists:sublist(ChatroomName,15)),
    list_to_integer(lists:flatten(S)).

to_atom(String) ->
    list_to_atom(String).