%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2024. 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(gen_event).
-moduledoc """
Generic event handling behavior.

This behavior module provides event handling functionality. It consists of a
generic event manager process with any number of event handlers that are added
and deleted dynamically.

An event manager implemented using this module has a standard set of interface
functions and includes functionality for tracing and error reporting. It also
fits into an OTP supervision tree. For more information, see
[OTP Design Principles](`e:system:events.md`).

Each event handler is implemented as a callback module exporting a predefined
set of functions. The relationship between the behavior functions and the
callback functions is as follows:

```text
gen_event module                   Callback module
----------------                   ---------------
gen_event:start
gen_event:start_monitor
gen_event:start_link       ----->  -

gen_event:add_handler
gen_event:add_sup_handler  ----->  Module:init/1

gen_event:notify
gen_event:sync_notify      ----->  Module:handle_event/2

gen_event:send_request
gen_event:call             ----->  Module:handle_call/2

-                          ----->  Module:handle_info/2

gen_event:delete_handler   ----->  Module:terminate/2

gen_event:swap_handler
gen_event:swap_sup_handler ----->  Module1:terminate/2
                                   Module2:init/1

gen_event:which_handlers   ----->  -

gen_event:stop             ----->  Module:terminate/2

-                          ----->  Module:code_change/3
```

As each event handler is one callback module, an event manager has many callback
modules that are added and deleted dynamically. `gen_event` is therefore more
tolerant of callback module errors than the other behaviors. If a callback
function for an installed event handler fails with `Reason`, or returns a bad
value `Term`, the event manager does not fail. It deletes the event handler by
calling callback function [`Module:terminate/2`](`c:terminate/2`), giving as
argument `{error,{'EXIT',Reason}}` or `{error,Term}`, respectively. No other
event handler is affected.

A `gen_event` process handles system messages as described in `m:sys`. The `sys`
module can be used for debugging an event manager.

Notice that an event manager _does_ trap exit signals automatically.

The `gen_event` process can go into hibernation (see `erlang:hibernate/3`) if a
callback function in a handler module specifies `hibernate` in its return value.
This can be useful if the server is expected to be idle for a long time.
However, use this feature with care, as hibernation implies at least two garbage
collections (when hibernating and shortly after waking up) and is not something
you want to do between each event handled by a busy event manager.

Notice that when multiple event handlers are invoked, it is sufficient that one
single event handler returns a `hibernate` request for the whole event manager
to go into hibernation.

Unless otherwise stated, all functions in this module fail if the specified
event manager does not exist or if bad arguments are specified.

> #### Note {: .info }
>
> For some important information about distributed signals, see the
> [_Blocking Signaling Over Distribution_](`e:system:ref_man_processes.md#blocking-signaling-over-distribution`)
> section in the _Processes_ chapter of the _Erlang Reference Manual_. Blocking
> signaling can, for example, cause call timeouts in `gen_event` to be
> significantly delayed.

## See Also

`m:supervisor`, `m:sys`
""".

%%% 
%%% A general event handler.
%%% Several handlers (functions) can be added.
%%% Each handler holds a state and will be called
%%% for every event received of the handler.
%%% 

%%% Modified by Magnus.
%%%       Take care of fault situations and made notify asynchronous.
%%% Re-written by Joe with new functional interface !
%%% Modified by Martin - uses proc_lib, sys and gen!

%%%
%%% NOTE: If init_ack() return values are modified, see comment
%%%       above monitor_return() in gen.erl!
%%%

-export([start/0, start/1, start/2,
         start_link/0, start_link/1, start_link/2,
         start_monitor/0, start_monitor/1, start_monitor/2,
         stop/1, stop/3,
	 notify/2, sync_notify/2,
	 add_handler/3, add_sup_handler/3, delete_handler/3, swap_handler/3,
	 swap_sup_handler/3, which_handlers/1, call/3, call/4,
         send_request/3, send_request/5,
         wait_response/2, receive_response/2, check_response/2,
         wait_response/3, receive_response/3, check_response/3,
         reqids_new/0, reqids_size/1,
         reqids_add/3, reqids_to_list/1,
         wake_hib/5]).

-export([init_it/6,
	 system_continue/3,
	 system_terminate/4,
	 system_code_change/4,
	 system_get_state/1,
	 system_replace_state/2,
	 format_status/2]).

-behaviour(sys).

%% logger callback
-export([format_log/1, format_log/2]).

-export_type([handler/0, handler_args/0, add_handler_ret/0,
              del_handler_ret/0, request_id/0, request_id_collection/0,
              format_status/0]).

-record(handler, {module             :: atom(),
		  id = false,
		  state,
		  supervised = false :: 'false' | pid()}).

-include("logger.hrl").

%%%=========================================================================
%%%  API
%%%=========================================================================

%% gen_event:start(Handler) -> {ok, Pid} | {error, What}
%%   gen_event:add_handler(Handler, Mod, Args) -> ok | Other
%%      gen_event:notify(Handler, Event) -> ok
%%      gen_event:call(Handler, Mod, Query) -> {ok, Val} | {error, Why}
%%      gen_event:call(Handler, Mod, Query, Timeout) -> {ok, Val} | {error, Why}
%%   gen_event:delete_handler(Handler, Mod, Args) -> Val
%%   gen_event:swap_handler(Handler, {OldMod, Args1}, {NewMod, Args2}) -> ok
%%   gen_event:which_handler(Handler) -> [Mod]
%% gen_event:stop(Handler) -> ok 

-doc """
Whenever a new event handler is added to an event manager, this function is
called to initialize the event handler.

If the event handler is added because of a call to `add_handler/3` or
`add_sup_handler/3`, `InitArgs` is the `Args` argument of these functions.

If the event handler replaces another event handler because of a call to
`swap_handler/3` or `swap_sup_handler/3`, or because of a `swap` return tuple
from one of the other callback functions, `InitArgs` is a tuple `{Args,Term}`,
where `Args` is the argument provided in the function call/return tuple and
`Term` is the result of terminating the old event handler, see `swap_handler/3`.

If successful, the function returns `{ok,State}` or `{ok,State,hibernate}`,
where `State` is the initial internal state of the event handler.

If `{ok,State,hibernate}` is returned, the event manager goes into hibernation
(by calling `proc_lib:hibernate/3`), waiting for the next event to occur.
""".
-callback init(InitArgs :: term()) ->
    {ok, State :: term()} |
    {ok, State :: term(), hibernate} |
    {error, Reason :: term()}.
-doc """
Whenever an event manager receives an event sent using `notify/2` or
`sync_notify/2`, this function is called for each installed event handler to
handle the event.

`Event` is the `Event` argument of
[`notify/2`](`notify/2`)/[`sync_notify/2`](`sync_notify/2`).

`State` is the internal state of the event handler.

- If `{ok,NewState}` or `{ok,NewState,hibernate}` is returned, the event handler
  remains in the event manager with the possible updated internal state
  `NewState`.
- If `{ok,NewState,hibernate}` is returned, the event manager also goes into
  hibernation (by calling `proc_lib:hibernate/3`), waiting for the next event to
  occur. It is sufficient that one of the event handlers return
  `{ok,NewState,hibernate}` for the whole event manager process to hibernate.
- If `{swap_handler,Args1,NewState,Handler2,Args2}` is returned, the event
  handler is replaced by `Handler2` by first calling
  [`Module:terminate(Args1,NewState)`](`c:terminate/2`) and then
  [`Module2:init({Args2,Term})`](`c:init/1`), where `Term` is the return value
  of [`Module:terminate/2`](`c:terminate/2`). For more information, see
  `swap_handler/3`.
- If `remove_handler` is returned, the event handler is deleted by calling
  [`Module:terminate(remove_handler,State)`](`c:terminate/2`).
""".
-callback handle_event(Event :: term(), State :: term()) ->
    {ok, NewState :: term()} |
    {ok, NewState :: term(), hibernate} |
    {swap_handler, Args1 :: term(), NewState :: term(),
     Handler2 :: (atom() | {atom(), Id :: term()}), Args2 :: term()} |
    remove_handler.
-doc """
Whenever an event manager receives a request sent using [`call/3,4`](`call/3`),
this function is called for the specified event handler to handle the request.

`Request` is the `Request` argument of `call/3,4`.

`State` is the internal state of the event handler.

The return values are the same as for
[`Module:handle_event/2`](`c:handle_event/2`) except that they also contain a
term `Reply`, which is the reply to the client as the return value of
`call/3,4`.
""".
-callback handle_call(Request :: term(), State :: term()) ->
    {ok, Reply :: term(), NewState :: term()} |
    {ok, Reply :: term(), NewState :: term(), hibernate} |
    {swap_handler, Reply :: term(), Args1 :: term(), NewState :: term(),
     Handler2 :: (atom() | {atom(), Id :: term()}), Args2 :: term()} |
    {remove_handler, Reply :: term()}.
-doc """
This function is called for each installed event handler when an event manager
receives any other message than an event or a synchronous request (or a system
message).

`Info` is the received message.

In particular, this callback will be made when a process terminated after
calling `add_sup_handler/3`. Any event handler attached to an event manager
which in turn has a supervised handler should expect callbacks of the shape
[`Module:handle_info({'EXIT', Pid, Reason}, State)`](`c:handle_info/2`).

For a description of `State` and possible return values, see
[`Module:handle_event/2`](`c:handle_event/2`).

> #### Note {: .info }
>
> This callback is optional, so callback modules need not export it. The
> `gen_event` module provides a default implementation of this function that
> logs about the unexpected `Info` message, drops it and returns `{ok, State}`.
""".
-callback handle_info(Info :: term(), State :: term()) ->
    {ok, NewState :: term()} |
    {ok, NewState :: term(), hibernate} |
    {swap_handler, Args1 :: term(), NewState :: term(),
     Handler2 :: (atom() | {atom(), Id :: term()}), Args2 :: term()} |
    remove_handler.
-doc """
Whenever an event handler is deleted from an event manager, this function is
called. It is to be the opposite of [`Module:init/1`](`c:init/1`) and do any
necessary cleaning up.

If the event handler is deleted because of a call to `delete_handler/3`,
`swap_handler/3`, or `swap_sup_handler/3`, `Arg` is the `Args` argument of this
function call.

`Arg={stop,Reason}` if the event handler has a supervised connection to a
process that has terminated with reason `Reason`.

`Arg=stop` if the event handler is deleted because the event manager is
terminating.

The event manager terminates if it is part of a supervision tree and it is
ordered by its supervisor to terminate. Even if it is _not_ part of a
supervision tree, it terminates if it receives an `'EXIT'` message from its
parent.

`Arg=remove_handler` if the event handler is deleted because another callback
function has returned `remove_handler` or `{remove_handler,Reply}`.

`Arg={error,Term}` if the event handler is deleted because a callback function
returned an unexpected value `Term`, or `Arg={error,{'EXIT',Reason}}` if a
callback function failed.

`State` is the internal state of the event handler.

The function can return any term. If the event handler is deleted because of a
call to `gen_event:delete_handler/3`, the return value of that function becomes
the return value of this function. If the event handler is to be replaced with
another event handler because of a swap, the return value is passed to the
`init` function of the new event handler. Otherwise the return value is ignored.

> #### Note {: .info }
>
> This callback is optional, so callback modules need not export it. The
> `gen_event` module provides a default implementation without cleanup.
""".
-callback terminate(Args :: (term() | {stop, Reason :: term()} |
                             stop | remove_handler |
                             {error, {'EXIT', Reason :: term()}} |
                             {error, term()}),
                    State :: term()) ->
    term().
