%
% the explicit fuser
% will be consulted by fuse.pl
%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% explicit fusion
%
% combining a list of constraints into 1 (preferably)
%
% depending on the input constraints, the result may
% be a single quantified constraint or a list
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

efuse([],[]).

efuse([C],C) :-
	!.						%a singleton list

efuse([C|Cs],Fused) :-
	efuse(Cs,Temp),					%recursively fuse the tail
	efuse(always,C,Temp,Preservation,Fused),	%then fuse the head into it
	(	(	Preservation=lost,
			!,
			format("WARNING: info. not preserved even ALWAYS is specified~n",[])
		);
		true
	).




%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% efuse/5
%
% explicitly fuse 2 constraints (in XCIF) into 1
%
% efuse(Fuse_mode,C1,C2,Preservation,Fused)
%
% Fuse_mode	=	the required fuse-mode
%			either 'when_match' OR 'always'
% C1		=	1st constraint
% C2		=	2nd one
% Preservation	=	returns the result of fusion,
%			whether information is 'lost' OR 'preserved'
% Fused		=	the fused constraint
%
% Semantically, we can always fuse 2 CIFs by concatenating
% them together (which means a logical "and"). However,
% we're looking for a meaningful fusion in which the CIFs
% are related because they refer to the same variable.
%

%
% when fusion is "always" needed and one of the lists is empty
% then we simply return the other list
%
% if we're asking for a "when_match" fusion and one list is empty,
% then it MUST fail
%

%
% C1 is empty
%
efuse(always,[],C2,preserved,C2).		%always fuse no matter the "fuse-mode"

%
% C2 is empty
%
efuse(always,C1,[],preserved,C1).		%always fuse no matter the "fuse-mode"



%
% C1 & C2 are UQC
%
% 1)	see if there is any recursive fusion between G1 & G2, G1 & P2, G2 & G1, G2 & P1
% 2)	match G1 with G2
%
efuse(Fuse_mode,C1,C2,Preservation,Fused) :-
	C1=restrict_subquery(all(_),G1,_),	%both are UQC
	C2=restrict_subquery(all(_),G2,_),
	!,
	%
	% step 1:	try to match C1 against individual elements in G2 & P2
	%
	efuse_uqc_with_uqc_step1(when_match,C1,C2,New_c1,New_c2),
	%
	% step 2:	match G1 against G2
	%
	find_matches(G1,G2,Matches),
	efuse_uqc_with_uqc_step2(Fuse_mode,New_c1,New_c2,Matches,Preservation,Fused).


%
% C1 is a UQC
% C2 is an EQC
%
efuse(Fuse_mode,C1,C2,Preservation,Fused) :-
	C1=restrict_subquery(all(_),G1,_),					%a UQC
	C2=restrict_subquery(some(_),G2,_),					%and a EQC
	!,
	efuse_uqc_with_eqc_step1(when_match,C1,C2,New_c2),			%NB: only C2 will be changed
	find_matches(G1,G2,Matches),
	efuse_uqc_with_eqc_step2(Fuse_mode,C1,New_c2,Matches,Preservation,Fused).



%
% C1 is an EQC
% C2 is a UQC
%
efuse(Fuse_mode,C1,C2,Preservation,Fused) :-
	C1=restrict_subquery(some(_),_,_),					%a EQC
	C2=restrict_subquery(all(_),_,_),					%and a UQC
	!,
	(	(	efuse_with_elements(when_match,C2,C1,P_flag,Fused),	%do it the other way round
			!,
			Preservation=P_flag					%it DOES fuse
		);
		(	Fuse_mode=always,					%ok, in fact we can use the original "fused-mode"
			append_cif(C1,C2,Fused),				%but I want to conserve the original order
			Preservation=preserved
		)
	).


%
% C1 is a UQC
% C2 is a list
%
% in this case:
% 1)	try to fuse C1 recursively with each element in the list C2
% 2)	match C1's generator G2 with the whole list C2 and see if there is any match
%
efuse(Fuse_mode,C1,C2,Preservation,Fused) :-
	C1=restrict_subquery(all(_),G1,_),							%C1 is a quantified constraint
	is_list(C2),
	!,
	(	(	efuse_uqc_with_list_step1(Fuse_mode,C1,C2,First_p_flag,New_c2),		%if it fuses
			!
		);
		New_c2=C2									%otherwise C2 remains
	),
	find_matches(G1,New_c2,Matches),							%match G1 against the whole list
	efuse_uqc_with_list_step2(Fuse_mode,C1,New_c2,First_p_flag,Matches,Preservation,Fused).	%then step 2



