%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2008-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

%%
-module(dbg_wx_win).

%% External exports
-export([init/0,
	 create_menus/4, %% For wx
	 add_break/3, update_break/2, delete_break/1,
	 motion/2,
	 confirm/2, notify/2, entry/4, open_help/2,
	 to_string/1, to_string/2,
	 find_icon/1
	]).

-record(break, {mb, smi, emi, dimi, demi}).
-include_lib("wx/include/wx.hrl").

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

%%--------------------------------------------------------------------
%% init() -> GS
%%   GS = term()
%%--------------------------------------------------------------------
init() ->
    _ = wx:new(),
    ok.

%%--------------------------------------------------------------------
%% create_menus(MenuBar, [Menu])
%%   MenuBar = gsobj()
%%   Menu = {Name, [Item]}
%%     Name = atom()
%%     Item = {Name, N} | {Name, N, Type} | {Name, N, cascade, [Item]}
%%          | separator
%%       N = no | integer()
%%       Type = check | radio
%% Create the specified menus and menuitems.
%%
%% Normal menuitems are specified as {Name, N}. Generates the event:
%%   {gs, _Id, click, {menuitem, Name}, _Arg}
%%
%% Check and radio menuitems are specified as {Name, N, check|radio}.
%% They are assumed to be children to a cascade menuitem! (And all children
%% to one cascade menuitem are assumed to be either check OR radio
%% menuitems)!
%% Selecting a check/radio menuitem generates the event:
%%   {gs, _Id, click, {menu, Menu}, _Arg}
%% where Menu is the name of the parent, the cascade menuitem.
%% Use selected(Menu) to retrieve which check/radio menuitems are
%% selected.
%%--------------------------------------------------------------------

create_menus(MB, [{Title,Items}|Ms], Win, Id0) ->
    Menu = wxMenu:new([]),
    put(Title,Menu),
    Id = create_menu_item(Menu, Items, Win, Id0, true),
    wxMenuBar:append(MB,Menu,menu_name(Title,ignore)),
    create_menus(MB,Ms,Win,Id);
create_menus(_MB,[], _Win,Id) ->
    Id.

create_menu_item(Menu, [separator|Is], Win, Id,Connect) ->
    _ = wxMenu:appendSeparator(Menu),
    create_menu_item(Menu,Is,Win,Id+1,Connect);
create_menu_item(Menu, [{Name, _N, cascade, Items}|Is], Win, Id0,Connect) ->
    Sub = wxMenu:new([]),
    Id = create_menu_item(Sub, Items, Win, Id0, false),
    _ = wxMenu:append(Menu, ?wxID_ANY, menu_name(Name,ignore), Sub),
    %% Simulate GS sub checkBox/RadioBox behaviour
    Self = self(),
    Butts = [{MI,get(MI)} || {MI,_,_} <- Items],
    IsChecked = fun({MiName,MI},Acc) ->
			case wxMenuItem:isChecked(MI) of
			    true -> [MiName|Acc];
			    false -> Acc
			end		
		end,
    Filter = fun(Ev,_) ->
		     Enabled = lists:foldl(IsChecked, [], Butts),
		     Self ! Ev#wx{userData={Name, Enabled}}
	     end,
    _ = wxMenu:connect(Win, command_menu_selected,
		       [{id,Id0},{lastId, Id-1},{callback,Filter}]),
    create_menu_item(Menu, Is, Win, Id, Connect);
create_menu_item(Menu, [{Name,Pos}|Is], Win, Id, Connect) -> 
    MenuId = case lists:member(Name, ['Debugger']) of
		 true -> ?wxID_HELP;
		 _ ->    Id
	     end,
    Item = wxMenu:append(Menu, MenuId, menu_name(Name,Pos)),
    put(Name,Item),
    if Connect -> 
	    wxMenu:connect(Win, command_menu_selected, [{id,MenuId},{userData, Name}]);
       true -> ignore
    end,
    create_menu_item(Menu,Is,Win,Id+1, Connect);