-doc """
This function is called for an installed event handler that is to update its
internal state during a release upgrade/downgrade, that is, when the instruction
`{update,Module,Change,...}`, is specified in the [`appup`](`e:sasl:appup.md`) file.

For more information, see [OTP Design Principles](`e:system:index.html`).

For an upgrade, `OldVsn` is `Vsn`, and for a downgrade, `OldVsn` is
`{down,Vsn}`. `Vsn` is defined by the `vsn` attribute(s) of the old version of
the callback module `Module`. If no such attribute is defined, the version is
the checksum of the Beam file.

`State` is the internal state of the event handler.

`Extra` is passed "as is" from the `{advanced,Extra}` part of the update
instruction.

The function is to return the updated internal state.

> #### Note {: .info }
>
> If a release upgrade/downgrade with `Change={advanced,Extra}` specified in the
> [`.appup`](`e:sasl:appup.md`) file is made when `c:code_change/3` isn't
> implemented the event handler will crash with an `undef` error reason.
""".
-callback code_change(OldVsn :: (term() | {down, term()}),
                      State :: term(), Extra :: term()) ->
    {ok, NewState :: term()}.
-doc """
This function is called by a `gen_event` process in in order to format/limit the
server state for debugging and logging purposes.

It is called in the following situations:

- One of [`sys:get_status/1,2`](`sys:get_status/1`) is invoked to get the
  `gen_event` status. `Opt` is set to the atom `normal` for this case.
- The event handler terminates abnormally and `gen_event` logs an error. `Opt`
  is set to the atom `terminate` for this case.

This function is useful for changing the form and appearance of the event
handler state for these cases. An event handler callback module wishing to
change the `sys:get_status/1,2` return value as well as how its state appears in
termination error logs, exports an instance of
[`format_status/2`](`c:format_status/2`) that returns a term describing the
current state of the event handler.

`PDict` is the current value of the process dictionary of `gen_event`.

`State` is the internal state of the event handler.

The function is to return `Status`, a term that change the details of the
current state of the event handler. Any term is allowed for `Status`. The
`gen_event` module uses `Status` as follows:

- When `sys:get_status/1,2` is called, `gen_event` ensures that its return value
  contains `Status` in place of the state term of the event handler.
- When an event handler terminates abnormally, `gen_event` logs `Status` in
  place of the state term of the event handler.

One use for this function is to return compact alternative state representations
to avoid that large state terms are printed in log files.

> #### Note {: .info }
>
> This callback is optional, so event handler modules need not export it. If a
> handler does not export this function, the `gen_event` module uses the handler
> state directly for the purposes described below.
""".
-deprecated_callback({format_status, 2, "use format_status/1 instead"}).
-doc(#{since => <<"OTP R14B">>}).
-callback format_status(Opt, StatusData) -> Status when
      Opt :: 'normal' | 'terminate',
      StatusData :: [PDict | State],
      PDict :: [{Key :: term(), Value :: term()}],
      State :: term(),
      Status :: term().
-doc """
A map that describes the `gen_event` process status.

The keys are:

- **`state`** - The internal state of the event handler.

- **`message`** - The message that caused the event handler to terminate.

- **`reason`** - The reason that caused the event handler to terminate.

- **`log`** - The [sys log](`sys:log/2`) of the server.

New associations may be added into the status map without prior notice.
""".
-type format_status() ::
        #{ state => term(),
           message => term(),
           reason => term(),
           log => [sys:system_event()] }.
