%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2008-2010. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%

%%
-module(dbg_wx_mon_win).

%% External exports
-export([init/0]).
-export([create_win/3, get_window/1,
	 show_option/3,
	 enable/2, is_enabled/1, select/2,
	 add_module/3, delete_module/2,
	 add_process/6, update_process/4, clear_processes/1,
	 add_break/3, update_break/2, delete_break/2,
	 clear_breaks/1, clear_breaks/2,
	 handle_event/2
	]).

-import(dbg_wx_win, [to_string/1, to_string/2]).

-include_lib("wx/include/wx.hrl").

-define(default_rows,10).

-record(moduleInfo, {module, menubtn}).
-record(procInfo, {pid, row}).
-record(breakInfo, {point, status, break}).
-record(break, {mb, smi, emi, dimi, demi}).  %% BUGBUG defined in dbg_ui_win
-record(winInfo, {window,       % gsobj()
		  grid,         % gsobj()
		  row,          % int() Last row in grid

		  focus,        % int() Selected row in grid

		  modules=[],   % [#moduleInfo{}] Known modules
		  processes=[], % [#procInfo{}] Known processes
		  breaks=[],    % [#breakInfo{}] Known breakpoints

		  listbox,      % gsobj() Listinng known modules

		  %% Auto attach buttons
		  fbutton,      % gsobj()
		  bbutton,      % gsobj()
		  ebutton,      % gsobj()
		  selected=[],  % ['First Call'|'On Break'|'On Exit']

		  slabel,       % showing Stack Trace option
		  blabel        % showing Back Trace Size
		 }).

%%====================================================================
%% External exports
%%====================================================================

init() ->
    dbg_wx_win:init().

%%--------------------------------------------------------------------
%% create_win(GS, Title, Menus) -> #winInfo{}
%%   GS = gsobj()
%%   Title = string()
%%   Menus = [menu()]  See dbg_ui_win.erl
%%--------------------------------------------------------------------

-define(GRID,1000).

-define(PAD, 5).
-define(Wf, 150).
-define(Wg, 770).
-define(W, 800).
-define(H, 390).

create_win(_Wx, Title, Menus) ->
    wx:batch(fun() -> create_win_batch(Title, Menus) end).
		      
create_win_batch(Title, Menus) ->
    Win = wxFrame:new(wx:null(), ?wxID_ANY, Title, 
		      [{size, {?W,?H}}]),
    wxFrame:connect(Win, close_window, [{skip, true}]),
    MenuBar = wxMenuBar:new(),
    dbg_wx_win:create_menus(MenuBar, Menus, Win, 1),
    wxFrame:setMenuBar(Win, MenuBar),
    
    MainSz = wxBoxSizer:new(?wxHORIZONTAL),
    LeftSz = wxBoxSizer:new(?wxVERTICAL),

    Panel = wxPanel:new(Win),
    Hlb = 200,
    Listbox = wxListBox:new(Panel, ?wxID_ANY, [{size,{?Wf,Hlb}},
					       {style,?wxLB_SINGLE}]),
    wxSizer:add(LeftSz,Listbox,[{proportion,1}, {border,3}]),
    wxListBox:connect(Listbox, command_listbox_doubleclicked),
    wxListBox:connect(Listbox, right_down),

    SBox = wxStaticBox:new(Panel, ?wxID_ANY, "Auto Attach:"),
    SBS  = wxStaticBoxSizer:new(SBox, ?wxVERTICAL),
    Fbtn = wxCheckBox:new(Panel, ?wxID_ANY, "First Call"),
    wxSizer:add(SBS,Fbtn),
    Bbtn = wxCheckBox:new(Panel, ?wxID_ANY, "On Break"),
    wxSizer:add(SBS,Bbtn),
    Ebtn = wxCheckBox:new(Panel, ?wxID_ANY, "On Exit"),
    wxSizer:add(SBS,Ebtn),
    wxFrame:connect(Panel, command_checkbox_clicked), 
    wxSizer:add(LeftSz,SBS, [{flag,?wxEXPAND}]),

    SLabel = wxStaticText:new(Panel, ?wxID_ANY, "Stack Trace:\n On (with tail)"), 
    wxSizer:add(LeftSz,SLabel),
    BLabel = wxStaticText:new(Panel, ?wxID_ANY, "Back Trace Size:\n 50000"), 
    wxSizer:add(LeftSz,BLabel),
    
    %% Create list_crtl / grid
    Grid = wxListCtrl:new(Panel, [{winid, ?GRID},
				  {style, ?wxLC_REPORT bor ?wxLC_SINGLE_SEL 
				   bor ?wxLC_HRULES },
				  {size, {600, -1}}]),
    LI = wxListItem:new(),
    wxListItem:setText(LI, "Pid"), 
    wxListItem:setAlign(LI, ?wxLIST_FORMAT_CENTRE),
    wxListCtrl:insertColumn(Grid, 0, LI),
    wxListItem:setText(LI, "Initial Call"),
    wxListItem:setAlign(LI, ?wxLIST_FORMAT_LEFT),
    wxListCtrl:insertColumn(Grid, 1, LI),
    wxListItem:setText(LI, "Name"), 
    wxListCtrl:insertColumn(Grid, 2, LI),
    wxListItem:setAlign(LI, ?wxLIST_FORMAT_CENTRE),
    wxListItem:setText(LI, "Status"), 
    wxListCtrl:insertColumn(Grid, 3, LI),
    wxListItem:setText(LI, "Information"), 
    wxListItem:setAlign(LI, ?wxLIST_FORMAT_LEFT),
    wxListCtrl:insertColumn(Grid, 4, LI),
    wxListItem:destroy(LI),
        
    wxListCtrl:setColumnWidth(Grid, 0, 80),
    wxListCtrl:setColumnWidth(Grid, 1, 150),
    wxListCtrl:setColumnWidth(Grid, 2, 100),
    wxListCtrl:setColumnWidth(Grid, 3, 70),
    wxListCtrl:setColumnWidth(Grid, 4, 200),
    wxListCtrl:connect(Grid, command_list_item_activated), 
    wxListCtrl:connect(Grid, command_list_item_selected), 
    wxListCtrl:connect(Grid, size, [{skip, true}]),
    wxListCtrl:connect(Grid, key_up, [{id, ?GRID}, {skip,true}]),

    wxWindow:connect(Win, enter_window, [{skip,true}]),
    wxWindow:setFocus(Grid),

    %% Put it in the window
    wxSizer:add(MainSz, LeftSz, [{border, 3}, {flag,?wxALL bor ?wxEXPAND}]),
    wxSizer:add(MainSz, Grid,   [{border, 3}, {flag,?wxALL bor ?wxEXPAND}, 
				 {proportion, 1}]),

    wxWindow:setSizer(Panel,MainSz),
    wxSizer:fit(MainSz, Win),
    wxSizer:setSizeHints(MainSz,Win),
    
    IconFile = dbg_wx_win:find_icon("erlang_bug.png"),
    Icon = wxIcon:new(IconFile, [{type,?wxBITMAP_TYPE_PNG}]),
    wxFrame:setIcon(Win, Icon),
    wxIcon:destroy(Icon),
    wxFrame:show(Win),
    dbg_wx_winman:raise(Win),
    #winInfo{window=Win, grid=Grid, row=0, focus=0,
	     listbox=Listbox,
	     fbutton=Fbtn, bbutton=Bbtn, ebutton=Ebtn,
	     slabel=SLabel, blabel=BLabel}.

%%--------------------------------------------------------------------
%% get_window(WinInfo) -> Window
%%   WinInfo = #winInfo{}
%%   Window = wxobj()
%%--------------------------------------------------------------------
get_window(WinInfo) ->
    WinInfo#winInfo.window.

%%--------------------------------------------------------------------
%% show_option(WinInfo, Option, Value) -> void()
%%   WinInfo = #winInfo{}
%%   Option = auto_attach | stack_trace | back_trace
%%   Value = [Flag]                          % Option==auto_attach
%%             Flag = init | break | exit
%%         | true | all | no_tail | false    % Option==stack_trace
%%         | int()                           % Option==back_trace
%%--------------------------------------------------------------------
show_option(WinInfo, Option, Value) ->
    case Option of	
	auto_attach ->
	    wx:foreach(fun(Button) ->
				  wxCheckBox:setValue(Button, false)
			  end,
			  option_buttons(WinInfo, [init, break, exit])),
	    wx:foreach(fun(Button) ->
				  wxCheckBox:setValue(Button, true)
			  end,
			  option_buttons(WinInfo, Value));

	stack_trace ->
	    Text = case Value of
		       all ->     "Stack Trace:\n On (with tail)";
		       true ->    "Stack Trace:\n On (with tail)";
		       no_tail -> "Stack Trace:\n On (no tail)";
		       false ->   "Stack Trace:\n Off"
		   end,
	    wxStaticText:setLabel(WinInfo#winInfo.slabel, Text);

	back_trace ->
	    Text = "Back Trace Size:\n " ++ integer_to_list(Value),
	    wxStaticText:setLabel(WinInfo#winInfo.blabel, Text)
    end.

option_buttons(WinInfo, [init|Flags]) ->
    [WinInfo#winInfo.fbutton|option_buttons(WinInfo, Flags)];
option_buttons(WinInfo, [break|Flags]) ->
    [WinInfo#winInfo.bbutton|option_buttons(WinInfo, Flags)];
option_buttons(WinInfo, [exit|Flags]) ->
    [WinInfo#winInfo.ebutton|option_buttons(WinInfo, Flags)];
option_buttons(_WinInfo, []) ->
    [].

%%--------------------------------------------------------------------
%% enable([MenuItem], Bool)
%% is_enabled(MenuItem) -> Bool
%%   MenuItem = atom()
%%   Bool = boolean()
%%--------------------------------------------------------------------
enable(MenuItems, Bool) ->
    lists:foreach(fun(MenuItem) ->
			  MI = get(MenuItem),
			  wxMenuItem:enable(MI, [{enable, Bool}])
		  end,
		  MenuItems).

is_enabled(MenuItem) ->
    MI = get(MenuItem),
    wxMenuItem:isEnabled(MI).

%%--------------------------------------------------------------------
%% select(MenuItem, Bool)
%%   MenuItem = atom()
%%   Bool = boolean()
%%--------------------------------------------------------------------
select(MenuItem, Bool) ->
    MI = get(MenuItem),
    wxMenuItem:check(MI, [{check, Bool}]).

%%--------------------------------------------------------------------
%% add_module(WinInfo, Name, Mod) -> WinInfo
%%   WinInfo = #winInfo{}
%%   Name = atom()
%%   Mod = atom()
%%--------------------------------------------------------------------
add_module(WinInfo, MenuName, Mod) ->
    Win = WinInfo#winInfo.window,
    Modules = WinInfo#winInfo.modules,
    case lists:keymember(Mod, #moduleInfo.module, Modules) of
	false ->
	    %% Create a menu for the module
	    Menu = get(MenuName),
	    Sub = wxMenu:new([]),
	    ViewItem = wxMenu:append(Sub, ?wxID_ANY, "View"), 
	    ViewId = wxMenuItem:getId(ViewItem),
	    wxMenu:connect(Win, command_menu_selected, 
			   [{id,ViewId}, {userData, {module,Mod,view}}]),
	    DelItem = wxMenu:append(Sub, ?wxID_ANY, "Delete"), 
	    DelId = wxMenuItem:getId(DelItem),
	    wxMenu:connect(Win, command_menu_selected, 
			   [{id,DelId}, {userData, {module,Mod,delete}}]),
	    MenuBtn = wxMenu:append(Menu, ?wxID_ANY, atom_to_list(Mod), Sub),
	    wxListBox:append(WinInfo#winInfo.listbox, atom_to_list(Mod)),
	    
	    ModInfo = #moduleInfo{module=Mod, menubtn={Menu,MenuBtn}},
	    WinInfo#winInfo{modules=[ModInfo | Modules]};
	true -> WinInfo
   end.
    
%%--------------------------------------------------------------------
%% delete_module(WinInfo, Mod) -> WinInfo
%%   WinInfo = #winInfo{}
%%   Mod = atom()
%%--------------------------------------------------------------------
delete_module(WinInfo, Mod) ->
    {value, ModInfo} = lists:keysearch(Mod, #moduleInfo.module,
				       WinInfo#winInfo.modules),
    {Menu, MenuBtn} = ModInfo#moduleInfo.menubtn,
    wxMenu:'Destroy'(Menu, MenuBtn),
    ListBox = WinInfo#winInfo.listbox,
    Id = wxListBox:findString(ListBox, atom_to_list(Mod)),
    wxListBox:delete(ListBox,Id),
    WinInfo#winInfo{modules=lists:keydelete(Mod, #moduleInfo.module,
					    WinInfo#winInfo.modules)}.

%%--------------------------------------------------------------------
%% add_process(WinInfo, Pid, Name, Function, Status, Info) -> WinInfo
%%   WinInfo = #winInfo{}
%%   Pid = pid()
%%   Name = undefined | atom()
%%   Function = {Mod, Func, Args}
%%   Status = idle | running | break | exit
%%   Info = {} | term()
%%--------------------------------------------------------------------
add_process(WinInfo, Pid, Name, {Mod,Func,Args}, Status, Info) ->
    Grid = WinInfo#winInfo.grid,
    Row = (WinInfo#winInfo.row),
    
    Name2 = case Name of undefined -> ""; _ -> to_string(Name) end,
    FuncS = to_string("~w:~w/~w", [Mod, Func, length(Args)]),
    Info2 = case Info of {} -> ""; _ -> to_string(Info) end,
    Pid2  = to_string("~p",[Pid]),
    
    Add = fun() ->
		  _Dbg = wxListCtrl:insertItem(Grid, Row,""),
		  %%wxListCtrl:setItemData(Grid,Temp,Row),
		  if (Row rem 2) =:= 0 -> 
			  wxListCtrl:setItemBackgroundColour(Grid, Row, {240,240,255});
		     true -> ignore
		  end,

		  wxListCtrl:setItem(Grid, Row, 0, Pid2),
		  wxListCtrl:setItem(Grid, Row, 1, FuncS),
		  wxListCtrl:setItem(Grid, Row, 2, Name2),
		  wxListCtrl:setItem(Grid, Row, 3, to_string(Status)),
		  wxListCtrl:setItem(Grid, Row, 4, Info2),
		  ok
	  end,
    wx:batch(Add),

    ProcInfo = #procInfo{pid=Pid, row=Row},
    WinInfo#winInfo{processes=[ProcInfo|WinInfo#winInfo.processes],
		    row=Row+1}.

%%--------------------------------------------------------------------
%% update_process(WinInfo, Pid, Status, Info)
%%   WinInfo = #winInfo{}
%%   Pid = pid()
%%   Status = idle | running | break | exit
%%   Info = {} | term()
%%--------------------------------------------------------------------
update_process(WinInfo, Pid, Status, Info) ->
    {value, ProcInfo} = lists:keysearch(Pid, #procInfo.pid,
					WinInfo#winInfo.processes),

    Grid = WinInfo#winInfo.grid,
    Row  = ProcInfo#procInfo.row,    
    Info2 = case Info of {} -> ""; _ -> Info end,
    wxListCtrl:setItem(Grid, Row, 3, to_string(Status)),
    wxListCtrl:setItem(Grid, Row, 4, to_string(Info2)).
  
%%--------------------------------------------------------------------
%% clear_processes(WinInfo) -> WinInfo
%%   WinInfo = #winInfo{}
%%--------------------------------------------------------------------
clear_processes(WinInfo) ->
    Grid = WinInfo#winInfo.grid,
    Max = WinInfo#winInfo.row,
    wx:batch(fun() -> clear_processes(Grid, Max-1) end),
    WinInfo#winInfo{row=0, focus=0, processes=[]}.

clear_processes(Grid, Row) when Row >= 0 ->
    Item = wxListItem:new(),
    wxListItem:setId(Item,Row),
    wxListItem:setColumn(Item, 3),
    case wxListCtrl:getItem(Grid, Item) of
	true -> 
	    case wxListItem:getText(Item) of
		"exit" ->
		    wxListItem:setColumn(Item, 0),
		    wxListCtrl:getItem(Grid, Item),
		    Pid = list_to_pid(wxListItem:getText(Item)),
		    dbg_wx_winman:clear_process(dbg_wx_trace:title(Pid));
		_ ->
		    ok
	    end;
	false ->
	    ignore
    end,
    wxListItem:destroy(Item),
    wxListCtrl:deleteItem(Grid, Row),
    clear_processes(Grid, Row-1);
clear_processes(_Grid, _Row) ->
    done.

%%--------------------------------------------------------------------
%% add_break(WinInfo, Name, {Point, Options}) -> WinInfo
%%   WinInfo = #winInfo{}
%%   Name = atom()
%%   Point = {Mod, Line}
%%   Options = [Status, Action, Mods, Cond]
%%     Status = active | inactive
%%     Action = enable | disable | delete
%%     Mods = null (not used)
%%     Cond = null | {Mod, Func}
%%--------------------------------------------------------------------
add_break(WinInfo, Menu, {Point, Options}) ->
    Break = dbg_wx_win:add_break(WinInfo#winInfo.window, Menu, Point),
    dbg_wx_win:update_break(Break, Options),
    BreakInfo = #breakInfo{point=Point, break=Break},
    WinInfo#winInfo{breaks=[BreakInfo|WinInfo#winInfo.breaks]}.

%%--------------------------------------------------------------------
%% update_break(WinInfo, {Point, Options})
%%   WinInfo = #winInfo{}
%%   Point = {Mod, Line}
%%   Options = [Status, Action, Mods, Cond]
%%     Status = active | inactive
%%     Action = enable | disable | delete
%%     Mods = null (not used)
%%     Cond = null | {Mod, Func}
%%--------------------------------------------------------------------
update_break(WinInfo, {Point, Options}) ->
    {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
					 WinInfo#winInfo.breaks),
    dbg_wx_win:update_break(BreakInfo#breakInfo.break, Options).

%%--------------------------------------------------------------------
%% delete_break(WinInfo, Point) -> WinInfo
%%   WinInfo = #winInfo{}
%%   Point = {Mod, Line}
%%--------------------------------------------------------------------
delete_break(WinInfo, Point) ->
    {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
					 WinInfo#winInfo.breaks),
    dbg_wx_win:delete_break(BreakInfo#breakInfo.break),
    WinInfo#winInfo{breaks=lists:keydelete(Point, #breakInfo.point,
					   WinInfo#winInfo.breaks)}.

%%--------------------------------------------------------------------
%% clear_breaks(WinInfo) -> WinInfo
%% clear_breaks(WinInfo, Mod) -> WinInfo
%%   WinInfo = #winInfo{}
%%--------------------------------------------------------------------
clear_breaks(WinInfo) ->
    lists:foreach(fun(BreakInfo) ->
			  dbg_wx_win:delete_break(BreakInfo#breakInfo.break)
		  end,
		  WinInfo#winInfo.breaks),
    WinInfo#winInfo{breaks=[]}.
clear_breaks(WinInfo, Mod) ->
    Fun =
	fun(BreakInfo) ->
		case BreakInfo#breakInfo.point of
		    {Mod, _Line} ->
			dbg_wx_win:delete_break(BreakInfo#breakInfo.break),
			false;
		    _ -> true
		end
	end,
    Breaks = lists:filter(Fun, WinInfo#winInfo.breaks),
    WinInfo#winInfo{breaks=Breaks}.
    
%%--------------------------------------------------------------------
%% handle_event(WxEvent, WinInfo) -> Command
%%   WxEvent = #wx{}
%%   WinInfo = #winInfo{}
%%   Command = ignore
%%           | stopped
%%           | {coords, {X,Y}}
%%
%%           | {shortcut, Key}
%%           | MenuItem | {Menu, [MenuItem]}
%%               MenuItem = Menu = atom()
%%           | {break, Point, What}
%%               What = delete | {status, Status} | {trigger, Trigger}
%%           | {module, Mod, What}
%%               What = view | delete
%%
%%           | {focus, Pid, WinInfo}
%%           | default
%%--------------------------------------------------------------------
%% Window events
handle_event(#wx{event=#wxSize{size={W,_}}}, #winInfo{grid=Grid}) ->
    wx:batch(fun() ->
		     Tot = wx:foldl(fun(C,Sum) -> 
					    Sum + wxListCtrl:getColumnWidth(Grid, C)
				    end, 0, [0,1,2,3]),
		     wxListCtrl:setColumnWidth(Grid, 4, W-Tot-4)
	     end),
    ignore;
handle_event(_Ev=#wx{event=#wxClose{}}, _WinInfo) ->
%%    io:format("~p Received ~p close ~p~n", [?MODULE, self(), _Ev]),
    stopped;

%% Menus and keyboard shortcuts
handle_event(#wx{userData={dbg_ui_winman, Win},
		 event=#wxCommand{type=command_menu_selected}}, _Wi) ->
    dbg_wx_winman:raise(Win),
    ignore;
handle_event(_Ev = #wx{event=#wxKey{keyCode=Key, controlDown=true}}, _WinInfo) ->
    if
	Key/=?WXK_UP, Key/=?WXK_DOWN, Key /=? WXK_RETURN -> 
	    try  
		{shortcut, list_to_atom([Key+($a-$A)])}
	    catch _:_ -> ignore
	    end;
	true -> 
	    ignore
    end;

handle_event(#wx{userData={break, Point, status}, 
		 event=#wxCommand{type=command_menu_selected}},
	     WinInfo) ->
    {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
					 WinInfo#winInfo.breaks),
    %% This is a temporary hack !!
    #breakInfo{break=#break{smi=Smi}} = BreakInfo,

    case wxMenuItem:getText(Smi) of
	"Enable" -> {break, Point, {status, active}};
	"Disable" -> {break, Point, {status, inactive}}
    end;

%% Listbox
handle_event(#wx{event=#wxCommand{type=command_listbox_doubleclicked, cmdString=ModS}}, 
	     _WinInfo) ->
    {module, list_to_atom(ModS), view};
handle_event(#wx{obj=ListBox, event=#wxMouse{type=right_down, x=X,y=Y}}, 
	     #winInfo{listbox=ListBox}) ->
    case wxListBox:hitTest(ListBox, {X,Y}) of
	?wxNOT_FOUND -> ignore;
	Row ->	    
	    ModS = wxListBox:getString(ListBox,Row),
	    io:format("Re-loading/interpreting: ~s~n", [ModS]),
	    int:i(list_to_atom(ModS)),
	    ignore
    end;

%% Auto attach buttons
handle_event(#wx{event=#wxCommand{type=command_checkbox_clicked}}, 
	     WinInfo) ->
    Check = fun(Button, NamesAcc) ->
		    case wxCheckBox:isChecked(Button) of
			true ->
			    Name = wxCheckBox:getLabel(Button),
			    [list_to_atom(Name)|NamesAcc];
			false ->
			    NamesAcc
		    end
	    end,
    Names = wx:foldl(Check, [],
		     [WinInfo#winInfo.ebutton,
		      WinInfo#winInfo.bbutton,
		      WinInfo#winInfo.fbutton]),
    {'Auto Attach', Names};

%% Process grid
handle_event(#wx{event=#wxList{type=command_list_item_selected,
			       itemIndex=Row}}, WinInfo) ->
    #winInfo{processes=Pids} = WinInfo,
    #procInfo{pid=Pid} = lists:keyfind(Row, #procInfo.row, Pids),
    {focus, Pid, WinInfo#winInfo{focus=Row}};
handle_event(#wx{event=#wxList{type=command_list_item_activated}}, 
	     _WinInfo) ->
    default;
handle_event(#wx{event=#wxMouse{type=enter_window}}, #winInfo{grid=Grid}) ->
    %% Keyboard focus
    wxWindow:setFocus(Grid),
    ignore;

%% Menu Events
handle_event(#wx{userData=Data, 
		 event=_Cmd=#wxCommand{type=command_menu_selected}},
	     _WinInfo) ->
    Data;
handle_event(_Event, _WinInfo) ->
%%    io:format("Ev: ~p~n",[_Event]),
    ignore.

%%====================================================================
%% Internal functions
%%====================================================================