%%%%%%%%%%%%
%
%    unify(F1, F2, Subst_in, Subst_out) - attempt to unify F1 and F2
%    under substitution Subst_in; if success, Subst_out is the unifying
%    substitution.  Commutative unification
%    is handled.  The set of most general unifiers is generated through
%    backtracking.
%    
%    Variables are identified by `tpvar(T)';
%    Commutative functions are identified by `commutative(F)'.
%
%%%%%%%%%%%%

unify(F1, F2, Sin, [V,Tout]) :-
    unify_new(F1, F2, Sin, [V,T]),
    apply_subst(T, [V,T], Tout).

unify_new(F1, F2, Sin, Sin) :-
    F1 == F2,
    !.
unify_new(F1, F2, Sin, Sout) :-
    deref(F1, Sin, T1),  % dereference if a variable
    deref(F2, Sin, T2),  % dereference if a variable
    deref_unify(T1, T2, Sin, Sout).

deref_unify(F1, F2, Sin, Sout) :-
    tpvar(F1),
    !,
    var_match(F1, F2, Sin, Sout).
deref_unify(F1, F2, Sin, Sout) :-
    tpvar(F2),
    !,
    var_match(F2, F1, Sin, Sout).
deref_unify(F1, F2, Sin, Sout) :- % this case is for constants and structures
    F1 =.. [H|Args1],
    F2 =.. [H|Args2],
    unify_special(H, Args1, Args2, Sin, Sout).

unify_special(_, L1, L2, Sin, Sout) :-
    list_unify(L1, L2, Sin, Sout). % regular unification
unify_special(H, [A1,B1], [A2,B2], Sin, Sout) :-
    commutative(H),       % if commutative, try for another mgu
    list_unify([A1,B1], [B2,A2], Sin, Sout).

list_unify([], [], Sub, Sub).
list_unify([H1|T1], [H2|T2], Sin, Sout) :-
    unify_new(H1, H2, Sin, S1),
    list_unify(T1, T2, S1, Sout).

var_match(V, T, Sub, Sub) :-
    V == T,
    !.
var_match(V, T, [Vars, Terms], [[V|Vars], [T|Terms]]) :-
    occur_check(V, T, [Vars, Terms]).

%%%%%%%%%%%%
%
%   deref(Term_in, Subst, Term_out) - dereference Term_in:
%   Apply the substitution until a non-variable is reached.
%   (Follow the chain of variables to the end without
%   applying the substitution to any non-variable terms.)
%
%%%%%%%%%%%%

deref(V, Sin, F) :-
    deref_mem(V, Sin, V1),
    deref(V1, Sin, F),
    !.
deref(V, _, V).

%   deref_mem fails if F is not an instantiated variable

deref_mem(F, [[V|_], [T|_]], T) :-
    F == V,
    !.
deref_mem(V, [[_|T1], [_|T2]], T) :-
    deref_mem(V, [T1, T2], T),
    !.

%%%%%%%%%%%%
%
%   occur_check(Var, Term, Substitution) - fails iff Var occurs 
%   in Term under Substitution (including Var == Term).
%
%%%%%%%%%%%%

occur_check(V, F, _) :-
    V == F,
    !,
    fail.
occur_check(V, F, Sub) :-
    deref_mem(F, Sub, F1),
    !,
    occur_check(V, F1, Sub).
occur_check(_, F, _) :-
    var(F),
    !.
occur_check(V, F, Sub) :-
    F =.. [_|Args],
    occur_check_list(V, Args, Sub).

occur_check_list(_, [], _).
occur_check_list(V, [H|T], Sub) :-
    occur_check(V, H, Sub),
    occur_check_list(V, T, Sub).

%%%%%%%%%%%%
%
%   apply_subst(Term_in, Subst, Term_out) - apply a substitution to a term
%
%%%%%%%%%%%%

apply_subst(Tin, Sub, Tout) :-
    deref(Tin, Sub, D),
    apply_deref(D, Sub, Tout).

apply_deref(T, _, T) :-
    var(T),
    !.
apply_deref(Fin, Sub, Fout) :-
    Fin =.. [H|Args_in],
    apply_list(Args_in, Sub, Args_out),
    Fout =.. [H|Args_out].

%%%%%%%%%%%%
%
%   apply_list(Term_in, Subst, Term_out) - apply a substitution to a 
%   list of terms
%
%%%%%%%%%%%%

apply_list([], _, []).
apply_list([Hin|Tin], Sub, [Hout|Tout]) :-
    apply_subst(Hin, Sub, Hout),
    apply_list(Tin, Sub, Tout).

write_subst(S) :-
    write('Substitution:'), nl,
    write_subst_list(S).

