%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 2015-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_alloc_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]).

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

-record(state,
	{
	  time = #ti{},
	  active = false,
	  parent,
	  wins,
	  mem,
	  samples,
          max,
	  panel,
	  paint,
	  appmon,
	  async
	}).

-define(ID_REFRESH_INTERVAL, 102).

-import(observer_perf_wx,
	[make_win/4, setup_graph_drawing/1, refresh_panel/4, interval_dialog/2,
	 add_data/5, precalc/4]).

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

init([Notebook, Parent, Config]) ->
    try
	TopP  = wxPanel:new(Notebook),
	Main  = wxBoxSizer:new(?wxVERTICAL),
	Panel = wxPanel:new(TopP),
	GSzr  = wxBoxSizer:new(?wxVERTICAL),
	BorderFlags = ?wxLEFT bor ?wxRIGHT,
	Carrier = make_win(alloc, Panel, GSzr, BorderFlags bor ?wxTOP),
	Utilz = make_win(utilz, Panel, GSzr, BorderFlags),
	wxWindow:setSizer(Panel, GSzr),
	wxSizer:add(Main, Panel, [{flag, ?wxEXPAND},{proportion,2}]),

	MemWin = create_mem_info(TopP),
	wxSizer:add(Main, MemWin, [{flag, ?wxEXPAND bor BorderFlags bor ?wxBOTTOM},
				   {proportion, 1}, {border, 5}]),
	wxWindow:setSizer(TopP, Main),
	Windows = [Carrier, Utilz],
	PaintInfo = setup_graph_drawing(Windows),
	{TopP, #state{parent= Parent,
		      panel = Panel,
		      wins  = Windows,
		      mem   = MemWin,
		      paint = PaintInfo,
		      time  = setup_time(Config),
                      max   = #{}
		     }
	}
    catch _:Err:Stacktrace ->
	    io:format("~p crashed ~tp: ~tp~n",[?MODULE, Err, Stacktrace]),
	    {stop, Err}
    end.

setup_time(Config) ->
    Freq = maps:get(fetch, Config, 1),
    #ti{disp=?DISP_FREQ/Freq,
        fetch=Freq,
        secs=maps:get(secs, Config, ?DISP_SECONDS)}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
