-module(day18).

-export ([solve/0, solve/1]).
-compile ([export_all]).

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

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

solution1() ->
    {ok, IO} = file:open("input.txt", 'read'),
    Ans = maybe_reduce(read_line(IO), IO),
    file:close(IO),
    magnitude(Ans).
    
solution2() ->
    {ok, IO} = file:open("input.txt", 'read'),
    Numbers = read_all_lines(read_line(IO), IO, []),
    file:close(IO),
    io:format("total:~p lines: ~p~n", [length(Numbers), Numbers]),
    max_magnitude(Numbers).

read_all_lines('eof', _IO, Acc) ->
    Acc;
read_all_lines(Line, IO, Acc) ->
    read_all_lines(read_line(IO), IO, [Line|Acc]).

read_line(IO) ->
    case file:read_line(IO) of
        {ok, Line} ->
            {ok, Tokens, _} = erl_scan:string(Line ++ "."),
            {ok, Term} = erl_parse:parse_term(Tokens),
            Term;
        'eof' -> 'eof'
    end.

max_magnitude(Numbers) when is_list(Numbers) ->
    lists:max([ max_magnitude(lists:split(I, Numbers)) || I <- lists:seq(1, length(Numbers))]);
max_magnitude({List1, List2}) ->
    [H|T] = lists:reverse(List1),
    max_magnitude(H, T ++ List2).

max_magnitude(Number, Rest) -> 
    lists:foldl(fun(N, MaxMag) ->
        case magnitude(reduce([Number, N])) of
            Mag  when Mag > MaxMag -> Mag;
            _ -> MaxMag
        end
        end, 0, Rest).
    

magnitude(A) when is_integer(A) -> A;
magnitude([A,B]) ->
    (3 * magnitude(A)) + (2 * magnitude(B)).

maybe_reduce(A, IO) ->
    case read_line(IO) of
        'eof' -> A;
        B ->
            maybe_reduce(reduce([A,B]), IO)
    end.

reduce(N) ->
    case maybe_explode(N, 0) of
        {true, New, _, _} ->
            reduce(New);
        false ->
            case maybe_split(N) of
                {true, New} ->
                    reduce(New);
                false ->
                    N
            end
    end.

maybe_explode([A,B], Depth) when Depth >= 4, is_integer(A), is_integer(B) ->
    {true, 0, A, B};
maybe_explode([A,B], Depth) ->
    case maybe_explode(A, Depth+1) of
        {true, New, AddA, AddB} ->
            {true, [New, add_explode({b, AddB}, B)], AddA, 0};
        false ->
            case maybe_explode(B, Depth+1) of
                {true, New, AddA, AddB} ->
                    {true, [add_explode({a, AddA}, A), New], 0, AddB};
                false ->
                    false
            end
    end;
maybe_explode(N, _) when is_integer(N) ->
    false.

add_explode({_, Add}, Num) when is_integer(Num) ->
    Add + Num;
add_explode({b, Add}, [A,B]) ->
    [add_explode({b, Add}, A), B];
add_explode({a, Add}, [A,B]) ->
    [A, add_explode({a,Add}, B)].

maybe_split(N) when is_integer(N) ->
    case N > 9 of
        true ->
            Left = N div 2,
            Right = case Left*2 == N of true -> Left; false -> Left+1 end,
            {true, [Left, Right]};
        false ->
            false
    end;
maybe_split([A, B]) ->
    case maybe_split(A) of
        {true, NewA} ->
            {true, [NewA, B]};
        false ->
            case maybe_split(B) of
                {true, NewB} ->
                    {true, [A, NewB]};
                false ->
                    false
            end
    end.