:- ['5_flux'].

:- ['5_cleanbot_simulator'].

state_update(Z1, clean, Z2, []) :-
   holds(at(X,Y), Z1),
   update(Z1, [cleaned(X,Y)], [], Z2).

state_update(Z1, turn, Z2, []) :-
   holds(facing(D), Z1),
   (D#<4 #/\ D1#=D+1) #\/ (D#=4 #/\ D1#=1),
   update(Z1, [facing(D1)], [facing(D)], Z2).

state_update(Z1, go, Z2, [Light]) :-
   holds(at(X,Y), Z1),
   holds(facing(D), Z1),
   adjacent(X, Y, D, X1, Y1),
   update(Z1, [at(X1,Y1)], [at(X,Y)], Z2),
   light(X1, Y1, Light, Z2).

adjacent(X, Y, D, X1, Y1) :-
   [X,Y,X1,Y1] :: 1..5,
   D :: 1..4,
   (D#=1) #/\ (X1#=X) #/\ (Y1#=Y+1)      % north
   #\/
   (D#=2) #/\ (X1#=X+1) #/\ (Y1#=Y)      % east
   #\/
   (D#=3) #/\ (X1#=X) #/\ (Y1#=Y-1)      % south
   #\/
   (D#=4) #/\ (X1#=X-1) #/\ (Y1#=Y).     % west

light(X, Y, Percept, Z) :-
   X_east #= X+1, X_west #= X-1, Y_north #= Y+1, Y_south #= Y-1,
   ( Percept = false,
       not_holds(occupied(X,Y), Z),
       not_holds(occupied(X_east,Y), Z), not_holds(occupied(X,Y_north), Z),
       not_holds(occupied(X_west,Y), Z), not_holds(occupied(X,Y_south), Z)
     ;
     Percept = true,
       or_holds([occupied(X,Y),
                 occupied(X_east,Y),occupied(X,Y_north),
                 occupied(X_west,Y),occupied(X,Y_south)], Z) ).

init(Z0) :-
   Z0= [at(4,5), facing(_) |Z],
        or_holds([facing(2), facing(3)], Z0),
   not_holds(occupied(1,1), Z), not_holds(occupied(2,1), Z),
   not_holds(occupied(1,2), Z), not_holds(occupied(2,2), Z),
   not_holds(occupied(3,2), Z), not_holds(occupied(4,2), Z),
   not_holds(occupied(4,3), Z), not_holds(occupied(4,4), Z),
   not_holds(occupied(4,5), Z), not_holds(occupied(3,5), Z),
   not_holds(occupied(2,5), Z), not_holds(occupied(1,5), Z),
   not_holds_all(cleaned(_,_), Z),
   consistent(Z0).

consistent(Z) :-
   holds(at(X,Y),Z,Z1) -> [X,Y]::[1..5], not_holds_all(at(_,_),Z1),
   holds(facing(D),Z,Z2) -> [D]::[1..4], not_holds_all(facing(_),Z2),
   not_holds_all(occupied(_,0), Z),
   not_holds_all(occupied(_,6), Z),
   not_holds_all(occupied(0,_), Z),
   not_holds_all(occupied(6,_), Z),
   duplicate_free(Z).

main :- init(Z0),
        execute(clean, Z0, Z1),
        Choicepoints = [[1,2,3,4]], Backtrack = [], 
        main_loop(Choicepoints, Backtrack, Z1).

main_loop([Choices|Choicepoints], Backtrack, Z) :-
   Choices = [Direction|Directions] ->
   ( go_in_direction(Direction, Z, Z1)
     -> execute(clean, Z1, Z2),
        Choicepoints1 = [[1,2,3,4], Directions | Choicepoints],
        Backtrack1 = [Direction | Backtrack],
        main_loop(Choicepoints1, Backtrack1, Z2)
     ;
     main_loop([Directions|Choicepoints], Backtrack, Z) )
   ;
   backtrack(Choicepoints, Backtrack, Z).

go_in_direction(D, Z1, Z2) :-
   knows_val([X,Y], at(X,Y), Z1),
   adjacent(X, Y, D, X1, Y1),
   \+ knows(cleaned(X1,Y1), Z1),
   knows_not(occupied(X1,Y1), Z1),
   turn_to_go(D, Z1, Z2).

backtrack(_, [], _).
backtrack(Choicepoints, [Direction|Backtrack], Z) :-
   Reverse is (Direction+1) mod 4 + 1,
   turn_to_go(Reverse, Z, Z1),
   main_loop(Choicepoints, Backtrack, Z1).

turn_to_go(D, Z1, Z2) :-
   knows(facing(D), Z1) -> execute(go, Z1, Z2) ;
   execute(turn, Z1, Z), turn_to_go(D, Z, Z2).