%
% C1 is a list
% C2 is a quantified constraint
%
efuse(Fuse_mode,C1,C2,Preservation,Fused) :-
	is_list(C1),
	C2=restrict_subquery(_,_,_),					%C2 is a quantified constraint
	!,
	(	(	efuse(when_match,C2,C1,P_flag,Fused),		%ok, do it the other way round
			!,
			Preservation=P_flag				%it depends on the recursive fusion
		);
		(	Fuse_mode=always,				%always needs a fusion
			P_flag=lost,					%info is lost in recursive fusion
			!,
			append_cif(C1,C2,Fused),			%then fuse by concatenating them together
			Preservation=preserved				%now information is preserved
		)
	).


%
% C1 is a UQC
% C2 is a construct
%
efuse(Fuse_mode,C1,C2,Preservation,Fused) :-
	C1=restrict_subquery(all(_),_,_),				%C1 is a UQC
	!,
	efuse_uqc_with_construct(Fuse_mode,C1,C2,Preservation,Fused).


%
% trapping errors
%
efuse(_,_,_,_,_) :-
	format("WARNING: efuse/5 has problem~n",[]),
	!,
	fail.

%
% when both C1 & C2 are lists
% and a fusion is always needed
% just concat them together
%
% *** we may have to refresh all variables here
%
efuse(always,C1,C2,preserved,Fused) :-
	append_cif(C1,C2,Fused).



%-------------------------------------------------------------------------
% efuse_all_matches/8
%
% here is the tricky bit...
%
% given a list of matches & 2 constraints, fuse them under all occasions!!!
%
%-------------------------------------------------------------------------

%
% end of matching list
%
% NOTE:	In general, when there is no match, we should concate the 2 CIFs
%	(that means a logical conjunction of the 2 CIFs).
%	However, in calling efuse/6, the concatenation should be already done
%	by the calling predicate.
%	So we within efuse/6, we can assume that there is at least 1 match.
%	When we hit the empty list, it is just the end of the matching-list
%	and we don't have to do anything.
%

efuse_all_matches(_,_,_,_,_,[],lost,[]).				%end of matching list

%
% no match at all
%
efuse_all_matches(Fuse_mode,Mode1,Mode2,C1,C2,[M|Ms],Preservation,Fused) :-
	M=match([],_,[],_),					%no matching of variable & no matching construct
	!,
	format("*** WARNING: No match. Should not be happing.~n",[]),
	efuse_all_matches(Fuse_mode,Mode1,Mode2,C1,C2,Ms,Preservation,Fused).	%take away the 1st match, proceed w/ the rest


%
% *** TRAPPING ERROR
%
% there are no matching variables but matching constructs
%
efuse_all_matches(Fuse_mode,Mode1,Mode2,C1,C2,[M|Ms],Preservation,Fused) :-
	M=match([],_,_,_),
	!,
	format("*** WARNING: NO matching variable but matching constructs~n",[]),
	efuse_all_matches(Fuse_mode,Mode1,Mode2,C1,C2,Ms,Preservation,Fused).	%take away the 1st match, proceed w/ the rest

%
% *** TRAPPING ERROR
%
% some matching variables but NO matching construct
%
efuse_all_matches(Fuse_mode,Mode1,Mode2,C1,C2,[M|Ms],Preservation,Fused) :-
	M=match(_,_,[],_),
	!,
	format("*** WARNING: matching variables but NO matching construct~n",[]),
	efuse_all_matches(Fuse_mode,Mode1,Mode2,C1,C2,Ms,Preservation,Fused).	%continue w/ the rest

%
% *** TRAPPING ERROR
%
% a list will never appear as the 1st constraint if there is a QC
%
efuse_all_matches(_,list,all,_,_,_,_,_) :-
	!,
	format("*** WARNING: list+all in efuse/6. This should not happen.~n",[]),
	fail.

efuse_all_matches(_,list,some,_,_,_,_,_) :-
	!,
	format("*** WARNING: list+some in efuse/6. This should not happen.~n",[]),
	fail.


%-----------
% ALL w/ ALL
%-----------

