%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2012. 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_ui_trace_win).
-compile([{nowarn_deprecated_function,{gs,button,2}},
          {nowarn_deprecated_function,{gs,button,3}},
          {nowarn_deprecated_function,{gs,checkbutton,2}},
          {nowarn_deprecated_function,{gs,config,2}},
          {nowarn_deprecated_function,{gs,create,4}},
          {nowarn_deprecated_function,{gs,destroy,1}},
          {nowarn_deprecated_function,{gs,editor,2}},
          {nowarn_deprecated_function,{gs,editor,3}},
          {nowarn_deprecated_function,{gs,entry,2}},
          {nowarn_deprecated_function,{gs,entry,3}},
          {nowarn_deprecated_function,{gs,frame,3}},
          {nowarn_deprecated_function,{gs,grid,3}},
          {nowarn_deprecated_function,{gs,gridline,2}},
          {nowarn_deprecated_function,{gs,label,2}},
          {nowarn_deprecated_function,{gs,label,3}},
          {nowarn_deprecated_function,{gs,menubar,2}},
          {nowarn_deprecated_function,{gs,read,2}},
          {nowarn_deprecated_function,{gs,window,2}},
          {nowarn_deprecated_function,{gs,window,3}}]).

%% External exports
-export([init/0]).
-export([create_win/4, get_window/1,
	 configure/2,
	 enable/2, is_enabled/1, select/2,
	 add_break/3, update_break/2, delete_break/2,
	 clear_breaks/1, clear_breaks/2,
	 display/1,                                   % Help messages
	 is_shown/2,                                  % Code area
	 show_code/3, show_no_code/1, remove_code/2,      
	 mark_line/3, unmark_line/1,
	 select_line/2, selected_line/1,
	 eval_output/2,                               % Evaluator area
	 update_bindings/1,                           % Bindings area
	 trace_output/1,                              % Trace area
	 handle_event/2
	]).
-export([helpwin/4,                                   % Help windows
	 helpwin/5]).

-record(breakInfo, {point, status, break}).
-record(winInfo, {window,          % gsobj()
		  size,            % {W, H}
		  flags,           % {F,F,F,F} F = open|close

		  marked_line=0,   % integer() Current line
		  selected_line=0, % integer() Selected line

		  breaks=[],       % [#breakInfo{}] Known breakpoints

		  editor,          % {Mod, Editor}  Visible code editor
		  editors=[]       % [{Mod,Editor}] Code editors
		 }).


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

%%--------------------------------------------------------------------
%% init() -> GS
%%   GS = term()
%%--------------------------------------------------------------------
init() ->
    dbg_ui_win:init().

%%--------------------------------------------------------------------
%% create_win(GS, Title, TraceWin, Menus) -> #winInfo{}
%%  GS = gsobj()
%%  Title = string()
%%  TraceWin = [WinArea]
%%    WinArea = 'Button|Evaluator|Bindings|Trace Area'
%%  Menus = [menu()]  See dbg_ui_win.erl
%%--------------------------------------------------------------------
create_win(GS, Title, TraceWin, Menus) ->
    Bu = flip(lists:member('Button Area', TraceWin)),
    Ev = flip(lists:member('Evaluator Area', TraceWin)),
    Bi = flip(lists:member('Bindings Area', TraceWin)),
    Tr = flip(lists:member('Trace Area', TraceWin)),
    
    Win = gs:window(trace_window, GS, [{title, Title},
				       {width, 550},
				       {configure,true}, {destroy,true},
				       {keypress,true}, {motion,true}]),

    MenuBar = gs:menubar(Win, []),
    dbg_ui_win:create_menus(MenuBar, Menus),
    dbg_ui_winman:windows_menu(MenuBar),

    FrameOpts = [{anchor,nw}, {relief,raised}, {bw,2}, {keypress,true}],
    Editor = code_area(2, 25, FrameOpts, Win),
    button_area(Bu, 2, 235, FrameOpts, Win),
    eval_area({Ev,Bi}, 2, 265, FrameOpts, Win),
    bind_area({Ev,Bi}, 300, 265, FrameOpts, Win),
    trace_area(Tr, 2, 475, FrameOpts, Win),

    Flags = {Bu, Ev, Bi, Tr},
    resizebar(rb1(Flags), 'RB1',   2, 225, 710,  10, Win),
    resizebar(rb2(Flags), 'RB2',   2, 465, 710,  10, Win),
    resizebar(rb3(Flags), 'RB3', 290, 265,  10, 200, Win),
    config_v(),
    config_h(),
    
    gs:config(Win,{height,
		   25 +
		   gs:read('CodeArea', height) +
		   gs:read('RB1', height) +
		   gs:read('ButtonArea', height) +
		   erlang:max(gs:read('EvalArea', height),
		       gs:read('BindArea', height)) +
		   gs:read('RB2', height) +
		   gs:read('TraceArea', height)}),
    
    gs:config(Win, {map, true}),
    #winInfo{window=Win, size={gs:read(Win,width), gs:read(Win,height)},
	     flags=Flags,
	     editor={'$top', Editor}, editors=[{'$top', Editor}]}.

flip(true) -> open;
flip(false) -> close.
    
%%--------------------------------------------------------------------
%% get_window(WinInfo) -> Window
%%   WinInfo = #winInfo{}
%%   Window = gsobj()
%%--------------------------------------------------------------------
get_window(WinInfo) ->
    WinInfo#winInfo.window.