create_menu_item(Menu, [{Name,N,check}|Is], Win, Id, Connect) ->
    Item = wxMenu:appendCheckItem(Menu, Id, menu_name(Name,N)),
    put(Name,Item),
    if Connect -> 
	    wxMenu:connect(Win, command_menu_selected, [{id,Id},{userData, Name}]);
       true -> ignore
    end,
    create_menu_item(Menu,Is,Win,Id+1,Connect);
create_menu_item(Menu, [{Name, N, radio}|Is], Win, Id,Connect) ->
    Item = wxMenu:appendRadioItem(Menu, Id, menu_name(Name,N)),
    put(Name,Item),
    if Connect -> 
	    wxMenu:connect(Win, command_menu_selected, [{id,Id},{userData, Name}]);
       true -> ignore
    end,
    create_menu_item(Menu,Is,Win,Id+1,Connect);
create_menu_item(_, [], _, Id,_) ->
    Id.

%%--------------------------------------------------------------------
%% add_break(Name, Point) -> #break{}
%%   Name = atom()
%%   Point = {Mod, Line}
%% The break will generate the following events:
%%   #wx{userData= {break, Point, Event}}
%%     Event = delete | {trigger, Action} | {status, Status}
%%       Action = enable | disable | delete
%%       Status = active | inactive
%%--------------------------------------------------------------------
add_break(Win, MenuName, Point) ->
    %% Create a name for the breakpoint
    {Mod, Line} = Point,
    Label = to_string("~w ~5w", [Mod, Line]),

    Menu = get(MenuName),
    %% Create a menu for the breakpoint
    Add = fun(Item,Action) ->
		  Id = wxMenuItem:getId(Item),
		  wxMenu:connect(Win, command_menu_selected, 
				 [{id,Id}, {userData, Action}])
	  end,
    Sub = wxMenu:new([]),
    Dis = wxMenu:append(Sub, ?wxID_ANY, "Disable"),
    Add(Dis, {break,Point,status}),
    Del = wxMenu:append(Sub, ?wxID_ANY, "Delete"),
    Add(Del, {break,Point,delete}),
    Trigger = wxMenu:new([]),
    Enable = wxMenu:appendRadioItem(Trigger, ?wxID_ANY,"Enable"),
    Add(Enable, {break,Point,{trigger,enable}}),
    TDisable = wxMenu:appendRadioItem(Trigger, ?wxID_ANY,"Disable"),
    Add(TDisable, {break,Point,{trigger,disable}}),
    Delete = wxMenu:appendRadioItem(Trigger, ?wxID_ANY,"Delete"),
    Add(Delete, {break,Point,{trigger,delete}}),

    _ = wxMenu:append(Sub, ?wxID_ANY, "Trigger Action", Trigger),
    MenuBtn = wxMenu:append(Menu,?wxID_ANY, Label, Sub),

    #break{mb={Menu,MenuBtn}, 
	   smi=Dis, emi=Enable, dimi=TDisable, demi=Delete}.