%
% ALL/ALL a perfect match of the 2 Gs
% or what left in G1 & G2 are not relevant
% (this is equivalent to a perfect match)
%
efuse_all_matches(_,all,all,C1,C2,[M|Ms],preserved,Fused) :-
	M=match(Var_map,Left1,_,Left2),				%get the left behinds on both generators
	findall(X,member(_:X,Var_map),Vars_involved),		%find all matching vars (w/ names in C2)
	cif_util:cif_about_var(Vars_involved,Left1,About1,_),	%get CIF about vars in G1
	cif_util:filter_generator(About1,[]),			%nothing about those vars left in G1's left-behind
	cif_about_var(Vars_involved,Left2,About2,_),		%get CIF about vars in G2
	cif_util:filter_generator(About2,[]),			%nothing about those vars left in G2's left-behind
	!,
	format(">>> ALL/ALL perfect/close-to-perfect match~n",[]),
	%
	%  ok, now replace all vars (w/ match) in C1 w/ names in C2
	%
	replace_var_in_cif(Var_map,C1,New_c1),			%replace var names in C1 w/ those in C2
	%
	% construct the Q, G, & P
	%
	New_c1=restrict_subquery(all(V1),G1,P1),		%disassemble C1
	C2=restrict_subquery(all(V2),G2,P2),			%and C2
	my_union(V1,V2,Vs),					%get a union of vars
	Q=all(Vs),						%construct the new quantifier w/ vars
	append_cif(G1,G2,G),					%get a union of the Gs
	append_cif(P1,P2,P),					%and fuse the 2 Ps
	F=restrict_subquery(Q,G,P),				%the 2 UQCs are combined into a single UQC
	(	(	Ms=[],						%if this is the last match
			!,
			Fused=F						%then that's it!
		);
		efuse_all_matches(when_match,all,all,C1,F,Ms,_,Fused)	%else continue w/ other matches
	).


%
% ALL w/ ALL, G1 is a subset of G2
% i.e. G1 generates a smaller set (subset) than G2
% i.e. C2 applies to a subset of C1
% i.e. C2 becomes a conditional constraint
%
efuse_all_matches(_,all,all,C1,C2,[M|Ms],preserved,Fused) :-
	M=match(Var_map,Left1,Match,Left2),
	findall(X,member(_:X,Var_map),Vars_involved),		%find all matching vars (w/ names in C2)
	cif_about_var(Vars_involved,Left1,[],_),		%nothing about them remains in G1
	!,
	format(">>> ALL/ALL G1=<G2 match~n",[]),

	%
	%
	%
	cif_util:filter_generate2(Left2,Gen_in_left2,Other_in_left2),	%extract generate/2 from Left2

	%
	% we use var names in C2
	%
	replace_var_in_cif(Var_map,C1,New_c1),				%use var names in C2
	New_c1=restrict_subquery(all(V1),_,P1),				%disassemble C1
	C2=restrict_subquery(all(V2),_,P2),				%and C2

	%
	% compose the conditional constraint
	%
	Conditional=generate_subquery([],[],if(If),Then,Else),
	findall(X,member(_:X,Var_map),Vars_involved),			%find all vars involved (w/ names in C2)
	If=Other_in_left2,						%those G2 left behind go to the IF
	cif_about_var(Vars_involved,P2,Then,Left_in_p2),		%those in P2 involving the vars go into the THEN
	Else=[icode_true],

	%
	% compose Q
	%
	my_union(V1,V2,Vs),
	Q=all(Vs),
	%
	% composing G
	%
	append_cif(Match,Gen_in_left2,Temp_g),
	append_cif(Temp_g,Left1,G),					%Left1 goes to G too
	%
	% check for previous conditional constraint w/ the same condition
	%
	%
	% composing P
	%
	append_cif(P1,Left_in_p2,Temp_p),
	add_conditional(Temp_p,Conditional,P),				%put the conditional constraint into P2
	F=restrict_subquery(Q,G,P),					%the 2 UQCs become a single conditional UQC
	(	(	Ms=[],						%the last match
			!,
			Fused=F						%ok, finished
		);
		efuse_all_matches(when_match,all,all,C1,F,Ms,_,Fused)	%otherwise proceed w/ other matches
	).

