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

-compile(export_all).

-export ([solve/0, solve/1, solve/2]).
-export ([read_input/0]).

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'),
    {ok, Line}  = file:read_line(IO),
    file:close(IO),
    hexstr_to_bin(string:strip(Line, 'both', $\n)).

hexstr_to_bin(S) ->
  hexstr_to_bin(S, []).
hexstr_to_bin([], Acc) ->
  list_to_binary(lists:reverse(Acc));
hexstr_to_bin([X,Y|T], Acc) ->
  {ok, [V], []} = io_lib:fread("~16u", [X,Y]),
  hexstr_to_bin(T, [V | Acc]);
hexstr_to_bin([X|T], Acc) ->
  {ok, [V], []} = io_lib:fread("~16u", lists:flatten([X,"0"])),
  hexstr_to_bin(T, [V | Acc]).

bin_to_hexstr(Bin) ->
  lists:flatten([io_lib:format("~2.16.0B", [X]) ||
    X <- binary_to_list(Bin)]).

solution1(Input) ->
  D = unpack(Input, [], not is_zero_bitstring(Input)),
  io:format("Pkt: ~p~n", [D]),
  count_versions(D, 0).

solution2(Input) ->
  D = unpack(Input, [], not is_zero_bitstring(Input)),
  io:format("Pkt: ~p~n", [D]),
  calc(D).

calc({_,literal, V}) ->
  V;
calc({_,Op, V}) ->
  ?MODULE:Op(V);
calc([{_,Op, V}]) ->
  ?MODULE:Op(V).

sum(Args) ->
  io:format("sum: ~p~n", [Args]),
  lists:foldl(fun(X, Acc) -> calc(X) + Acc end, 0, Args).

product(Args) ->
  io:format("product: ~p~n", [Args]),
  lists:foldl(fun(X, Acc) -> calc(X) * Acc end, 1, Args).

min(Args) ->
  io:format("min: ~p~n", [Args]),
  lists:min([calc(X) || X <- Args]).

max(Args) ->
  io:format("max: ~p~n", [Args]),
  lists:max([calc(X) || X <- Args]).

lt([A,B] = Args) ->
  io:format("lt: ~p~n", [Args]),
  case calc(A) < calc(B) of
    'true' -> 1;
    'false' -> 0
  end.

gt([A,B] = Args) ->
  io:format("gt: ~p~n", [Args]),
  case calc(A) > calc(B) of
    'true' -> 1;
    'false' -> 0
  end.

eq([A,B] = Args) ->
  io:format("eq: ~p~n", [Args]),
  case calc(A) == calc(B) of
    'true' -> 1;
    'false' -> 0
  end.

count_versions([{Ver, Type, Rest}|T], Acc)  ->
    count_versions(T, count_versions(Rest, Acc) + Ver);
count_versions(_, Acc) ->
  Acc.

is_zero_bitstring(BitString) ->
    Size = erlang:bit_size(BitString),
    <<0:Size>> =:= BitString.

unpack(Data, [], 'false') ->
  Data;
unpack(Input, Acc, 'false') ->
  Acc;

unpack(Input, Acc, 'true') ->
  {{Ver, Type, Data}, Rest} = unpack(Input),
  io:format("unpacking data: ~p~n", [Data]),
  lists:reverse(unpack(Rest, Acc ++ [{Ver, Type, unpack(Data, [], is_bitstring(Data) andalso not is_zero_bitstring(Data))}], not is_zero_bitstring(Rest))).

%% Literal
unpack(<<V:3, 4:3, Rest/bitstring>>) ->
  io:format("V:~p, T:literal~n", [V]),
  unpack_literal(V, Rest);

%% Operator length type id 0
unpack(<<V:3, T:3, 0:1, Rest/bitstring>>) ->
  io:format("V:~p, T:operator I:0~n", [V]),
  unpack_operator_0(V, T, Rest);

%% Operator length type id 1
unpack(<<V:3, T:3, 1:1, Rest/bitstring>>) ->
  io:format("V:~p, T:operator I:1~n", [V]),
  unpack_operator_1(V, T, Rest).

unpack_literal(V, Pkt) ->
    unpack_literal(V, Pkt, <<>>, 0). 

unpack_literal(V, <<1:1, Value:4, Rest/bitstring>>, Acc, Count) ->
    unpack_literal(V, Rest, <<Acc/bitstring, Value:4>>, Count + 1);
unpack_literal(V, <<0:1, Value:4, Rest/bitstring>>, Acc, Count) ->
    L = <<Acc/bitstring, Value:4>>,
    Size = (Count + 1) * 4,
    io:format("unpack_literal value: ~p size:~p rest:~p~n", [Value, Size, Rest]),
    <<X:(Size)>> = L,
    {{V, literal, X}, Rest}. 

unpack_operator_0(V, T, <<Length:15, Rest0/bitstring>>) ->
    io:format("operator0 length:~p~n", [Length]),
    <<Pkt:(Length)/bitstring, Rest/bitstring>> = Rest0,
    {Rest1, Decoded} = unpack_len(Length, Rest0, []),
    {{V, operator_type(T), Decoded}, Rest1}.

unpack_operator_1(V, T, <<Num:11, Rest/bitstring>>) ->
    {Rest1, Decoded} = unpack_count(Num, Rest, []),
    io:format("operator1 num of packets:~p Decoded:~p, Rest:~p~n", [Num,Decoded,Rest1]),
    {{V, operator_type(T), Decoded}, Rest1}.

unpack_len(0, Pkt, Acc) ->
  {Pkt, Acc};
unpack_len(Length, Pkt, Acc) ->
  {L, R}   = unpack(Pkt),
  io:format("unpack_len: length:before ~p after: ~p~n", [Length, Length - bit_size(Pkt) + bit_size(R)]),
  unpack_len(Length - bit_size(Pkt) + bit_size(R), R, Acc ++ [L]).

unpack_count(0, Pkt, Acc) ->
  {Pkt, Acc};
unpack_count(Num, Pkt, Acc) ->
  {L, R} = unpack(Pkt),
  unpack_count(Num - 1, R, Acc ++ [L]).

operator_type(0) -> 'sum';
operator_type(1) -> 'product';
operator_type(2) -> 'min';
operator_type(3) -> 'max';
operator_type(5) -> 'gt';
operator_type(6) -> 'lt';
operator_type(7) -> 'eq'.
