%% to compile: erlc day3A.erl
%% to run: erl -noshell -s day5 solve
%%
-module(day14).

-export ([solve/0, solve/1, solve/2]).
-export ([expand_pair/4, read_input/0]).
-compile ([export_all]).

solve() ->
    solve(['1']),
    solve(['2']),
    init:stop().

solve(A) ->
    solve(A, read_input()).

solve(['1'], D) ->
    io:format("The solution to ~p puzzle1 is: ~p~n", [?MODULE, solve(1, D)]);
solve(1, D) ->
    solution1(D);
solve(['2'], D) ->
    io:format("The solution to ~p puzzle2 is: ~p~n", [?MODULE, solve(2, D)]);
solve(2, D) ->
    solution2(D).

read_input() ->
    {ok, IO} = file:open("input.txt", 'read'),
    Data = read_input(IO),
    file:close(IO),
    Data.

read_input(IO) ->
	read_input(IO, []).

read_input(IO, Input) ->
    case read_line(IO) of
	'eof' -> make_map(Input);
	Line -> read_input(IO, Input ++ [Line])
    end.

read_line(IO) ->
	case file:read_line(IO) of
		'eof' -> 'eof';
		{ok, Line} -> parse_line(Line)
	end.

parse_line(Line) ->
    string:tokens(Line, " ->\n").

make_map(Input) -> make_map(Input, maps:new()).

make_map([], Map) -> Map;
make_map([[P, [Res]]|Rest], Map) ->
	NewMap = maps:put(P, Res, Map),
        make_map(Rest, NewMap).

solution1(PolyMap) ->
   Ets = ets:new(polymer, [set, public, named_table]),
   Polymer = "FNFPPNKPPHSOKFFHOFOC",
   init_counters(Ets, PolyMap),
   io:format("~p~n", [ets:tab2list(Ets)]),
   Pairs = make_pairs(Polymer, []),
   io:format("expand pairs: ~p~n",[Pairs]),
   count_pairs(Pairs, Ets),
   [count_elems(Pair, Ets, 1) || Pair <- Pairs],
   loop(Ets, PolyMap, 10),
   get_max_elm(Ets) - get_min_elm(Ets).

solution2(PolyMap) ->
   Ets = ets:new(polymer, [set, public, named_table]),
   Polymer = "FNFPPNKPPHSOKFFHOFOC",
   init_counters(Ets, PolyMap),
   io:format("~p~n", [ets:tab2list(Ets)]),
   Pairs = make_pairs(Polymer, []),
   io:format("expand pairs: ~p~n",[Pairs]),
   count_pairs(Pairs, Ets),
   [count_elems(Pair, Ets, 1) || Pair <- Pairs],
   loop(Ets, PolyMap, 40),
   get_max_elm(Ets) - get_min_elm(Ets).

get_max_elm(Ets) ->
    Ents = ets:tab2list(Ets),
    {V, _} = lists:max([{V,X} || {{elem, X},V} <- Ents]),
    V.

get_min_elm(Ets) ->
    Ents = ets:tab2list(Ets),
    {V, _} = lists:min([{V,X} || {{elem, X},V} <- Ents]),
    V.

loop(Ets, _PolyMap, 0) -> ets:tab2list(Ets);
loop(Ets, PolyMap, Count) ->
	Counters = get_counters(Ets),
	[update_ets(Ets, PolyMap, Counter) || Counter <- Counters],
	loop(Ets, PolyMap, Count - 1).

init_counters(Ets, PolyMap) ->
     maps:foreach(fun([X,Y] = Key, _Value) -> ets:insert(Ets, {{elem,X},0}), ets:insert(Ets, {{elem,Y},0}), ets:insert(Ets, {{pair, Key}, 0}) end, PolyMap).

update_ets(_Ets, _PolyMap, {_Key, 0}) -> ok;
update_ets(Ets, PolyMap, {[X,Y] = Key, Value}) ->
	V = maps:get(Key, PolyMap),
	count_pair([X,V],Ets, Value),
	count_pair([V,Y],Ets, Value),
	count_pair(Key,Ets, -1 * Value),
	count_elem(V, Ets, Value).
	
get_counters(Ets) ->
    Ents = ets:tab2list(Ets),
    [{X,V} || {{pair, X},V} <- Ents].

make_pairs([_], Pairs) -> Pairs;
make_pairs([X,Y|Rest], Pairs) -> make_pairs([Y|Rest], [[X,Y]|Pairs]).

count_pairs([], _Ets) -> ok;
count_pairs([P|Rest], Ets) ->
	count_pair(P, Ets, 1),
	count_pairs(Rest, Ets).

count_pair(P, Ets, V) ->
	ets:update_counter(Ets, {pair,P}, {2,V}).

count_elems([X,Y], Ets, V) ->
	count_elem(X, Ets, V),
	count_elem(Y, Ets, V).

count_elem(Elem, Ets, V) ->
	ets:update_counter(Ets, {elem,Elem}, {2,V}).

expand_pair(_P, _PolyMap, _Ets, 0) -> ok;
expand_pair([P1,P2], PolyMap, Ets, Count) when is_list(P1), is_list(P2) ->
	expand_pair(P1,PolyMap, Ets, Count - 1);
expand_pair([X,Y] = P, PolyMap, Ets, Count) ->
	[V] = maps:get(P, PolyMap),
	ets:update_counter(Ets, [V], {2,1}),       
	[expand_pair(Pa, PolyMap, Ets, Count) || Pa <- [[X,V]] ++ [[V,Y]]].


pairs([], _PolyMap, Pairs, _Ets, 0) ->
	Pairs;
pairs([], PolyMap, Pairs, Ets, Count) ->
	io:format("new pairs: ~p~n", [Pairs]),
	pairs(Pairs, PolyMap, [], Ets, Count - 1);
pairs([[X,Y] = P|Rest], PolyMap, Pairs, Ets, Count) ->
	[V] = maps:get(P, PolyMap),
	ets:update_counter(Ets, [V], {2,1}),       
	pairs(Rest, PolyMap, Pairs ++ [[X,V]] ++ [[V,Y]], Ets, Count).

count(Polymer) ->
	Ps = lists:usort(Polymer),
	[ {count_p(P, Polymer), [P]} || P  <- Ps].

count_p(P, Polymer) ->
	lists:foldl(fun(X, Acc) -> case X == P of 'true' -> Acc + 1; _ -> Acc end end, 0, Polymer).

steps(Polymer, _PolyMap, 0) -> Polymer;
steps(Polymer, PolyMap, Count) ->
   Insert = get_insert_list(Polymer, PolyMap),
   steps(lists:flatten(lists:zipwith(fun(A, B) -> [A,B] end, Polymer, Insert)), PolyMap, Count - 1).

get_insert_list(Polymer, PolyMap) ->
	get_insert_list(Polymer, PolyMap, []).

get_insert_list([_], _PolyMap, InsertList) -> lists:reverse([[]|InsertList]);
get_insert_list([P1,P2|Rest], PolyMap, InsertList) ->
	V = maps:get([P1,P2], PolyMap),
	get_insert_list([P2|Rest], PolyMap, [V|InsertList]).