-doc """
This function is called by a `gen_event` process in in order to format/limit the
server state for debugging and logging purposes.

It is called in the following situations:

- One of [`sys:get_status/1,2`](`sys:get_status/1`) is invoked to get the
  `gen_event` status.
- The event handler terminates abnormally and `gen_event` logs an error.

This callback is used to limit the status of the event handler returned by
[`sys:get_status/1,2`](`sys:get_status/1`) or sent to `m:logger`.

The callback gets a map `Status` describing the current status and shall return
a map `NewStatus` with the same keys, but it may transform some values.

Two possible use cases for this callback is to remove sensitive information from
the state to prevent it from being printed in log files, or to compact large
irrelevant status items that would only clutter the logs.

_Example_:

```erlang
format_status(Status) ->
  maps:map(
    fun(state,State) ->
            maps:remove(private_key, State);
       (message,{password, _Pass}) ->
            {password, removed};
       (_,Value) ->
            Value
    end, Status).
```

> #### Note {: .info }
>
> This callback is optional, so event handler modules need not export it. If a
> handler does not export this function, the `gen_event` module uses the handler
> state directly for the purposes described below.
>
> If this callback is exported but fails, to hide possibly sensitive data, the
> default function will instead return the fact that
> [`format_status/1`](`c:format_status/1`) has crashed.
""".
-doc(#{since => <<"OTP 25.0">>}).
-callback format_status(Status) -> NewStatus when
      Status    :: format_status(),
      NewStatus :: format_status().

-optional_callbacks(
    [handle_info/2, terminate/2, code_change/3, format_status/1, format_status/2]).

%%---------------------------------------------------------------------------

-type handler()          :: atom() | {atom(), term()}.
-type handler_args()     :: term().
-type add_handler_ret()  :: ok | term() | {'EXIT',term()}.
-type del_handler_ret()  :: ok | term() | {'EXIT',term()}.

-doc """
The name given to an event manager when starting it.

- *`{local,Name}`* - the event manager is registered locally as
  `Name` using [`register/2`](`register/2`).
- *`{global,GlobalName}`* - The event manager is registered
  globally as `GlobalName` using `global:register_name/2`. If no name is
  provided, the event manager is not registered.
- *`{via,Module,ViaName}`*, the event manager registers with the
  registry represented by `Module`. The `Module` callback is to export the
  functions `register_name/2`, `unregister_name/1`, `whereis_name/1`, and
  `send/2`, which are to behave as the corresponding functions in `m:global`.
  Thus, `{via,global,GlobalName}` is a valid reference.
""".
-type emgr_name() :: {'local', atom()} | {'global', term()}
                   | {'via', atom(), term()}.
-type debug_flag() :: 'trace' | 'log' | 'statistics' | 'debug'
                    | {'logfile', string()}.
-doc "Options that can be used to configure an event handler when it is started.".
-type options() :: [{'timeout', timeout()}
                   | {'debug', [debug_flag()]}
                   | {'spawn_opt', [proc_lib:start_spawn_option()]}
                   | {'hibernate_after', timeout()}].
-doc """
A reference used to locate an event manager.

The reference can be any of the following:

- The pid of the event manager
- `Name`, if the event manager is locally registered
- `{Name,Node}`, if the event manager is locally registered at another node
- `{global,GlobalName}`, if the event manager is globally registered
- `{via,Module,ViaName}`, if the event manager is registered through an
  alternative process registry
""".
-type emgr_ref()  :: atom() | {atom(), node()} |  {'global', term()}
                   | {'via', atom(), term()} | pid().
-type start_ret() :: {'ok', pid()} | {'error', term()}.
-type start_mon_ret() :: {'ok', {pid(),reference()}} | {'error', term()}.

-doc "An opaque request identifier. See `send_request/3` for details.".
-opaque request_id() :: gen:request_id().

-doc """
An opaque collection of request identifiers (`t:request_id/0`) where each
request identifier can be associated with a label chosen by the user. For more
information see `reqids_new/0`.
""".
-opaque request_id_collection() :: gen:request_id_collection().

-doc """
Used to set a time limit on how long to wait for a response using either
`receive_response/2`, `receive_response/3`, `wait_response/2`, or
`wait_response/3`. The time unit used is `millisecond`. Currently valid values:

- **`0..4294967295`** - Timeout relative to current time in milliseconds.

- **`infinity`** - Infinite timeout. That is, the operation will never time out.

- **`{abs, Timeout}`** - An absolute
  [Erlang monotonic time](`erlang:monotonic_time/1`) timeout in milliseconds.
  That is, the operation will time out when
  [`erlang:monotonic_time(millisecond)`](`erlang:monotonic_time/1`) returns a
  value larger than or equal to `Timeout`. `Timeout` is not allowed to identify
  a time further into the future than `4294967295` milliseconds. Identifying the
  timeout using an absolute timeout value is especially handy when you have a
  deadline for responses corresponding to a complete collection of requests
  (`t:request_id_collection/0`) , since you do not have to recalculate the
  relative time until the deadline over and over again.
""".
-type response_timeout() ::
        timeout() | {abs, integer()}.

%%---------------------------------------------------------------------------

-define(NO_CALLBACK, 'no callback module').

%% -----------------------------------------------------------------
%% Starts a generic event handler.
%% start()
%% start(MgrName | Options)
%% start(MgrName, Options)
%% start_link()
%% start_link(MgrName | Options)
%% start_link(MgrName, Options)
%%    MgrName ::= {local, atom()} | {global, term()} | {via, atom(), term()}
%%    Options ::= [{timeout, Timeout} | {debug, [Flag]} | {spawn_opt,SOpts}]
%%       Flag ::= trace | log | {logfile, File} | statistics | debug
%%          (debug == log && statistics)
%% Returns: {ok, Pid} |
%%          {error, {already_started, Pid}} |
%%          {error, Reason}
%% -----------------------------------------------------------------

-doc(#{equiv => start([])}).
-spec start() -> start_ret().
start() ->
    gen:start(?MODULE, nolink, ?NO_CALLBACK, [], []).

-doc """
Creates a stand-alone event manager process, that is, an event manager that is
not part of a supervision tree and thus has no supervisor.

For a description of the arguments and return values, see
[`start_link/1`](`start_link/1`).
""".
-spec start(EventMgrNameOrOptions :: emgr_name() | options()) -> start_ret().
start(Name) when is_tuple(Name) ->
    gen:start(?MODULE, nolink, Name, ?NO_CALLBACK, [], []);
start(Options) when is_list(Options) ->
    gen:start(?MODULE, nolink, ?NO_CALLBACK, [], Options);
start(Arg) ->
    error(badarg, [Arg]).

-doc """
Creates a stand-alone event manager process, that is, an event manager that is
not part of a supervision tree and thus has no supervisor.

For a description of the arguments and return values, see
[`start_link/2`](`start_link/2`).
""".
-doc(#{since => <<"OTP 20.0">>}).
-spec start(EventMgrName :: emgr_name(), Options :: options()) -> start_ret().
start(Name, Options) when is_tuple(Name), is_list(Options) ->
    gen:start(?MODULE, nolink, Name, ?NO_CALLBACK, [], Options);
start(Name, Options) ->
    error(badarg, [Name, Options]).

-doc(#{equiv => start_link([])}).
-spec start_link() -> start_ret().
start_link() ->
    gen:start(?MODULE, link, ?NO_CALLBACK, [], []).

-doc """
Creates an event manager process as part of a supervision tree.

If called with `t:emgr_name/0`, then it is equivalent to [`start(EventMgrName, [])`](`start/2`).

If called with `t:options/0`, then a nameless event manager is created using `Options`.

For a description of the arguments and return values, see
[`start_link/2`](`start_link/2`).
""".
-spec start_link(EventMgrNameOrOptions :: emgr_name() | options()) -> start_ret().
start_link(Name) when is_tuple(Name) ->
    gen:start(?MODULE, link, Name, ?NO_CALLBACK, [], []);
start_link(Options) when is_list(Options) ->
    gen:start(?MODULE, link, ?NO_CALLBACK, [], Options);
start_link(Arg) ->
    error(badarg, [Arg]).

-doc """
Creates an event manager process as part of a supervision tree.

The function is to be called, directly or indirectly, by the supervisor.
For example, it ensures that the event manager is linked to the caller (supervisor).

- If option `{hibernate_after,HibernateAfterTimeout}` is present, the
  `gen_event` process awaits any message for `HibernateAfterTimeout`
  milliseconds and if no message is received, the process goes into hibernation
  automatically (by calling `proc_lib:hibernate/3`).

If the event manager is successfully created, the function returns `{ok,Pid}`,
where `Pid` is the pid of the event manager.

If a process with the specified `EventMgrName` exists already, the function
returns `{error,{already_started,OtherPid}}`, where `OtherPid` is the pid of
that process, and the event manager process exits with reason `normal`.

If the event manager fails to start within the specified start timeout
`{timeout,Time}`, which is very unlikely since the start does not interact with
other processes, the function returns `{error,timeout}` and the failed event
manager is killed with [`exit(_, kill)`](`erlang:exit/2`).

If `start_link/1,2` returns `{error,_}`, the started event manager process has
terminated. If an `'EXIT'` message was delivered to the calling process (due to
the process link), that message has been consumed.

> #### Warning {: .warning }
>
> Before OTP 26.0, if the started event manager failed to register its name,
> this founction could return `{error,{already_started,OtherPid}}` _before_ the
> started event manager process had terminated so starting again might fail
> because the registered name was not yet unregistered, and an `'EXIT'` message
> could arrive later to the process calling this function.
>
> But if the start timed out, this function killed the started event manager
> process and returned `{error,timeout}`, and then the process link
> `{'EXIT',Pid,killed}` message _was_ consumed.
>
> The start was made synchronous in OTP 26.0 and the guarantee was implemented
> that no process link `'EXIT'` message from a failed start will linger in the
> caller's inbox.
""".
-doc(#{since => <<"OTP 20.0">>}).
-spec start_link(EventMgrName :: emgr_name(), Options :: options()) -> start_ret().
start_link(Name, Options) when is_tuple(Name), is_list(Options) ->
    gen:start(?MODULE, link, Name, ?NO_CALLBACK, [], Options);
start_link(Name, Options) ->
    error(badarg, [Name, Options]).

-doc(#{equiv => start_monitor([])}).
-doc(#{since => <<"OTP 23.0">>}).
-spec start_monitor() -> start_mon_ret().
start_monitor() ->
    gen:start(?MODULE, monitor, ?NO_CALLBACK, [], []).

-doc """
Creates a stand-alone event manager process, that is, an event manager that is
not part of a supervision tree (and thus has no supervisor) and atomically sets
up a monitor to the newly created process.

For a description of the arguments and return values, see `start_monitor/2` and
`start_link/1`.
""".
-doc(#{since => <<"OTP 23.0">>}).
-spec start_monitor(EventMgrNameOrOptions :: emgr_name() | options()) -> start_mon_ret().
start_monitor(Name) when is_tuple(Name) ->
    gen:start(?MODULE, monitor, Name, ?NO_CALLBACK, [], []);
start_monitor(Options) when is_list(Options) ->
    gen:start(?MODULE, monitor, ?NO_CALLBACK, [], Options);
start_monitor(Arg) ->
    error(badarg, [Arg]).

-doc """
Creates a stand-alone event manager process, that is, an event manager that is
not part of a supervision tree (and thus has no supervisor) and atomically sets
up a monitor to the newly created process.

For a description of the arguments and return values, see
[`start_link/2`](`start_link/2`). Note that the return value on successful
start differs from `start_link/2`. `start_monitor/0,1,2` will return
`{ok,{Pid,Mon}}` where `Pid` is the process identifier of the process, and `Mon`
is a reference to the monitor set up to monitor the process. If the start is not
successful, the caller will be blocked until the `DOWN` message has been
received and removed from the message queue.
""".
-doc(#{since => <<"OTP 23.0">>}).
-spec start_monitor(EventMgtName :: emgr_name(), Options :: options()) -> start_mon_ret().
start_monitor(Name, Options) when is_tuple(Name), is_list(Options) ->
    gen:start(?MODULE, monitor, Name, ?NO_CALLBACK, [], Options);
start_monitor(Name, Options) ->
    error(badarg, [Name, Options]).

%% -spec init_it(pid(), 'self' | pid(), emgr_name(), module(), [term()], [_]) -> 
-doc false.
init_it(Starter, self, Name, Mod, Args, Options) ->
    init_it(Starter, self(), Name, Mod, Args, Options);
init_it(Starter, Parent, Name0, _, _, Options) ->
    process_flag(trap_exit, true),
    Name = gen:name(Name0),
    Debug = gen:debug_options(Name, Options),
	HibernateAfterTimeout = gen:hibernate_after(Options),
    proc_lib:init_ack(Starter, {ok, self()}),
    loop(Parent, Name, [], HibernateAfterTimeout, Debug, false).

-doc """
Adds a new event handler to event manager `EventMgrRef`. The event manager calls
[`Module:init/1`](`c:init/1`) to initiate the event handler and its internal
state.

`Handler` is the name of the callback module `Module` or a tuple `{Module,Id}`,
where `Id` is any term. The `{Module,Id}` representation makes it possible to
identify a specific event handler when many event handlers use the same callback
module.

`Args` is any term that is passed as the argument to
[`Module:init/1`](`c:init/1`).

If [`Module:init/1`](`c:init/1`) returns a correct value indicating successful
completion, the event manager adds the event handler and this function returns
`ok`. If [`Module:init/1`](`c:init/1`) fails with `Reason` or returns
`{error,Reason}`, the event handler is ignored and this function returns
`{'EXIT',Reason}` or `{error,Reason}`, respectively.
""".
-spec add_handler(EventMgrRef :: emgr_ref(), Handler :: handler(), Args :: term()) -> term().
add_handler(M, Handler, Args) -> rpc(M, {add_handler, Handler, Args}).

-doc """
Adds a new event handler in the same way as `add_handler/3`, but also supervises
the connection by linking the event handler and the calling process.

- If the calling process later terminates with `Reason`, the event manager
  deletes any supervised event handlers by calling
  [`Module:terminate/2`](`c:terminate/2`), then calls
  [`Module:handle_info/2`](`c:handle_info/2`) for each remaining handler.
- If the event handler is deleted later, the event manager sends a message
  `{gen_event_EXIT,Handler,Reason}` to the calling process. `Reason` is one of
  the following:

  - `normal`, if the event handler has been removed because of a call to
    [`delete_handler/3`](`delete_handler/3`), or `remove_handler` has been
    returned by a callback function (see below).
  - `shutdown`, if the event handler has been removed because the event manager
    is terminating.
  - `{swapped,NewHandler,Pid}`, if the process `Pid` has replaced the event
    handler with another event handler `NewHandler` using a call to
    `swap_handler/3` or `swap_sup_handler/3`.
  - A term, if the event handler is removed because of an error. Which term
    depends on the error.

For a description of the arguments and return values, see `add_handler/3`.
""".
-spec add_sup_handler(EventMgrRef :: emgr_ref(), Handler :: handler(), Args :: term()) -> term().
add_sup_handler(M, Handler, Args)  ->
    rpc(M, {add_sup_handler, Handler, Args, self()}).

-doc """
Sends an asynchronous event notification to event manager `EventMgrRef`. The event
manager calls [`Module:handle_event/2`](`c:handle_event/2`) for each installed
event handler to handle the event.

`Event` is any term that is passed as one of the arguments to
[`Module:handle_event/2`](`c:handle_event/2`).

`notify/1` does not fail even if the specified event manager does not exist,
unless it is specified as `Name`.
""".

-spec notify(EventMgrRef :: emgr_ref(), Event :: term()) -> 'ok'.
notify(M, Event) -> send(M, {notify, Event}).

-doc """
Sends a synchronous event notification to event manager `EventMgrRef`. The event
manager calls [`Module:handle_event/2`](`c:handle_event/2`) for each installed event
handler to handle the event. This function will return `ok` after the event has
been handled by all event handlers.

`Event` is any term that is passed as one of the arguments to
[`Module:handle_event/2`](`c:handle_event/2`).
""".
-spec sync_notify(EventMgrRef :: emgr_ref(), Event :: term()) -> 'ok'.
sync_notify(M, Event) -> rpc(M, {sync_notify, Event}).

-doc(#{equiv => call(EventMgrRef, Handler, Request, 5000)}).
-spec call(EventMgrRef :: emgr_ref(), Handler :: handler(), Request :: term()) -> term().
call(M, Handler, Query) -> call1(M, Handler, Query).

-doc """
Makes a synchronous call to event handler `Handler` installed in event manager
`EventMgrRef` by sending a request and waiting until a reply arrives or a
time-out occurs. The event manager calls
[`Module:handle_call/2`](`c:handle_call/2`) to handle the request.

`Request` is any term that is passed as one of the arguments to
[`Module:handle_call/2`](`c:handle_call/2`).

`Timeout` is an integer greater than zero that specifies how many milliseconds
to wait for a reply, or the atom `infinity` to wait indefinitely. Defaults
to 5000. If no reply is received within the specified time, the function call
fails.

The return value `Reply` is defined in the return value of
[`Module:handle_call/2`](`c:handle_call/2`). If the specified event handler is
not installed, the function returns `{error,bad_module}`. If the callback
function fails with `Reason` or returns an unexpected value `Term`, this
function returns `{error,{'EXIT',Reason}}` or `{error,Term}`, respectively.

When this call fails it [exits](`erlang:exit/1`) the calling process. The exit
term is on the form `{Reason, Location}` where
`Location = {gen_event,call,ArgList}`. See
[`gen_server:call/3` ](`gen_server:call/3`)that has a description of relevant
values for the `Reason` in the exit term.
""".
-spec call(EventMgrRef :: emgr_ref(), Handler :: handler(), Request :: term(), Timeout :: timeout()) -> term().
call(M, Handler, Query, Timeout) -> call1(M, Handler, Query, Timeout).

-doc """
Sends an asynchronous `call` request `Request` to event handler `Handler`
installed in the event manager identified by `EventMgrRef` and returns a request
identifier `ReqId`. The return value `ReqId` shall later be used with
`receive_response/2`, `wait_response/2`, or `check_response/2` to fetch the
actual result of the request.

Besides passing the request identifier directly to these functions, it can also
be saved in a request identifier collection using `reqids_add/3`. Such a
collection of request identifiers can later be used in
order to get one response corresponding to a request in the collection by
passing the collection as argument to `receive_response/3`, `wait_response/3`,
or `check_response/3`. If you are about to save the request identifier in a
request identifier collection, you may want to consider using `send_request/5`
instead.

The call
`gen_event:receive_response(gen_event:send_request(EventMgrRef, Handler, Request), Timeout)`
can be seen as equivalent to
[`gen_event:call(EventMgrRef, Handler, Request, Timeout)`](`call/3`), ignoring
the error handling.

The event manager calls [`Module:handle_call/2`](`c:handle_call/2`) to handle
the request.

`Request` is any term that is passed as one of the arguments to
[`Module:handle_call/2`](`c:handle_call/2`).
""".
-doc(#{since => <<"OTP 23.0">>}).
-spec send_request(EventMgrRef::emgr_ref(), Handler::handler(), Request::term()) ->
          ReqId::request_id().
send_request(M, Handler, Request) ->
    try
        gen:send_request(M, self(), {call, Handler, Request})
    catch
        error:badarg ->
            error(badarg, [M, Handler, Request])
    end.

-doc """
Sends an asynchronous `call` request `Request` to event handler `Handler`
installed in the event manager identified by `EventMgrRef`. The `Label` will be
associated with the request identifier of the operation and added to the returned
request identifier collection `NewReqIdCollection`.

The collection can later be used in order to get one response corresponding to a
request in the collection by passing the collection as argument to `receive_response/3`,
`wait_response/3`, or `check_response/3`.

The same as calling
[`gen_event:reqids_add`](`reqids_add/3`)`(`[`gen_event:send_request`](`send_request/3`)`(EventMgrRef, Handler, Request), Label, ReqIdCollection)`,
but calling [`send_request/5`](`send_request/5`) is slightly more efficient.
""".
-doc(#{since => <<"OTP 25.0">>}).
-spec send_request(EventMgrRef::emgr_ref(),
                   Handler::handler(),
                   Request::term(),
                   Label::term(),
                   ReqIdCollection::request_id_collection()) ->
          NewReqIdCollection::request_id_collection().
send_request(M, Handler, Request, Label, ReqIdCol) ->
    try
        gen:send_request(M, self(), {call, Handler, Request}, Label, ReqIdCol)
    catch
        error:badarg ->
            error(badarg, [M, Handler, Request, Label, ReqIdCol])
    end.

-doc """
Wait for a response corresponding to the request identifier `ReqId`. The request
must have been made by `send_request/3` to the `gen_statem` process. This
function must be called from the same process from which `send_request/3` was
made.

`WaitTime` specifies how long to wait for a response. If no response is received
within the specified time, the function returns `timeout` and no cleanup is
done, and thus the function can be invoked repeatedly until a reply is returned.

The return value `Reply` is defined in the return value of
[`Module:handle_call/2`](`c:handle_call/2`).

If the specified event handler is not installed, the function returns
`{error,bad_module}`. If the callback function fails with `Reason` or returns an
unexpected value `Term`, this function returns `{error,{'EXIT',Reason}}` or
`{error,Term}`, respectively. If the event manager dies before or during the
request this function returns `{error,{Reason, EventMgrRef}}`.

The difference between `receive_response/2` and
[`wait_response/2`](`wait_response/2`) is that
[`receive_response/2`](`receive_response/2`) abandons the request at timeout so
that a potential future response is ignored, while
[`wait_response/2`](`wait_response/2`) does not.
""".
-doc(#{since => <<"OTP 23.0">>}).
-spec wait_response(ReqId, WaitTime) -> Result when
      ReqId :: request_id(),
      WaitTime :: response_timeout(),
      Response :: {reply, Reply::term()}
                | {error, {Reason::term(), emgr_ref()}},
      Result :: Response | 'timeout'.

wait_response(ReqId, WaitTime) ->
    try gen:wait_response(ReqId, WaitTime) of
        {reply, {error, _} = Err} -> Err;
        Return -> Return
    catch
        error:badarg ->
            error(badarg, [ReqId, WaitTime])
    end.

-doc """
Wait for a response corresponding to a request identifier saved in
`ReqIdCollection`. All request identifiers of `ReqIdCollection` must correspond
to requests that have been made using `send_request/3` or `send_request/5`, and
all requests must have been made by the process calling this function.

The `Label` in the response equals the `Label` associated with the request
identifier that the response corresponds to. The `Label` of a request identifier
is associated when [saving the request id](`reqids_add/3`) in a request
identifier collection, or when sending the request using `send_request/5`.

Compared to `wait_response/2`, the returned result associated with a specific
request identifier or an exception associated with a specific request identifier
will be wrapped in a 3-tuple. The first element of this tuple equals the value
that would have been produced by [`wait_response/2`](`wait_response/2`), the
second element equals the `Label` associated with the specific request
identifier, and the third element `NewReqIdCollection` is a possibly modified
request identifier collection.

If `ReqIdCollection` is empty, `no_request` will be returned. If no response is
received before the `WaitTime` timeout has triggered, the atom `timeout` is
returned. It is valid to continue waiting for a response as many times as needed
up until a response has been received and completed by `check_response()`,
`receive_response()`, or `wait_response()`.

The difference between `receive_response/3` and
[`wait_response/3`](`wait_response/3`) is that
[`receive_response/3`](`receive_response/3`) abandons requests at timeout so
that potential future responses are ignored, while
[`wait_response/3`](`wait_response/3`) does not.

If `Delete` equals `true`, the association with `Label` will have been deleted
from `ReqIdCollection` in the resulting `NewReqIdCollection`. If `Delete` equals
`false`, `NewReqIdCollection` will equal `ReqIdCollection`. Note that deleting
an association is not for free and that a collection containing already handled
requests can still be used by subsequent calls to
[`wait_response/3`](`wait_response/3`), `check_response/3`, and
`receive_response/3`. However, without deleting handled associations, the above
calls will not be able to detect when there are no more outstanding requests to
handle, so you will have to keep track of this some other way than relying on a
`no_request` return. Note that if you pass a collection only containing
associations of already handled or abandoned requests to
[`wait_response/3`](`wait_response/3`), it will always block until a timeout
determined by `WaitTime` is triggered and then return `no_reply`.
""".
-doc(#{since => <<"OTP 25.0">>}).
-spec wait_response(ReqIdCollection, WaitTime, Delete) -> Result when
      ReqIdCollection :: request_id_collection(),
      WaitTime :: response_timeout(),
      Delete :: boolean(),
      Response :: {reply, Reply::term()} |
                  {error, {Reason::term(), emgr_ref()}},
      Result :: {Response,
                 Label::term(),
                 NewReqIdCollection::request_id_collection()} |
                'no_request' |
                'timeout'.

wait_response(ReqIdCol, WaitTime, Delete) ->
    try gen:wait_response(ReqIdCol, WaitTime, Delete) of
        {{reply, {error, _} = Err}, Label, NewReqIdCol} ->
            {Err, Label, NewReqIdCol};
        Return ->
            Return
    catch
        error:badarg ->
            error(badarg, [ReqIdCol, WaitTime, Delete])
    end.

-doc """
Receive a response corresponding to the request identifier `ReqId`. The request
must have been made by `send_request/3` to the `gen_statem` process. This
function must be called from the same process from which `send_request/3` was
made.

`Timeout` specifies how long to wait for a response. If no response is received
within the specified time, the function returns `timeout`. Assuming that the
server executes on a node supporting aliases (introduced in OTP 24) the request
will also be abandoned. That is, no response will be received after a timeout.
Otherwise, a stray response might be received at a later time.

The return value `Reply` is defined in the return value of
[`Module:handle_call/2`](`c:handle_call/2`).

If the specified event handler is not installed, the function returns
`{error,bad_module}`. If the callback function fails with `Reason` or returns an
unexpected value `Term`, this function returns `{error,{'EXIT',Reason}}` or
`{error,Term}`, respectively. If the event manager dies before or during the
request this function returns `{error,{Reason, EventMgrRef}}`.

The difference between `wait_response/2` and
[`receive_response/2`](`receive_response/2`) is that
[`receive_response/2`](`receive_response/2`) abandons the request at timeout so
that a potential future response is ignored, while
[`wait_response/2`](`wait_response/2`) does not.
""".
-doc(#{since => <<"OTP 24.0">>}).
-spec receive_response(ReqId, Timeout) -> Result when
      ReqId :: request_id(),
      Timeout :: response_timeout(),
      Response :: {reply, Reply::term()} |
                  {error, {Reason::term(), emgr_ref()}},
      Result :: Response | 'timeout'.

receive_response(ReqId, Timeout) ->
    try gen:receive_response(ReqId, Timeout) of
        {reply, {error, _} = Err} -> Err;
        Return -> Return
    catch
        error:badarg ->
            error(badarg, [ReqId, Timeout])
    end.

-doc """
Receive a response corresponding to a request identifier saved in
`ReqIdCollection`. All request identifiers of `ReqIdCollection` must correspond
to requests that have been made using `send_request/3` or `send_request/5`, and
all requests must have been made by the process calling this function.

The `Label` in the response equals the `Label` associated with the request
identifier that the response corresponds to. The `Label` of a request identifier
is associated when [adding the request id](`reqids_add/3`) in a request
identifier collection, or when sending the request using `send_request/5`.

Compared to `receive_response/2`, the returned result associated with a specific
request identifier will be wrapped in a 3-tuple. The first element of this tuple
equals the value that would have been produced by
[`receive_response/2`](`receive_response/2`), the second element equals the
`Label` associated with the specific request identifier, and the third element
`NewReqIdCollection` is a possibly modified request identifier collection.

If `ReqIdCollection` is empty, the atom `no_request` will be returned.

`Timeout` specifies how long to wait for a response. If no response is received
within the specified time, the function returns `timeout`. Assuming that the
server executes on a node supporting aliases (introduced in OTP 24) all requests
identified by `ReqIdCollection` will also be abandoned. That is, no responses
will be received after a timeout. Otherwise, stray responses might be received
at a later time.

The difference between [`receive_response/3`](`receive_response/3`) and
`wait_response/3` is that [`receive_response/3`](`receive_response/3`) abandons
the requests at timeout so that potential future responses are ignored, while
[`wait_response/3`](`wait_response/3`) does not.

If `Delete` equals `true`, the association with `Label` will have been deleted
from `ReqIdCollection` in the resulting `NewReqIdCollection`. If `Delete` equals
`false`, `NewReqIdCollection` will equal `ReqIdCollection`. Note that deleting
an association is not for free and that a collection containing already handled
requests can still be used by subsequent calls to
[`receive_response/3`](`receive_response/3`), `check_response/3`, and
`wait_response/3`. However, without deleting handled associations, the above
calls will not be able to detect when there are no more outstanding requests to
handle, so you will have to keep track of this some other way than relying on a
`no_request` return. Note that if you pass a collection only containing
associations of already handled or abandoned requests to
[`receive_response/3`](`receive_response/3`), it will always block until a
timeout determined by `Timeout` is triggered.
""".
-doc(#{since => <<"OTP 25.0">>}).
-spec receive_response(ReqIdCollection, Timeout, Delete) -> Result when
      ReqIdCollection :: request_id_collection(),
      Timeout :: response_timeout(),
      Delete :: boolean(),
      Response :: {reply, Reply::term()} |
                  {error, {Reason::term(), emgr_ref()}},
      Result :: {Response,
                 Label::term(),
                 NewReqIdCollection::request_id_collection()} |
                'no_request' |
                'timeout'.

receive_response(ReqIdCol, Timeout, Delete) ->
    try gen:receive_response(ReqIdCol, Timeout, Delete) of
        {{reply, {error, _} = Err}, Label, NewReqIdCol} ->
            {Err, Label, NewReqIdCol};
        Return ->
            Return
    catch
        error:badarg ->
            error(badarg, [ReqIdCol, Timeout, Delete])
    end.

-doc """
Check if `Msg` is a response corresponding to the request identifier `ReqId`.
The request must have been made by `send_request/3`.

If `Msg` is a response corresponding to `ReqId` the response is returned;
otherwise, `no_reply` is returned and no cleanup is done, and thus the function
must be invoked repeatedly until a response is returned.

If the specified event handler is not installed, the function returns
`{error,bad_module}`. If the callback function fails with `Reason` or returns an
unexpected value `Term`, this function returns `{error,{'EXIT',Reason}}` or
`{error,Term}`, respectively. If the event manager dies before or during the
request this function returns `{error,{Reason, EventMgrRef}}`.
""".
-doc(#{since => <<"OTP 23.0">>}).
-spec check_response(Msg, ReqId) -> Result when
      Msg :: term(),
      ReqId :: request_id(),
      Response :: {reply, Reply::term()} |
                  {error, {Reason::term(), emgr_ref()}},
      Result :: Response | 'no_reply'.

check_response(Msg, ReqId) ->
    try gen:check_response(Msg, ReqId) of
        {reply, {error, _} = Err} -> Err;
        Return -> Return
    catch
        error:badarg ->
            error(badarg, [Msg, ReqId])
    end.

-doc """
Check if `Msg` is a response corresponding to a request identifier saved in
`ReqIdCollection`. All request identifiers of `ReqIdCollection` must correspond
to requests that have been made using `send_request/3` or `send_request/5`, and
all requests must have been made by the process calling this function.

The `Label` in the response equals the `Label` associated with the request
identifier that the response corresponds to. The `Label` of a request identifier
is associated when [saving the request id](`reqids_add/3`) in a request
identifier collection, or when sending the request using `send_request/5`.

Compared to `check_response/2`, the returned result associated with a specific
request identifier or an exception associated with a specific request identifier
will be wrapped in a 3-tuple. The first element of this tuple equals the value
that would have been produced by [`check_response/2`](`check_response/2`), the
second element equals the `Label` associated with the specific request
identifier, and the third element `NewReqIdCollection` is a possibly modified
request identifier collection.

If `ReqIdCollection` is empty, the atom `no_request` will be returned. If `Msg`
does not correspond to any of the request identifiers in `ReqIdCollection`, the
atom `no_reply` is returned.

If `Delete` equals `true`, the association with `Label` will have been deleted
from `ReqIdCollection` in the resulting `NewReqIdCollection`. If `Delete` equals
`false`, `NewReqIdCollection` will equal `ReqIdCollection`. Note that deleting
an association is not for free and that a collection containing already handled
requests can still be used by subsequent calls to
[`check_response/3`](`check_response/3`), `receive_response/3`, and
`wait_response/3`. However, without deleting handled associations, the above
calls will not be able to detect when there are no more outstanding requests to
handle, so you will have to keep track of this some other way than relying on a
`no_request` return. Note that if you pass a collection only containing
associations of already handled or abandoned requests to
[`check_response/3`](`check_response/3`), it will always return `no_reply`.
""".
-doc(#{since => <<"OTP 25.0">>}).
-spec check_response(Msg, ReqIdCollection, Delete) -> Result when
      Msg :: term(),
      ReqIdCollection :: request_id_collection(),
      Delete :: boolean(),
      Response :: {reply, Reply::term()} |
                  {error, {Reason::term(), emgr_ref()}},
      Result :: {Response,
                 Label::term(),
                 NewReqIdCollection::request_id_collection()} |
                'no_request' |
                'no_reply'.

check_response(Msg, ReqIdCol, Delete) ->
    try gen:check_response(Msg, ReqIdCol, Delete) of
        {{reply, {error, _} = Err}, Label, NewReqIdCol} ->
            {Err, Label, NewReqIdCol};
        Return ->
            Return
    catch
        error:badarg ->
            error(badarg, [Msg, ReqIdCol, Delete])
    end.

-doc """
Returns a new empty request identifier collection. A request identifier
collection can be utilized in order the handle multiple outstanding requests.

Request identifiers of requests made by `send_request/3` can be saved in a
request identifier collection using `reqids_add/3`. Such a collection of request
identifiers can later be used in order to get one response corresponding to a
request in the collection by passing the collection as argument to
`receive_response/3`, `wait_response/3`, or, `check_response/3`.

`reqids_size/1` can be used to determine the amount of request identifiers in a
request identifier collection.
""".
-doc(#{since => <<"OTP 25.0">>}).
-spec reqids_new() ->
          NewReqIdCollection::request_id_collection().

reqids_new() ->
    gen:reqids_new().

-doc "Returns the amount of request identifiers saved in `ReqIdCollection`.".
-doc(#{since => <<"OTP 25.0">>}).
-spec reqids_size(ReqIdCollection::request_id_collection()) ->
          non_neg_integer().

reqids_size(ReqIdCollection) ->
    try
        gen:reqids_size(ReqIdCollection)
    catch
        error:badarg -> error(badarg, [ReqIdCollection])
    end.

-doc """
Saves `ReqId` and associates a `Label` with the request identifier by adding
this information to `ReqIdCollection` and returning the resulting request
identifier collection.
""".
-doc(#{since => <<"OTP 25.0">>}).
-spec reqids_add(ReqId::request_id(), Label::term(),
                 ReqIdCollection::request_id_collection()) ->
          NewReqIdCollection::request_id_collection().

reqids_add(ReqId, Label, ReqIdCollection) ->
    try
        gen:reqids_add(ReqId, Label, ReqIdCollection)
    catch
        error:badarg -> error(badarg, [ReqId, Label, ReqIdCollection])
    end.

-doc """
Returns a list of `{ReqId, Label}` tuples which corresponds to all request
identifiers with their associated labels present in the `ReqIdCollection`
collection.
""".
-doc(#{since => <<"OTP 25.0">>}).
-spec reqids_to_list(ReqIdCollection::request_id_collection()) ->
          [{ReqId::request_id(), Label::term()}].

reqids_to_list(ReqIdCollection) ->
    try
        gen:reqids_to_list(ReqIdCollection)
    catch
        error:badarg -> error(badarg, [ReqIdCollection])
    end.

-doc """
Deletes an event handler from event manager `EventMgrRef`. The event manager
calls [`Module:terminate/2`](`c:terminate/2`) to terminate the event handler.

`Args` is any term that is passed as one of the arguments to
[`Module:terminate/2`](`c:terminate/2`).

The return value is the return value of [`Module:terminate/2`](`c:terminate/2`).
If the specified event handler is not installed, the function returns
`{error,module_not_found}`. If the callback function fails with `Reason`, the
function returns `{'EXIT',Reason}`.
""".
-spec delete_handler(EventMgrRef :: emgr_ref(), Handler :: handler(), Args :: term()) -> term().
delete_handler(M, Handler, Args) -> rpc(M, {delete_handler, Handler, Args}).

-doc """
Replaces an old event handler with a new event handler in event manager
`EventMgrRef`.

For a description of `OldHandler` and `NewHandler`, see `add_handler/3`.

First the old event handler `OldHandler` is deleted. The event manager calls
`OldModule:terminate(Args1, ...)`, where `OldModule` is the callback module of
`OldHandler`, and collects the return value.

Then the new event handler `NewHandler` is added and initiated by calling
[`NewModule:init({Args2,Term})`](`c:init/1`), where `NewModule` is the callback
module of `OldHandler` and `Term` is the return value of
[`OldModule:terminate/2`](`c:terminate/2`). This makes it possible to transfer
information from `OldHandler` to `NewHandler`.

The new handler is added even if the the specified old event handler is not
installed, in which case `Term=error`, or if
[`OldModule:terminate/2`](`c:terminate/2`) fails with `Reason`, in which case
`Term={'EXIT',Reason}`. The old handler is deleted even if
[`NewModule:init/1`](`c:init/1`) fails.

If there was a supervised connection between `OldHandler` and a process `Pid`,
there is a supervised connection between `NewHandler` and `Pid` instead.

If [`NewModule:init/1`](`c:init/1`) returns a correct value, this function returns
`ok`. If [`NewModule:init/1`](`c:init/1`) fails with `Reason` or returns an
unexpected value `Term`, this function returns `{error,{'EXIT',Reason}}` or
`{error,Term}`, respectively.
""".
-spec swap_handler(EventMgrRef :: emgr_ref(),
                   OldHandler :: {handler(), term()},
                   NewHandler :: {handler(), term()}) ->
	    'ok' | {'error', term()}.
swap_handler(M, {H1, A1}, {H2, A2}) -> rpc(M, {swap_handler, H1, A1, H2, A2}).

-doc """
Replaces an event handler in event manager `EventMgrRef` in the same way as
[`swap_handler/3`](`swap_handler/3`), but also supervises the connection between
`NewHandler` and the calling process.

For a description of the arguments and return values, see `swap_handler/3`.
""".
-spec swap_sup_handler(EventMgrRef :: emgr_ref(),
                       OldHandler :: {handler(), term()},
                       NewHandler :: {handler(), term()}) ->
	    'ok' | {'error', term()}.
swap_sup_handler(M, {H1, A1}, {H2, A2}) ->
    rpc(M, {swap_sup_handler, H1, A1, H2, A2, self()}).

-doc """
Returns a list of all event handlers installed in event manager `EventMgrRef`.

For a description of `Handler`, see `add_handler/3`.
""".
-spec which_handlers(EventMgrRef :: emgr_ref()) -> [handler()].
which_handlers(M) -> rpc(M, which_handlers).

-doc(#{equiv => stop(EventMgrRef, normal, infinity)}).
-spec stop(EventMgrRef :: emgr_ref()) -> 'ok'.
stop(M) ->
    gen:stop(M).

-doc """
Orders event manager `EventMgrRef` to exit with the specifies `Reason` and waits
for it to terminate. Before terminating, `gen_event` calls
[`Module:terminate(stop,...)`](`c:terminate/2`) for each installed event
handler.

The function returns `ok` if the event manager terminates with the expected
reason. Any other reason than `normal`, `shutdown`, or `{shutdown,Term}` causes
an error report to be issued using `m:logger`.

`Timeout` is an integer greater than zero that specifies how many milliseconds
to wait for the event manager to terminate, or the atom `infinity` to wait
indefinitely. If the event manager has not terminated
within the specified time, the call exits the calling process with reason
`timeout`.

If the process does not exist, the call exits the calling process with reason
`noproc`, and with reason `{nodedown,Node}` if the connection fails to the
remote `Node` where the server runs.
""".
-doc(#{since => <<"OTP 18.0">>}).
-spec stop(EventMgrRef :: emgr_ref(), Reason :: term(), Timeout :: timeout()) -> 'ok'.
stop(M, Reason, Timeout) ->
    gen:stop(M, Reason, Timeout).

rpc(M, Cmd) ->
    {ok, Reply} = gen:call(M, self(), Cmd, infinity),
    Reply.

call1(M, Handler, Query) ->
    Cmd = {call, Handler, Query},
    try gen:call(M, self(), Cmd) of
	{ok, Res} ->
	    Res
    catch
	exit:Reason ->
	    exit({Reason, {?MODULE, call, [M, Handler, Query]}})
    end.

call1(M, Handler, Query, Timeout) ->
    Cmd = {call, Handler, Query},
    try gen:call(M, self(), Cmd, Timeout) of
	{ok, Res} ->
	    Res
    catch
	exit:Reason ->
	    exit({Reason, {?MODULE, call, [M, Handler, Query, Timeout]}})
    end.

send({global, Name}, Cmd) ->
    catch global:send(Name, Cmd),
    ok;
send({via, Mod, Name}, Cmd) ->
    catch Mod:send(Name, Cmd),
    ok;
send(M, Cmd) ->
    M ! Cmd,
    ok.

loop(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, true) ->
     proc_lib:hibernate(?MODULE, wake_hib, [Parent, ServerName, MSL, HibernateAfterTimeout, Debug]);
loop(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, _) ->
    fetch_msg(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, false).

-doc false.
wake_hib(Parent, ServerName, MSL, HibernateAfterTimeout, Debug) ->
    fetch_msg(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, true).

fetch_msg(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, Hib) ->
    receive
	Msg ->
	    decode_msg(Msg, Parent, ServerName, MSL, HibernateAfterTimeout, Debug, Hib)
    after HibernateAfterTimeout ->
	    loop(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, true)
    end.

decode_msg(Msg, Parent, ServerName, MSL, HibernateAfterTimeout, Debug, Hib) ->
    case Msg of
	{system, From, Req} ->
	    sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
				  [ServerName, MSL, HibernateAfterTimeout, Hib],Hib);
	{'EXIT', Parent, Reason} ->
	    terminate_server(Reason, Parent, MSL, ServerName);
	_Msg when Debug =:= [] ->
	    handle_msg(Msg, Parent, ServerName, MSL, HibernateAfterTimeout, []);
	_Msg ->
	    Debug1 = sys:handle_debug(Debug, fun print_event/3,
				      ServerName, {in, Msg}),
	    handle_msg(Msg, Parent, ServerName, MSL, HibernateAfterTimeout, Debug1)
    end.

handle_msg(Msg, Parent, ServerName, MSL, HibernateAfterTimeout, Debug) ->
    case Msg of
	{notify, Event} ->
	    {Hib,MSL1} = server_notify(Event, handle_event, MSL, ServerName),
	    loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, Hib);
	{_From, Tag, {sync_notify, Event}} ->
	    {Hib, MSL1} = server_notify(Event, handle_event, MSL, ServerName),
	    reply(Tag, ok),
	    loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, Hib);
	{'EXIT', From, Reason} ->
	    MSL1 = handle_exit(From, Reason, MSL, ServerName),
	    loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, false);
	{_From, Tag, {call, Handler, Query}} ->
	    {Hib, Reply, MSL1} = server_call(Handler, Query, MSL, ServerName),
	    reply(Tag, Reply),
	    loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, Hib);
	{_From, Tag, {add_handler, Handler, Args}} ->
	    {Hib, Reply, MSL1} = server_add_handler(Handler, Args, MSL),
	    reply(Tag, Reply),
	    loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, Hib);
	{_From, Tag, {add_sup_handler, Handler, Args, SupP}} ->
	    {Hib, Reply, MSL1} = server_add_sup_handler(Handler, Args, MSL, SupP),
	    reply(Tag, Reply),
	    loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, Hib);
	{_From, Tag, {delete_handler, Handler, Args}} ->
	    {Reply, MSL1} = server_delete_handler(Handler, Args, MSL,
						  ServerName),
	    reply(Tag, Reply),
	    loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, false);
	{_From, Tag, {swap_handler, Handler1, Args1, Handler2, Args2}} ->
	    {Hib, Reply, MSL1} = server_swap_handler(Handler1, Args1, Handler2,
						     Args2, MSL, ServerName),
	    reply(Tag, Reply),
	    loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, Hib);
	{_From, Tag, {swap_sup_handler, Handler1, Args1, Handler2, Args2,
		     Sup}} ->
	    {Hib, Reply, MSL1} = server_swap_handler(Handler1, Args1, Handler2,
						Args2, MSL, Sup, ServerName),
	    reply(Tag, Reply),
	    loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, Hib);
	{_From, Tag, stop} ->
	    catch terminate_server(normal, Parent, MSL, ServerName),
	    reply(Tag, ok);
	{_From, Tag, which_handlers} ->
	    reply(Tag, the_handlers(MSL)),
	    loop(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, false);
	{_From, Tag, get_modules} ->
	    reply(Tag, get_modules(MSL)),
	    loop(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, false);
	Other  ->
	    {Hib, MSL1} = server_notify(Other, handle_info, MSL, ServerName),
	    loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, Hib)
    end.

terminate_server(Reason, Parent, MSL, ServerName) ->
    stop_handlers(MSL, ServerName),
    do_unlink(Parent, MSL),
    exit(Reason).

reply(From, Reply) ->
    gen:reply(From, Reply).

%% unlink the supervisor process of all supervised handlers.
%% We do not want a handler supervisor to EXIT due to the
%% termination of the event manager (server).
%% Do not unlink Parent !
do_unlink(Parent, MSL) ->
    lists:foreach(fun(Handler) when Handler#handler.supervised =:= Parent ->
			  true;
		     (Handler) when is_pid(Handler#handler.supervised) ->
			  unlink(Handler#handler.supervised),
			  true;
		     (_) ->
			  true
		  end,
		  MSL).

%% First terminate the supervised (if exists) handlers and
%% then inform other handlers.
%% We do not know if any handler really is interested but it
%% may be so !
handle_exit(From, Reason, MSL, SName) ->
    MSL1 = terminate_supervised(From, Reason, MSL, SName),
    {_,MSL2}=server_notify({'EXIT', From, Reason}, handle_info, MSL1, SName),
    MSL2.

terminate_supervised(Pid, Reason, MSL, SName) ->
    F = fun(Ha) when Ha#handler.supervised =:= Pid ->
		do_terminate(Ha#handler.module,
			     Ha,
			     {stop,Reason},
			     Ha#handler.state,
			     {parent_terminated, {Pid,Reason}},
			     SName,
			     shutdown),
		false;
	   (_) ->
		true
	end,
    lists:filter(F, MSL).

%%-----------------------------------------------------------------
%% Callback functions for system messages handling.
%%-----------------------------------------------------------------
-doc false.
system_continue(Parent, Debug, [ServerName, MSL, HibernateAfterTimeout, Hib]) ->
    loop(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, Hib).

-doc false.
-spec system_terminate(_, _, _, [_]) -> no_return().
system_terminate(Reason, Parent, _Debug, [ServerName, MSL, _HibernateAfterTimeout, _Hib]) ->
    terminate_server(Reason, Parent, MSL, ServerName).

%%-----------------------------------------------------------------
%% Module here is sent in the system msg change_code.  It specifies
%% which module should be changed.
%%-----------------------------------------------------------------
-doc false.
system_code_change([ServerName, MSL, HibernateAfterTimeout, Hib], Module, OldVsn, Extra) ->
    MSL1 = lists:zf(fun(H) when H#handler.module =:= Module ->
			    {ok, NewState} =
				Module:code_change(OldVsn,
						   H#handler.state, Extra),
			    {true, H#handler{state = NewState}};
		       (_) -> true
		    end,
		    MSL),
    {ok, [ServerName, MSL1, HibernateAfterTimeout, Hib]}.

-doc false.
system_get_state([_ServerName, MSL, _HibernateAfterTimeout, _Hib]) ->
    {ok, [{Mod,Id,State} || #handler{module=Mod, id=Id, state=State} <- MSL]}.

-doc false.
system_replace_state(StateFun, [ServerName, MSL, HibernateAfterTimeout, Hib]) ->
    {NMSL, NStates} =
		lists:unzip([begin
				 Cur = {Mod,Id,State},
				 try
				     NState = {Mod,Id,NS} = StateFun(Cur),
				     {HS#handler{state=NS}, NState}
				 catch
				     _:_ ->
					 {HS, Cur}
				 end
			     end || #handler{module=Mod, id=Id, state=State}=HS <- MSL]),
    {ok, NStates, [ServerName, NMSL, HibernateAfterTimeout, Hib]}.

%%-----------------------------------------------------------------
%% Format debug messages.  Print them as the call-back module sees
%% them, not as the real erlang messages.  Use trace for that.
%%-----------------------------------------------------------------
print_event(Dev, {in, Msg}, Name) ->
    case Msg of
	{notify, Event} ->
	    io:format(Dev, "*DBG* ~tp got event ~tp~n", [Name, Event]);
	{_,_,{call, Handler, Query}} ->
	    io:format(Dev, "*DBG* ~tp(~tp) got call ~tp~n",
		      [Name, Handler, Query]);
	_ ->
	    io:format(Dev, "*DBG* ~tp got ~tp~n", [Name, Msg])
    end;
print_event(Dev, Dbg, Name) ->
    io:format(Dev, "*DBG* ~tp : ~tp~n", [Name, Dbg]).


%% server_add_handler(Handler, Args, MSL) -> {Ret, MSL'}.
%%   where MSL = [#handler{}]
%%   Ret goes to the top level MSL' is the new internal state of the
%%   event handler

server_add_handler({Mod,Id}, Args, MSL) ->
    Handler = #handler{module = Mod,
		       id = Id},
    server_add_handler(Mod, Handler, Args, MSL);
server_add_handler(Mod, Args, MSL) ->
    Handler = #handler{module = Mod},
    server_add_handler(Mod, Handler, Args, MSL).

server_add_handler(Mod, Handler, Args, MSL) ->
    case catch Mod:init(Args) of
        {ok, State} ->
	    {false, ok, [Handler#handler{state = State}|MSL]};
        {ok, State, hibernate} ->
	    {true, ok, [Handler#handler{state = State}|MSL]};
        Other ->
            {false, Other, MSL}
    end.

%% Set up a link to the supervising process.
%% (Ought to be unidirected links here, Erl5.0 !!)
%% NOTE: This link will not be removed then the
%% handler is removed in case another handler has
%% own link to this process.
server_add_sup_handler({Mod,Id}, Args, MSL, Parent) ->
    link(Parent),
    Handler = #handler{module = Mod,
		       id = Id,
		       supervised = Parent},
    server_add_handler(Mod, Handler, Args, MSL);
server_add_sup_handler(Mod, Args, MSL, Parent) ->
    link(Parent),
    Handler = #handler{module = Mod,
		       supervised = Parent},
    server_add_handler(Mod, Handler, Args, MSL).

%% server_delete_handler(HandlerId, Args, MSL) -> {Ret, MSL'}

server_delete_handler(HandlerId, Args, MSL, SName) ->
    case split(HandlerId, MSL) of
	{Mod, Handler, MSL1} ->
	    {do_terminate(Mod, Handler, Args,
			  Handler#handler.state, delete, SName, normal),
	     MSL1};
	error ->
	    {{error, module_not_found}, MSL}
    end.

%% server_swap_handler(Handler1, Args1, Handler2, Args2, MSL, SN) -> MSL'
%% server_swap_handler(Handler1, Args1, Handler2, Args2, MSL, Sup, SN) -> MSL'

server_swap_handler(Handler1, Args1, Handler2, Args2, MSL, SName) ->
    {State2, Sup, MSL1} = split_and_terminate(Handler1, Args1, MSL,
					      SName, Handler2, false),
    case s_s_h(Sup, Handler2, {Args2, State2}, MSL1) of
	{Hib, ok, MSL2} ->
	    {Hib, ok, MSL2};
	{Hib, What, MSL2} ->
	    {Hib, {error, What}, MSL2}
    end.

server_swap_handler(Handler1, Args1, Handler2, Args2, MSL, Sup, SName) ->
    {State2, _, MSL1} = split_and_terminate(Handler1, Args1, MSL,
					    SName, Handler2, Sup),
    case s_s_h(Sup, Handler2, {Args2, State2}, MSL1) of
	{Hib, ok, MSL2} ->
	    {Hib, ok, MSL2};
	{Hib, What, MSL2} ->
	    {Hib, {error, What}, MSL2}
    end.

s_s_h(false, Handler, Args, MSL) ->
    server_add_handler(Handler, Args, MSL);
s_s_h(Pid, Handler, Args, MSL) ->
    server_add_sup_handler(Handler, Args, MSL, Pid).

split_and_terminate(HandlerId, Args, MSL, SName, Handler2, Sup) ->
    case split(HandlerId, MSL) of
	{Mod, Handler, MSL1} ->
	    OldSup = Handler#handler.supervised,
	    NewSup = if
			 not Sup -> OldSup;
			 true    -> Sup
		     end,
	    {do_terminate(Mod, Handler, Args,
			  Handler#handler.state, swapped, SName,
			  {swapped, Handler2, NewSup}),
	     OldSup,
	     MSL1};
	error ->
            {error, false, MSL}
    end.

%% server_notify(Event, Func, MSL, SName) -> MSL'

server_notify(Event, Func, [Handler|T], SName) ->
    case server_update(Handler, Func, Event, SName) of
	{ok, Handler1} ->
	    {Hib, NewHandlers} = server_notify(Event, Func, T, SName),
	    {Hib, [Handler1|NewHandlers]};
	{hibernate, Handler1} ->
	    {_Hib, NewHandlers} = server_notify(Event, Func, T, SName),
	    {true, [Handler1|NewHandlers]};
	no ->
	    server_notify(Event, Func, T, SName)
    end;
server_notify(_, _, [], _) ->
    {false, []}.

%% server_update(Handler, Func, Event, ServerName) -> Handler1 | no

server_update(Handler1, Func, Event, SName) ->
    Mod1 = Handler1#handler.module,
    State = Handler1#handler.state,
    case catch Mod1:Func(Event, State) of
	{ok, State1} ->
	    {ok, Handler1#handler{state = State1}};
	{ok, State1, hibernate} ->
	    {hibernate, Handler1#handler{state = State1}};
	{swap_handler, Args1, State1, Handler2, Args2} ->
	    do_swap(Mod1, Handler1, Args1, State1, Handler2, Args2, SName);
	remove_handler ->
	    do_terminate(Mod1, Handler1, remove_handler, State,
			 remove, SName, normal),
	    no;
        {'EXIT', {undef, [{Mod1, handle_info, [_,_], _}|_]}} ->
            ?LOG_WARNING(#{label=>{gen_event,no_handle_info},
                           module=>Mod1,
                           message=>Event},
                         #{domain=>[otp],
                           report_cb=>fun gen_event:format_log/2,
                           error_logger=>
                               #{tag=>warning_msg, % warningmap??
                                 report_cb=>fun gen_event:format_log/1}}),
            {ok, Handler1};
	Other ->
	    do_terminate(Mod1, Handler1, {error, Other}, State,
			 Event, SName, crash),
	    no
    end.

do_swap(Mod1, Handler1, Args1, State1, Handler2, Args2, SName) ->
    %% finalise the existing handler
    State2 = do_terminate(Mod1, Handler1, Args1, State1,
			  swapped, SName,
			  {swapped, Handler2, Handler1#handler.supervised}),
    {Mod2, Handler} = new_handler(Handler2, Handler1),
    case catch Mod2:init({Args2, State2}) of
	{ok, State2a} ->
	    {ok, Handler#handler{state = State2a}};
	Other ->
	    report_terminate(Handler, crash, {error, Other}, SName, false),
	    no
    end.

new_handler({Mod,Id}, Handler1) ->
    {Mod, #handler{module = Mod,
		   id = Id,
		   supervised = Handler1#handler.supervised}};
new_handler(Mod, Handler1) ->
    {Mod, #handler{module = Mod,
		   supervised = Handler1#handler.supervised}}.


-spec split(handler(), [#handler{}]) ->
	{atom(), #handler{}, [#handler{}]} | 'error'.

split(Ha, MSL) -> split(Ha, MSL, []).

split({Mod,Id}, [Ha|T], L) when Ha#handler.module =:= Mod,
                                Ha#handler.id =:= Id ->
    {Mod, Ha, lists:reverse(L, T)};
split(Mod, [Ha|T], L) when Ha#handler.module =:= Mod,
                           not Ha#handler.id ->
    {Mod, Ha, lists:reverse(L, T)};
split(Ha, [H|T], L) ->
    split(Ha, T, [H|L]);
split(_, [], _) ->
    error.

%% server_call(Handler, Query, MSL, ServerName) ->
%%    {Reply, MSL1}

server_call(Handler, Query, MSL, SName) ->
    case search(Handler, MSL) of
	{ok, Ha} ->
	    case server_call_update(Ha, Query, SName) of
		{no, Reply} ->
		    {false, Reply, delete(Handler, MSL)};
		{{ok, Ha1}, Reply} ->
		    {false, Reply, replace(Handler, MSL, Ha1)};
		{{hibernate, Ha1}, Reply} ->
		    {true, Reply, replace(Handler, MSL, Ha1)}
	    end;
	false ->
	    {false, {error, bad_module}, MSL}
    end.

search({Mod, Id}, [Ha|_MSL]) when Ha#handler.module =:= Mod,
				  Ha#handler.id =:= Id ->
    {ok, Ha};
search(Mod, [Ha|_MSL]) when Ha#handler.module =:= Mod,
			    not Ha#handler.id ->
    {ok, Ha};
search(Handler, [_|MSL]) ->
    search(Handler, MSL);
search(_, []) ->
    false.

delete({Mod, Id}, [Ha|MSL]) when Ha#handler.module =:= Mod,
                                 Ha#handler.id =:= Id ->
    MSL;
delete(Mod, [Ha|MSL]) when Ha#handler.module =:= Mod,
                           not Ha#handler.id ->
    MSL;
delete(Handler, [Ha|MSL]) ->
    [Ha|delete(Handler, MSL)];
delete(_, []) ->
    [].

replace({Mod, Id}, [Ha|MSL], NewHa) when Ha#handler.module =:= Mod,
                                         Ha#handler.id =:= Id ->
    [NewHa|MSL];
replace(Mod, [Ha|MSL], NewHa) when Ha#handler.module =:= Mod,
                                   not Ha#handler.id ->
    [NewHa|MSL];
replace(Handler, [Ha|MSL], NewHa) ->
    [Ha|replace(Handler, MSL, NewHa)];
replace(_, [], NewHa) ->
    [NewHa].

%% server_call_update(Handler, Query, ServerName) ->
%%    {{Handler1, State1} | 'no', Reply}

server_call_update(Handler1, Query, SName) ->
    Mod1 = Handler1#handler.module,
    State = Handler1#handler.state,
    case catch Mod1:handle_call(Query, State) of
	{ok, Reply, State1} ->
	    {{ok, Handler1#handler{state = State1}}, Reply};
	{ok, Reply, State1, hibernate} ->
	    {{hibernate, Handler1#handler{state = State1}},
	     Reply};
	{swap_handler, Reply, Args1, State1, Handler2, Args2} ->
	    {do_swap(Mod1,Handler1,Args1,State1,Handler2,Args2,SName), Reply};
	{remove_handler, Reply} ->
	    do_terminate(Mod1, Handler1, remove_handler, State,
			 remove, SName, normal),
	    {no, Reply};
	Other ->
	    do_terminate(Mod1, Handler1, {error, Other}, State,
			 Query, SName, crash),
	    {no, {error, Other}}
    end.

do_terminate(Mod, Handler, Args, State, LastIn, SName, Reason) ->
    case erlang:function_exported(Mod, terminate, 2) of
	true ->
	    Res = (catch Mod:terminate(Args, State)),
	    report_terminate(Handler, Reason, Args, State, LastIn, SName, Res),
	    Res;
	false ->
	    report_terminate(Handler, Reason, Args, State, LastIn, SName, ok),
	    ok
    end.

-spec report_terminate(_, How, _, _, _, _, _) -> ok when
      How :: crash | normal | shutdown | {swapped, handler(), false | pid()}.
report_terminate(Handler, crash, {error, Why}, State, LastIn, SName, _) ->
    report_terminate(Handler, Why, State, LastIn, SName);
report_terminate(Handler, How, _, State, LastIn, SName, _) ->
    %% How == normal | shutdown | {swapped, NewHandler, NewSupervisor}
    report_terminate(Handler, How, State, LastIn, SName).

report_terminate(Handler, Reason, State, LastIn, SName) ->
    report_error(Handler, Reason, State, LastIn, SName),
    case Handler#handler.supervised of
	false ->
	    ok;
	Pid ->
	    Pid ! {gen_event_EXIT,handler(Handler),Reason},
	    ok
    end.

report_error(_Handler, normal, _, _, _)             -> ok;
report_error(_Handler, shutdown, _, _, _)           -> ok;
report_error(_Handler, {swapped,_,_}, _, _, _)      -> ok;
report_error(Handler, Exit, State, LastIn, SName) ->

    %% The reason comes from a catch expression, so we remove
    %% the 'EXIT' and stacktrace from it so that the format_status
    %% callback does not have deal with that.
    {Reason, ReasonFun} =
        case Exit of
            {'EXIT',{R,ST}} ->
                {R, fun(Reason) -> {'EXIT',{Reason,ST}} end};
            {'EXIT',R} ->
                {R, fun(Reason) -> {'EXIT',Reason} end};
            R ->
                {R, fun(Reason) -> Reason end}
        end,
    Status = gen:format_status(
               Handler#handler.module,
               terminate,
               #{ state => State,
                  message => LastIn,
                  reason => Reason
                },
               [get(), State]),
    ?LOG_ERROR(#{label=>{gen_event,terminate},
                 handler=>handler(Handler),
                 name=>SName,
                 last_message=>maps:get(message,Status),
                 state=>maps:get('$status',Status,maps:get(state,Status)),
                 reason=>ReasonFun(maps:get(reason,Status)),
                 process_label=>proc_lib:get_label(self())},
               #{domain=>[otp],
                 report_cb=>fun gen_event:format_log/2,
                 error_logger=>#{tag=>error,
                                 report_cb=>fun gen_event:format_log/1}}).

%% format_log/1 is the report callback used by Logger handler
%% error_logger only. It is kept for backwards compatibility with
%% legacy error_logger event handlers. This function must always
%% return {Format,Args} compatible with the arguments in this module's
%% calls to error_logger prior to OTP-21.0.
-doc false.
format_log(Report) ->
    Depth = error_logger:get_format_depth(),
    FormatOpts = #{chars_limit => unlimited,
                   depth => Depth,
                   single_line => false,
                   encoding => utf8},
    format_log_multi(limit_report(Report, Depth), FormatOpts).

limit_report(Report, unlimited) ->
    Report;
limit_report(#{label:={gen_event,terminate},
               last_message:=LastIn,
               state:=State,
               reason:=Reason,
               process_label:=ProcessLabel}=Report,
             Depth) ->
    Report#{last_message => io_lib:limit_term(LastIn, Depth),
            state => io_lib:limit_term(State, Depth),
            reason => io_lib:limit_term(Reason, Depth),
            process_label => io_lib:limit_term(ProcessLabel, Depth)};
limit_report(#{label:={gen_event,no_handle_info},
               message:=Msg}=Report,
             Depth) ->
    Report#{message => io_lib:limit_term(Msg, Depth)}.

%% format_log/2 is the report callback for any Logger handler, except
%% error_logger.
-doc false.
format_log(Report, FormatOpts0) ->
    Default = #{chars_limit => unlimited,
                depth => unlimited,
                single_line => false,
                encoding => utf8},
    FormatOpts = maps:merge(Default, FormatOpts0),
    IoOpts =
        case FormatOpts of
            #{chars_limit:=unlimited} ->
                [];
            #{chars_limit:=Limit} ->
                [{chars_limit,Limit}]
        end,
    {Format,Args} = format_log_single(Report, FormatOpts),
    io_lib:format(Format, Args, IoOpts).

format_log_single(#{label:={gen_event,terminate},
                    handler:=Handler,
                    name:=SName,
                    last_message:=LastIn,
                    state:=State,
                    reason:=Reason,
                    process_label:=ProcessLabel},
                  #{single_line:=true, depth:=Depth}=FormatOpts) ->
    P = p(FormatOpts),
    Reason1 = fix_reason(Reason),
    Format1 = lists:append(["Generic event handler ",P," crashed. "
                            "Installed: ",P,
                            case ProcessLabel of
                                undefined -> "";
                                _ -> ". Label: "++P
                            end,
                            ". Last event: ",P,
                            ". State: ",P,". Reason: ",P,"."]),
    Args1 =
        case Depth of
            unlimited ->
                [Handler,SName] ++
                case ProcessLabel of
                    undefined -> [];
                    _ -> [ProcessLabel]
                end ++
                [LastIn,State,Reason1];
            _ ->
                [Handler,Depth,SName,Depth] ++
                case ProcessLabel of
                    undefined -> [];
                    _ -> [ProcessLabel,Depth]
                end ++
                [LastIn,Depth,State,Depth,Reason1,Depth]
        end,
    {Format1, Args1};
format_log_single(#{label:={gen_event,no_handle_info},
                    module:=Mod,
                    message:=Msg},
                  #{single_line:=true,depth:=Depth}=FormatOpts) ->
    P = p(FormatOpts),
    Format = lists:append(["Undefined handle_info in ",P,
                           ". Unhandled message: ",P,"."]),
    Args =
        case Depth of
            unlimited ->
                [Mod,Msg];
            _ ->
                [Mod,Depth,Msg,Depth]
        end,
    {Format,Args};
format_log_single(Report,FormatOpts) ->
    format_log_multi(Report,FormatOpts).

format_log_multi(#{label:={gen_event,terminate},
                   handler:=Handler,
                   name:=SName,
                   last_message:=LastIn,
                   state:=State,
                   reason:=Reason,
                   process_label:=ProcessLabel},
                 #{depth:=Depth}=FormatOpts) ->
    Reason1 = fix_reason(Reason),
    P = p(FormatOpts),
    Format =
        lists:append(["** gen_event handler ",P," crashed.\n",
                      "** Was installed in ",P,"\n",
                      case ProcessLabel of
                          undefined -> [];
                          _ -> "** Process label == "++P++"\n"
                      end,
                      "** Last event was: ",P,"\n",
                      "** When handler state == ",P,"\n",
                      "** Reason == ",P,"\n"]),
    Args =
        case Depth of
            unlimited ->
                [Handler, SName] ++
                case ProcessLabel of
                    undefined -> [];
                    _ -> [ProcessLabel]
                end ++
                [LastIn,State,Reason1];
            _ ->
                [Handler, Depth, SName, Depth] ++
                case ProcessLabel of
                    undefined -> [];
                    _ -> [ProcessLabel, Depth]
                end ++
                [LastIn, Depth, State, Depth, Reason1, Depth]
        end,
    {Format,Args};
format_log_multi(#{label:={gen_event,no_handle_info},
                   module:=Mod,
                   message:=Msg},
                 #{depth:=Depth}=FormatOpts) ->
    P = p(FormatOpts),
    Format =
        "** Undefined handle_info in ~p\n"
        "** Unhandled message: "++P++"\n",
    Args =
        case Depth of
            unlimited ->
                [Mod,Msg];
            _ ->
                [Mod,Msg,Depth]
        end,
    {Format,Args}.

fix_reason({'EXIT',{undef,[{M,F,A,_L}|_]=MFAs}=Reason}) ->
    case code:is_loaded(M) of
        false ->
            {'module could not be loaded',MFAs};
        _ ->
            case erlang:function_exported(M, F, length(A)) of
                true ->
                    Reason;
                false ->
                    {'function not exported',MFAs}
            end
    end;
fix_reason({'EXIT',Reason}) ->
    Reason;
fix_reason(Reason) ->
    Reason.

p(#{single_line:=Single,depth:=Depth,encoding:=Enc}) ->
    "~"++single(Single)++mod(Enc)++p(Depth);
p(unlimited) ->
    "p";
p(_Depth) ->
    "P".

single(true) -> "0";
single(false) -> "".

mod(latin1) -> "";
mod(_) -> "t".

handler(Handler) when not Handler#handler.id ->
    Handler#handler.module;
handler(Handler) ->
    {Handler#handler.module, Handler#handler.id}.

the_handlers(MSL) ->
    [handler(Handler) || Handler <- MSL].

%% stop_handlers(MSL, ServerName) -> []

stop_handlers([Handler|T], SName) ->
    Mod = Handler#handler.module,
    do_terminate(Mod, Handler, stop, Handler#handler.state,
		 stop, SName, shutdown),
    stop_handlers(T, SName);
stop_handlers([], _) ->
    [].

%% Message from the release_handler.
%% The list of modules got to be a set, i.e. no duplicate elements!
get_modules(MSL) ->
    Mods = [Handler#handler.module || Handler <- MSL],
    ordsets:to_list(ordsets:from_list(Mods)).

%%-----------------------------------------------------------------
%% Status information
%%-----------------------------------------------------------------
-doc false.
format_status(Opt, StatusData) ->
    [PDict, SysState, Parent, Debug, [ServerName, MSL, _HibernateAfterTimeout, _Hib]] = StatusData,
    Header = gen:format_status_header("Status for event handler", ServerName),
    {FmtMSL, Logs} =
        lists:mapfoldl(
          fun(#handler{module = Mod, state = State} = MS, Logs) ->
                  Status = gen:format_status(
                             Mod, Opt, #{ log => Logs, state => State },
                             [PDict, State]),
                  {MS#handler{state=maps:get('$status',Status,maps:get(state,Status))},
                   maps:get(log,Status)}
          end, sys:get_log(Debug), MSL),
    [{header, Header},
     {data, [{"Status", SysState},
             {"Logged Events", Logs},
	     {"Parent", Parent}]},
     {items, {"Installed handlers", FmtMSL}}].
