--- /dev/null
+-module(life_observer).
+-behaviour(gen_event).
+
+
+%% API
+-export([start_link/2
+ ,register_with_logger/0
+ ,add_handler/2
+ ,delete_handler/0
+ ,log_generation/4
+ ]).
+
+%% Callbacks
+-export([init/1
+ ,terminate/2
+ ,code_change/3
+ ,handle_event/2
+ ,handle_call/2
+ ,handle_info/2
+ ]).
+
+
+-define(EVENT_MGR_REF, ?MODULE).
+-define(HANDLER, ?MODULE).
+
+-define(PATH_DIR__DATA, "data").
+-define(CSV_DELIMITER, ",").
+
+
+-record(state, {log_file :: file:io_device()}).
+
+
+%% ============================================================================
+%% API
+%% ============================================================================
+
+start_link(X, Y) ->
+ EventMgrName = {local, ?EVENT_MGR_REF},
+ {ok, PID} = gen_event:start_link(EventMgrName),
+ ok = add_handler(X, Y),
+ {ok, PID}.
+
+
+register_with_logger() ->
+ error_logger:add_report_handler(?HANDLER).
+
+
+add_handler(X, Y) ->
+ Args = [X, Y],
+ gen_event:add_handler(?EVENT_MGR_REF, ?HANDLER, Args).
+
+
+delete_handler() ->
+ Args = [],
+ gen_event:delete_handler(?EVENT_MGR_REF, ?HANDLER, Args).
+
+
+log_generation(GenID, GenDuration, Dead, Alive) ->
+ Event = {generation, GenID, GenDuration, Dead, Alive},
+ gen_event:notify(?EVENT_MGR_REF, Event).
+
+
+%% ============================================================================
+%% Callbacks (unused)
+%% ============================================================================
+
+code_change(_Old, State, _Other) -> {ok, State}.
+handle_call(_Request, State) -> Reply = ok, {ok, Reply, State}.
+handle_info(_Info, State) -> {ok, State}.
+
+
+%% ============================================================================
+%% Callbacks
+%% ============================================================================
+
+init([X, Y]) ->
+ N = X * Y,
+ {{Year, Month, Day}, {Hour, Min, Sec}} = calendar:local_time(),
+ DateTime = lists:flatten(io_lib:format(
+ "~4.10.0B-~2.10.0B-~2.10.0B--~2.10.0B:~2.10.0B:~2.10.0B",
+ [Year, Month, Day, Hour, Min, Sec]
+ )),
+
+ FileNameComponents = [DateTime | [integer_to_list(I) || I <- [N, X, Y]]],
+ FileName = string:join(FileNameComponents, "--") ++ ".csv",
+ FilePath = filename:join(?PATH_DIR__DATA, FileName),
+
+ ok = validate_makedir(file:make_dir(?PATH_DIR__DATA)),
+ {ok, LogFile} = file:open(FilePath, write),
+
+ CSVHeaders = ["Generation", "Duration", "Dead", "Alive"],
+ ok = do_write(LogFile, list_to_csvline(CSVHeaders)),
+
+ {ok, #state{log_file=LogFile}}.
+
+
+terminate(_Reason, #state{log_file=LogFile}=State) ->
+ file:close(LogFile),
+ {ok, State}.
+
+
+handle_event({generation, GenID, GenDuration, Dead, Alive},
+ #state{log_file=LogFile}=State) ->
+
+ Values = [integer_to_list(GenID)
+ ,float_to_string(GenDuration)
+ ,integer_to_list(Dead)
+ ,integer_to_list(Alive)
+ ],
+ ok = do_write(LogFile, list_to_csvline(Values)),
+ {ok, State}.
+
+
+%% ============================================================================
+%% Internal
+%% ============================================================================
+
+validate_makedir(ok) -> ok;
+validate_makedir({error, eexist}) -> ok;
+validate_makedir(Other) -> Other.
+
+
+do_write(File, Line) ->
+ ok = io:format(File, "~s~n", [Line]).
+
+
+list_to_csvline(List) ->
+ string:join(List, ?CSV_DELIMITER).
+
+
+float_to_string(Float) ->
+ io_lib:format("~f", [Float]).