handle_event(#wx{id=?ID_REFRESH_INTERVAL, event=#wxCommand{type=command_menu_selected}},
	     #state{active=Active, 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 not Active ->
	    {noreply, State#state{time=Ti}};
	Ti -> %% Changed fetch interval, drop all data
	    {noreply, restart_fetcher(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)
    Win = lists:keyfind(Panel, #win.panel, Windows),
    refresh_panel(Active, Win, Ti, Paint),
    ok.
%%%%%%%%%%
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({Key, {promise_reply, {badrpc, _}}}, #state{async=Key} = State) ->
    {noreply, State#state{active=false, appmon=undefined}};

handle_info({Key, {promise_reply, SysInfo}},
	    #state{async=Key, samples=Data, max=Max0,
                   active=Active, wins=Wins0, time=#ti{tick=Tick, disp=Disp0}=Ti} = S0) ->
    Disp = trunc(Disp0),
    Next = max(Tick - Disp, 0),
    erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}),
    Info = alloc_info(SysInfo),
    Max = lists:foldl(fun calc_max/2, Max0, Info),
    {Wins, Samples} = add_data(Info, Data, Wins0, Ti, Active),
    S1 = S0#state{time=Ti#ti{tick=Next}, wins=Wins, samples=Samples, max=Max, async=undefined},
    if Active ->
	    update_alloc(S0, Info, Max),
	    State = precalc(S1),
	    {noreply, State};
       true ->
	    {noreply, S1}
    end;

handle_info({refresh, Seq},
	    State = #state{panel=Panel, appmon=Node, time=#ti{tick=Seq, disp=DispF}=Ti})
  when (Seq+1) < (DispF*1.5) ->
    Next = Seq+1,
    State#state.active andalso (catch wxWindow:refresh(Panel)),
    erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, Next}),
    if Seq =:= (trunc(DispF)-1) ->
	    Req = request_info(Node),
	    {noreply, State#state{time=Ti#ti{tick=Next}, async=Req}};
       true ->
	    {noreply, State#state{time=Ti#ti{tick=Next}}}
    end;
handle_info({refresh, _S}, #state{}=State) ->
    {noreply, State};

handle_info({active, Node}, State = #state{parent=Parent, panel=Panel, appmon=Old}) ->
    create_menus(Parent, []),
    try
	Node = Old,
	wxWindow:refresh(Panel),
	{noreply, precalc(State#state{active=true})}
    catch _:_ ->
	    {noreply, restart_fetcher(Node, State)}
    end;

handle_info(not_active, State = #state{appmon=_Pid}) ->
    {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{}) ->
    ok.
code_change(_, _, State) ->
    State.

%%%%%%%%%%

request_info(Node) ->
    ReplyTo = self(),
    spawn(fun() ->
                  Res = rpc:call(Node, observer_backend, sys_info, []),
                  ReplyTo ! {self(), {promise_reply, Res}}
          end).

restart_fetcher(Node, #state{panel=Panel, wins=Wins0, time=Ti} = State) ->
    case rpc:call(Node, observer_backend, sys_info, []) of
        {badrpc, _} -> State;
        SysInfo ->
            Info = alloc_info(SysInfo),
            Max = lists:foldl(fun calc_max/2, #{}, Info),
            {Wins, Samples} = add_data(Info, {0, queue:new()}, Wins0, Ti, true),
            erlang:send_after(1000 div ?DISP_FREQ, self(), {refresh, 0}),
            wxWindow:refresh(Panel),
            precalc(State#state{active=true, appmon=Node, time=Ti#ti{tick=0},
                                wins=Wins, samples=Samples, max=Max})
    end.

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

calc_max({Name, _, Cs}, Max0) ->
    case maps:get(Name, Max0, 0) of
        Value when Value < Cs ->
            Max0#{Name=>Cs};
        _V ->
            Max0
    end.

update_alloc(#state{mem=Grid}, Fields, Max) ->
    wxWindow:freeze(Grid),
    Last = wxListCtrl:getItemCount(Grid),
    Update = fun({Name, BS, CS}, Row) ->
		     (Row >= Last) andalso wxListCtrl:insertItem(Grid, Row, ""),
                     MaxV = maps:get(Name, Max, CS),
		     wxListCtrl:setItem(Grid, Row, 0, observer_lib:to_str(Name)),
		     wxListCtrl:setItem(Grid, Row, 1, observer_lib:to_str(BS div 1024)),
		     wxListCtrl:setItem(Grid, Row, 2, observer_lib:to_str(CS div 1024)),
                     wxListCtrl:setItem(Grid, Row, 3, observer_lib:to_str(MaxV div 1024)),
		     Row + 1
	     end,
    wx:foldl(Update, 0, Fields),
    wxWindow:thaw(Grid),
    Fields.

alloc_info(SysInfo) ->
    AllocInfo = proplists:get_value(alloc_info, SysInfo, []),
    alloc_info(AllocInfo, [], 0, 0, true).

alloc_info([{Type,Instances}|Allocators],TypeAcc,TotalBS,TotalCS,IncludeTotal) ->
    {BS,CS,NewTotalBS,NewTotalCS,NewIncludeTotal} =
	sum_alloc_instances(Instances,0,0,TotalBS,TotalCS),
    alloc_info(Allocators,[{Type,BS,CS}|TypeAcc],NewTotalBS,NewTotalCS,
	       IncludeTotal andalso NewIncludeTotal);
alloc_info([],TypeAcc,TotalBS,TotalCS,IncludeTotal) ->
    Types = [X || X={_,BS,CS} <- TypeAcc, (BS>0 orelse CS>0)],
    case IncludeTotal of
	true ->
	    [{total,TotalBS,TotalCS} | lists:reverse(Types)];
	false ->
	    lists:reverse(Types)
    end.

sum_alloc_instances(false,BS,CS,TotalBS,TotalCS) ->
    {BS,CS,TotalBS,TotalCS,false};
sum_alloc_instances([{_,_,Data}|Instances],BS,CS,TotalBS,TotalCS) ->
    {NewBS,NewCS,NewTotalBS,NewTotalCS} =
	sum_alloc_one_instance(Data,BS,CS,TotalBS,TotalCS),
    sum_alloc_instances(Instances,NewBS,NewCS,NewTotalBS,NewTotalCS);
sum_alloc_instances([],BS,CS,TotalBS,TotalCS) ->
    {BS,CS,TotalBS,TotalCS,true}.

sum_alloc_one_instance([{_,[{blocks,TypedBlocks},{carriers_size,CS,_,_}]}|
			Rest],OldBS,OldCS,TotalBS,TotalCS) ->
    %% OTP 23 and later.
    BS = sum_alloc_block_list(TypedBlocks, 0),
    sum_alloc_one_instance(Rest,OldBS+BS,OldCS+CS,TotalBS+BS,TotalCS+CS);
sum_alloc_one_instance([{_,[{blocks_size,BS,_,_},{carriers_size,CS,_,_}]}|
			Rest],OldBS,OldCS,TotalBS,TotalCS) ->
    %% OTP 22 and earlier.
    sum_alloc_one_instance(Rest,OldBS+BS,OldCS+CS,TotalBS+BS,TotalCS+CS);
sum_alloc_one_instance([_|Rest],BS,CS,TotalBS,TotalCS) ->
    sum_alloc_one_instance(Rest,BS,CS,TotalBS,TotalCS);
sum_alloc_one_instance([],BS,CS,TotalBS,TotalCS) ->
    {BS,CS,TotalBS,TotalCS}.

sum_alloc_block_list([{_Type, [{size, Current, _, _}]} | Rest], Acc) ->
    %% We ignore the type since we're returning a summary of all blocks in the
    %% carriers employed by a certain instance.
    sum_alloc_block_list(Rest, Current + Acc);
sum_alloc_block_list([{_Type, [{size, Current}]} | Rest], Acc) ->
    sum_alloc_block_list(Rest, Current + Acc);
sum_alloc_block_list([_ | Rest], Acc) ->
    sum_alloc_block_list(Rest, Acc);
sum_alloc_block_list([], Acc) ->
    Acc.

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

create_mem_info(Parent) ->
    Style = ?wxLC_REPORT bor ?wxLC_SINGLE_SEL bor ?wxLC_HRULES bor ?wxLC_VRULES,
    Grid = wxListCtrl:new(Parent, [{style, Style}]),

    Li = wxListItem:new(),
    Scale = observer_wx:get_scale(),
    AddListEntry = fun({Name, Align, DefSize}, Col) ->
			   wxListItem:setText(Li, Name),
			   wxListItem:setAlign(Li, Align),
			   wxListCtrl:insertColumn(Grid, Col, Li),
			   wxListCtrl:setColumnWidth(Grid, Col, DefSize*Scale),
			   Col + 1
		   end,
    ListItems = [{"Allocator Type",  ?wxLIST_FORMAT_LEFT,  200},
		 {"Block size (kB)",  ?wxLIST_FORMAT_RIGHT, 150},
		 {"Carrier size (kB)",?wxLIST_FORMAT_RIGHT, 150},
                 {"Max Carrier size (kB)",?wxLIST_FORMAT_RIGHT, 150}
                ],
    lists:foldl(AddListEntry, 0, ListItems),
    wxListItem:destroy(Li),

    Grid.

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

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