%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2006-2017. 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_SUITE).
-include_lib("common_test/include/ct.hrl").
-include_lib("wx/include/wx.hrl").
-include_lib("observer/src/observer_tv.hrl").

-define(ID_LOGVIEW, 5).

%% Test server specific exports
-export([all/0, suite/0,groups/0]).
-export([init_per_testcase/2, end_per_testcase/2,
	 init_per_group/2, end_per_group/2,
	 init_per_suite/1, end_per_suite/1
	]).

%% Test cases
-export([app_file/1, appup_file/1,
	 basic/1, process_win/1, table_win/1,
         port_win_when_tab_not_initiated/1
	]).

%% Default timetrap timeout (set in init_per_testcase)
-define(default_timeout, ?t:minutes(2)).

suite() -> [{ct_hooks,[ts_install_cth]}].

all() ->
    [app_file, appup_file, {group, gui}].

groups() ->
    [{gui, [],
      [basic,
       process_win,
       table_win,
       port_win_when_tab_not_initiated
      ]
     }].

init_per_suite(Config) ->
    Config.

end_per_suite(_Config) ->
    ok.

init_per_testcase(_Case, Config) ->
    Dog = ?t:timetrap(?default_timeout),
    [{watchdog, Dog} | Config].

end_per_testcase(_Case, Config) ->
    Dog = ?config(watchdog, Config),
    ?t:timetrap_cancel(Dog),
    ok.

init_per_group(gui, Config) ->
    try
	case os:type() of
	    {unix,darwin} ->
		exit("Can not test on MacOSX");
	    {unix, _} ->
		io:format("DISPLAY ~s~n", [os:getenv("DISPLAY")]),
		case ct:get_config(xserver, none) of
		    none -> ignore;
		    Server -> os:putenv("DISPLAY", Server)
		end;
	    _ -> ignore
	end,
	wx:new(),
	wx:destroy(),
	Config
    catch
	_:undef ->
	    {skipped, "No wx compiled for this platform"};
	_:Reason ->
	    SkipReason = io_lib:format("Start wx failed: ~p", [Reason]),
	    {skipped, lists:flatten(SkipReason)}
    end.
end_per_group(_, _) ->
    ok.

app_file(suite) ->
    [];
app_file(doc) ->
    ["Testing .app file"];
app_file(Config) when is_list(Config) ->
    ?line ok = ?t:app_test(observer),
    ok.

%% Testing .appup file
appup_file(Config) when is_list(Config) ->
    ok = ?t:appup_test(observer).

-define(DBG(Foo), io:format("~p: ~p~n",[?LINE, catch Foo])).

basic(suite) -> [];
basic(doc) -> [""];
basic(Config) when is_list(Config) ->
    timer:send_after(100, "foobar"), %% Otherwise the timer server gets added to procs
    ProcsBefore = processes(),
    NumProcsBefore = length(ProcsBefore),

    ok = observer:start(),
    Notebook = setup_whitebox_testing(),

    io:format("Notebook ~p~n",[Notebook]),
    Count = wxNotebook:getPageCount(Notebook),
    true = Count >= 6,
    0 = wxNotebook:getSelection(Notebook),
    timer:sleep(500),
    Check = fun(N, TestMore) ->
		    TestMore andalso
			test_page(wxNotebook:getPageText(Notebook, N),
				  wxNotebook:getCurrentPage(Notebook)),
		    timer:sleep(200),
		    ok = wxNotebook:advanceSelection(Notebook)
	    end,
    %% Just verify that we can toggle through all pages
    [_|_] = [Check(N, false) || N <- lists:seq(1, Count)],
    %% Cause it to resize
    Frame = get_top_level_parent(Notebook),
    {W,H} = wxWindow:getSize(Frame),
    wxWindow:setSize(Frame, W+10, H+10),
    [_|_] = [Check(N, true) || N <- lists:seq(0, Count-1)],

    ok = observer:stop(),
    timer:sleep(2000), %% stop is async
    ProcsAfter = processes(),
    NumProcsAfter = length(ProcsAfter),
    if NumProcsAfter=/=NumProcsBefore ->
	    ct:log("Before but not after:~n~p~n",
		   [[{P,process_info(P)} || P <- ProcsBefore -- ProcsAfter]]),
	    ct:log("After but not before:~n~p~n",
		   [[{P,process_info(P)} || P <- ProcsAfter -- ProcsBefore]]),
	    ct:fail("leaking processes");
       true ->
	    ok
    end,
    ok.