%
% all w/ all, G2 is a subset of G1
% i.e. G2 generates a smaller subset (subset) than G1
% i.e. C1 applies to a subset of C2
% i.e. C1 becomes a conditional constraint
%
efuse_all_matches(_,all,all,C1,C2,[M|Ms],preserved,Fused) :-
	M=match(Var_map,Left1,Match,Left2),
	findall(X,member(_:X,Var_map),Vars_involved),			%find all vars involved (w/ names in C2)
	cif_about_var(Vars_involved,Left2,[],_),			%nothing about them remains in G1
	!,
	format(">>> ALL/ALL G1>=G2 match~n",[]),

	%
	% now there is nothing about the involved vars left in G2
	% and there are 2 cases:
	% 1)	there isn't any generate/2 in Left1
	% 2)	there are some generate/2 in Left1 (remain of G1)
	%
	% now collect those generate/2 in Left1
	% they have to appear in the new G
	%
	cif_util:filter_generate2(Left1,Gen_in_left1,Other_in_left1),	%extract generate/2 from Left1

	%
	% we use var names in C2
	%
	replace_var_in_cif(Var_map,C1,New_c1),				%use var names in C2
	New_c1=restrict_subquery(all(V1),_,P1),				%disassemble C1
	C2=restrict_subquery(all(V2),_,P2),				%and C2
	%
	% compose the conditional constraint
	%
	% C1 will be a conditional constraint and the extra condition(s)
	% will be those in Left1 concerning the involved vars
	%
	% the new G will be those left behind from Left1 + Left2 + Match
	% i.e. G1+G2-'those in Left2 concerning the involved vars'
	%
	Conditional=generate_subquery([],[],if(If),Then,Else),
	findall(X,member(_:X,Var_map),Vars_involved),			%find vars involved in the match
	If=Other_in_left1,						%all non-generate/2 in Left1 go to the IF
	cif_about_var(Vars_involved,P1,Then,Left_in_p1),
	Else=[icode_true],

	%
	% compose Q
	%
	my_union(V1,V2,Vs),
	Q=all(Vs),
	%
	% composing G
	%
	% "Match" goes into the new G
	% also "Gen_in_left1"
	% and "Left2"
	%
	append_cif(Match,Gen_in_left1,Temp_g),				%generate/2 in Left1 go to the new G
	append_cif(Temp_g,Left2,G),					%Left2 also goes to the new G

	%
	% composing P
	%

	% put the conditional constraint into P2
	%
	%
	append_cif(P2,Left_in_p1,Temp_p),
	add_conditional(Temp_p,Conditional,P),				%put the conditional constraint into P2

	%
	% now the new restrict_subquery/3 w/ conditional constraint is formed
	%
	F=restrict_subquery(Q,G,P),					%the 2 UQCs become a single conditional UQC
	(	(	Ms=[],						%the last match
			!,
			Fused=F						%ok, finished
		);
		efuse_all_matches(when_match,all,all,C1,F,Ms,_,Fused)	%otherwise proceed w/ other matches
	).


%
% all w/ all
% G1 & G2 overlaps, but neither is a subset of the other
%
efuse_all_matches(_,all,all,C1,C2,[M|Ms],preserved,Fused) :-
	M=match(Var_map,Left1,Match,Left2),
	format(">>> G1 intersects G2 not empty~n",[]),
	replace_var_in_cif(Var_map,C1,New_c1),				%use C2's var names
	New_c1=restrict_subquery(all(V1),_,P1),				%get vars & P in C1 w/ replaced names
	C2=restrict_subquery(all(V2),_,P2),				%get vars & P in C2
	findall(X,member(_:X,Var_map),Vars_involved),			%find vars involved in the match
	%
	% compose the 1st conditional constraint from C1
	%
	% we have to extract all generate/2 anyway
	% the extra conditions become the IF part
	%
	%
	cif_about_var(Vars_involved,Left1,About1,In_g1),		%find those about vars in Left1 (G1)
	filter_generate2(About1,Gen1,If1),				%get all generate/2 in it, leftbehind forms IF
	cif_about_var(Vars_involved,P1,Then1,In_orig_p1),		%find the THEN part, the rest in P
	Else1=[icode_true],
	Conditional_1=generate_subquery([],[],if(If1),Then1,Else1),
	%
	% compose the 2nd conditional constraint from C2
	%
	cif_about_var(Vars_involved,Left2,About2,In_g2),		%find those about vars in Left2 (G2)
	filter_generate2(About2,Gen2,If2),				%get all generate/2 in it, leftbehind forms IF
	cif_about_var(Vars_involved,P2,Then2,In_orig_p2),		%find the THEN part, the rest goest to P
	Else2=[icode_true],
	Conditional_2=generate_subquery([],[],if(If2),Then2,Else2),	%the conditional constraint
	%
	% compose Q
	% we use all vars in C1 & C2
	%
	my_union(V1,V2,Vs),
	Q=all(Vs),
	%
	% then G
	%
	% for G, we take the intersection of G1 & G2
	% and the extra bits in G1 & G2 that are not relate to the involved vars
	%
	append_cif(In_g1,Gen1,Temp_g1),
	append_cif(In_g2,Gen2,Temp_g2),
	append_cif(Temp_g1,Temp_g2,Temp_g),				%put in the remainings from G1 & G2
	append_cif(Match,Temp_g,G),
	%
	% then P
	%
