%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 2012-2025. 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(observer_perf_wx).
-moduledoc false.

-export([start_link/3]).

%% wx_object callbacks
-export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3,
	 handle_event/2, handle_sync_event/3, handle_cast/2]).

%% Drawing wrappers for DC and GC areas
-export([make_win/4, setup_graph_drawing/1,
	 refresh_panel/4, precalc/4, add_data/5, interval_dialog/2,
	 haveGC/0, make_gc/2, destroy_gc/1,
	 setPen/2, setFont/3, setBrush/2,
	 strokeLine/5, strokeLines/2, drawRoundedRectangle/6,
	 drawText/4, getTextExtent/2]).

-behaviour(wx_object).
-include_lib("wx/include/wx.hrl").
-include("observer_defs.hrl").

-define(ID_REFRESH_INTERVAL, 102).

-define(BW, 5).
-define(BH, 5).

-record(state,
	{
	  time = #ti{},
	  active = false,
	  parent,
	  samples,        %% Orig data store
	  wins=[],     %% per window content
	  panel,
	  paint,
	  appmon
	}).

-define(wxGC, wxGraphicsContext).

-record(paint, {font, small, fg, pen, pen2, pens, dot_pens, usegc = false}).

start_link(Notebook, Parent, Config) ->
    wx_object:start_link(?MODULE, [Notebook, Parent, Config], []).

init([Notebook, Parent, Config]) ->
    try
	Panel = wxPanel:new(Notebook),
	Main  = wxBoxSizer:new(?wxVERTICAL),
	MemIO = wxBoxSizer:new(?wxHORIZONTAL),

	CPU = make_win(runq, Panel, Main, ?wxALL),
	MEM = make_win(memory, Panel, MemIO, ?wxLEFT),
	IO  = make_win(io, Panel, MemIO, ?wxLEFT bor ?wxRIGHT),

	wxSizer:add(Main, MemIO, [{flag, ?wxEXPAND bor ?wxDOWN},
				  {proportion, 1}, {border, 5}]),
	wxWindow:setSizer(Panel, Main),
	Windows = [CPU, MEM, IO],
	PaintInfo = setup_graph_drawing(Windows),

	process_flag(trap_exit, true),
	State0 = #state{parent=Parent,
			panel =Panel,
			wins = Windows,
			paint=PaintInfo,
			samples=reset_data(),
                        time=#ti{fetch=maps:get(fetch, Config, ?FETCH_DATA),
                                 secs=maps:get(secs, Config, ?DISP_SECONDS)}
		       },
	{Panel, State0}
    catch _:Err:Stacktrace ->
	    io:format("~p crashed ~tp: ~tp~n",[?MODULE, Err, Stacktrace]),
	    {stop, Err}
    end.

make_win(Name, Parent, Sizer, Border) ->
    Style = ?wxFULL_REPAINT_ON_RESIZE bor ?wxCLIP_CHILDREN,
    Panel = wxPanel:new(Parent, [{style,Style}]),
    Opts = [{flag, ?wxEXPAND bor Border}, {proportion, 1}, {border, 5}],
    wxSizer:add(Sizer, Panel, Opts),
    #win{name=Name, panel=Panel}.