test_page("Load Charts" ++ _, _Window) ->
    %% Just let it display some info and hopefully it doesn't crash
    timer:sleep(2000),
    ok;
test_page("Applications" ++ _, _Window) ->
    ok = application:start(mnesia),
    timer:sleep(1000),  %% Give it time to refresh
    Active = get_active(),
    FakeEv = #wx{event=#wxCommand{type=command_listbox_selected, cmdString="mnesia"}},
    Active ! FakeEv,
    timer:sleep(1000),  %% Give it time to refresh
    ok = application:stop(mnesia),
    timer:sleep(1000),  %% Give it time to refresh
    ok;

test_page("Processes" ++ _, _Window) ->
    timer:sleep(500),  %% Give it time to refresh
    Active = get_active(),
    Active ! refresh_interval,
    ChangeSort = fun(N) ->
			 FakeEv = #wx{event=#wxList{type=command_list_col_click, col=N}},
			 Active ! FakeEv,
			 timer:sleep(200)
		 end,
    [ChangeSort(N) || N <- lists:seq(1,5) ++ [0]],
    Focus = #wx{event=#wxList{type=command_list_item_focused, itemIndex=2}},
    Active ! Focus,
    Activate = #wx{event=#wxList{type=command_list_item_activated}},
    Active ! Activate,
    timer:sleep(1000),  %% Give it time to refresh
    ok;

test_page("Ports" ++ _, _Window) ->
    timer:sleep(500),  %% Give it time to refresh
    Active = get_active(),
    Active ! refresh_interval,
    ChangeSort = fun(N) ->
			 FakeEv = #wx{event=#wxList{type=command_list_col_click, col=N}},
			 Active ! FakeEv,
			 timer:sleep(200)
		 end,
    [ChangeSort(N) || N <- lists:seq(1,4) ++ [0]],
    Activate = #wx{event=#wxList{type=command_list_item_activated,
				 itemIndex=2}},
    Active ! Activate,
    timer:sleep(1000),  %% Give it time to refresh
    ok;

test_page("Table" ++ _, _Window) ->
    Tables = [ets:new(list_to_atom("Test-" ++ [C]), [public]) || C <- lists:seq($A, $Z)],
    Table = lists:nth(3, Tables),
    ets:insert(Table, [{N,100-N} || N <- lists:seq(1,100)]),

    Active = get_active(),
    Active ! refresh_interval,
    ChangeSort = fun(N) ->
			 FakeEv = #wx{event=#wxList{type=command_list_col_click, col=N}},
			 Active ! FakeEv,
			 timer:sleep(200)
		 end,
    [ChangeSort(N) || N <- lists:seq(1,5) ++ [0]],
    timer:sleep(1000),
    Focus = #wx{event=#wxList{type=command_list_item_selected, itemIndex=2}},
    Active ! Focus,
    Activate = #wx{event=#wxList{type=command_list_item_activated, itemIndex=2}},
    Active ! Activate,

    Info = 407, %% whitebox...
    Active ! #wx{id=Info},
    timer:sleep(1000),
    ok;

test_page("Trace Overview" ++ _, _Window) ->
    timer:sleep(500),  %% Give it time to refresh
    Active = get_active(),
    Active ! refresh_interval,
    timer:sleep(1000),  %% Give it time to refresh
    ok;

test_page(Title, Window) ->
    io:format("Page ~p: ~p~n", [Title, Window]),
    %% Just let it display some info and hopefully it doesn't crash
    timer:sleep(1000),
    ok.


