-module(counter).
-export([new/0, incr/2, tracker/0, counter/0, count/2, counting/1, counts/1]).

%- Called from outside -------------------
new() ->
    Table = ets:new(tracker, [set, public]),
    Tracker = spawn(fun counter:tracker/0),
    Tracker ! { table, Table },
    { Tracker, Table }.

incr(Whatever, { Tracker, Table }) ->
    Counter = find_counter(Whatever, Tracker, Table),
    Counter ! { incr }.

count(Whatever, { _, Tracking }) ->
    Lookup = ets:lookup(Tracking, Whatever),
    find_count(Lookup).

counting({ _, Tracking }) ->
    Table = ets:tab2list(Tracking),
    lists:map(fun({ K, _ }) -> K end, Table).

counts({ _, Tracking} ) ->
    Table = ets:tab2list(Tracking),
    lists:map(fun({ K, _ }) -> { K, counter:count(K, { 0, Tracking }) } end, Table).

%- Internal ------------------------------

find_count([]) ->
    0;
find_count([{ _, Counter }]) ->
    Counter ! { report, self() },
    receive
	{ count, Count } ->
	    Count
    end.


find_counter(Whatever, Tracker, Table) ->
    case ets:lookup(Table, Whatever) of
	[ { _, Counter } ] -> Counter;
	[] ->
	    Counter = spawn(fun counter:counter/0),
	    Tracker ! { pid, Whatever, Counter },
	    Counter
    end.

%- The Pid-tracker process ---------------
tracker() ->
    tracker_loop([], 0).

tracker_loop(Tracking, Table) ->
    receive
	{ table, T } -> tracker_loop(Tracking, T);
	{ pid, Whatever, Pid } ->
	    ets:insert(Table, { Whatever, Pid }),
	    tracker_loop([Pid | Tracking], Table);
	{ report, To } ->
	    To ! Tracking,
	    tracker_loop(Tracking, Table)
    end.

%- The counter process -------------------
counter() ->
    counter_loop(0).

counter_loop(Count) ->
    receive
	{ incr } ->
	    counter_loop(Count + 1);
	{ report, To } ->
	    To ! { count, Count },
	    counter_loop(Count)
    end.