:-use_module(library(clpfd)).
:-use_module(library(lists)).

pf(Primary ) :- % Primary = [h,p,p,h,p,p,h,...] p/0 h/1
       statistics(runtime,_),
       constrain(Primary,Tertiary,Energy),
       labeling([ff,minimize(Energy)],Tertiary),
       statistics(runtime,[_,T]),
       write('Tempo:'),write(T),nl,
       write('Energy: '),write(Energy),nl,
       write(Tertiary),nl,
       print_solution(Primary,Tertiary).
pf(_) :-
       statistics(runtime,[_,T]),
       write('no solutions: time '),write(T),nl.

% Tertiary = [X1,Y1,X2,Y2,X3,Y3,...]

constrain(Primary,Tertiary,Energy) :- 
       length(Primary,N),
       M is 2*N,
       length(Tertiary,M),
       Min is 0, %  N - integer(sqrt(N)), % 0
       Max is M, %  N + integer(sqrt(N)),   % 2*N
       domain(Tertiary,Min,Max),
       starting_point(Tertiary,N),
       avoid_self_loops(Tertiary),
       next_constraints(Tertiary),
       energy_constraint(Primary,Tertiary,Energy).

starting_point([N,N,N,N1|T],N) :-
      N1 is N + 1.
%      all_up([N,N,N,N1|T], Up, N, N),
%      lex_chain([[N,N,N,N1|T],Up]).   % Mats - break symmetry

avoid_self_loops(Tertiary) :-
       positions_to_integers(Tertiary, ListaInteri),
       all_distinct(ListaInteri).

positions_to_integers([X,Y|R], [I|S]):-
       I  #= X*100+Y,
       positions_to_integers(R,S).
positions_to_integers([],[]).

next_constraints([_,_]).
next_constraints([X1,Y1,X2,Y2|C]) :-
       next(X1,Y1,X2,Y2),
       next_constraints([X2,Y2|C]).

next(X1,Y1,X2,Y2):-
       domain([Dx,Dy],0,1),
       Dx #= abs(X1-X2),
       Dy #= abs(Y1-Y2),
       Dx + Dy #= 1.

energy_constraint([_],_,0).
energy_constraint([A,B|Primary],[XA,YA,XB,YB|Tertiary],E) :-
       energy_contribution_of_A(0,A,XA,YA,Primary,Tertiary,E1),
       energy_constraint([B|Primary],[XB,YB|Tertiary],E2),
       E #= E1 + E2.

energy_contribution_of_A(_,_,_,_,[],[],0).
energy_contribution_of_A(0,A,XA,YA,[_|Primary],[_,_|Tertiary],E):-
       energy_contribution_of_A(1,A,XA,YA,Primary,Tertiary,E).
energy_contribution_of_A(1,A,XA,YA,[B|Primary],[XB,YB|Tertiary],E):-
       energy(A,XA,YA,B,XB,YB,C),
       energy_contribution_of_A(0,A,XA,YA,Primary,Tertiary,E1),
       E #= E1 + C.

energy(h,XA,YA,h,XB,YB,C) :-
       C in -1..0,
       DX #= abs(XA - XB),
       DY #= abs(YA - YB),
       1 #= DX + DY  #<=> C #= -1.
energy(h,_,_,p,_,_,0).
energy(p,_,_,h,_,_,0).
energy(p,_,_,p,_,_,0).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

all_up([], [], _, _).
all_up([_,_|Tert], [X,Y|Up], X, Y) :-
    Y1 is Y+1,
    all_up(Tert, Up, X, Y1).

%avoid_self_loops(Tertiary):-
%    positions_to_rectangles(Tertiary, Rectanges),
%    disjoint2(Rectanges).

%positions_to_rectangles([],[]) :- !.
%positions_to_rectangles([X,Y|R], [r(X,1,Y,1)|S]):-
%    positions_to_rectangles(R,S).

print_solution(Primary,Tertiary) :-
     length(Tertiary,N2),
     M is N2*N2,
     length(Matrix,M),
     scan(0,0,N2,Primary,Tertiary,Matrix),
     write_matrix(Matrix,N2).

scan(N,_,N,_,_,_) :- !.
scan(I,N,N,Primary,Tertiary,Matrix) :-
    !,
    I1 is I+1,
    scan(I1,0,N,Primary,Tertiary,Matrix).
scan(I,J,N,Primary,Tertiary,Matrix) :-
    N is 2*I,
    N is 2*J,
    !,  %%% caso particolare primo punto
    nth0(0,Primary,AMINO),
    (AMINO == h, POL = 'H';
     AMINO == p, POL = 'P'),
    K is I*N+J, %%% 
    nth0(K,Matrix,POL),
    J1 is J+1,
    scan(I,J1,N,Primary,Tertiary,Matrix).
scan(I,J,N,Primary,Tertiary,Matrix) :-
    (member_pair(I,J,Tertiary,Val),
     nth0(Val,Primary,POL),
     !;
     POL = ' '),
    K is I*N+J, %%% 
    nth0(K,Matrix,POL),
    J1 is J+1,
    scan(I,J1,N,Primary,Tertiary,Matrix).
        
%%%%
member_pair(I,J,[I,J|_],0) :- !.
member_pair(I,J,[_,_|R],K) :-
    member_pair(I,J,R,K1),
    K is K1 + 1.
      
%%%% 

write_matrix([],_) :-!.
write_matrix(Matrix,N) :-
   write_row(Matrix,Rest,N),
   write_matrix(Rest,N).

write_row(Matrix,Rest,N) :-
   length(First,N),
   append(First,Rest,Matrix),
   (null_row(First),!;
    write_vector(First),nl).   
   
write_vector([]) :-!.
write_vector([A|R]) :-
     write(A),
     write_vector(R).

null_row([]).
null_row([' '|R]) :-
     null_row(R).     
     
