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

-export ([solve/0, solve/1, solve/2]).
-export ([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' -> Input;
		Line -> read_input(IO, Input ++ [Line])
	end.

read_line(IO) ->
	case file:read_line(IO) of
		'eof' -> 'eof';
		{ok, Line} -> [ X - $0 || X <- Line, [X] /= "\n"]
	end.

solution1(Input) ->
    Graph = astar:graph(Input),
    astar:search(Graph, {0, 0}, {99, 99}).

solution2(Input) ->
    Graph = astar:graph(Input),
    astar:search(Graph, {0, 0}, {499, 499}).

coords([H|_] =  Table) ->
  X = length(H),
  Y = length(Table),
  lists:flatten([[{A,B} || A <- lists:seq(1,X)] || B <- lists:seq(1,Y)]).


%init_data({X,Y}, Table) when X =< 100 andalso Y =< 100 ->
%    lists:nth(X, lists:nth(Y, Table));
init_data({X,Y}, Table) ->
    X1 = 
    case (X rem 100) of
        0 -> 100;
        A -> A
    end,
    Y1 =
    case (Y rem 100) of
        0 -> 100;
        B -> B
    end,
    D = lists:nth(X1, lists:nth(Y1, Table)),
    D1 = (D + ((X-1) div 100) + ((Y-1) div 100)) rem 9,
    D2 = 
    case D1 of
	    0 -> 9;
	    C -> C
    end,
%%    io:format("~p -> ~p -> ~p~n", [{X,Y},{X1,Y1},{D,D2}]),
    D2.

get_value({X,Y}, _Table)  when X < 1; Y < 1; X > 500; Y > 500 -> 'invalid';
get_value({X,Y}, Table) ->
%%        {C, V} = lists:keyfind(C, 1, Data),
%%       V.
    X1 =
    case (X rem 100) of
        0 -> 100;
        A -> A
    end,
    Y1 =
    case (Y rem 100) of
        0 -> 100;
        B -> B
    end,
    D = lists:nth(X1, lists:nth(Y1, Table)),
    D1 = (D + ((X-1) div 100) + ((Y-1) div 100)) rem 9,
    D2 =
    case D1 of
            0 -> 9;
            C -> C
    end,
%%    io:format("~p -> ~p -> ~p~n", [{X,Y},{X1,Y1},{D,D2}]),
    D2.

get_neighbors({X,Y}, Data) ->
    Neigbours = [{X+1,Y},{X-1,Y},{X,Y+1},{X,Y-1}],
    lists:filter(fun({_,E}) -> E /= 'invalid' end, [{C, get_value(C, Data)} || C <- Neigbours]).

build_graph(Coords, Data) ->
	Graph = digraph:new(),
	[digraph:add_vertex(Graph, C) || C <- Coords],
	add_edges(Graph, Data, Coords).

make_coords(Coords, Loop) ->
	[{X  * Loop , Y * Loop} || {X,Y} <- Coords].

add_edges(Graph, Data, []) -> Graph;
add_edges(Graph, Data, [C|Rest]) ->
    Neighbors = get_neighbors(C, Data),
    [digraph:add_edge(Graph, C, X, V) || {X,V} <- Neighbors],
    add_edges(Graph, Data, Rest).

dijkstra(Graph,Start_node_name) ->
    Paths = dict:new(),
    Unvisited = gb_sets:new(),
    Unvisited_nodes = gb_sets:insert({0,Start_node_name,root},Unvisited),
    Paths_updated = loop_through_nodes(Graph,Paths,Unvisited_nodes),
    Paths_updated.


loop_through_nodes(Graph,Paths,Unvisited_nodes) ->
    %% We need this condition to stop looping through the Unvisited nodes if it is empty
    case gb_sets:is_empty(Unvisited_nodes) of
        false -> 
            {{Current_weight,Current_name,Previous_node}, Unvisited_nodes_updated} = gb_sets:take_smallest(Unvisited_nodes),
              case dict:is_key(Current_name,Paths) of
                false ->
                    Paths_updated = dict:store(Current_name,{Previous_node,Current_weight},Paths),
                    Out_edges = digraph:out_edges(Graph,Current_name),
                    Unvisited_nodes_updated_2 = loop_through_edges(Graph,Out_edges,Paths_updated,Unvisited_nodes_updated,Current_weight),
                    loop_through_nodes(Graph,Paths_updated,Unvisited_nodes_updated_2);
                true ->
                    loop_through_nodes(Graph,Paths,Unvisited_nodes_updated)
	    end;
        true -> 
            Paths
    end.

loop_through_edges(Graph,[],Paths,Unvisited_nodes,Current_weight) ->
    Unvisited_nodes;

loop_through_edges(Graph,Edges,Paths,Unvisited_nodes,Current_weight) ->
    [Current_edge|Rest_edges] = Edges,
    {Current_edge,Current_node,Neighbour_node,Edge_weight} = digraph:edge(Graph,Current_edge),
    case dict:is_key(Neighbour_node,Paths) of
        false ->
            Unvisited_nodes_updated = gb_sets:insert({Current_weight+Edge_weight,Neighbour_node,Current_node},Unvisited_nodes),
            loop_through_edges(Graph,Rest_edges,Paths,Unvisited_nodes_updated,Current_weight);
        true ->
            loop_through_edges(Graph,Rest_edges,Paths,Unvisited_nodes,Current_weight)
    end.