%%--------------------------------------------------------------------
%% update_break(Break, Options)
%%   Break = #break{}
%%   Options = [Status, Action, Mods, Cond]
%%     Status = active | inactive
%%     Action = enable | disable | delete
%%     Mods = null (not used)
%%     Cond = null | {Mod, Func}
%%--------------------------------------------------------------------
update_break(Break, Options) ->
    [Status, Trigger|_] = Options,

    Label = case Status of
		active -> "Disable";
		inactive -> "Enable"
	    end,
    wxMenuItem:setText(Break#break.smi, Label),

    TriggerMI = case Trigger of
		    enable -> Break#break.emi;
		    disable -> Break#break.dimi;
		    delete -> Break#break.demi
		end,
    wxMenuItem:check(TriggerMI).

%%--------------------------------------------------------------------
%% delete_break(Break)
%%   Break = #break{}
%%--------------------------------------------------------------------
delete_break(Break) ->
    {Menu, MenuBtn} = Break#break.mb,
    wxMenu:'Destroy'(Menu,MenuBtn).

%%--------------------------------------------------------------------
%% motion(X, Y) -> {X, Y}
%%   X = Y = integer()
%%--------------------------------------------------------------------
motion(X, Y) ->
    receive
	{gs, _Id, motion, _Data, [NX,NY]} ->
	    motion(NX, NY)
    after 0 ->
	    {X, Y}
    end.


%%--------------------------------------------------------------------
%% confirm(MonWin, String) -> ok | cancel
%%--------------------------------------------------------------------

confirm(Win,Message) ->
    MD = wxMessageDialog:new(Win,to_string(Message),
			     [{style, ?wxOK bor ?wxCANCEL}, 
			      {caption, "Confirm"}]),
    Res = case wxDialog:showModal(MD) of
	      ?wxID_OK -> ok;
	      _ -> cancel
	  end,
    wxDialog:destroy(MD),
    Res.

%%--------------------------------------------------------------------
%% notify(MonWin, String) -> ok 
%%--------------------------------------------------------------------

notify(Win,Message) ->
    MD = wxMessageDialog:new(Win,to_string(Message),
			     [{style, ?wxOK}, 
			      {caption, "Confirm"}]),
    wxDialog:showModal(MD),
    wxDialog:destroy(MD),
    ok.

%%--------------------------------------------------------------------
%% entry(Parent, Title, Prompt, {Type, Value}) -> {Prompt, Val} | cancel
%%--------------------------------------------------------------------

entry(Parent, Title, Prompt, {Type, Value}) ->
    Ted = wxTextEntryDialog:new(Parent, to_string(Prompt),
				[{caption, to_string(Title)},
				 {value, Value}]),

    case wxDialog:showModal(Ted) of
	?wxID_OK ->
	    Res = case verify(Type, wxTextEntryDialog:getValue(Ted)) of
		      {edit,NewVal} ->
			  {Prompt,NewVal};
		      ignore ->
			  cancel
		  end,
	    wxTextEntryDialog:destroy(Ted),
	    Res;
	_ ->
	    cancel
    end.


verify(Type, Str) ->
    case erl_scan:string(Str, 1, [text]) of
	{ok, Tokens, _EndLine} when Type==term ->
	    case erl_eval:extended_parse_term(Tokens++[{dot, erl_anno:new(1)}]) of
		{ok, Value} -> {edit, Value};
		_Error -> 
		    ignore
	    end;
	{ok, [{Type, _Line, Value}], _EndLine} when Type/=term ->
	    {edit, Value};
	_Err ->
	    ignore
    end.

%%--------------------------------------------------------------------
%% open_help/2
%%    opens browser with help file
%%--------------------------------------------------------------------
open_help(_Parent, HelpHtmlFile) ->
    wx_misc:launchDefaultBrowser("file://" ++ HelpHtmlFile).

%%--------------------------------------------------------------------
%% to_string(Term) -> [integer()]
%% to_string(Format,Args) -> [integer()]
%%--------------------------------------------------------------------
    
to_string(Atom) when is_atom(Atom) ->
    atom_to_list(Atom);
to_string(Integer) when is_integer(Integer) ->
    integer_to_list(Integer);
to_string([]) -> "";
to_string(List) when is_list(List) ->
    try unicode:characters_to_list(List)
    catch _:_ ->
            io_lib:format("~tp",[List])
    end;
to_string(Term) ->
    io_lib:format("~tp",[Term]).

to_string(Format,Args) ->
    io_lib:format(Format, Args).

menu_name(Atom, N) when is_atom(Atom) -> 
    menu_name(atom_to_list(Atom),N);
menu_name("Help", _) -> %% Mac needs this to be exactly this
    "&Help";
menu_name(Str, Pos) when is_integer(Pos) ->
    {S1,[Key|_]=S2} = lists:split(Pos,Str),
    S1 ++ [$&|S2] ++ "\tCtrl+" ++ string:uppercase([Key]);
menu_name(Str,_) ->
    Str.

%%--------------------------------------------------------------------
%% find_icon(File) -> Path or exists
%%--------------------------------------------------------------------

find_icon(File) ->
    PrivDir = code:priv_dir(debugger),
    PrivIcon = filename:append(PrivDir, File),
    case filelib:is_regular(PrivIcon) of
	true -> PrivIcon;
	false -> 
	    CurrDir = filename:dirname(code:which(?MODULE)),	    
	    CurrIcon = filename:append(CurrDir, File),
	    true = filelib:is_regular(CurrIcon),
	    CurrIcon
    end.