write_subst_list([[Vh|Vt], [Th|Tt]]) :-
    write('    '), write(Vh), write('  ->  '), write(Th), nl,
    write_subst_list([Vt, Tt]).
write_subst_list([[],[]]).

%%%%%%%%%%%%%%%
%
%   unify2(F1, F2, Vlist, Sin, [V,Tout]) - similar to unify/4 except
%   that variables are identified by being members of Vlist.
%
%%%%%%%%%%%%%%%

unify2(F1, F2, Vlist, Sin, [V,Tout]) :-
    v_unify_new(F1, F2, Vlist, Sin, [V,T]),
    apply_subst(T, [V,T], Tout).

v_unify_new(F1, F2, _, Sin, Sin) :-
    F1 == F2,
    !.
v_unify_new(F1, F2, Vlist, Sin, Sout) :-
    deref(F1, Sin, T1),  % dereference if a variable
    deref(F2, Sin, T2),  % dereference if a variable
    v_deref_unify(T1, T2, Vlist, Sin, Sout).

v_deref_unify(F1, F2, Vlist, Sin, Sout) :-
    member_1(F1, Vlist),
    !,
    var_match(F1, F2, Sin, Sout).
v_deref_unify(F1, F2, Vlist, Sin, Sout) :-
    member_1(F2, Vlist),
    !,
    var_match(F2, F1, Sin, Sout).
v_deref_unify(F1, F2, Vlist, Sin, Sout) :- % constants and structures
    F1 =.. [H|Args1],
    F2 =.. [H|Args2],
    v_unify_special(H, Args1, Args2, Vlist, Sin, Sout).

v_unify_special(_, L1, L2, Vlist, Sin, Sout) :-
    v_list_unify(L1, L2, Vlist, Sin, Sout). % regular unification
v_unify_special(H, [A1,B1], [A2,B2], Vlist, Sin, Sout) :-
    commutative(H),       % if commutative, try for another mgu
    v_list_unify([A1,B1], [B2,A2], Vlist, Sin, Sout).

v_list_unify([], [], _, Sub, Sub).
v_list_unify([H1|T1], [H2|T2], Vlist, Sin, Sout) :-
    v_unify_new(H1, H2, Vlist, Sin, S1),
    v_list_unify(T1, T2, Vlist, S1, Sout).

member_1(X,[H|_]) :-
    X == H.
member_1(X,[_|T]) :-
    member_1(X,T).

%%%%%%%%%%%%
%
%   halfmatch(F1, F2, Subst_in, Subst_out) - one-way unification -
%   succeeds iff F2 is an instance of F1.  Commutative functions
%   are handled.
%
%%%%%%%%%%%%

halfmatch(F1, F2, Sin, Sout) :-
    tpvar(F1),
    !,
    hm_var(F1, F2, Sin, Sout). % if F1 already instantiated, it must be to F2 modulo commutativity

halfmatch(F1, F2, Sin, Sout) :-
    F1 =.. [H|T1],
    F2 =.. [H|T2],      % fails if tpvar(F2)
    hm_special(H, T1, T2, Sin, Sout).

hm_var(V, T, [[V1|V2],[T1|T2]], [[V1|V2],[T1|T2]]) :-
    V == V1,
    !,
    c_id(T,T1).
hm_var(V, T, [[V1|V2],[T1|T2]], [[V1|V3],[T1|T3]]) :-
    hm_var(V, T, [V2,T2], [V3,T3]).
hm_var(V, T, [[],[]], [[V],[T]]).

hm_special(_, L1, L2, Sin, Sout) :-
    list_hm(L1, L2, Sin, Sout).
hm_special(H, [A1,B1], [A2,B2], Sin, Sout) :-
    commutative(H), % It's commutative, so try fo another mgu
    list_hm([A1,B1], [B2,A2], Sin, Sout).

list_hm([], [], Sub, Sub).
list_hm([H1|T1], [H2|T2], Sin, Sout) :-
    halfmatch(H1, H2, Sin, S1),
    list_hm(T1, T2, S1, Sout).

%%%%%%%%%%%%
%
%  c_id(T1, T2) - identity modulo commutativity
%
%%%%%%%%%%%%

c_id(F1, F2) :-
    tpvar(F1),
    !,
    F1 == F2.
c_id(F1, F2) :-
    F1 =.. [H|T1],
    F2 =.. [H|T2],
    c_id_list(T1, T2),
    !.
c_id(F1, F2) :-
    F1 =.. [H,A1,B1],
    F2 =.. [H,A2,B2],
    commutative(H),
    c_id(A1, B2),
    !,
    c_id(A2, B1),
    !.

c_id_list([], []).
c_id_list([H1|T1], [H2|T2]) :-
    c_id(H1, H2),
    c_id_list(T1, T2).
