-module(eintro).

%%-compile([export_all]).
-export([
         tracer/2,
         tracer/3,
         trace_mon/2,
         trace_mon_call/3,
         trace_loop/0,
         loop/1,
         sv_proc/1,
         sv/0,
         fc_loop/0,
         fc_test_ext/0
        ]).

tracer(PidSpec, What) ->
    spawn(?MODULE, trace_mon, [PidSpec, What]).

tracer(PidSpec, What, Params) ->
    spawn(?MODULE, trace_mon_call, [PidSpec, What, Params]).

trace_mon(PidSpec, What)
  when is_pid(PidSpec);
       PidSpec =:= all;
       PidSpec =:= new ->
    erlang:trace(PidSpec, true, What),
    trace_loop().

trace_mon_call(PidSpec, What, {MFA, MatchSpec, Flags})
  when is_pid(PidSpec);
       PidSpec =:= all;
       PidSpec =:= new ->
    erlang:trace(PidSpec, true, What),
    erlang:trace_pattern(MFA, MatchSpec, Flags),
    trace_loop().

trace_loop() ->
    receive

        %% procs
        {trace, Pid1, spawn, Pid2, MFA} ->
            io:format("~p spawns ~p with ~p~n", [Pid1, Pid2, MFA]);
        {trace, Pid, exit, Reason} ->
            io:format("~p exits with ~p~n", [Pid, Reason]);
        {trace, Pid, register, Name} ->
            io:format("~p registered as ~p~n", [Pid, Name]);
        {trace, Pid, unregister, Name} ->
            io:format("~p unregistered as ~p~n", [Pid, Name]);
        {trace, Pid1, link, Pid2} ->
            io:format("~p links to ~p~n", [Pid1, Pid2]);
        {trace, Pid1, unlink, Pid2} ->
            io:format("~p removes the link from ~p~n", [Pid1, Pid2]);
        {trace, Pid1, getting_linked, Pid2} ->
            io:format("~p gets linked to ~p~n", [Pid1, Pid2]);
        {trace, Pid1, getting_unlinked, Pid2} ->
            io:format("~p gets unlinked from ~p~n", [Pid1, Pid2]);

        %% running
        {trace, Pid, in, MFA} ->
            io:format("~p will run in ~p~n", [Pid, MFA]);
        {trace, Pid, out, MFA} ->
            io:format("~p was running in ~p~n", [Pid, MFA]);

        %% garbage_collection
        {trace, Pid, gc_start, Info} ->
            io:format("~p in garbage collection, info:~n~p~n", [Pid, Info]);
        {trace, Pid, gc_end, Info} ->
            io:format("~p garbage collected, info:~n~p~n", [Pid, Info]);

        %% send
        {trace, Pid, send, Msg, ToPid} ->
            io:format("~p sends ~p to ~p~n", [Pid, Msg, ToPid]);
        {trace, Pid, send_to_non_existing_process, Msg, ToPid} ->
            io:format("~p sends ~p to non existing process ~p~n", [Pid, Msg, ToPid]);

        %% receive
        {trace, Pid, 'receive', Msg} ->
            io:format("~p receives ~p~n", [Pid, Msg]);

        %% call
        {trace, Pid, call, MFA} ->
            io:format("~p calls ~p~n", [Pid, MFA]);

        _ -> pass
    end,
    trace_loop().

loop(Count) ->
    receive
        pass ->
            loop(Count - 1);
        stop ->
            io:format("Count: ~p~n", [Count]),
            ok
    end.

sv_proc(TE) ->
    process_flag(trap_exit, TE),
    sv().

sv() ->
    receive
        {link, Pid} ->
            io:format("Link with ~p~n", [Pid]),
            link(Pid);
        {unlink, Pid} ->
            io:format("Unlink from ~p~n", [Pid]),
            unlink(Pid);
        {'EXIT', FromPid, Reason} ->
            io:format("Trap exit from ~p with reason ~p~n", [FromPid, Reason]);
        _Other ->
            io:format("Other message: ~p~n", [_Other])
    end,
    sv().

%% Functions call
fc_loop() ->
    receive
        local ->
            fc_test();
        external ->
            ?MODULE:fc_test_ext();
        lambda ->
            Fun = fun() -> ok end,
            Fun()
    end,
    fc_loop().

fc_test() ->
    ok.

fc_test_ext() ->
    ok.