%%--------------------------------------------------------------------
%% configure(WinInfo, TraceWin) -> WinInfo
%%   WinInfo = #winInfo{}
%%  TraceWin = [WinArea]
%%    WinArea = 'Button|Evaluator|Bindings|Trace Area'
%% Window areas should be opened or closed.
%%--------------------------------------------------------------------
configure(WinInfo, TraceWin) ->
    {Bu1, Ev1, Bi1, Tr1} = OldFlags = WinInfo#winInfo.flags,
    Bu2 = flip(lists:member('Button Area', TraceWin)),
    Ev2 = flip(lists:member('Evaluator Area', TraceWin)),
    Bi2 = flip(lists:member('Bindings Area', TraceWin)),
    Tr2 = flip(lists:member('Trace Area', TraceWin)),
    NewFlags = {Bu2, Ev2, Bi2, Tr2},

    Win = WinInfo#winInfo.window,
    W = gs:read(Win, width),
    H = gs:read(Win, height),

    H2 = if
	     Bu1 =:= close, Bu2 =:= open ->
		 resize_button_area(open, width, W-4),
		 gs:config('ButtonArea', {height, 30}),
		 H+30;
	     Bu1 =:= open, Bu2 =:= close ->
		 gs:config('ButtonArea', [{width, 0}, {height, 0}]),
		 H-30;
	     true -> H
	 end,
    H3 = if
	     Ev1 =:= close, Ev2 =:= open, Bi1 =:= open ->
		 Wnew1 = round((W-10-4)/2), % W = window/2 - rb - pads
		 Hbi1 = gs:read('BindArea', height), % H = bind area h
		 resize_eval_area(open, width, Wnew1),
		 resize_eval_area(open, height, Hbi1),
		 gs:config('RB3', {width, 10}),
		 gs:config('RB3', {height, Hbi1}),
		 resize_bind_area(open, width,
				  Wnew1-gs:read('BindArea', width)),
		 H2;
	     Ev1 =:= close, Ev2 =:= open, Bi1 =:= close ->
		 resize_eval_area(open, width, W-4),
		 resize_eval_area(open, height, 200),
		 H2+200;
	     Ev1 =:= open, Ev2 =:= close, Bi1 =:= open ->
		 gs:config('EvalArea', [{width,0}, {height,0}]),
		 gs:config('RB3', [{width, 0}, {height, 0}]),
		 Wnew2 = W-4,
		 resize_bind_area(open, width,
				  Wnew2-gs:read('BindArea', width)),
		 H2;
	     Ev1 =:= open, Ev2 =:= close, Bi1 =:= close ->
		 Hs1 = gs:read('EvalArea', height),
		 gs:config('EvalArea', [{width, 0}, {height, 0}]),
		 H2-Hs1;
	     true -> H2
	 end,
    H4 = if
	     Bi1 =:= close, Bi2 =:= open, Ev2 =:= open ->
		 Wnew3 = round((W-10-4)/2), % W = window/2 - rb - pads
		 Hs2 = gs:read('EvalArea', height), % H = eval area h
		 resize_bind_area(open, width, Wnew3),
		 resize_bind_area(open, height, Hs2),
		 gs:config('RB3', [{width,10},{height,Hs2}]),
		 resize_eval_area(open, width,
				  Wnew3-gs:read('EvalArea', width)),
		 H3;
	     Bi1 =:= close, Bi2 =:= open, Ev2 =:= close ->
		 resize_bind_area(open, width, W-4),
		 resize_bind_area(open, height, 200),
		 H3+200;
	     Bi1 =:= open, Bi2 =:= close, Ev2 =:= open ->
		 gs:config('BindArea', [{width, 0}, {height, 0}]),
		 gs:config('RB3', [{width, 0}, {height, 0}]),
		 Wnew4 = W-4,
		 resize_eval_area(open, width,
				  Wnew4-gs:read('EvalArea', width)),
		 H3;
	     Bi1 =:= open, Bi2 =:= close, Ev2 =:= close ->
		 Hbi2 = gs:read('BindArea', height),
		 gs:config('BindArea', [{width, 0}, {height, 0}]),
		 H3-Hbi2;
	     true -> H3
	 end,
    H5 = if
	     Tr1 =:= close, Tr2 =:= open ->
		 resize_trace_area(open, width, W-4),
		 resize_trace_area(open, height, 200),
		 H4+200;
	     Tr1 =:= open, Tr2 =:= close ->
		 Hf = gs:read('TraceArea', height),
		 gs:config('TraceArea', [{width, 0}, {height, 0}]),
		 H4-Hf;
	     true -> H4
	 end,
    gs:config(Win, {height, H5}),

    RB1old = rb1(OldFlags), RB1new = rb1(NewFlags),
    if
	RB1old =:= close, RB1new =:= open ->
	    gs:config('RB1', [{width, W-4}, {height, 10}]),
	    gs:config(Win, {height, gs:read(Win, height)+10});
	RB1old =:= open, RB1new =:= close ->
	    gs:config('RB1', [{width, 0}, {height, 0}, lower]),
	    gs:config(Win, {height, gs:read(Win, height)-10});
	true -> ignore
    end,

    RB2old = rb2(OldFlags), RB2new = rb2(NewFlags),
    if
	RB2old =:= close, RB2new =:= open ->
	    gs:config('RB2', [{width, W-4}, {height, 10}]),
	    gs:config(Win, {height,gs:read(Win, height)+10});
	RB2old =:= open, RB2new =:= close ->
	    gs:config('RB2', [{width, 0}, {height, 0}, lower]),
	    gs:config(Win, {height, gs:read(Win, height)-10});
	true -> ignore
    end,
    config_v(),
    config_h(),

    flush_configure(),
    
    WinInfo#winInfo{size={gs:read(Win, width), gs:read(Win, height)},
		    flags=NewFlags}.

flush_configure() ->
    receive
	{gs, _Id, configure, _Data, _Arg} ->
	    flush_configure()
    after 100 ->
	    true
    end.

%%--------------------------------------------------------------------
%% enable([MenuItem], Bool)
%% is_enabled(MenuItem) -> Bool
%%   MenuItem = atom()
%%   Bool = boolean()
%%--------------------------------------------------------------------
enable(MenuItems, Bool) ->
    lists:foreach(fun(MenuItem) ->
			  gs:config(MenuItem, {enable, Bool}),
			  case is_button(MenuItem) of
			      {true, Button} ->
				  gs:config(Button, {enable, Bool});
			      false -> ignore
			  end
		  end,
		  MenuItems).

is_enabled(MenuItem) ->
    gs:read(MenuItem, enable).

%%--------------------------------------------------------------------
%% select(MenuItem, Bool)
%%   MenuItem = atom()
%%   Bool = boolean()
%%--------------------------------------------------------------------
select(MenuItem, Bool) ->
    dbg_ui_win:select(MenuItem, Bool).