process_win(suite) -> [];
process_win(doc) -> [""];
process_win(Config) when is_list(Config) ->
    % Stop SASL if already started
    SaslStart = case whereis(sasl_sup) of
                  undefined -> false;
                  _         -> application:stop(sasl),
                               true
                end,
    % Define custom sasl and log_mf_h app vars
    Privdir=?config(priv_dir,Config),
    application:set_env(sasl, sasl_error_logger, tty),
    application:set_env(sasl, error_logger_mf_dir, Privdir),
    application:set_env(sasl, error_logger_mf_maxbytes, 1000),
    application:set_env(sasl, error_logger_mf_maxfiles, 5),
    application:start(sasl),
    ok = observer:start(),
    ObserverNB = setup_whitebox_testing(),
    Parent = get_top_level_parent(ObserverNB),
    % Activate log view
    whereis(observer) ! #wx{id = ?ID_LOGVIEW, event = #wxCommand{type = command_menu_selected}},
    timer:sleep(1000),
    % Process window tests (use sasl_sup for a non empty Log tab)
    Frame = observer_procinfo:start(whereis(sasl_sup), Parent, self()),
    PIPid = wx_object:get_pid(Frame),
    PIPid ! {get_debug_info, self()},
    Notebook = receive {procinfo_debug, NB} -> NB end,
    Count = wxNotebook:getPageCount(Notebook),
    Check = fun(_N) ->
		    ok = wxNotebook:advanceSelection(Notebook),
		    timer:sleep(400)
	    end,
    [_|_] = [Check(N) || N <- lists:seq(1, Count)],
    PIPid ! #wx{event=#wxClose{type=close_window}},
    observer:stop(),
    application:stop(sasl),
    case SaslStart of
         true  -> application:start(sasl);
         false -> ok
    end,
    ok.

table_win(suite) -> [];
table_win(doc) -> [""];
table_win(Config) when is_list(Config) ->
    Tables = [ets:new(list_to_atom("Test-" ++ [C]), [public]) || C <- lists:seq($A, $Z)],
    Table = lists:nth(3, Tables),
    ets:insert(Table, [{N,100-N} || N <- lists:seq(1,100)]),
    ok = observer:start(),
    Notebook = setup_whitebox_testing(),
    Parent = get_top_level_parent(Notebook),
    TObj = observer_tv_table:start_link(Parent, [{node,node()}, {type,ets}, {table,#tab{name=foo, id=Table}}]),
    %% Modal can not test edit..
    %% TPid = wx_object:get_pid(TObj),
    %% TPid ! #wx{event=#wxList{type=command_list_item_activated, itemIndex=12}},
    timer:sleep(3000),
    wx_object:get_pid(TObj) ! #wx{event=#wxClose{type=close_window}},
    observer:stop(),
    ok.

%% Test PR-1296/OTP-14151
%% Clicking a link to a port before the port tab has been activated the
%% first time crashes observer.
port_win_when_tab_not_initiated(Config) ->
    {ok,Port} = gen_tcp:listen(0,[]),
    ok = observer:start(),
    Notebook = setup_whitebox_testing(),
    observer ! {open_link,erlang:port_to_list(Port)},
    timer:sleep(1000),
    observer:stop(),
    ok.

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

get_top_level_parent(Window) ->
    Parent =  wxWindow:getParent(Window),
    case wx:is_null(Parent) of
	true -> Window;
	false -> get_top_level_parent(Parent)
    end.

setup_whitebox_testing() ->
    %% So that if we die observer exists
    link(whereis(observer)),
    {Env, Notebook, _Active} = get_observer_debug(),
    wx:set_env(Env),
    Notebook.

get_active() ->
    {_, _, Active} = get_observer_debug(),
    Active.

get_observer_debug() ->
    observer ! {get_debug_info, self()},
    receive
	{observer_debug, Env, Notebook, Active} ->
	    {Env, Notebook, Active}
    end.