%	append_cif(P,In_orig_p1,Temp_p),				%put in the remainings from P1 & P2
	Temp_p=In_orig_p1,
	append_cif(Temp_p,In_orig_p2,Temp_p_again),
	add_conditional(Temp_p_again,Conditional_1,Temp_p_again2),	%put in the 1st conditional constraint
	add_conditional(Temp_p_again2,Conditional_2,P),			%put in the 2nd conditional constraint
	F=restrict_subquery(Q,G,P),					%now this is the fused constraint
	(	(	Ms=[],						%if there is no further match
			!,
			Fused=F						%just return this one
		);
		efuse_all_matches(when_match,all,all,C1,F,Ms,_,Fused)	%otherwise proceed w/ matching list
	).


%------------
% ALL w/ SOME
%------------

%
% ALL/SOME perfect match of G1 & G2
% or what left in them are irrelevant
%
efuse_all_matches(Fuse_mode,all,some,C1,C2,[M|Ms],Preservation,Fused) :-
	M=match(Var_map,Left1,_,Left2),				%get the left behinds on both generators
	findall(X,member(_:X,Var_map),Vars_involved),		%find all vars involved (w/ names in C2)
	cif_with_var(Vars_involved,Left1,[],_),		%nothing about them remains in G1
	cif_with_var(Vars_involved,Left2,[],_),		%nothing about them remains in G2
	!,
	format(">>> ALL/SOME prefect/close-to-perfect match~n",[]),
	replace_var_in_cif(Var_map,C1,New_c1),			%use var names of C2
	New_c1=restrict_subquery(all(V1),G1,P1),		%C1 is a UQC
	C2=restrict_subquery(some(V2),G2,P2),			%C2 is a EQC
	%
	% compose Q
	%
	my_union(V1,V2,Vs),
	Q=some(Vs),
	%
	% then G
	%
	append_cif(G1,G2,G),
	%
	% then P
	%
	append_cif(P1,P2,P),					%add P1 to P2
	F=restrict_subquery(Q,G,P),				%forming the new C2 which is enhanced by C1
	(	(	Ms=[],					%stop if this is the last match
			!,
			Temp_fused=F,
			P_flag=lost				%info. is NOT preserved in a all/some fusion
		);
		efuse_all_matches(when_match,all,some,C1,F,Ms,P_flag,Temp_fused)	%continue with the remaining matches
	),
	(	(	Fuse_mode=always,			%if always fusing
			!,
			append_cif(C1,Temp_fused,Fused),	%then still keep C1 as UQC
			Preservation=preserved			%info. preserved in this way
		);
		(	Fused=Temp_fused,			%otherwise C1 is skipped
			Preservation=P_flag			%and preservation depends on P_flag
		)
	).


%
% ALL/SOME and G1=<G2
%
% C2 will be enhanced by C1 but C1 remains intact
%
efuse_all_matches(Fuse_mode,all,some,C1,C2,[M|Ms],Preservation,Fused) :-
	M=match(Var_map,Left1,_,_),
	findall(X,member(_:X,Var_map),Vars_involved),			%find all vars involved (w/ names in C2)
	cif_with_var(Vars_involved,Left1,[],_),			%nothing about them remains in G1
