-module(gof).
%-export([]).
-compile(export_all).
-include_lib("eunit/include/eunit.hrl").

sets_test() ->
    ?assertEqual(true,sets:is_element(a, sets:from_list([a,b,c,d,d]))),
    ?assertEqual(false,sets:is_element(f, sets:from_list([a,b,c,d,d]))),
    ?assertEqual(true,true).


show(World) -> show(World,{0,0,20,20}).
show(World,{X,Y,W,H}) ->
    AllX = lists:seq(X,X+W-1),
    AllY = lists:seq(Y,Y+H-1),
    Symbol = fun(true) -> $+;(false) -> $ end,
    WhoIsAlive = fun({Nx,Ny}) -> Symbol(sets:is_element({Nx,Ny},World)) end,
    Grid = [ [ WhoIsAlive({Cx,Cy}) || Cx <- AllX ] || Cy <- AllY ],
    Grid.
show_test() ->
    ?assertEqual(["     ",
                  " ++  ",
                  "  +  ",
                  "   + ",
                  "     "],
                 show(sets:from_list([{1,1},{2,1},{2,2},{3,3}]),
                      {0,0,5,5})).

build(List) -> build(List,{0,0}).
build([],_) -> sets:new();
build([Line|Rest],{X,Y}) when is_list(Line),
                              is_integer(X),
                              is_integer(Y),
                              length(Line) > 0-> 
    Pairs = lists:zip(Line,lists:seq(0,length(Line)-1)),
    OnlyAlive = lists:filter(fun({$+,_})->true;(_)->false end, Pairs),
    Coordinate = lists:map( fun({_,I})->{X+I,Y} end, OnlyAlive ),
    World = sets:from_list(Coordinate),
    sets:union(World,build(Rest,{X,Y+1}));
build(_,_) -> sets:new().
build_test() ->
    ?assertEqual(sets:from_list([{1,1},{1,2},{2,2},{2,1}]),
                 build(["    ",
                        " ++ ",
                        " ++ ",
                        "    "])).


isnode({X,Y}) when is_integer(X), is_integer(Y) -> true;
isnode(_) -> false.
isnode_test() ->
    ?assertEqual(true,isnode({1,2})),
    ?assertEqual(true,isnode({-1,-2})),
    ?assertEqual(false,isnode({2})).

neighbor({X,Y}) when is_integer(X), is_integer(Y)-> 
    sets:from_list([{X+H,Y+V} || H <- [-1,0,1], V <- [-1,0,1], {H,V} =/= {0,0} ]).
neighbor_test() ->
    ?assertEqual(build(["+++","+ +","+++"],{-1,-1}),neighbor({0,0})).

empty(Cells,World) ->
    sets:filter(fun(Element) -> not sets:is_element(Element,World) end,Cells).
empty_test() ->
    [ ?assertEqual(build(Result), empty(build(View), build(World))) || 
      {Result,View,World} <- [ 
            {[],["+  ","   ","  +"],["+  "," + ","  +"]},
            {[],[],["+++","+++","+++"]},
            {["+ "," +"],["+ "," +"],[]},
            {["  "," +"],["+ ","++"],["++","+ "]},
            {[],["++","+ "],["++","+ "]}
      ]
    ].

alife(Cells,World) ->
    sets:intersection(Cells,World).
alife_test() ->
    [   ?assertEqual(build(Result),alife(build(View),build(World))) ||
        {Result,View,World} <- [
            {["+ ","+ "],["+ ","+ "],["++","++"]},
            {[],[],[a,b,c]},
            {[],[a,c],[]},
            {[a,c],[a,c,d],[a,b,c]},
            {[h],[h],[h,a,b,c]}
        ]
    ].

is_two_or_three(2) -> true;
is_two_or_three(3) -> true;
is_two_or_three(_) -> false.


survive(Cell,World) ->
    Around = neighbor(Cell),
    Neighbor = alife(Around,World),
    is_two_or_three(sets:size(Neighbor)).
survive_test() ->
    ?assertEqual(true, 
                 survive({1,1}, 
                         build(["++ ",
                                "++ ",
                                "   "]))),
    ?assertEqual(true, 
                 survive({1,1}, 
                         build(["   ",
                                "  +",
                                " + "]))),
    ?assertEqual(false, 
                 survive({1,1}, 
                         build(["   ",
                                "   ",
                                "+  "]))),
    ?assertEqual(false, 
                 survive({1,0},
                         build([" + ",
                                "   ",
                                " + "]))).

birth(Cell,World) ->
    Around = neighbor(Cell),
    Neighbor = alife(Around,World),
    sets:size(Neighbor) =:= 3.
birth_test() ->
    ?assertEqual(true,
                 birth({0,0},
                       sets:from_list([{1,1},{1,0},{0,1}]))),
    ?assertEqual(false,
                 birth({0,1},
                       sets:from_list([{1,1},{1,0},{0,1}]))),
    ?assertEqual(false,
                 birth({1,1},
                       sets:from_list([{1,1},{1,0},{0,1}]))).

born(Cell,World) ->
    Around = neighbor(Cell),
    Nests = empty(Around,World),
    sets:filter(fun(Element) -> birth(Element,World) end, Nests).
born_test() ->
    ?assertEqual(sets:from_list([{0,0}]),
                 born({1,1},
                      sets:from_list([{1,1},{1,0},{0,1}]))).