setup_graph_drawing(Panels) ->
    Do = fun(#win{panel=Panel}) ->
		 wxWindow:setBackgroundStyle(Panel, ?wxBG_STYLE_PAINT),
		 wxPanel:connect(Panel, paint, [callback])
	 end,
    _ = [Do(Panel) || Panel <- Panels],
    UseGC = haveGC(),
    Version28 = ?wxMAJOR_VERSION =:= 2 andalso ?wxMINOR_VERSION =:= 8,
    Scale = observer_wx:get_scale(),
    {Font, SmallFont}
	= if UseGC, Version28 ->
		  %% Def font is really small when using Graphics contexts in 2.8
		  %% Hardcode it
		  F = wxFont:new(Scale * 12,?wxFONTFAMILY_DECORATIVE,?wxFONTSTYLE_NORMAL,?wxFONTWEIGHT_BOLD),
		  SF = wxFont:new(Scale * 10, ?wxFONTFAMILY_DECORATIVE, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL),
		  {F, SF};
	     true ->
		  DefFont = wxSystemSettings:getFont(?wxSYS_DEFAULT_GUI_FONT),
		  DefSize = wxFont:getPointSize(DefFont),
		  DefFamily = wxFont:getFamily(DefFont),
		  F = wxFont:new(Scale * (DefSize-1), DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_BOLD),
		  SF = wxFont:new(Scale * (DefSize-2), DefFamily, ?wxFONTSTYLE_NORMAL, ?wxFONTWEIGHT_NORMAL),
		  {F, SF}
	  end,
    BG = wxWindow:getBackgroundColour((hd(Panels))#win.panel),
    Fg = case observer_lib:is_darkmode(BG) of
             false -> {0,0,0};
             true  -> wxSystemSettings:getColour(?wxSYS_COLOUR_BTNTEXT)
         end,

    PenColor = case observer_lib:is_darkmode(BG) of
                   false -> {0,0,0};
                   true  -> {0,0,0}
               end,
    Pens = [wxPen:new(Col, [{width, Scale}, {style, ?wxSOLID}])
            || Col <- tuple_to_list(colors())],
    DotPens = [wxPen:new(Col, [{width, Scale}, {style, ?wxDOT}])
               || Col <- tuple_to_list(colors())],
    #paint{usegc = UseGC,
	   font  = Font,
	   small = SmallFont,
           fg    = Fg,  %% Text color
	   pen   = wxPen:new(PenColor),
	   pen2  = wxPen:new(PenColor, [{width, Scale}]),
	   pens  = list_to_tuple(Pens),
           dot_pens = list_to_tuple(DotPens)
	  }.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
handle_event(#wx{id=?ID_REFRESH_INTERVAL, event=#wxCommand{type=command_menu_selected}},
	     #state{panel=Panel, appmon=Old, wins=Wins0, time=#ti{fetch=F0} = Ti0} = State) ->
    case interval_dialog(Panel, Ti0) of
	Ti0 -> {noreply, State};
	#ti{fetch=F0} = Ti -> %% Same fetch interval force refresh
	    Wins = [W#win{max=undefined} || W <- Wins0],
	    {noreply, precalc(State#state{time=Ti, wins=Wins})};
	Ti when Old =:= undefined ->
	    {noreply, State#state{time=Ti}};
	Ti -> %% Changed fetch interval, drop all data
	    {noreply, restart_fetcher(node(Old), State#state{time=Ti})}
    end;
handle_event(#wx{event=#wxCommand{type=command_menu_selected}},
	     State = #state{}) ->
    {noreply, State};

handle_event(Event, _State) ->
    error({unhandled_event, Event}).

%%%%%%%%%%
handle_sync_event(#wx{obj=Panel, event = #wxPaint{}},_,
		  #state{active=Active, time=Ti, paint=Paint, wins=Windows}) ->
    %% Sigh workaround bug on MacOSX (Id in paint event is always 0)
    %% Panel = element(Id, Windows),
    Win = lists:keyfind(Panel, #win.panel, Windows),
    refresh_panel(Active, Win, Ti, Paint),
    ok.

refresh_panel(Active, #win{name=_Id, panel=Panel}=Win, Ti, #paint{usegc=UseGC}=Paint) ->
    %% PaintDC must be created in a callback to work on windows.
    %% Nothing is drawn until wxPaintDC is destroyed.
    GC = make_gc(Panel, UseGC),
    if Active -> draw_win(GC, Win, Ti, Paint);
       true ->   ignore
    end,
    destroy_gc(GC).

%%%%%%%%%%
handle_call(get_config, _, #state{time=Ti}=State) ->
    #ti{fetch=Fetch, secs=Range} = Ti,
    {reply, #{fetch=>Fetch, secs=>Range}, State};

handle_call(Event, From, _State) ->
    error({unhandled_call, Event, From}).

handle_cast(Event, _State) ->
    error({unhandled_cast, Event}).
%%%%%%%%%%
handle_info({stats, 1, _, _, _} = Stats,
	    #state{panel=Panel, samples=Data, active=Active, wins=Wins0,
                   appmon=Node, time=#ti{tick=Tick, disp=Disp0}=Ti} = State0) ->
    if Active ->
	    Disp = trunc(Disp0),
	    Next = max(Tick - Disp, 0),
	    erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}),
	    {Wins, Samples} = add_data(Stats, Data, Wins0, Ti, Active, Node),
	    State = precalc(State0#state{time=Ti#ti{tick=Next}, wins=Wins, samples=Samples}),
	    wxWindow:refresh(Panel),
	    {noreply, State};
       true ->
	    {Wins1, Samples} = add_data(Stats, Data, Wins0, Ti, Active, Node),
	    Wins = [W#win{max=undefined} || W <- Wins1],
	    {noreply, State0#state{samples=Samples, wins=Wins, time=Ti#ti{tick=0}}}
    end;

handle_info({refresh, Seq}, #state{panel=Panel, time=#ti{tick=Seq, disp=DispF}=Ti} = State0)
  when (Seq+1) < (DispF*1.5) ->
    Next = Seq+1,
    erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}),
    State = precalc(State0#state{time=Ti#ti{tick=Next}}),
    catch wxWindow:refresh(Panel),
    {noreply, State};
handle_info({refresh, _}, State) ->
    {noreply, State};

handle_info({active, Node}, #state{parent=Parent, panel=Panel, appmon=Old} = State) ->
    create_menus(Parent, []),
    try
	Node = node(Old),
	wxWindow:refresh(Panel),
	erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, 0}),
	{noreply, State#state{active=true}}
    catch _:_ ->
	    {noreply,restart_fetcher(Node, State)}
    end;

handle_info(not_active, State = #state{appmon=_Pid}) ->
    %% Pid ! exit,
    {noreply, State#state{active=false}};

handle_info({'EXIT', Old, _}, State = #state{appmon=Old}) ->
    {noreply, State#state{active=false, appmon=undefined}};

handle_info(_Event, State) ->
    %% io:format("~p:~p: ~tp~n",[?MODULE,?LINE,_Event]),
    {noreply, State}.

%%%%%%%%%%
terminate(_Event, #state{appmon=Pid}) ->
    catch Pid ! exit,
    ok.
code_change(_, _, State) ->
    State.

restart_fetcher(Node, #state{appmon=Old, panel=Panel, time=#ti{fetch=Freq}=Ti, wins=Wins0}=State) ->
    catch Old ! exit,
    Me = self(),
    Pid = spawn_link(Node, observer_backend, fetch_stats, [Me, round(1000/Freq)]),
    wxWindow:refresh(Panel),
    Wins = [W#win{state=undefined} || W <- Wins0],
    precalc(State#state{active=true, appmon=Pid, samples=reset_data(),
			wins=Wins, time=Ti#ti{tick=0}}).

reset_data() ->
    {0, queue:new()}.

add_data(Stats, Q, Wins, Ti, Active) ->
    add_data(Stats, Q, Wins, Ti, Active, ignore).

add_data(Stats, {N, Q0}, Wins, #ti{fetch=Fetch, secs=Secs}, Active, Node)
  when N > (Secs*Fetch+1) ->
    {{value, Drop}, Q} = queue:out(Q0),
    add_data_1(Wins, Stats, N, {Drop,Q}, Active, Node);
add_data(Stats, {N, Q}, Wins, _, Active, Node) ->
    add_data_1(Wins, Stats, N+1, {empty, Q}, Active, Node).

add_data_1([#win{state={_,St}}|_]=Wins0, Last, N, {Drop, Q}, Active, Node)
  when St /= undefined ->
    try
	{Wins, Stat} =
	    lists:mapfoldl(fun(Win0, Entry) ->
				   {Win1,Stat} = add_data_2(Win0, Last, Entry),
				   case Active of
				       true ->
					   Win = add_data_3(Win1, N, Drop, Stat, Q),
					   {Win, Stat};
				       false ->
					   {Win1, Stat}
				   end
			   end, #{}, Wins0),
	{Wins, {N,queue:in(Stat#{}, Q)}}
    catch no_scheduler_change ->
	    {[Win#win{state=init_data(Id, Last), info=info(Id, Last, Node)}
	      || #win{name=Id}=Win <- Wins0], {0,queue:new()}}
    end;

add_data_1(Wins, Stats, 1, {_, Q}, _, Node) ->
    {[Win#win{state=init_data(Id, Stats), info=info(Id, Stats, Node)}
      || #win{name=Id}=Win <- Wins], {0,Q}}.

add_data_2(#win{name=Id, state=S0}=Win, Stats, Map) ->
    {V1, S1} = collect_data(Id, Stats, S0),
    {Win#win{state=S1}, Map#{Id=>V1}}.

add_data_3(#win{name=Id, max={{OldMax, OldEntry},_,_,_},
		geom=#{scale:={WS,HS}}, state={Max,_},
		graphs=Graphs}=Win,
	   N, Drop0, Last, Q1)
  when N > 3 ->
    Drop = case Drop0 of
	       #{Id:=D} -> D;
	       _ -> Drop0
	   end,
    case {max_value(Max), Drop =:= OldEntry} of
	{OldMax, false} ->
	    #{Id:=V4} = Last,
	    {{value, #{Id:=V3}},Q2} = queue:out_r(Q1),
	    {{value, #{Id:=V2}},Q3} = queue:out_r(Q2),
	    {{value, #{Id:=V1}},_}  = queue:out_r(Q3),
	    Vals = [V1,V2,V3,V4],
	    Gs = tuple_size(V1),
	    Info = lists:zip(lists:seq(Gs, 1, -1), Graphs),
	    Lines = [add_lines(Vals, Drop, Prev, I, WS, HS) || {I, Prev} <- Info],
	    Win#win{graphs=Lines, no_samples=N};
	_W -> %% Max changed Trigger complete recalc
	    Win#win{max=undefined}
    end;
add_data_3(Win, _, _, _,_) ->
    %% Trigger complete recalc
    Win#win{max=undefined}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

create_menus(Parent, _) ->
    View = {"View", [#create_menu{id = ?ID_REFRESH_INTERVAL, text = "Graph Settings"}]},
    observer_wx:create_menus(Parent, [{"File", []}, View]).

interval_dialog(Parent0, #ti{fetch=Fetch0, secs=Secs0}=Ti) ->
    Parent = observer_lib:get_wx_parent(Parent0),
    Dialog = wxDialog:new(Parent, ?wxID_ANY, "Load Chart Settings",
			  [{style, ?wxDEFAULT_DIALOG_STYLE bor
				?wxRESIZE_BORDER}]),
    {Sl1,FetchSl} = slider(Dialog, "Sample (ms)", trunc(1000 / Fetch0), 100, 10000),
    {Sl2, SecsSl} = slider(Dialog, "Length (min)", Secs0 div 60, 1, 10),
    TopSizer = wxBoxSizer:new(?wxVERTICAL),
    Flags = [{flag, ?wxEXPAND bor ?wxTOP bor ?wxLEFT bor ?wxRIGHT},
	     {border, 5}, {proportion, 1}],
    wxSizer:add(TopSizer, Sl1, Flags),
    wxSizer:add(TopSizer, Sl2, Flags),
    wxSizer:add(TopSizer, wxDialog:createButtonSizer(Dialog, ?wxOK bor ?wxCANCEL), Flags),
    wxWindow:setSizerAndFit(Dialog, TopSizer),
    wxSizer:setSizeHints(TopSizer, Dialog),
    Res = case wxDialog:showModal(Dialog) of
	      ?wxID_OK ->
		  Fetch = 1000 / wxSlider:getValue(FetchSl),
		  Secs = wxSlider:getValue(SecsSl) * 60,
		  Ti#ti{fetch=Fetch, secs=Secs, disp=?DISP_FREQ/Fetch};
	      ?wxID_CANCEL ->
		  Ti
	  end,
    wxDialog:destroy(Dialog),
    Res.

slider(Parent, Str, Value, Min, Max) ->
    Sz = wxBoxSizer:new(?wxHORIZONTAL),
    Center = [{flag, ?wxALIGN_CENTER_VERTICAL}],
    wxSizer:add(Sz, wxStaticText:new(Parent, ?wxID_ANY, Str), [{proportion, 1}|Center]),
    Opt = [{style, ?wxSL_HORIZONTAL bor ?wxSL_LABELS}],
    Slider = wxSlider:new(Parent, ?wxID_ANY, Value, Min, Max, Opt),
    wxSizer:add(Sz, Slider, [{proportion, 2}|Center]),
    case Min > 1 of
	false ->
	    {Sz, Slider};
	true ->
	    CB = fun(#wx{event=Ev},_) -> step(Ev, Slider, Min) end,
	    wxSlider:connect(Slider, scroll_thumbtrack, [{callback, CB}]),
	    wxSlider:connect(Slider, scroll_changed, [{callback, CB}]),
	    {Sz, Slider}
    end.

step(_Ev = #wxScroll{commandInt=Value}, Slider, Min) ->
    Val = Min * round(Value / Min),
    wxSlider:setValue(Slider, Val),
    ok.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

mk_max() -> {0, undefined}.
max_value({Max,_}) -> Max.
%% max_data({_,Data}) -> Data. matched in function head

lmax(MState, Tuple, Tuple) when is_tuple(Tuple) ->
    lmax(MState, tuple_to_list(Tuple), Tuple);
lmax(MState, Values, State) ->
    Max = max_value(MState),
    New = lists:max([Max|Values]),
    case New >= Max of
	false -> MState;
	true -> {New, State}
    end.

init_data(runq, {stats, _, T0, _, _}) -> {mk_max(),lists:sort(T0)};
init_data(io,   {stats, _, _, {{_,In0}, {_,Out0}}, _}) -> {mk_max(), {In0,Out0}};
init_data(memory, _) -> {mk_max(), info(memory, undefined, undefined)};
init_data(alloc, _) -> {mk_max(), unused};
init_data(utilz, _) -> {mk_max(), unused}.

info(runq, {stats, _, T0, _, _}, Node) ->
    Dirty = get_dirty_cpu(Node),
    {lists:seq(1, length(T0)-Dirty), Dirty};
info(memory, _, _) -> [total, processes, atom, binary, code, ets];
info(io, _, _) -> [input, output];
info(alloc, First, _) -> [Type || {Type, _, _} <- First];
info(utilz, First, _) -> [Type || {Type, _, _} <- First];
info(_, [], _) -> [].

get_dirty_cpu(Node) ->
    case rpc:call(node(Node), erlang, system_info, [dirty_cpu_schedulers]) of
        {badrpc,_R} -> 0;
        N -> N
    end.

collect_data(runq, {stats, _, T0, _, _}, {Max,S0}) ->
    S1 = lists:sort(T0),
    Delta = calc_delta(S1, S0),
    Sample = list_to_tuple(Delta),
    {Sample, {lmax(Max,Delta,Sample), S1}};
collect_data(io, {stats, _, _, {{_,In0}, {_,Out0}}, _}, {Max,{PIn,POut}}) ->
    In = In0-PIn,
    Out = Out0-POut,
    Sample = {In, Out},
    {Sample, {lmax(Max, [In,Out], Sample), {In0, Out0}}};
collect_data(memory, {stats, _, _, _, MemInfo}, {Max, MemTypes}) ->
    Vs = [Value || {Type,Value} <- MemInfo, lists:member(Type, MemTypes)],
    Sample = list_to_tuple(Vs),
    {Sample, {lmax(Max, Vs, Sample),MemTypes}};
collect_data(alloc, MemInfo, Max) ->
    Vs = [Carrier || {_Type,_Block,Carrier} <- MemInfo],
    Sample = list_to_tuple(Vs),
    {Sample, {lmax(Max, Vs, Sample),unused}};
collect_data(utilz, MemInfo, Max) ->
    Vs = [round(100*Block/Carrier) || {_Type,Block,Carrier} <- MemInfo],
    Sample = list_to_tuple(Vs),
    {Sample, {lmax(Max,Vs,Sample),unused}}.

calc_delta([{Id, WN, TN}|Ss], [{Id, WP, TP}|Ps]) ->
    [100*(WN-WP) div (TN-TP)|calc_delta(Ss, Ps)];
calc_delta([], []) -> [];
calc_delta(_, _) -> throw(no_scheduler_change).

precalc(#state{samples=Data0, paint=Paint, time=Ti, wins=Wins0}=State) ->
    Wins = [precalc(Ti, Data0, Paint, Win) || Win <- Wins0],
    State#state{wins=Wins}.

precalc(Ti, {NoSamples,Q}, Paint, #win{name=Id, panel=Panel}=Win) ->
    Size = wxWindow:getClientSize(Panel),
    case Win of
	#win{max=Max, no_samples=NoSamples, size=Size} when is_tuple(Max) ->
	    Win;
	_SomeThingChanged ->
	    Hs = [Vals || #{Id:=Vals} <- queue:to_list(Q)],
	    Max = lists:foldl(fun(Vals,Max) -> lmax(Max, Vals, Vals) end,
			      mk_max(), Hs),
	    MaxDisp = calc_max(Id, Max),
	    #{scale:={WS,HS}} = Props = window_geom(Size, MaxDisp, Ti, Panel, Paint),
	    NoGraphs = try tuple_size(hd(Hs)) catch _:_ -> 0 end,
	    Graphs = [make_lines(Hs, I, WS, HS) || I <- lists:seq(NoGraphs, 1, -1)],
	    State = case Win#win.state of
			undefined -> {Max, undefined};
			{_, St} -> {Max, St}
		    end,
	    Win#win{geom=Props, size=Size,
		    max=MaxDisp,
		    graphs=Graphs,
		    no_samples=NoSamples,
		    state=State}
    end.

window_geom({W,H}, {_, Max, _Unit, MaxUnit},
	    #ti{secs=Secs, fetch=FetchFreq},
	    Panel, #paint{font=Font}) ->
    Str1 = observer_lib:to_str(MaxUnit),
    Str2 = observer_lib:to_str(MaxUnit div 2),
    Str3 = observer_lib:to_str(0),
    {TW,TH,_,_} = wxWindow:getTextExtent(Panel, Str1, [{theFont, Font}]),
    {SpaceW, _,_,_} = wxWindow:getTextExtent(Panel, "W", [{theFont, Font}]),
    X0 = ?BW+TW+?BW,
    X1 = W-?BW*4,
    MaxTextY = TH+?BH,
    BottomTextY = H-?BH-TH,
    Y0 = MaxTextY + (TH / 2),
    Y1 = BottomTextY - TH - ?BH,

    ScaleW = (X1-X0-1)/(Secs*FetchFreq),
    ScaleH = (Y1-Y0-1) / Max,
    #{p0=>{X0,Y0}, p1=>{X1,Y1}, scale=>{ScaleW, ScaleH},
      txsz=>{TW,TH,SpaceW}, txt=>{BottomTextY, MaxTextY}, strs=>{Str1,Str2,Str3}}.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
draw_win(DC, #win{name=Name, no_samples=Samples, geom=#{scale:={WS,HS}},
                  graphs=Graphs, max={_,Max,_,_}, info=Info}=Win,
	 #ti{tick=Tick, fetch=FetchFreq, secs=Secs, disp=DispFreq}=Ti,
	 Paint=#paint{pens=Pens, dot_pens=Dots}) when Samples >= 2, Graphs =/= [] ->
    %% Draw graphs
    {X0,Y0,DrawBs} = draw_borders(DC, Ti, Win, Paint),
    Offset = Tick / DispFreq,
    Full = case Samples > (1+Secs*FetchFreq) of
	       true -> 1;
	       false -> 2
	   end,
    Start = X0 + (max(Secs*FetchFreq+Full-Samples, 0) - Offset)*WS,
    Last = Secs*FetchFreq*WS+X0,
    Dirty = case {Name, Info} of
                {runq, {_, DCpu}} -> DCpu;
                _ -> 0
            end,
    NoGraphs = length(Graphs),
    NoCpu = NoGraphs - Dirty,
    Draw = fun(Lines0, N) ->
                   case Dirty > 0 andalso N > NoCpu of
                       true  -> setPen(DC, element(1+ ((N-NoCpu-1) rem tuple_size(Dots)), Dots));
                       false -> setPen(DC, element(1+ ((N-1) rem tuple_size(Pens)), Pens))
                   end,
		   Order = lists:reverse(Lines0),
		   [{_,Y}|Lines] = translate(Order, {Start, Y0}, 0, WS, {X0,Max*HS,Last}, []),
		   strokeLines(DC, [{Last,Y}|Lines]),
		   N-1
	   end,
    lists:foldl(Draw, NoGraphs, Graphs),
    DrawBs(),
    ok;

draw_win(DC, #win{no_samples=Samples} = Win,Ti, #paint{fg=Fg, small=Small}=Paint) ->
    %% Draw Error Msg
    try draw_borders(DC, Ti, Win, Paint) of
	{X0,_Y0,DrawBs} ->
	    Text = case Samples =< 1 of
		       true  -> "Waiting for data";
		       false -> "Information not available"
		   end,
	    setFont(DC, Small, Fg),
	    {_,WW} = getSize(DC),
	    drawText(DC, Text, X0 + 100, WW div 2),
	    DrawBs(),
	    ok
    catch _:_ -> %% Early redraws fail
	    ok
    end.

translate([{X0,Y}|Rest], {Sx,Sy}=Start, N, WS, {Cx,Cy,Cw}=Clip, Acc) ->
    X = min((N-X0)*WS+Sx,Cw),
    Next = if X0 > 0 -> N; true -> N+1 end,
    case X =< Cx of
	true ->
	    translate(Rest, Start, Next, WS, Clip, [{Cx,Sy-min(Cy,Y)}]);
	false ->
	    translate(Rest, Start, Next, WS, Clip, [{X,Sy-min(Cy,Y)}|Acc])
    end;
translate([], _, _, _, _, Acc) ->
    Acc.

add_lines(Vals, Drop, OldLines, I, WS, HS) ->
    Lines = strip(OldLines, Drop, 2),
    New = make_lines(Vals, I, WS, HS),
    New ++ Lines.

strip([{X,_}|Rest], Drop, N) when X > 0.0001, N > 0 ->
    strip(Rest, Drop, N);
strip([_|Rest], Drop, N) when N > 0 ->
    strip(Rest, Drop, N-1);
strip(List, empty, _) -> List;
strip(List, _, _) ->
    lists:reverse(strip(lists:reverse(List), empty, 1)).

make_lines(Ds = [Data|_], N, WS, HS) ->
    Y = element(N,Data),
    make_lines(Ds, N, WS, HS, Y, []).

make_lines([D1 | Ds = [D2|Rest]], N, WS, HS, Y0, Acc0) ->
    Y1 = element(N,D1),
    Y2 = element(N,D2),
    Y3 = case Rest of
	     [D3|_] -> element(N,D3);
	     [] -> Y2
	 end,
    This = {0, Y1*HS},
    Acc = if (abs(Y1-Y2) * HS) < 3.0 -> [This|Acc0];
	     WS < 3.0 -> [This|Acc0];
	     true -> make_splines(Y0,Y1,Y2,Y3,WS,HS,[This|Acc0])
	  end,
    make_lines(Ds, N, WS, HS, Y1, Acc);
make_lines([_D1], _N, _WS, _HS, _Y0, Acc) ->
    Acc.

make_splines(Y00,Y10,Y20,Y30,WS,HS,Acc) ->
    Y1 = Y10*HS,
    Y2 = Y20*HS,
    Steps = min(abs(Y1-Y2), WS/2),
    if Steps > 2 ->
	    Y0 = Y00*HS,
	    Y3 = Y30*HS,
	    Tan = spline_tan(Y0,Y1,Y2,Y3),
	    Delta = 1/Steps,
	    splines(Steps-1, 0.0, Delta, Tan, Y1,Y2, Acc);
       true ->
	    Acc
    end.

splines(N, XD, XD0, Tan, Y1,Y2, Acc) when N > 0 ->
    Delta = XD+XD0,
    Y = max(0, spline(Delta, Tan, Y1,Y2)),
    splines(N-1, Delta, XD0, Tan, Y1, Y2, [{1.0-Delta, Y}|Acc]);
splines(_N, _XD, _XD0, _Tan, _Y1,_Y2, Acc) ->
    Acc.

spline(T, {M1, M2}, Y1, Y2) ->
    %% Hermite Basis Funcs
    T2 = T*T,  T3 = T*T*T,
    H1 = 2*T3-3*T2+1,
    H2 = -2*T3+3*T2,
    H3 = T3-2*T2+T,
    H4 = T3-T2,
    %% Result
    M1*H3 + Y1*H1 + Y2*H2 + M2*H4.

spline_tan(Y0, Y1, Y2, Y3) ->
    S = 1.0,
    C = 0.5,
    %% Calc tangent values
    M1 = S*C*(Y2-Y0),
    M2 = S*C*(Y3-Y1),
    {M1,M2}.

draw_borders(DC, #ti{secs=Secs, fetch=FetchFreq},
	     #win{name=Type, geom=Geom, info=Info, max={_,_,Unit,_}},
	     #paint{pen=Pen, pen2=Pen2, fg=Fg, font=Font, small=Small}) ->
    #{p0:={GraphX0, GraphY0}, p1:={GraphX1,GraphY1}, scale:={ScaleW0,_},
      txsz:={TW,TH,SpaceW}, txt:={BottomTextY, MaxTextY}, strs:={Str1,Str2,Str3}} = Geom,

    ScaleW = ScaleW0*FetchFreq,
    TopTextX = ?BW*3+TW,
    SecondsY = BottomTextY - TH,

    GraphY25 = GraphY0 + (GraphY1 - GraphY0) / 4,
    GraphY50 = GraphY0 + (GraphY1 - GraphY0) / 2,
    GraphY75 = GraphY0 + 3*(GraphY1 - GraphY0) / 4,

    setFont(DC, Small, Fg),
    Align = fun(Str, Y) ->
		    {StrW, _} = getTextExtent(DC, Str),
		    drawText(DC, Str, GraphX0 - StrW - ?BW, Y)
	    end,
    Align(Str1, MaxTextY),
    Align(Str2, GraphY50 - (TH / 2)),
    Align(Str3, GraphY1 - (TH / 2) + 1),

    setPen(DC, Pen),
    DrawSecs = fun(Sec, {Pos, Prev}) ->
		       Str = observer_lib:to_str(Sec) ++ "s",
		       X = GraphX0+Pos,
		       strokeLine(DC, X, GraphY0, X, GraphY1+5),
		       TxtX = X-SpaceW,
		       case TxtX > Prev of
			   true ->
			       drawText(DC, Str,  TxtX, SecondsY),
			       TxtW = SpaceW*length(Str),
			       {Pos + 10*ScaleW, TxtX+TxtW};
			   false ->
			       {Pos + 10*ScaleW, Prev}
		       end
	       end,
    lists:foldl(DrawSecs, {0, 0}, lists:seq(Secs,0, -10)),

    strokeLine(DC, GraphX0-3, GraphY25, GraphX1, GraphY25),
    strokeLine(DC, GraphX0-3, GraphY50, GraphX1, GraphY50),
    strokeLine(DC, GraphX0-3, GraphY75, GraphX1, GraphY75),

    setFont(DC, Font, Fg),

    Text = fun(X,Y, Str, PenId) ->
		   if PenId == 0 ->
			   setFont(DC, Font, Fg);
		      PenId > 0 ->
			   Id = 1 + ((PenId-1) rem tuple_size(colors())),
			   setFont(DC, Font, element(Id, colors()))
		   end,
		   drawText(DC, Str, X, Y),
		   {StrW, _} = getTextExtent(DC, Str),
		   StrW + X + ?BW*2
	   end,

    case Type of
	runq ->
            {TextInfo, DirtyCpus} = Info,
	    drawText(DC, "Scheduler Utilization (%) ", TopTextX, ?BH),
	    TN0 = Text(TopTextX, BottomTextY, "Scheduler: ", 0),
            Id = fun(Id, Pos0) ->
                         Text(Pos0, BottomTextY, integer_to_list(Id), Id)
                 end,
	    TN1 = lists:foldl(Id, TN0, TextInfo),
            TN2 = Text(TN1, BottomTextY, "Dirty cpu: ", 0),
	    TN3 = lists:foldl(Id, TN2, lists:seq(1, DirtyCpus)),
            _ = Text(TN3, BottomTextY, "(dotted)", 0),
            ok;
	memory ->
	    drawText(DC, "Memory Usage " ++ Unit, TopTextX,?BH),
	    lists:foldl(fun(MType, {PenId, Pos0}) ->
				Str = to_string(MType),
				Pos = Text(Pos0, BottomTextY, Str, PenId),
				{PenId+1, Pos}
			end, {1, TopTextX}, Info);
	io ->
	    drawText(DC, "IO Usage " ++ Unit, TopTextX,?BH),
	    lists:foldl(fun(MType, {PenId, Pos0}) ->
				Str = to_string(MType),
				Pos = Text(Pos0, BottomTextY, Str, PenId),
				{PenId+1, Pos}
			end, {1, TopTextX}, Info);
	alloc ->
	    drawText(DC, "Carrier Size " ++ Unit, TopTextX,?BH);
	utilz ->
	    drawText(DC, "Carrier Utilization (%)" ++ Unit, TopTextX,?BH),
	    lists:foldl(fun(MType, {PenId, Pos0}) ->
				Str = to_string(MType),
				Pos = Text(Pos0, BottomTextY, Str, PenId),
				{PenId+1, Pos}
			end, {1, TopTextX}, Info)
    end,
    DrawBorder = fun() ->
			 setPen(DC, Pen2),
			 strokeLines(DC, [{GraphX0, GraphY0-1}, {GraphX0, GraphY1+1},
					  {GraphX1, GraphY1+1}, {GraphX1, GraphY0-1},
					  {GraphX0, GraphY0-1}])
		 end,
    {GraphX0+1, GraphY1, DrawBorder}.

to_string(Atom) ->
    Name = atom_to_list(Atom),
    case lists:reverse(Name) of
	"colla_" ++ Rev ->
	    uppercase(lists:reverse(Rev));
	_ ->
	    uppercase(Name)
    end.

uppercase([C|Rest]) ->
    [C-$a+$A|Rest].

calc_max(Type, Max) ->
    bytes(Type, Max).

bytes(runq, Max) ->
    Upper = calc_max1(max_value(Max)),
    {Max, Upper, "", Upper};
bytes(utilz, Max) ->
    Upper = calc_max1(max_value(Max)),
    {Max, Upper, "", Upper};
bytes(_, Max) ->
    B = max_value(Max),
    KB = B div 1024,
    MB = KB div 1024,
    GB = MB div 1024,
    if
	GB > 10 ->
	    Upper = calc_max1(GB),
	    {Max, Upper*1024*1024*1024, "(GB)", Upper};
	MB > 10 ->
	    Upper = calc_max1(MB),
	    {Max, Upper*1024*1024, "(MB)", Upper};
	KB >  0 ->
	    Upper = calc_max1(KB),
	    {Max, Upper*1024, "(KB)", Upper};
	true  ->
	    Upper = calc_max1(B),
	    {Max, Upper, "(B)", Upper}
    end.

calc_max1(Max) when Max < 10 ->
    10;
calc_max1(Max) ->
    case Max div 10 of
	X when X < 10 ->
	    case Max rem 10 of
		0 -> Max;
		_ ->
		    (X+1)*10
	    end;
	X ->
	    10*calc_max1(X)
    end.

colors() ->
    {{240, 100, 100}, {0, 128, 0},     {25, 45, 170},  {255, 165, 0},
     {220, 220, 40},  {100, 240, 240},{240, 100, 240}, {160, 40,  40},
     {100, 100, 240}, {140, 140, 0},  {25, 200, 100},  {120, 25, 240},
     {255, 140, 163}, {25, 120, 120}, {120, 25, 120},  {110, 90, 60}
    }.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% wxDC and ?wxGC wrappers

make_gc(Panel,UseGC) ->
    DC = case os:type() of
	     {win32, _} ->
		 %% Ugly hack to avoid flickering on windows, works on windows only
		 %% But the other platforms are doublebuffered by default
		 DC0 = wx:typeCast(wxBufferedPaintDC:new(Panel), wxPaintDC),
		 wxDC:clear(DC0),
		 DC0;
	     _ ->
		 wxPaintDC:new(Panel)
	 end,
    if UseGC -> {?wxGC:create(DC), DC};
       true ->  {false, DC}
    end.

destroy_gc({GC, DC}) ->
    (GC =/= false) andalso ?wxGC:destroy(GC),
    case DC =/= false andalso wx:getObjectType(DC) of
	false -> ok;
	Type -> Type:destroy(DC)
    end.

haveGC() ->
    try
	wxGraphicsRenderer:getDefaultRenderer(),
	true
    catch _:_  -> false
    end.

getSize({_, DC}) ->
    wxDC:getSize(DC).

setPen({false, DC}, Pen) ->
    wxDC:setPen(DC, Pen);
setPen({GC, _}, Pen) ->
    ?wxGC:setPen(GC, Pen).

setFont({false, DC}, Font, Color) ->
    wxDC:setTextForeground(DC, Color),
    wxDC:setFont(DC, Font);
setFont({GC, _}, Font, Color) ->
    ?wxGC:setFont(GC, Font, Color).

setBrush({false, DC}, Brush) ->
    wxDC:setBrush(DC, Brush);
setBrush({GC, _}, Brush) ->
    ?wxGC:setBrush(GC, Brush).

strokeLine({false, DC}, X0, Y0, X1, Y1) ->
    wxDC:drawLine(DC, {round(X0), round(Y0)}, {round(X1), round(Y1)});
strokeLine({GC, _}, X0, Y0, X1, Y1) ->
    ?wxGC:strokeLine(GC, X0, Y0, X1, Y1).

strokeLines(_, [_]) -> ok;
strokeLines({false, DC}, Lines) ->
    wxDC:drawLines(DC, [{round(X), round(Y)} || {X,Y} <- Lines]);
strokeLines({GC, _}, Lines) ->
    ?wxGC:strokeLines(GC, Lines).

drawRoundedRectangle({false, DC}, X0, Y0, X1, Y1, R) ->
    wxDC:drawRoundedRectangle(DC, {round(X0), round(Y0)}, {round(X1), round(Y1)}, round(R));
drawRoundedRectangle({GC, _}, X0, Y0, X1, Y1, R) ->
    ?wxGC:drawRoundedRectangle(GC, X0, Y0, X1, Y1, R).

drawText({false, DC}, Str, X, Y) ->
    wxDC:drawText(DC, Str, {round(X),round(Y)});
drawText({GC, _}, Str, X, Y) ->
    ?wxGC:drawText(GC, Str, X, Y).

getTextExtent({false, DC}, Str) ->
    wxDC:getTextExtent(DC, Str);
getTextExtent({GC, _}, Str) ->
    {W,H,_,_} = ?wxGC:getTextExtent(GC, Str),
    {W,H}.