%	M=match(Var_map,[],Match,Left2),				%G1 is a subset of G2, i.e. Left1 is empty
	!,
	format(">>> ALL/SOME G1=<G2 match~n",[]),
	%
	% we use var names in C2
	%
	replace_var_in_cif(Var_map,C1,New_c1),				%use var names in C2
	New_c1=restrict_subquery(all(_),_,P1),				%disassemble C1
	C2=restrict_subquery(some(V2),G2,P2),				%and C2
	%
	% compose Q, G & P
	%
	Q=some(V2),
	G=G2,
	append_cif(P1,P2,P),						%add P1 into P2
	F=restrict_subquery(Q,G,P),					%the 2 UQCs become a single conditional UQC
	(	(	Ms=[],						%stop if this is the last match
			!,
			Temp_fused=F,
			P_flag=lost					%info. is NOT preserved in a all/some fusion
		);
		efuse_all_matches(when_match,all,some,C1,F,Ms,P_flag,Temp_fused)	%continue with the remaining matches
	),
	(	(	Fuse_mode=always,			%if always fusing
			!,
			append_cif(C1,Temp_fused,Fused),	%then still keep C1 as UQC
			Preservation=preserved			%info. preserved in this way
		);
		(	Fused=Temp_fused,			%otherwise C1 is skipped
			Preservation=P_flag			%and preservation depends on P_flag
		)
	).




%
% ALL w/ LIST, and G1=<C2 (not G2)
%
efuse_all_matches(Fuse_mode,all,list,C1,C2,[M|Ms],Preservation,Fused) :-
%	M=match(Var_map,[],_,_),				%nothing left in G1 after matching
	M=match(Var_map,Left1,_,_),				%nothing left in G1 after matching
	C1=restrict_subquery(all(_),_,P1),			%C1 is a UQC

	findall(X,member(X:_,Var_map),Vars_involved),		%find vars involved
	cif_about_var(Vars_involved,Left1,[],_),		%get CIF about vars in G1
	!,
	replace_var_in_cif(Var_map,P1,New_p1),			%use vars in C2 to form new P1
	append_cif(C2,New_p1,F),				%now this "new-P1" also applies to C2
	efuse_all_matches(when_match,all,list,C1,C2,Ms,P_flag,Fs),		%proceed w/ other matches
	append_cif(F,Fs,Temp),					%concate them together
	(	(	Fuse_mode=always,
			!,
			append_cif(C1,Temp,Fused),		%we have to retain C1 as a UQC
			Preservation=preserved
		);
		(	Fused=Temp,
			Preservation=P_flag
		)
	).


%
% all w/ list, but part of G1 doesn't match w/ C2
%
efuse_all_matches(_,all,list,C1,C2,[M|Ms],Preservation,Fused) :-
	M=match(_,_,_,_),					%something is left in G1 after matching w/ C2
	C1=restrict_subquery(all(_),_,_),
	!,
	format("WARNING: code under construction",[]),
	efuse_all_matches(when_match,all,list,C1,C2,Ms,Preservation,Fs),
	append_cif(_,Fs,Fused).

efuse_all_matches(_,construct,all,C1,C2,All_matches,Preservation,Fused) :-
	format("WARNING: should not be here",[]),
	swap_matches(All_matches,Swapped_matches),
	efuse_all_matches(when_match,all,some,C2,C1,Swapped_matches,Preservation,Fused).

efuse_all_matches(_,some,all,C1,C2,All_matches,Preservation,Fused) :-
	swap_matches(All_matches,Swapped_matches),
	efuse_all_matches(when_match,all,some,C2,C1,Swapped_matches,Preservation,Fused).



%-------------------------------------------------------
% Step 1 in fusing 2 UQCs
%
% see if C1 & C2 can be recursively fused into the other
%
% NB:	We don't return a preservation-flag here as
%	we don't care about it anyway.
%	We'll take take of it in step 2
%-------------------------------------------------------

efuse_uqc_with_uqc_step1(_,C1,C2,New_c1,New_c2) :-
	C1=restrict_subquery(Q1,G1,P1),
	C2=restrict_subquery(Q2,G2,P2),
	Q1=all(_),
	Q2=all(_),
	%
	% look for recursive matches in Gs & Ps
	%
	(	(	efuse_with_elements(when_match,C1,G2,_,G2_fused),	%fuse C1 w/ elements in G2
			!
		);
		G2_fused=G2							%no change if failed to fuse
	),
	(	(	efuse(when_match,C1,P2,_,P2_fused),			%fuse C1 w/ elements in P2
			!
		);
		P2_fused=P2
	),
	(	(	efuse_with_elements(when_match,C2,G1,_,G1_fused),	%fuse C2 w/ elements in G1
			!
		);
		G1_fused=G1
	),
	(	(	efuse(when_match,C2,P1,_,P1_fused),			%fuse C2 w/ elements in P1
			!
		);
		P1_fused=P1
	),
	New_c1=restrict_subquery(Q1,G1_fused,P1_fused),
	New_c2=restrict_subquery(Q2,G2_fused,P2_fused).