%%--------------------------------------------------------------------
%% add_break(WinInfo, Name, {Point, Options}) -> WinInfo
%%   WinInfo = #winInfo{}
%%   Name = atom() Menu name
%%   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, {{Mod,Line},[Status|_Options]}=Break) ->
    case lists:keyfind(Mod, 1, WinInfo#winInfo.editors) of
	{Mod, Editor} ->
	    add_break_to_code(Editor, Line, Status);
	false -> ignore
    end,
    add_break_to_menu(WinInfo, Menu, Break).

add_break_to_code(Editor, Line, Status) ->
    Color = if Status =:= active -> red; Status =:= inactive -> blue end,
    config_editor(Editor, [{overwrite,{{Line,0},"-@-  "}},
			   {fg,{{{Line,0},{Line,lineend}}, Color}}]).

add_break_to_menu(WinInfo, Menu, {Point, [Status|_Options]=Options}) ->
    Break = dbg_ui_win:add_break(Menu, Point),
    dbg_ui_win:update_break(Break, Options),
    BreakInfo = #breakInfo{point=Point, status=Status, break=Break},
    WinInfo#winInfo{breaks=[BreakInfo|WinInfo#winInfo.breaks]}.

%%--------------------------------------------------------------------
%% update_break(WinInfo, {Point, Options}) -> WinInfo
%%   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, {{Mod,Line},[Status|_Options]}=Break) ->
    case lists:keyfind(Mod, 1, WinInfo#winInfo.editors) of
	{Mod, Editor} ->
	    add_break_to_code(Editor, Line, Status);
	false -> ignore
    end,
    update_break_in_menu(WinInfo, Break).

update_break_in_menu(WinInfo, {Point, [Status|_Options]=Options}) ->
    {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
					 WinInfo#winInfo.breaks),
    dbg_ui_win:update_break(BreakInfo#breakInfo.break, Options),
    BreakInfo2 = BreakInfo#breakInfo{status=Status},
    WinInfo#winInfo{breaks=lists:keyreplace(Point, #breakInfo.point,
					    WinInfo#winInfo.breaks,
					    BreakInfo2)}.

%%--------------------------------------------------------------------
%% delete_break(WinInfo, Point) -> WinInfo
%%   WinInfo = #winInfo{}
%%   Point = {Mod, Line}
%%--------------------------------------------------------------------
delete_break(WinInfo, {Mod,Line}=Point) ->
    case lists:keyfind(Mod, 1, WinInfo#winInfo.editors) of
	{Mod, Editor} -> delete_break_from_code(Editor, Line);
	false -> ignore
    end,
    delete_break_from_menu(WinInfo, Point).

delete_break_from_code(Editor, Line) ->
    Prefix = string:substr(integer_to_list(Line)++":   ", 1, 5),
    config_editor(Editor, [{overwrite,{{Line,0},Prefix}},
			   {fg,{{{Line,0},{Line,lineend}}, black}}]).

delete_break_from_menu(WinInfo, Point) ->
    {value, BreakInfo} = lists:keysearch(Point, #breakInfo.point,
					 WinInfo#winInfo.breaks),
    dbg_ui_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) ->
    clear_breaks(WinInfo, all).
clear_breaks(WinInfo, Mod) ->
    Remove = if
		 Mod =:= all -> WinInfo#winInfo.breaks;
		 true ->
		     lists:filter(fun(#breakInfo{point={Mod2,_L}}) ->
					  if
					      Mod2 =:= Mod -> true;
					      true -> false
					  end
				  end,
				  WinInfo#winInfo.breaks)
	     end,
    lists:foreach(fun(#breakInfo{point=Point}) ->
			  delete_break(WinInfo, Point)
		  end,
		  Remove),
    Remain = WinInfo#winInfo.breaks -- Remove,
    WinInfo#winInfo{breaks=Remain}.

%%--------------------------------------------------------------------
%% display(Arg)
%%   Arg = idle | {Status,Mod,Line} | {running,Mod}
%%       | {exit,Where,Reason} | {text,Text}
%%     Status = break | wait | Level
%%       Level = int()
%%     Mod = atom()
%%     Line = integer()
%%     Where = {Mod,Line} | null
%%     Reason = term()
%%     Text = string()
%%--------------------------------------------------------------------
display(Arg) ->
    Str = case Arg of
	      idle -> "State: uninterpreted";
	      {exit, {Mod,Line}, Reason} ->
		  gs:config(trace_window, raise),
		  io_lib:format("State: EXITED [~w.erl/~w], Reason:~w",
				[Mod, Line, Reason]);
	      {exit, null, Reason} ->
		  gs:config(trace_window, raise),
		  io_lib:format("State: EXITED [uninterpreted], "
				"Reason:~w", [Reason]);
	      {Level, null, _Line} when is_integer(Level) ->
		  io_lib:format("*** Call level #~w "
				"(in non-interpreted code)",
				[Level]);
	      {Level, Mod, Line} when is_integer(Level) ->
		  io_lib:format("*** Call level #~w [~w.erl/~w]",
				[Level, Mod, Line]);
	      {Status, Mod, Line} ->
		  What = case Status of
			     wait -> 'receive';
			     _ -> Status
			 end,
		  io_lib:format("State: ~w [~w.erl/~w]",
				[What, Mod, Line]);
	      {running, Mod} ->
		  io_lib:format("State: running [~w.erl]", [Mod]);
	      {text, Text}  -> Text
	  end,
    gs:config(info_window, {label,{text,lists:flatten(Str)}}).

%%--------------------------------------------------------------------
%% is_shown(WinInfo, Mod) -> {true, WinInfo} | false
%% show_code(WinInfo, Mod, Contents) -> WinInfo
%% show_no_code(WinInfo) -> WinInfo
%% remove_code(WinInfo, Mod) -> WinInfo
%%   WinInfo = #winInfo{}
%%   Mod = atom()
%%   Contents = string()
%% Note: remove_code/2 should not be used for currently shown module.
%%--------------------------------------------------------------------
is_shown(WinInfo, Mod) ->
    case lists:keyfind(Mod, 1, WinInfo#winInfo.editors) of
	{Mod, Editor} ->
	    gs:config(Editor, raise),
	    {true, WinInfo#winInfo{editor={Mod, Editor}}};
	false -> false
    end.

show_code(WinInfo, Mod, Contents) ->
    Editors = WinInfo#winInfo.editors,
    {Flag, Editor} = case lists:keyfind(Mod, 1, Editors) of
			 {Mod, Ed} -> {existing, Ed};
			 false -> {new, code_editor()}
		     end,
    %% Insert code and update breakpoints, if any
    config_editor(Editor, [raise, clear]),
    show_code(Editor, Contents),
    lists:foreach(fun(BreakInfo) ->
			  case BreakInfo#breakInfo.point of
			      {Mod2, Line} when Mod2 =:= Mod ->
				  Status = BreakInfo#breakInfo.status,
				  add_break_to_code(Editor, Line,Status);
			      _Point -> ignore
			  end
		  end,
		  WinInfo#winInfo.breaks),
    case Flag of
	existing ->
	    WinInfo#winInfo{editor={Mod, Editor}};
	new ->
	    WinInfo#winInfo{editor={Mod, Editor},
			    editors=[{Mod, Editor} | Editors]}
    end.
	
show_code(Editor, Text) when length(Text) > 1500 ->
    %% Add some text at a time so that other processes may get scheduled
    Str = string:sub_string(Text, 1, 1500),
    config_editor(Editor, {insert,{'end', Str}}),
    show_code(Editor, string:sub_string(Text, 1501));
show_code(Editor, Text) ->
    config_editor(Editor, {insert,{'end',Text}}).

show_no_code(WinInfo) ->
    {'$top', Editor} = lists:keyfind('$top', 1, WinInfo#winInfo.editors),
    gs:config(Editor, raise),
    WinInfo#winInfo{editor={'$top', Editor}}.

remove_code(WinInfo, Mod) ->
    Editors = WinInfo#winInfo.editors,
    case lists:keyfind(Mod, 1, Editors) of
	{Mod, Editor} ->
	    gs:destroy(Editor),
	    WinInfo#winInfo{editors=lists:keydelete(Mod, 1, Editors)};
	false ->
	    WinInfo
    end.

%%--------------------------------------------------------------------
%% mark_line(WinInfo, Line, How) -> WinInfo
%%   WinInfo = #winInfo{}
%%   Line = integer()
%%   How = break | where
%% Mark the code line where the process is executing.
%%--------------------------------------------------------------------
mark_line(WinInfo, Line, How) ->
    {_Mod, Editor} = WinInfo#winInfo.editor,
    mark_line2(Editor, WinInfo#winInfo.marked_line, false),
    mark_line2(Editor, Line, How),
    if
	Line =/= 0 -> config_editor(Editor, {vscrollpos, Line-5});
	true -> ignore
    end,
    WinInfo#winInfo{marked_line=Line}.

unmark_line(WinInfo) ->
    mark_line(WinInfo, 0, false).

mark_line2(Editor, Line, How) ->
    Prefix = case How of
		 break -> "-->";
		 where -> ">>>";
		 false -> "   "
	     end,
    Font = if
	       How =:= false -> dbg_ui_win:font(normal);
	       true -> dbg_ui_win:font(bold)
	   end,
    config_editor(Editor, [{overwrite, {{Line,5}, Prefix}},
			   {font_style,
			    {{{Line,0},{Line,lineend}}, Font}}]).

%%--------------------------------------------------------------------
%% select_line(WinInfo, Line) -> WinInfo
%% selected_line(WinInfo) -> undefined | Line
%%   WinInfo = #winInfo{}
%%   Line = integer()
%% Select/unselect a line (unselect if Line=0).
%%--------------------------------------------------------------------
select_line(WinInfo, Line) ->
    {_Mod, Editor} = WinInfo#winInfo.editor,

    %% Since 'Line' may be specified by the user in the 'Go To Line'
    %% help window, it must be checked that it is correct
    Size = gs:read(Editor, size),
    if
	Line =:= 0 ->
	    select_line(Editor, WinInfo#winInfo.selected_line, false),
	    WinInfo#winInfo{selected_line=0};
	Line < Size ->
	    select_line(Editor, Line, true),
	    config_editor(Editor, {vscrollpos, Line-5}),
	    WinInfo#winInfo{selected_line=Line};
	true ->
	    WinInfo
    end.

select_line(Editor, Line, true) ->
    config_editor(Editor, {selection, {{Line,0}, {Line,lineend}}});
select_line(Editor, _Line, false) ->
    config_editor(Editor, {selection, {{1,0}, {1,0}}}).

selected_line(WinInfo) ->
    case WinInfo#winInfo.selected_line of
	0 -> undefined;
	Line -> Line
    end.

%%--------------------------------------------------------------------
%% eval_output(Str, Face)
%%   Str = string()
%%   Face = normal | bold
%%--------------------------------------------------------------------
eval_output(Text, Face) ->
    Y1 = gs:read('EvalEditor', size),
    config_editor('EvalEditor', {insert,{'end',Text}}),
    Y2 = gs:read('EvalEditor', size),

    Font = dbg_ui_win:font(Face),
    config_editor('EvalEditor',
		  [{font_style, {{{Y1,0},{Y2,lineend}}, Font}},
		   {vscrollpos,Y2}]).

%%--------------------------------------------------------------------
%% update_bindings(Bs)
%%   Bs = [{Var,Val}]
%%--------------------------------------------------------------------
update_bindings(Bs) ->
    gs:config('BindGrid', {rows, {1,length(Bs)+1}}),
    Font = dbg_ui_win:font(normal),
    Last =
	lists:foldl(fun({Var, Val}, Row) ->
			    Opts = [{text, {1,atom_to_list(Var)}},
				    {text, {2,io_lib:format("~P",
							    [Val, 4])}},
				    {doubleclick, true},
				    {data, {binding,{Var,Val}}}],
			    case gs:read('BindGrid',{obj_at_row,Row}) of
				undefined ->
				    gs:gridline('BindGrid',
						[{row, Row},
						 {height, 14},
						 {font, Font} | Opts]);
				GridLine ->
				    gs:config(GridLine, Opts)
			    end,
			    Row+1
		    end,
		    2,
		    Bs),
    delete_gridlines(Last).

delete_gridlines(Row) -> 
    case gs:read('BindGrid', {obj_at_row, Row}) of
	undefined -> true;
	GridLine ->
	    gs:destroy(GridLine),
	    delete_gridlines(Row+1)
    end.

%%--------------------------------------------------------------------
%% trace_output(Str)
%%   Str = string()
%%--------------------------------------------------------------------
trace_output(Str) ->
    Font = dbg_ui_win:font(normal),
    config_editor('TraceEditor',
		  [{insert, {'end',Str}},
		   {fg, {{{1,0},'end'},black}},
		   {font_style, {{{1,0},'end'},Font}}]),
    Max = gs:read('TraceEditor', size),
    config_editor('TraceEditor', {vscrollpos, Max}).
    
%%--------------------------------------------------------------------
%% handle_event(GSEvent, WinInfo) -> Command
%%   GSEvent = {gs, Id, Event, Data, Arg}
%%   WinInfo = #winInfo{}
%%   Command = ignore
%%           | {win, WinInfo}
%%           | stopped
%%           | {coords, {X,Y}}
%%
%%           | {shortcut, Key}
%%           | MenuItem | {Menu, [MenuItem]}
%%               MenuItem = Menu = atom()
%%           | {break, Point, What}
%%               What = add | delete | {status,Status} |{trigger,Trigger}
%%           | {module, Mod, view}
%%
%%           | {user_command, Cmd}
%%
%%           | {edit, {Var, Val}}
%%--------------------------------------------------------------------
%% Window events
handle_event({gs, _Id, configure, _Data, [W, H|_]}, WinInfo) ->
    case WinInfo#winInfo.size of
	{W, H} -> ignore;
	_Size ->
	    configure(WinInfo, W, H),
	    {win, WinInfo#winInfo{size={W, H}}}
    end;
handle_event({gs, _Id, destroy, _Data, _Arg}, _WinInfo) ->
    stopped;
handle_event({gs, _Id, motion, _Data, [X,Y]}, WinInfo) ->
    {LastX, LastY} = dbg_ui_win:motion(X, Y),
    Win = WinInfo#winInfo.window,
    {coords, {gs:read(Win, x)+LastX-5, gs:read(Win, y)+LastY-5}};
handle_event({gs, RB, buttonpress, resizebar, _Arg}, WinInfo) ->
    resize(WinInfo, RB), % Resize window contents
    ignore;

%% Menus, buttons and keyboard shortcuts
handle_event({gs, _Id, keypress, _Data, [Key,_,_,1]}, _WinInfo) ->
    {shortcut, Key};
handle_event({gs, _Id, click, {dbg_ui_winman, Win}, _Arg}, _WinInfo) ->
    dbg_ui_winman:raise(Win),
    ignore;
handle_event({gs, _Id, click, {menuitem, Name}, _Arg}, _WinInfo) ->
    Name;
handle_event({gs, _Id, click, {menu, Menu}, _Arg}, _WinInfo) ->
    Names = dbg_ui_win:selected(Menu),
    {Menu, Names};
handle_event({gs, _Id, click, {break, Point, What}, _Arg}, _WinInfo) ->
    {break, Point, What};
handle_event({gs, _Id, click, {module, Mod, view}, _Arg}, _WinInfo) ->
    {module, Mod, view};

%% Code area
handle_event({gs, Editor, buttonpress, code_editor, _Arg}, WinInfo) ->
    {Row, _Col} = gs:read(Editor, insertpos),
    Again = receive
		{gs, Editor, buttonpress, code_editor, _} ->
		    gs:read(Editor, insertpos)
	    after 500 ->
		    false
	    end,
    case Again of
	{Row, _} ->
	    {Mod, _Editor} = WinInfo#winInfo.editor,
	    Point = {Mod, Row},
	    case lists:keymember(Point, #breakInfo.point,
				 WinInfo#winInfo.breaks) of
		false -> {break, Point, add};
		true  -> {break, Point, delete}
	    end;
	{Row2, _} ->
	    select_line(Editor, Row2, true),
	    {win, WinInfo#winInfo{selected_line=Row2}};
	false ->
	    select_line(Editor, Row, true),
	    {win, WinInfo#winInfo{selected_line=Row}}
    end;

%% Button area
handle_event({gs, _Id, click, {button, Name}, _Arg}, _WinInfo) ->
    Name;

%% Evaluator area
handle_event({gs, 'EvalEntry', keypress, _Data, ['Return'|_]}, _WI) ->
    Command = case gs:read('EvalEntry', text) of
		  [10] ->
		      eval_output("\n", normal),
		      ignore;
		  Cmd ->
		      eval_output([$>, Cmd, 10], normal),
		      {user_command, Cmd}
	      end,
    gs:config('EvalEntry', [{text,""}, {focus,false}]),
    Command;

%% Bindings area
handle_event({gs, _Id, click, {binding, {Var, Val}}, _Arg}, _WinInfo) ->
    Str = io_lib:format("< ~p = ~p~n", [Var, Val]),
    eval_output(Str, bold),
    ignore;
handle_event({gs, _Id, doubleclick, {binding, B}, _Arg}, _WinInfo) ->
    {edit, B};

handle_event(_GSEvent, _WinInfo) ->
    ignore.


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

%%--Code Area---------------------------------------------------------

code_area(X, Y, FrameOpts, Win) ->
    gs:frame('CodeArea', Win,
	     [{x,X}, {y,Y}, {width,546}, {height,400} | FrameOpts]),
    gs:label(info_window, 'CodeArea',
	     [{label,{text,""}}, {font,dbg_ui_win:font(normal)},
	      {x,5}, {y,10}, {width,406}, {height,15},
	      {anchor,nw}, {align,w}]),
    code_editor('CodeEditor', 536, 365).

code_editor() ->
    W = gs:read('CodeEditor', width),
    H = gs:read('CodeEditor', height),
    code_editor(null, W, H).

code_editor(Name, W, H) ->
    Editor = if
		 Name =:= null -> gs:editor('CodeArea', []);
		 true -> gs:editor(Name, 'CodeArea', [])
	     end,
    gs:config(Editor, [{x,5}, {y,30}, {width,W}, {height,H},
		       {keypress,false}, {buttonpress,true}, 
		       {data,code_editor}]),
    config_editor(Editor, [{vscroll,right}, {hscroll,bottom}]),
    Font = dbg_ui_win:font(normal),
    config_editor(Editor, [{wrap,none}, {fg,{{{1,0},'end'},black}},
			   {font, Font},
			   {font_style, {{{1,0},'end'},Font}}]),
    Editor.

resize_code_area(WinInfo, Key, Diff) ->
    gs:config('CodeArea', {Key,gs:read('CodeArea', Key)+Diff}),
    case Key of
	width ->
	    gs:config(info_window, {Key,gs:read(info_window,Key)+Diff});
	height -> ignore
    end,

    %% Resize all code editors
    Value = gs:read('CodeEditor', Key)+Diff,
    gs:config('CodeEditor', {Key,Value}),
    Editors = WinInfo#winInfo.editors,
    lists:foreach(fun({_Mod, Editor}) ->
			  gs:config(Editor, {Key,Value})
		  end,
		  Editors).

%%--Button Area-------------------------------------------------------

buttons() ->
    [{'Step','StepButton'}, {'Next','NextButton'},
     {'Continue','ContinueButton'}, {'Finish','FinishButton'},
     {'Where','WhereButton'}, {'Up','UpButton'}, {'Down','DownButton'}].

is_button(Name) ->
    case lists:keyfind(Name, 1, buttons()) of
	{Name, Button} -> {true, Button};
	false -> false
    end.

button_area(Bu, X, Y, FrameOpts, Win) ->
    {W,H} = case Bu of
		open -> {546,30};
		close -> {0,0}
	    end,
    gs:frame('ButtonArea', Win,
	     [{x,X}, {y,Y}, {width,W}, {height,H} | FrameOpts]),
    Font = dbg_ui_win:font(normal),
    lists:foldl(fun({Name, Button}, Xb) ->
			gs:button(Button, 'ButtonArea',
				  [{label, {text,Name}}, {font,Font},
				   {x, Xb}, {y, 1},
				   {width, 77}, {height, 24},
				   {data, {button,Name}}]),
			Xb+78
		end,
		1,
		buttons()).

resize_button_area(close, width, _Diff) ->
    ignore;
resize_button_area(open, width, Diff) ->
    gs:config('ButtonArea', {width, gs:read('ButtonArea', width)+Diff}).

%%--Evaluator Area----------------------------------------------------

eval_area({Ev,Bi}, X, Y, FrameOpts, Win) ->
    {W,H} = if
		Ev =:= open -> {289,200};
		true -> {0,0}
	    end,
    Font = dbg_ui_win:font(normal),
    gs:frame('EvalArea', Win,
	     [{x,X}, {y,Y}, {width,W}, {height,H} | FrameOpts]),
    gs:label('EvalArea',
	     [{label,{text,"Evaluator:"}}, {font, Font},
	      {x,5}, {y,35}, {width,80}, {height,25},
	      {anchor,sw}, {align,center}]),
    gs:entry('EvalEntry', 'EvalArea',
	     [{font, Font},
	      {x,80}, {y,35}, {width,185}, {height,25},
	      {anchor,sw}, {keypress,true}]),
    gs:editor('EvalEditor', 'EvalArea',
	      [{x,5}, {y,35}, {width, 280}, {height, 160},
	       {keypress,false},
	       {vscroll,right}, {hscroll,bottom},
	       {wrap,none}, {fg,{{{1,0},'end'},black}},
	       {font, Font},
	       {font_style,{{{1,0},'end'},Font}}]),
    gs:config('EvalEditor', {enable, false}),
    if
	Ev =:= open, Bi =:= close -> resize_eval_area(Ev, width, 257);
	true -> ignore
    end.

resize_eval_area(close, _Key, _Diff) ->
    ignore;
resize_eval_area(open, Key, Diff) ->
    New = gs:read('EvalArea', Key)+Diff,
    gs:config('EvalArea', {Key,New}),
    case Key of
	width ->
	    gs:config('EvalEntry', {width,New-104}),
	    gs:config('EvalEditor', {width,New-9});
	height ->
	    gs:config('EvalEditor', {height,New-40})
    end.

%%--Bindings Area-----------------------------------------------------

bind_area({Ev,Bi}, X, Y, FrameOpts, Win) ->
    {W,H} = if
		Bi =:= open -> {249,200};
		true -> {0,0}
	    end,
    gs:frame('BindArea', Win,
	     [{x,X}, {y,Y}, {width,W}, {height,H} | FrameOpts]),

    Font = dbg_ui_win:font(bold),
    gs:grid('BindGrid', 'BindArea',
	    [{x,2}, {y,2}, {height,193}, {width,241},
	     {fg,black}, {vscroll,right}, {hscroll,bottom},
	     {font,Font},
	     calc_columnwidths(241), {rows, {1,50}}]), 
    gs:gridline('BindGrid',
		[{row,1}, {height,14}, {fg,blue},
		 {text,{1,"Name"}}, {text,{2,"Value"}}, {font,Font}]),
    gs:config('BindGrid', {rows,{1,1}}),
    if
	Bi =:= open, Ev =:= close -> resize_bind_area(Bi, width, 297);
	true -> ignore
    end.

resize_bind_area(close, _Key, _Diff) ->
    ignore;
resize_bind_area(open, Key, Diff) ->
    New = gs:read('BindArea', Key)+Diff,
    gs:config('BindArea', {Key,New}),
    case Key of
	width ->
	    gs:config('BindGrid', {width,New-8}),
	    Cols = calc_columnwidths(New-8),
	    gs:config('BindGrid', Cols);
	height ->
	    gs:config('BindGrid', {height,New-7})
    end.

calc_columnwidths(Width) ->    
    if Width =< 291 -> 
	    {columnwidths,[90,198]};
       true -> 
	    S = (Width)/(90+198),
	    {columnwidths,[round(90*S),round(198*S)]}
    end.

%%--Trace Area--------------------------------------------------------

trace_area(Tr, X, Y, FrameOpts, Win) ->
    {W,H} = case Tr of
		open -> {546,200};
		close -> {0,0}
	    end,
    gs:frame('TraceArea', Win,
	     [{x,X}, {y,Y}, {width,W}, {height,H} | FrameOpts]),
    Editor = gs:editor('TraceEditor', 'TraceArea',
		       [{x,5}, {y,5}, {width,536}, {height,190},
			{keypress,false}]),
    Font = dbg_ui_win:font(normal),
    config_editor(Editor,
		  [{vscroll,right}, {hscroll,bottom},
		   {wrap,none},{fg,{{{1,0},'end'},black}},
		   {font, Font},
		   {font_style,{{{1,0},'end'},Font}}]).

resize_trace_area(close, _Key, _Diff) ->
    ignore;
resize_trace_area(open, Key, Diff) ->
    New = gs:read('TraceArea', Key)+Diff,
    gs:config('TraceArea', {Key,New}),
    gs:config('TraceEditor', {Key,New-10}).

%%--Editors-----------------------------------------------------------

config_editor(Editor, Opts) ->
    gs:config(Editor, {enable,true}),
    gs:config(Editor, Opts),
    gs:config(Editor, {enable,false}).

%%--Resize Bars-------------------------------------------------------
%% The resize bars are used to resize the areas within the window.

%%--------------------------------------------------------------------
%% resizebar(Flag, Name, X, Y, W, H, Obj) -> resizebar()
%%   Flag = open | close
%%   Name = atom()
%%   X = Y = integer()  Coordinates relative to Obj
%%   W = H = integer()  Width and height
%%   Obj = gsobj()
%% Creates a 'resize bar', a frame object over which the cursor will
%% be of the 'resize' type.
%%--------------------------------------------------------------------
resizebar(Flag, Name, X, Y, W, H, Obj) ->
    {W2,H2} = case Flag of
		  open  -> {W,H};
		  close -> {0,0}
	      end,
    gs:create(frame, Name, Obj, [{x,X}, {y,Y}, {width,W2}, {height,H2},
				 {bw,2},
				 {cursor,resize},
				 {buttonpress,true}, {buttonrelease,true},
				 {data,resizebar}]).

rb1({_Bu,Ev,Bi,Tr}) ->
    if
	Ev =:= close, Bi =:= close, Tr =:= close -> close;
	true -> open
    end.
    
rb2({_Bu,Ev,Bi,Tr}) ->
    if
	Tr =:= open ->
	    if
		Ev =:= close, Bi =:= close -> close;
		true -> open
	    end;
	true -> close
    end.
    
rb3({_Bu,Ev,Bi,_Tr}) ->
    if
	Ev =:= open, Bi =:= open -> open;
	true -> close
    end.

%%--Configuration-----------------------------------------------------
%% Resize the window as well as its contents

%%--------------------------------------------------------------------
%% config_v()
%% Reconfigure the window vertically.
%%--------------------------------------------------------------------
config_v() ->
    Y1 = 25+gs:read('CodeArea', height),
    gs:config('RB1', {y,Y1}),
    
    Y2 = Y1+gs:read('RB1', height),
    gs:config('ButtonArea', {y,Y2}),
    
    Y3 = Y2+gs:read('ButtonArea', height),
    gs:config('EvalArea', {y,Y3}),
    gs:config('RB3', {y,Y3}),
    gs:config('BindArea', {y,Y3}),
    
    Y4 = Y3 + erlang:max(gs:read('EvalArea', height),
		  gs:read('BindArea', height)),
    gs:config('RB2', {y,Y4}),
    
    Y5 = Y4 + gs:read('RB2', height),
    gs:config('TraceArea', {y,Y5}).

%%--------------------------------------------------------------------
%% config_h()
%% Reconfigure the window horizontally.
%%--------------------------------------------------------------------
config_h() ->
    X1 = 2+gs:read('EvalArea', width),
    gs:config('RB3', {x,X1}),
    
    X2 = X1+gs:read('RB3', width),
    gs:config('BindArea', {x,X2}).

%%--------------------------------------------------------------------
%% configure(WinInfo, W, H)
%% The window has been resized, now its contents must be resized too.
%%--------------------------------------------------------------------
configure(WinInfo, NewW, NewH) ->
    {Bu,Ev,Bi,Tr} = Flags = WinInfo#winInfo.flags,
    
    OldW = gs:read('CodeArea', width)+4,
    OldH = 25+gs:read('CodeArea', height)+
	gs:read('RB1', height)+
	gs:read('ButtonArea', height)+
	erlang:max(gs:read('EvalArea', height), gs:read('BindArea', height))+
	gs:read('RB2', height)+
	gs:read('TraceArea', height),

    %% Adjust width unless it is unchanged or less than minimum width
    if
	OldW =/= NewW ->
	    {Dcode,Deval,Dbind} = configure_widths(OldW,NewW,Flags),
	    resize_code_area(WinInfo, width, Dcode),
	    case rb1(Flags) of
		open ->
		    gs:config('RB1', {width,gs:read('RB1',width)+Dcode});
		close -> ignore
	    end,
	    resize_button_area(Bu, width, Dcode),
	    resize_eval_area(Ev, width, Deval),
	    resize_bind_area(Bi, width, Dbind),
	    case rb2(Flags) of
		open ->
		    gs:config('RB2', {width,gs:read('RB2',width)+Dcode});
		close -> ignore
	    end,
	    resize_trace_area(Tr, width, Dcode),
	    config_h();
	true -> ignore
    end,
    
    %% Adjust height unless it is unchanged or less than minimum height
    if
	OldH =/= NewH ->
	    {Dcode2,Deval2,Dtrace} = configure_heights(OldH,NewH,Flags),
	    resize_code_area(WinInfo, height, Dcode2),
	    resize_eval_area(Ev, height, Deval2),
	    case rb3(Flags) of
		open ->
		    gs:config('RB3',
			      {height,gs:read('RB3',height)+Deval2});
		close -> ignore
	    end,
	    resize_bind_area(Bi, height, Deval2),
	    resize_trace_area(Tr, height, Dtrace),
	    config_v();
	true -> ignore
    end.

%% Compute how much the width of each frame must be increased or
%% decreased in order to adjust to the new window width.
configure_widths(OldW, NewW, Flags) ->
    {_Bu,Ev,Bi,_Tr} = Flags,

    %% Difference between old and new width, considering min window width
    Diff = abs(erlang:max(OldW,330)-erlang:max(NewW,330)),
    
    %% Check how much the frames can be resized in reality
    Limits = if
		 %% Window larger
		 NewW > OldW ->
		     if
			 Ev =:= open, Bi =:= open -> {0,Diff,Diff};
			 Ev =:= open -> {0,Diff,0};
			 Bi =:= open -> {0,0,Diff};
			 true -> {Diff,0,0}
		     end;
		 
		 %% Window smaller; get difference between min size
		 %% and current size
		 OldW>NewW ->
		     if
			 Ev =:= open, Bi =:= open ->
			     {0,
			      gs:read('EvalArea',width)-204,
			      gs:read('BindArea',width)-112};
			 Ev =:= open -> {0,Diff,0};
			 Bi =:= open -> {0,0,Diff};
			 true -> {Diff,0,0}
		     end
	     end,
    
    case Limits of

	%% No Shell or Bind frame, larger window
	{T,0,0} when NewW > OldW -> {T,0,0};
	
	%% No Shell or Bind frame, smaller window
	{T,0,0} when OldW > NewW -> {-T,0,0};

	%% Window larger; divide Diff among the frames and return result
	{_,Sf,B} when NewW > OldW ->
	    {_,Sf2,B2} = divide([{0,0},{0,Sf},{0,B}],Diff),
	    {Sf2+B2,Sf2,B2};

	%% Window smaller; divide Diff among the frames and return
	%% the inverted result (the frames should shrink)
	{_,Sf,B} when OldW>NewW ->
	    {_,Sf2,B2} = divide([{0,0},{0,Sf},{0,B}],Diff),
	    {-(Sf2+B2),-Sf2,-B2}
    end.

%% Compute how much the height of each frame must be increased or
%% decreased in order to adjust to the new window height.
configure_heights(OldH, NewH, Flags) ->
    {_Bu,Ev,Bi,Tr} = Flags,

    %% Difference between old and new height, considering min win height
    MinH = min_height(Flags),
    Diff = abs(erlang:max(OldH,MinH)-erlang:max(NewH,MinH)),
    
    %% Check how much the frames can be resized in reality
    {T,Sf,Ff} = if
		  %% Window larger
		  NewH > OldH ->
		      {Diff,
		       if
			   Ev =:= close, Bi =:= close -> 0;
			   true -> Diff
		       end,
		       if
			   Tr =:= open -> Diff;
			   true -> 0
		       end};

		  %% Window smaller; get difference between min size
		  %% and current size
		  OldH > NewH ->
		      {gs:read('CodeArea',height)-100,
		       if
			   Ev =:= close, Bi =:= close -> 0;
			   true ->
			       if
				   Ev =:= open ->
				       gs:read('EvalArea',height)-100;
				   Bi =:= open ->
				       gs:read('BindArea',height)-100
			       end
		       end,
		       if
			   Tr =:= open -> gs:read('TraceArea',height)-100;
			   true -> 0
		       end}
	      end,
    
    if
	%% Window larger; divide Diff among the frames and return result
	NewH>OldH -> divide([{0,T},{0,Sf},{0,Ff}],Diff);

	%% Window smaller; divide Diff among the frames and return
	%% the inverted result (the frames should shrink)
	OldH>NewH ->
	    {T2,Sf2,Ff2} = divide([{0,T},{0,Sf},{0,Ff}],Diff),
	    {-T2,-Sf2,-Ff2}
    end.

%% Compute minimum window height
min_height(Flags) ->
    {Bu,S,Bi,F} = Flags,
    H1 = 25 + 100 + 2, % Upper pad + Trace frame + lower pad
    H2 = H1 + bu(Bu) + s_bi(S,Bi) + f(F),
    H3 = case rb1(Flags) of
	     open -> H2+10;
	     close -> H2
	 end,
    H4 = case rb2(Flags) of
	     open -> H3+10;
	     close -> H3
	 end,
    H4.

bu(close) -> 0;
bu(open)  -> 30.

s_bi(close,close) -> 0;
s_bi(_,_) -> 100.

f(close) -> 0;
f(open)  -> 100.

%% Try to distribute Diff as evenly as possible between E1, E2 and E3.
divide([{T,T},{S,S},{F,F}], _Diff) ->
    {T,S,F};
divide(L, Diff) ->
    [{T,Tmax},{S,Smax},{F,Fmax}] = L,

    %% Count how many elements in L can still be filled
    Rem = remaining(L),
    
    %% Divide Diff by Rem
    D = Diff div Rem,

    if
	%% All of Diff has been distributed
	D =:= 0 -> {T,S,F};
	true ->
	    %% For each element, try to add as much as possible of D
	    {NewT,Dt} = divide2(D,T,Tmax),
	    {NewS,Ds} = divide2(D,S,Smax),
	    {NewF,Df} = divide2(D,F,Fmax),
    
	    %% Recur with a list of elements with new current values
	    %% and decreased Diff
	    divide([{NewT,Tmax},{NewS,Smax},{NewF,Fmax}],
		   (Diff rem Rem)+Dt+Ds+Df)
    end.

%% Count the number of 'non-filled' elements in L, ie where Curr<Max.
remaining([]) -> 0;
remaining([{Max,Max}|T]) -> remaining(T);
remaining([_H|T]) -> 1 + remaining(T).

divide2(_Diff, Max, Max) -> {Max,0};
divide2(Diff, Curr, Max) ->
    New = Curr+Diff,
    if
	New>Max -> {Max,New-Max};
	true -> {New,0}
    end.

%%--Resizing using resize bars----------------------------------------
%% Motions event will move the ResizeBar accordingly in Win, when
%% the mouse button is released, the window is reconfigured.

resize(WinInfo, ResizeBar) ->		
    
    %% Get window dimensions
    W = gs:read(WinInfo#winInfo.window, width),
    H = gs:read(WinInfo#winInfo.window, height),
    
    %% Call resize loop with min and max for the resize bars derived
    %% from the window dimensions
    resizeloop(WinInfo, ResizeBar, null,
	       rblimits('RB1',W,H),
	       rblimits('RB2',W,H),
	       rblimits('RB3',W,H)).

resizeloop(WI, RB, Prev, {Min1,Max1}, {Min2,Max2}, {Min3,Max3}) ->
    receive
	{gs,_,motion,_,[_,Y]} when RB =:= 'RB1', Y > Min1, Y < Max1 ->
	    gs:config('RB1', {y,Y}),
	    resizeloop(WI, RB, Y, {Min1,Max1}, {Min2,Max2}, {Min3,Max3});
	{gs,_,motion,_,_} when RB =:= 'RB1' ->
	    resizeloop(WI, RB, Prev, {Min1,Max1}, {Min2,Max2}, {Min3,Max3});
	
	{gs,_,motion,_,[_,Y]} when RB =:= 'RB2', Y > Min2, Y < Max2 ->
	    gs:config('RB2', {y,Y}),
	    resizeloop(WI, RB, Y, {Min1,Max1}, {Min2,Max2}, {Min3,Max3});
	{gs,_,motion,_,_} when RB =:= 'RB2' ->
	    resizeloop(WI, RB, Prev, {Min1,Max1}, {Min2,Max2}, {Min3,Max3});
	 
	{gs,_,motion,_,[X,_]} when RB =:= 'RB3', X > Min3, X < Max3 ->
	    gs:config('RB3', {x,X}),
	    resizeloop(WI, RB, X, {Min1,Max1}, {Min2,Max2}, {Min3,Max3});
	{gs,_,motion,_,_} when RB =:= 'RB3' ->
	    resizeloop(WI, RB, Prev, {Min1,Max1}, {Min2,Max2}, {Min3,Max3});
	
	{gs,_,buttonrelease,_,_} ->
	    resize_win(WI, RB, Prev)
    end.

resize_win(_WinInfo, _RB, null) -> ignore;
resize_win(WinInfo, 'RB1', Y) ->
    {_Bu,S,Bi,F} = Flags = WinInfo#winInfo.flags,
    H = gs:read('CodeArea', height),
    Diff = H-(Y-25),

    %% Resize Code, Evaluator and Binding areas
    resize_code_area(WinInfo, height, -Diff),
    if
	S =:= close, Bi =:= close, F =:= open ->
	    resize_trace_area(open, height, Diff);
	true ->
	    resize_eval_area(S, height, Diff),
	    resize_bind_area(Bi, height, Diff)
    end,

    %% Resize vertical resize bar
    case rb3(Flags) of
	open -> gs:config('RB3', {height,gs:read('RB3',height)+Diff});
	close -> ignore
    end,
    
    %% Adjust the frames y coordinates
    config_v();
resize_win(WinInfo, 'RB2', Y) ->
    {_Bu,S,Bi,F} = Flags = WinInfo#winInfo.flags,
    Prev = gs:read('TraceArea',y),
    Diff = Prev-(Y+10),
    
    %% Resize Trace, Evaluator and Binding areas
    resize_trace_area(F, height, Diff),
    resize_eval_area(S, height, -Diff),
    resize_bind_area(Bi, height, -Diff),

    %% Resize vertical resize bar
    case rb3(Flags) of
	open -> gs:config('RB3', {height,gs:read('RB3',height)-Diff});
	close -> ignore
    end,

    %% Adjust the frames y coordinates
    config_v();

resize_win(WinInfo, 'RB3', X) ->
    {_Bu,S,Bi,_F} = WinInfo#winInfo.flags,
    Prev = gs:read('BindArea', x),
    Diff = Prev-(X+10),
    
    %% Resize Binding and Trace areas
    resize_bind_area(Bi, width, Diff),
    resize_eval_area(S, width, -Diff),

    %% Adjust the frames x coordinates
    config_h().

%% Given the window dimensions, return the limits for a  resize bar.
rblimits('RB1',_W,H) ->
    
    %% Code frame should not have height <100
    Min = 126,
    
    %% Max is decided by a minimum distance to 'RB2'
    %% unless 'RB2' is invisible and 'CodeArea' is visible
    %% (=> EvalFrame and BindFrame invisible) in which case
    %% TraceFrame should not have height <100
    RB2 = gs:read('RB2',height),
    FF = gs:read('TraceArea',height),
    Max = case RB2 of
	      0 when FF =/= 0 ->
		  H-112;
	      _ ->
		  Y = gs:read('RB2',y),
		  erlang:max(Min,Y-140)
	  end,
    
    {Min,Max};
rblimits('RB2',_W,H) ->
    %% TraceFrame should not have height < 100
    Max = H-112,
    %% Min is decided by a minimum distance to 'RB1'
    Y = gs:read('RB1',y),
    Min = erlang:min(Max,Y+140),
    {Min,Max};

rblimits('RB3',W,_H) ->
    %% Neither CodeArea nor BindArea should occupy 
    %% less than 1/3 of the total window width and EvalFrame should
    %% be at least 289 pixels wide
    {erlang:max(round(W/3),289),round(2*W/3)}.


%%====================================================================
%% 'Go To Line' and 'Search' help windows
%%====================================================================

helpwin(gotoline, WinInfo, GS, Coords) ->
    spawn_link(?MODULE, helpwin, [gotoline, WinInfo, GS, Coords,self()]);
helpwin(search, WinInfo, GS, Coords) ->
    spawn_link(?MODULE, helpwin, [search, WinInfo, GS, Coords, self()]).

helpwin(Type, WinInfo, GS, Coords, AttPid) ->
    {_Mod, Editor} =  WinInfo#winInfo.editor,
    Data = case Type of
	       gotoline -> null;
	       search ->
		   {{1, 0}, false}
	   end,
    Win = helpwin(Type, GS, Coords),
    helpwin_loop(Type, AttPid, Editor, Data, Win).

helpwin_loop(Type, AttPid, Editor, Data, Win) ->
    receive
	{gs, _Id, destroy, _Data, _Arg} ->
	    helpwin_stop(Type, AttPid, Editor, Data),
	    true;

	{gs, _Id, keypress, _Data, ['Return'|_]} ->
	    gs:config(btn(Win), flash),
	    Data2 = helpwin_action(Type, default,
				   AttPid, Editor, Data, Win),
	    helpwin_loop(Type, AttPid, Editor, Data2, Win);
	{gs, _Id, keypress, _Data, _Arg} ->
	    helpwin_loop(Type, AttPid, Editor, Data, Win);
	
	{gs, _Id, click, _Data, ["Clear"]} ->
	    gs:config(ent(Win), {delete, {0,last}}),
	    Data2 = helpwin_clear(Type, AttPid, Editor, Data, Win),
	    helpwin_loop(Type, AttPid, Editor, Data2, Win);
	{gs, _Id, click, _Data, ["Close"]} ->
	    helpwin_stop(Type, AttPid, Editor, Data),
	    true;
	{gs, _Id, click, Action, _Arg} ->
	    Data2 =
		helpwin_action(Type, Action, AttPid, Editor, Data, Win),
	    helpwin_loop(Type, AttPid, Editor, Data2, Win)
    end.

helpwin_stop(gotoline, _AttPid, _Editor, _Data) ->
    ignore;
helpwin_stop(search, _AttPid, Editor, {Pos, _CS}) ->
    unmark_string(Editor, Pos).

helpwin_clear(gotoline, _AttPid, _Editor, Data, _Win) ->
    Data;
helpwin_clear(search, _AttPid, Editor, {Pos, CS}, Win) ->
    unmark_string(Editor, Pos),
    gs:config(lbl(Win), {label, {text,""}}),
    {{1, 0}, CS}.

helpwin_action(gotoline, default, AttPid, _Editor, Data, Win) ->
    case string:strip(gs:read(ent(Win), text)) of
	"" -> ignore;
	Str ->
	    case catch list_to_integer(Str) of
		{'EXIT', _Reason} -> ignore;
		Line -> AttPid ! {gui, {gotoline, Line}}
	    end
    end,
    Data;
helpwin_action(search, case_sensitive, _AttPid, _Ed, {Pos, CS}, _Win) ->
    Bool = if CS =:= true -> false; CS =:= false -> true end,
    {Pos, Bool};
helpwin_action(search, default, _AttPid, Editor, {Pos, CS}, Win) ->
    gs:config(lbl(Win), {label, {text, ""}}),
    unmark_string(Editor, Pos),
    case gs:read(ent(Win), text) of
	"" -> {Pos, CS};
	Str ->
	    gs:config(lbl(Win), {label, {text,"Searching..."}}),
	    Str2 = lowercase(CS, Str),
	    case search(Str2, Editor, gs:read(Editor, size), Pos, CS) of
		{Row, Col} ->
		    gs:config(lbl(Win), {label, {text,""}}),
		    mark_string(Editor, {Row, Col}, Str),
		    {{Row, Col}, CS};
		not_found ->
		    gs:config(lbl(Win), {label, {text,"Not found"}}),
		    {Pos, CS}
	    end
    end.

search(_Str, _Editor, Max, {Row, _Col}, _CS) when Row>Max ->
    not_found;
search(Str, Editor, Max, {Row, Col}, CS) ->
    SearchIn = lowercase(CS, gs:read(Editor,
				     {get,{{Row,Col+1},{Row,lineend}}})),
    case string:str(SearchIn, Str) of
	0 -> search(Str, Editor, Max, {Row+1, 0}, CS);
	N -> {Row, Col+N}
    end.

lowercase(true, Str) -> Str;
lowercase(false, Str) ->
    [if Char >= $A, Char =< $Z -> Char+32;
	true -> Char
     end || Char <- Str].

mark_string(Editor, {Row, Col}, Str) ->
    Between = {{Row,Col}, {Row,Col+length(Str)}},
    Font = dbg_ui_win:font(bold),
    gs:config(Editor, [{vscrollpos, Row-5},
		       {font_style, {Between, Font}},
		       {fg, {Between, red}}]).

unmark_string(Editor, {Row, Col}) ->
    Between = {{Row,Col}, {Row,lineend}},
    Font = dbg_ui_win:font(normal),
    gs:config(Editor, [{vscrollpos, Row-5},
		       {font_style, {Between, Font}},
		       {fg, {Between, black}}]).

helpwin(Type, GS, {X, Y}) ->
    W = 200, Pad = 10, Wbtn = 50,

    Title = case Type of search -> "Search"; gotoline -> "Go To Line" end,
    Win = gs:window(GS, [{title, Title}, {x, X}, {y, Y}, {width, W},
			 {destroy, true}]),
    
    Ent = gs:entry(Win, [{x, Pad}, {y, Pad}, {width, W-2*Pad},
			 {keypress, true}]),
    Hent = gs:read(Ent, height),

    Font = dbg_ui_win:font(normal),

    {Ybtn, Lbl} =
	case Type of
	    search ->
		Ycb = Pad+Hent,
		gs:checkbutton(Win, [{label, {text, "Case Sensitive"}},
				     {font, Font},
				     {align, w},
				     {x, Pad}, {y, Ycb},
				     {width, W-2*Pad}, {height, 15},
				     {data, case_sensitive}]),
		Ylbl = Ycb+15,
		{Ylbl+Hent+Pad,
		 gs:label(Win, [{x, Pad}, {y, Ylbl},
				{width, W-2*Pad}, {height, Hent}])};
	    gotoline -> {Pad+Hent+Pad, null}
	end,

    BtnLbl = case Type of search -> "Search"; gotoline -> "Go" end,
    Btn = gs:button(Win, [{label, {text, BtnLbl}}, {font, Font},
			  {x, W/2-3/2*Wbtn-Pad}, {y, Ybtn},
			  {width, Wbtn}, {height, Hent},
			  {data, default}]),
    gs:button(Win, [{label, {text, "Clear"}}, {font, Font},
		    {x, W/2-1/2*Wbtn}, {y, Ybtn},
		    {width, Wbtn}, {height, Hent}]),
    gs:button(Win, [{label, {text, "Close"}}, {font, Font},
		    {x, W/2+1/2*Wbtn+Pad}, {y, Ybtn},
		    {width, Wbtn}, {height, Hent}]),

    H = Ybtn+Hent+Pad,
    gs:config(Win, [{height, H}, {map, true}]),
    {Ent, Lbl, Btn}.

ent(Win) -> element(1, Win).
lbl(Win) -> element(2, Win).
btn(Win) -> element(3, Win).