%-------------------------------------------------------
% Step 2 in fusing 2 UQCs
% match G1 (in C1) with G2 (in C2)
%-------------------------------------------------------
%
% no match between G1 & G2
% only fuse when match
%
efuse_uqc_with_uqc_step2(when_match,_,_,[],_,_) :-
	!,
	fail.							%fail

%
% no match between G1 & G2
% always fuse
%
efuse_uqc_with_uqc_step2(always,C1,C2,[],preserved,Fused) :-
	append_cif(C1,C2,Fused).				%just concate them and info. is preserved

%
% some matches between G1 & G2
%
efuse_uqc_with_uqc_step2(Fuse_mode,C1,C2,Matches,Preservation,Fused) :-
	efuse_all_matches(Fuse_mode,all,all,C1,C2,Matches,Preservation,Fused).



%
% Step 1 of fusing a UQC w/ an EQC
%
% NB: in fusing w/ a EQC, info. can't be preserved anyway
%
efuse_uqc_with_eqc_step1(when_match,C1,C2,New_c2) :-
	C2=restrict_subquery(some(V2),G2,P2),					%and a EQC
	(	(	efuse_with_elements(when_match,C1,G2,_,G2_fused),	%recursively fuse C1 into G2 when there is a match
			!
		);
		G2_fused=G2
	),
	(	(	efuse_with_elements(when_match,C1,P2,_,P2_fused),	%recursively fuse C1 into P2 when there is a match
			!
		);
		P2_fused=P2
	),
	New_c2=restrict_subquery(some(V2),G2_fused,P2_fused).

%
% step 2
%
efuse_uqc_with_eqc_step2(_,_,_,[],lost,[]).

efuse_uqc_with_eqc_step2(Fuse_mode,C1,C2,Matches,Preservation,Fused) :-
	efuse_all_matches(Fuse_mode,all,some,C1,C2,Matches,Preservation,Fused).		%and proceed w/ fusion





%---------------------------------------------------
% step1 in fusing a UQC w/ a list
%
% match C1 against individual element in the list C2
%---------------------------------------------------

efuse_uqc_with_list_step1(Fuse_mode,C1,C2,Preservation,Fused) :-
	C1=restrict_subquery(all(_),_,_),
	is_list(C2),
	efuse_with_elements(Fuse_mode,C1,C2,Preservation,Fused),		%there is(are) recursive fusion(s) w/ element(s)
	!.

efuse_uqc_with_list_step1(always,C1,C2,preserved,Fused) :-			%always match
	C1=restrict_subquery(all(_),_,_),
	is_list(C2),
	append_cif(C1,C2,Fused).						%so concate them


%--------------------------------
% step 2 in fusing a UQC w/ a list
%
% given the match(es) between G1 (of C1) against the whole list C2
%--------------------------------

%
% no match, previously NO fusion, fuse only when match
% thus FAIL
%
efuse_uqc_with_list_step2(when_match,_,_,lost,[],lost,_) :-
	!,
	fail.

%
% no match, previously NO fusion, always fuse
% thus concate them together
%
efuse_uqc_with_list_step2(always,C1,C2,lost,[],preserved,Fused) :-
	append_cif(C1,C2,Fused).

%
% no match, previously HAS fusion (info. is preserved)
% no matter "when_match" or "always", job is done
%
efuse_uqc_with_list_step2(_,_,C2,preserved,[],preserved,C2) :-
	!.

%
% HAS match, previous step preserved info.
%
efuse_uqc_with_list_step2(Fuse_mode,C1,C2,preserved,Matches,preserved,Fused) :-
	efuse_all_matches(Fuse_mode,all,list,C1,C2,Matches,_,Fused).

%
% HAS match, previous step DIDN'T preserve info.
% so it depends on this step
%
efuse_uqc_with_list_step2(Fuse_mode,C1,C2,lost,Matches,Preservation,Fused) :-
	efuse_all_matches(Fuse_mode,all,list,C1,C2,Matches,Preservation,Fused).




%---------------------------------------
% fuse a UQC with some special construct
%---------------------------------------

efuse_uqc_with_construct(Fuse_mode,C1,C2,Preservation,Fused) :-
	C2 =.. [Functor|Args],							%get the list of args
	efuse_with_elements(Fuse_mode,C1,Args,Preservation,Fused_args),		%fuse w/ args only when-match
	!,
	Fused =.. [Functor|Fused_args].

efuse_uqc_with_construct(always,C1,C2,preserved,Fused) :-
	append_cif(C1,C2,Fused).






%--------------------------------------------------------------------------------
% fuse a UQC with every element in a list
%
% efuse_with_elements(+Fuse_mode,+C1,+C2,-Preservation,-Fused)
%
% Fused_mode	=	always OR when_match
% C1,C2		=	the 2 constraints to fuse, C2 must be a list in this case
% Preservation	=	preserved OR lost
%			the resulting information preservation
% Fused		=	the fused constraint
%
% efuse_with_elements/5 recursively fuses C1 with each elements
% in the list C2. It DOESN'T look for any match between C1 & the whole list.
% Only within each element.
%--------------------------------------------------------------------------------

%
% fuse only when match
%
efuse_with_elements(when_match,C1,C2,Preservation,Fused) :-
	efuse_with_elements(C1,C2,Preservation,Fused).

%
% always fuse
% and in this case, it DOES fuse
%
efuse_with_elements(always,C1,C2,Preservation,Fused) :-
	efuse_with_elements(C1,C2,Preservation,Fused),
	!.

%
% always fuse
% but NO matching this time, so we have to fuse them by concatenation
%
efuse_with_elements(always,C1,C2,preserved,Fused) :-
	append(C1,C2,Fused).


%
% here is efuse_with_elements/4, which uses "when_match" only
%
% notice that there is NO clause to handle C2=[]
% because there is no match between C1 and an empty list, so it must fail
%

%
% C1 is a UQC
% C2 is a list
%
% try fusing C1 into the head of C2
% then fuse C1 with elements in the rest of the list
%
efuse_with_elements(C1,[C2_head|C2_tail],Preservation,Fused) :-
	C1=restrict_subquery(all(_),_,_),				%C1 is a UQC
	efuse(when_match,C1,C2_head,Preserve_1,Fused_head),		%fuse with head
	!,
	(	%
		% now try the tail
		%
		(	%
			% both head & tail fuse
			%
			efuse_with_elements(C1,C2_tail,Preserve_2,Fused_tail),	%fuse with elements in tail
			!,							%and it DOES fuse
			append_cif(Fused_head,Fused_tail,Fused),
			states_or([Preserve_1,Preserve_2],Preservation)
		);
		(	%
			% now the tail DOESN'T fuse, but the head is fused
			%
			Preservation=Preserve_1,
			append_cif(Fused_head,C2_tail,Fused)
		)
	).

%
% C1 is a UQC
% C2 is a list
%
% the same as the previous case but this time the head DOESN'T fuse
%
efuse_with_elements(C1,[C2_head|C2_tail],Preservation,Fused) :-
	C1=restrict_subquery(all(_),_,_),				%C1 is a UQC
	!,
	%
	% the head DOESN'T fuse, now it all depends on the tail
	%
	efuse_with_elements(C1,C2_tail,Preservation,Fused_tail),	%fuse with elements in tail
	append_cif(C2_head,Fused_tail,Fused).




%-----------------------------------------------------
% add a conditional constraint into the P part of a QC
%-----------------------------------------------------

%
% in the 1st case, there is already a conditional constraint w/ the same IF
% so we combine the new conditional w/ the existing one
%
add_conditional(P,Conditional,New_p) :-
	Conditional=generate_subquery([],[],If,Then,Else),				%the conditional to add
	Existing_conditional=generate_subquery([],[],If,Existing_then,Existing_else),	%look for an existing one w/ the same IF
	member(Existing_conditional,P),							%if it is in there
	!,
	delete(P,Existing_conditional,Temp),						%remove the existing one
	append_cif(Existing_then,Then,New_then),					%add the new one's THEN
	append_cif(Existing_else,Else,New_else),					%and the ELSE too
	New_conditional=generate_subquery([],[],If,New_then,New_else),			%compose the new conditional
	append_cif(Temp,New_conditional,New_p).						%that's the new one

%
% in the 2nd case, there is NO existing conditional constraint there
% so we just add the new one in
%
add_conditional(P,Conditional,New_p) :-
	append_cif(P,Conditional,New_p).

