%
% the constraint fusing mediator
%

%
% ECLiPSe operators
%
:- op(800,xfx,infers).
:- op(700,xfx,#=).
:- op(700,xfx,#<).
:- op(700,xfx,#>).
:- op(700,xfx,#=<).
:- op(700,xfx,#=>).
:- op(900,fx,~).
:- op(500,xfx,::).
:- op(400,xfx,..).



:- use_module(library(lists)).
:- use_module('../../prolog/messaging/messaging').		%the messaging module
:- use_module('../../prolog/util/cmdline').			%the command-line module
:- use_module('../../prolog/util/is_daplex_variable').
:- use_module('../../prolog/util/flatten').
:- use_module('../../prolog/util/cif_util').
:- use_module('../../prolog/util/ksl').
:- use_module('../../prolog/util/print_cif').
:- use_module(fuse).			%constraint fusing module
:- use_module(transform).		%CIF->XCIF transformation module
:- ensure_loaded(eclipse_gen).		%ECLiPSe code generating module
:- use_module(filter_empty_slot).	%module that filters empty slot
:- use_module(split_csp).		%module that decomposes CSP into sub-CSP & DBQs
:- use_module(dbq_gen).			%module that generates DB queries

%================================================================================
%
% go			:	Use config-file, host:port from command-line.
%				If no config is specified, use default name 'cpe_m.config'.
% go(Config)		:	Use a specific config file.
% go(Host,Port)		:	Use the linda server at Host:Port.
% go(Config,Host,Port)	:	Use config-file, host & port.
%
%================================================================================



%
% use command-line config filename
%
go :-
	ksl:read_agent_config,
	ksl:get_config(linda_host,Host),
	ksl:get_config(linda_port,Port),
	start(Host,Port).


%===========================================
%
% start/2
%
% config file is read
% start agent with Linda server at host:port
%
%===========================================

start(Host,Port) :-
	init(Host,Port),
	main_loop,
	shutdown.


%=======================
%
% initializing the agent
%
%=======================

init(Host,Port) :-
	format("*** initialising FM~n",[]),
	messaging:init_messaging([server_host:Host,server_port:Port]).
%
% FORGET ABOUT THIS FOR THIS MOMENT!!!
%
%	send_register,
%	send_advertise.



%===================
%
% shutting the agent
%
%===================

shutdown :-
	format("*** shutting down CPE-M...~n",[]),
	messaging:halt_messaging.



%..................................
%
% FM specific initialising steps
%
%..................................

%
% send register to F
%
/*
send_register :-
	ksl:get_config(name,Sender),				%use my name as SENDER
	ksl:get_config(facilitator,Receiver),			%get F's name as RECEIVER
	messaging:generate_id('reg_',Num),			%generate a reply-with id
	Reply_with=whatever_term(Num),
	Ontology=advertisement,
	Language=cif,
	Perf=register,
	Content=[
		[]:[],
		imperative([],[],
			[
			create(agent,var(uevar1),
				[
				scalar('krl(aberdeen,godzilla)',[]),
				scalar(aberdeen,[])
				]
			),
			let_single(linda_port,[var(uevar1)],12345,[])
			]
			)
		],
	messaging:set_argument(
		[	sender:Sender,
			receiver:Receiver,
			reply_with:Reply_with,
			ontology:Ontology,
			language:Language,
			content:Content
		],Params),
	messaging:compose_message(Sender,Receiver,Perf,Params,Message),
	format("*** CPE-M sending registration~n",[]),
	messaging:send_message(Message).
*/

%
% send advertise to F
%
/*
send_advertise :-
	findall(X,compose_single_advertisement(X),Advertisements),	%compose a list of adverts
	send_advertise(Advertisements).					%send them
*/

%
% send a list of adverts
%
/*
send_advertise([]).

send_advertise([A|As]) :-
	format("<<< sending ADVERTISE~n",[]),
	write(A),nl,
	send_message(A),
	send_advertise(As).
*/

%
% compose a single advert
%
/*
compose_single_advertisement(Message) :-
	advertise_pattern(Perf_list,Pattern),				%get the advertisment pattern
	%
	Perf=advertise,							%performative
	Language=cif,							%language
	Ontology=advertisement,						%ontology
	ksl:get_config(name,Sender),
	ksl:get_config(facilitator,Receiver),
	%
	% now generate some id
	%
%	messaging:generate_id(Reply_with_num),				%generate a reply-with
	cif_util:generate_counter(adv_id,'x_',Adv_id),			%generate a advertisement id
	cif_util:generate_daplex_variable(uevar,Adv_obj),		%generate a daplex var for the adv
	Reply_with=Adv_id,						%the stupid requirement
	%
	% compose the content
	%
	Create=create(advertisement,Adv_obj,[scalar('krl(aberdeen,godzilla)',[]),scalar(Adv_id,[])]),
	Goal=(	member(X,Perf_list),
		Template=let_multi(performativeList,[Adv_obj],X,[])
		),
	findall(Template,call(Goal),Let_multi),
	Let_single=let_single(pattern,[Adv_obj],Pattern,[]),
	append([Create],Let_multi,Temp),
	append(Temp,[Let_single],Ham),
	Content=[[]:[],imperative([],[],Ham)],				%now that's the content
*/
/*
	Content=[[]:[],
		imperative([], [],
			[create(advertisement, var(a),
				[scalar(Me, []), scalar(Adv_id, [])]
				),
			let_multi(performativeList, [var(a)], ask_one, []),
			let_multi(performativeList, [var(a)], ask_all, []),
			let_single(pattern, [var(a)],Pattern,[])
			]
		)
		],
*/
/*
	messaging:set_argument(						%set the CCQL params
		[
		sender:Sender,
		receiver:Receiver,
		language:Language,
		ontology:Ontology,
		reply_with:Reply_with,
		content:Content
		],Params),
	messaging:compose_message(Sender,Receiver,Perf,Params,Message).	%compose the adv message
*/


%==============
%
% the main loop
%
%==============

main_loop :-
	format("*** starting FM main loop~n",[]),
	ksl:get_config(name,Me),
	repeat,
	messaging:receive_message(Me,Message),
	format("... something received...~n",[]),
	messaging:decompose_message(Sender,Me,Perf,Param,Message),
	format("... handling income message...~n",[]),
	handle_message(Sender,Me,Perf,Param,Message).		%MUST fail for looping



%::::::::::::::::::::::::::::
%
% handling different messages
%
%::::::::::::::::::::::::::::

%....
%
%PING
%
%....

handle_message(Sender,Me,ping,Params,Message) :-
	format(">>> PING received~n",[]),
	messaging:decompose_message(Context,_,Message),
	messaging:decompose_context(Version,Cid,_,_,_,Context),
	Check_list=[if_not(reply_with:_,'reply_with not found')],
	check_params(Params,Check_list,_,Error),
	handle(ping,Sender,Me,Params,Error,Reply_core),					%handle ping
	%
	% compose reply
	%
	messaging:decompose_message(Context,_,Message),				%get original context
	messaging:decompose_context(Version,Cid,_,_,_,Context),			%get version & cid
	messaging:compose_context(Version,Cid,Me,Sender,_,Reply_context),	%compose the reply-context
	messaging:compose_message(Reply_context,Reply_core,Reply),		%compose reply
	format("<<< sending reply~n",[]),
	send_message(Reply),
	!,
	fail.									%fail to loop



%.......
%
% ASK_IF
%
%.......

handle_message(Sender,Me,ask_if,Params,Message) :-
	format(">>> ASK_IF received~n",[]),
	Check_list=[	if_not(language:cif,'language must be cif'),
			if_not(ontology:shared,'ontology must be shared'),
			if_not(reply_with:_,'reply_with not found'),
			if_not(content:_,'content missing')
			],
	check_params(Params,Check_list,_,Error),
	handle(ask_if,Sender,Me,Params,Error,Reply_core),
	messaging:decompose_message(Context,_,Message),				%get original context
	messaging:decompose_context(Version,Cid,_,_,_,Context),			%get version & cid
	messaging:compose_context(Version,Cid,Me,Sender,_,Reply_context),	%compose reply context
	messaging:compose_message(Reply_context,Reply_core,Reply),		%compose reply
	format("<<< sending reply~n",[]),
	send_message(Reply),
	!,
	fail.


%........
%
% ASK_ONE
%
%........

handle_message(Sender,Me,ask_one,Params,Message) :-
	format(">>> ASK_ONE received~n",[]),
	Check_list=[	if_not(language:cif,'language must be cif'),
			if_not(ontology:shared,'ontology must be shared'),
			if_not(reply_with:_,'reply_with not found'),
			if_not(content:_,'content missing')
			],
	check_params(Params,Check_list,_,Error),
	handle(ask_one,Sender,Me,Params,Error,Reply_core),
	messaging:decompose_message(Context,_,Message),				%get original context
	messaging:decompose_context(Version,Cid,_,_,_,Context),			%get versio & cid
	messaging:compose_context(Version,Cid,Me,Sender,_,Reply_context),	%compose the context
	messaging:compose_message(Reply_context,Reply_core,Reply),		%compose message
	format("<<< sending reply~n",[]),
	send_message(Reply),
	write(Reply),nl,
	!,
	fail.

%........
%
% ASK_ALL
%
%........

handle_message(Sender,Me,ask_all,Params,Message) :-
	format(">>> ASK_ALL received~n",[]),
	Check_list=[	if_not(language:cif,'language must be cif'),
			if_not(ontology:shared,'ontology must be shared'),
			if_not(reply_with:_,'reply_with not found'),
			if_not(content:_,'content missing')
			],
	check_params(Params,Check_list,_,Error),
	handle(ask_all,Sender,Me,Params,Error,Reply_core),
	messaging:decompose_message(Context,_,Message),				%get original context
	messaging:decompose_context(Version,Cid,_,_,_,Context),			%get versio & cid
	messaging:compose_context(Version,Cid,Me,Sender,_,Reply_context),	%compose reply context
	messaging:compose_message(Reply_context,Reply_core,Reply),		%compose message
	format("<<< sending reply~n",[]),
	send_message(Reply),
	!,
	fail.



%..................................................
%
% HALT (non-CCQL compliant, debugging purpose only)
%
%..................................................

handle_message(_,_,halt,_,_) :-
	format("FM>>> agent terminating...~n",[]),
	!.


%...................................................
%
% DEBUG (non-CCQL compliant, debugging purpose only)
%
%...................................................

handle_message(_,_,debug,_,_) :-
	format("DEBUGGING invoked by message...~n",[]),
	send_register,
	send_advertise,
	!,
	fail.


%...........
%
% ALL OTHERS
%
%...........

handle_message(Sender,Me,Perf,Params,Message) :-
	format("*** WARNING: unknown performative received~n",[]),
	write(Message),nl,
	format("... composing SORRY~n",[]),
	%
	% compose SORRY to sender
	%
	Check_list=[if_not(reply_with:_,'reply_with not found')],               %check for params
        check_params(Params,Check_list,_,Error),
	handle(Perf,Sender,Me,Error,Params,Reply_core),
	messaging:decompose_message(Context,_,Message),				%get original context
	messaging:decompose_context(Version,Cid,_,_,_,Context),			%get versio & cid
	messaging:compose_context(Version,Cid,Me,Sender,_,Reply_context),	%compose the context
	messaging:compose_message(Reply_context,Reply_core,Reply),		%compose message
	send_message(Reply),
	format("<<< SORRY sent~n",[]),
	flush_output(user),
	!,
	fail.



%=========================================================================
%
% check_params/4
%
% check if params satisfy the required value
% return error message if appropriate
%
% check_params(+Params,+Check_list,-Problem_pair,-Error)
%
% Params	=	list of params to check
% Check_list	=	check-list in if_not(Pattern,Error_message) format
% Problem_pair	=	the param pair having problem
% Error		=	the error message return, 'none' if no error found
%
%=========================================================================

check_params(_,[],_:_,none).

check_params(Param,[C|Cs],Problematic_pair,Error) :-
	C=if_not(Arg:Expected_value,This_error),
	(	(	var(Expected_value),					%expecting any value
			messaging:get_argument(Param,Arg:Expected_value),	%found it there
			!,
			check_params(Param,Cs,Problematic_pair,Error)		%proceed
		);
		(	nonvar(Expected_value),					%expecting a certain value
			messaging:get_argument(Param,Arg:Current_value),	%get current one
			nonvar(Current_value),					%which is not a var
			Current_value=Expected_value,				%and they can unify
			!,
			check_params(Param,Cs,Problematic_pair,Error)
		);
		(	nonvar(Expected_value),
			messaging:get_argument(Param,Arg:Current_value),
			var(Current_value),
			!,
			Error=This_error,
			Problematic_pair=Arg:Current_value
		);
		(	Error=This_error,
			Problematic_pair=Arg:Expected_value
		)
	).






%:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
%
% HANDLING DIFFERENT SITUATIONS IN DIFFERENT PERFORMATIVES
%
%:::::::::::::::::::::::::::::::::::::::::::::::::::::::::

%.....
%
% PING
%
%.....

%
% no problem, get reply-with
%
handle(ping,Sender,Me,Params,none,Reply_core) :-
	messaging:get_argument(Params,reply_with:Reply_with),		%get reply_with
	!,
	messaging:set_argument([in_reply_to:Reply_with,			%in-reply-to the last ping
				sender:Me,				%send by me
				receiver:Sender				%back to original sender
				],
				Reply_params),	%use as in_reply_to
	messaging:compose_ccql_core(pong,Reply_params,Reply_core),	%compose PONG core
	format("... PONG composed~n",[]).

%
% no problem, but cannot get reply-with
%
handle(ping,Sender,Me,_,none,Reply_core) :-
	messaging:set_argument([sender:Me,				%still send by me but no in-reply-to here
				receiver:Sender
				],
				Reply_params),
	messaging:compose_ccql_core(pong,Reply_params,Reply_core),	%compose PONG core
	format("... PONG composed~n",[]).

%
% has error
%
handle(ping,Sender,Me,_,Error,Reply_core) :-
	messaging:set_argument([content:Error,
				sender:Me,
				receiver:Sender
				],
				Reply_params),
	messaging:compose_ccql_core(error,Reply_params,Reply_core),	%compose ERROR core
	format("... ERROR composed~n",[]).

handle(ping,_,_,_,_,_) :-
	format("*** WARNING: internal error in handling PING~n",[]),
	!,
	fail.



%.......
%
% ASK-IF
%
%.......

%
% no error, and I can handle this ask_if
%
handle(ask_if,Sender,Me,Params,none,Reply_core) :-
	messaging:get_argument(Params,[	reply_with:Reply_with,		%get reply_with
					content:Pattern]),		%and pattern
	match_capability(Pattern),					%within my capability
	!,
	messaging:set_argument([in_reply_to:Reply_with,
				sender:Me,
				receiver:Sender,
				content:Pattern
				],
				Reply_params),	%use as in_reply_to
	messaging:compose_ccql_core(tell,Reply_params,Reply_core),	%compose TELL core
	format("... TELL composed~n",[]).

%
% no error, but I can't handle this ask_if
%
handle(ask_if,Sender,Me,Params,none,Reply_core) :-
	messaging:get_argument(Params,[	reply_with:Reply_with,		%get reply_with
					content:Pattern]),		%and pattern
	messaging:set_argument([in_reply_to:Reply_with,
				sender:Me,
				receiver:Sender,
				content:Pattern
				],
				Reply_params),				%use as in_reply_to
	messaging:compose_ccql_core(sorry,Reply_params,Reply_core),	%compose TELL core
	format("... SORRY composed~n",[]).

%
% has error, can find reply-with
%
handle(ask_if,Sender,Me,Params,Error,Reply_core) :-
	messaging:get_argument(Params,reply_with:Reply_with),		%get reply_with
	!,
	messaging:set_argument([content:Error,
				sender:Me,
				receiver:Sender,
				in_reply_to:Reply_with
				],					%use as in-reply-to
				Reply_params),
	messaging:compose_ccql_core(error,Reply_params,Reply_core),	%compose error core
	format("... ERROR composed~n",[]).

%
% has error, cannot find reply-with
%
handle(ask_if,Sender,Me,_,Error,Reply_core) :-
	messaging:set_argument([content:Error,
				sender:Me,
				receiver:Sender
				],
				Reply_params),
	messaging:compose_ccql_core(error,Reply_params,Reply_core),	%compose error core
	format("... ERROR composed~n",[]).

handle(ask_if,_,_,_,_,_) :-
	format("*** WARNING: internal error in handling ASK-IF~n",[]),
	!,
	fail.


%........
%
% ASK-ONE
%
%........

%
% no error, and I can handle it
%
handle(ask_one,Sender,Me,Params,none,Reply_core) :-
	messaging:get_argument(Params,[reply_with:Reply_with,		%get reply_with
					content:Pattern]),		%get pattern
	match_capability(Pattern),					%I can handle it
	!,
	handle_pattern(ask_one,Pattern,Result),
	!,
	messaging:set_argument([in_reply_to:Reply_with,
				sender:Me,
				receiver:Sender,
				content:Result
				],
				Reply_params),	%use as in_reply_to
	messaging:compose_ccql_core(tell,Reply_params,Reply_core),	%compose TELL core
	format("... TELL composed~n",[]).

%
% no error, but I cannot handle this pattern
%
handle(ask_one,Sender,Me,Params,none,Reply_core) :-
	!,
	messaging:get_argument(Params,[reply_with:Reply_with,		%get reply_with
					content:Pattern]),		%get pattern
	messaging:set_argument([in_reply_to:Reply_with,
				sender:Me,
				receiver:Sender,
				content:Pattern
				],
				Reply_params),				%use as in_reply_to
	messaging:compose_ccql_core(sorry,Reply_params,Reply_core),	%compose TELL core
	format("... SORRY composed~n",[]).

%
% has error, reply_with found
%
handle(ask_one,Sender,Me,Params,Error,Reply_core) :-
	messaging:get_argument(Params,reply_with:Reply_with),		%get reply_with
	!,
	messaging:set_argument([in_reply_to:Reply_with,
				sender:Me,
				receiver:Sender,
				content:Error],
				Reply_params),
	messaging:compose_ccql_core(error,Reply_params,Reply_core),	%compose error core
	format("... ERROR composed~n",[]).

%
% has error, no reply_with found
%
handle(ask_one,Sender,Me,_,Error,Reply_core) :-
	!,
	messaging:set_argument([content:Error,
				sender:Me,
				receiver:Sender
				],
				Reply_params),
	messaging:compose_ccql_core(error,Reply_params,Reply_core),	%compose error core
	format("... ERROR composed~n",[]).

handle(ask_one,_,_,_,_,_) :-
	format("*** WARNING: internal error in handling ASK-ONE~n",[]),
	!,
	fail.


%........
%
% ASK_ALL
%
%........

%
% no error, and I can handle it
%
handle(ask_all,Sender,Me,Params,none,Reply_core) :-
	messaging:get_argument(Params,[	reply_with:Reply_with,		%get reply_with
					content:Pattern]),		%get pattern
	match_capability(Pattern),					%I can handle it
	!,
	handle_pattern(ask_all,Pattern,Result),
	messaging:set_argument([in_reply_to:Reply_with,
				sender:Me,
				receiver:Sender,
				content:Result
				],
				Reply_params),
	messaging:compose_ccql_core(tell,Reply_params,Reply_core),	%compose TELL core
	format("... TELL composed~n",[]).

%
% no error, but I cannot handle pattern
%
handle(ask_all,Sender,Me,Params,none,Reply_core) :-
	messaging:get_argument(Params,[	reply_with:Reply_with,		%get reply_with
					content:Pattern]),		%get pattern
	!,
	messaging:set_argument([in_reply_to:Reply_with,
				sender:Me,
				receiver:Sender,
				content:Pattern
				],
				Reply_params),
	messaging:compose_ccql_core(sorry,Reply_params,Reply_core),	%compose TELL core
	format("... SORRY composed~n",[]).

%
% error, reply_with found
%
handle(ask_all,Sender,Me,Params,Error,Reply_core) :-
	messaging:get_argument(Params,reply_with:Reply_with),		%get reply_with
	!,
	messaging:set_argument([in_reply_to:Reply_with,
				sender:Me,
				receiver:Sender,
				content:Error
				],
				Reply_params),
	messaging:compose_ccql_core(error,Reply_params,Reply_core),	%compose error core
	format("... ERROR composed~n",[]).

%
% error, reply_with not found
%
handle(ask_all,Sender,Me,_,Error,Reply_core) :-
	messaging:set_argument([content:Error,
				sender:Me,
				receiver:Sender
				],
				Reply_params),
	messaging:compose_ccql_core(error,Reply_params,Reply_core),	%compose error core
	format("... ERROR composed~n",[]),
	!.

handle(ask_all,_,_,_,_,_) :-
	format("*** ERROR: internal error in handling ASK-ALL~n",[]),
	!,
	fail.


%................................
%
% all other unknown performatives
%
%................................

%
% no error, get reply-with
%
handle(Perf,Sender,Me,Params,none,Reply_core) :-
	messaging:get_argument(Params,[reply_with:Reply_with]),		%get reply-with
	!,
	%
	% compose error message
	%
	name(Perf,Perf_string),
	flatten(["cannot handle ",Perf_string],Error_string),                   %compose error message string
	name(Error_message,Error_string),
	messaging:set_argument([in_reply_to:Reply_with,			%compose reply params w/ in-reply-to & content
				sender:Me,
				receiver:Sender,
				content:Error_message
				],
				Reply_params),
	messaging:compose_ccql_core(sorry,Reply_params,Reply_core).	%compose SORRY core

%
% has error, cannot find reply-with
%
handle(Perf,Sender,Me,_,none,Reply_core) :-
	%
	% compose error message
	%
	name(Perf,Perf_string),
	flatten(["cannot handle ",Perf_string],Error_string),                   %compose error message string
	name(Error_message,Error_string),
	messaging:set_argument([content:Error_message,
				sender:Me,
				receiver:Sender
				],
				Reply_params),				%otherwise just put in the content
        messaging:compose_ccql_core(sorry,Reply_params,Reply_core),	%compose SORRY core
	!.

handle(Perf,_,_,_,_,_) :-
	format("*** ERROR: internal error in handling unknown performative [~q]~n",[Perf]),
	!,
	fail.


%====================
%
% match_capability/1
%
% match my capability
%
%====================

match_capability(_).

/*	advertise_pattern(Ad_perfs,Ad_pattern),
	member(Perf,Ad_perfs),

match_capability(Pattern) :-
	Pattern=[	[Output_var]:[router],			%output pattern
			[compatible_cpe(Input_args,Output_var)]	%predicate pattern
		],
		is_list(Input_args),				%a list of constraints
		is_daplex_variable(Output_var).			%output is a daplex var

match_capability(Pattern) :-
	Pattern=[	[Output_var]:[router],
			[usable_cpe(Input_args,Output_var)]
		],
		is_list(Input_args),
		is_daplex_variable(Output_var).
*/

%===================================
%
% patterns for making advertisements
%
%===================================

%advertise_pattern([ask_if,ask_all,ask_one],'generate(pc_config,set_of(cif),Constraints,router,OutputVar)').
/*
advertise_pattern([ask_if,ask_all,ask_one],'generate(compatible_cpe,set_of(cif),Constraints,router,OutputVar)').
advertise_pattern([ask_if,ask_all,ask_one],'generate(usable_cpe,set_of(cif),Constraints,router,OutputVar)').
*/


%::::::::::::::::::::::::::::::::::::::::::
%
% handling different patterns in ask_*
% i.e. processing mediator service requests
%
%::::::::::::::::::::::::::::::::::::::::::

handle_pattern(_,_,_) :-
	format("... trying handle_pattern/2~n",[]),
	fail.

%............................
%
% ASK_ONE on pc_config
%
%............................

handle_pattern(ask_one,Pattern,Result) :-
	Pattern=[	Output_vars:Output_types,
			generate(pc_config,[set_of(cif),set_of(cif)],[Cif,Empty],Output_types,Output_vars)
		],
	format("... ASK_ONE handling pattern pc_config/2~n",[]),
	%
	% fuse & solve CIFs
	%
	fuse_and_solve(ask_one,Cif,Empty,Result).


%............................
%
% ASK_ALL on pc_config
%
%............................

handle_pattern(ask_all,Pattern,Result) :-
	Pattern=[	Output_vars:Output_types,
			generate(pc_config,[set_of(cif),set_of(cif)],[Cif,Empty],Output_types,Output_vars)
		],
	format("... ASK_ALL handling pattern pc_config/2",[]),
	%
	% fuse & solve
	%
	fuse_and_solve(ask_all,Cif,Empty,Result).




%
% fuse & solve a list of CIFs
%

fuse_and_solve(Mode,Cifs,Empty_slots,Result) :-
	format("...transforming CIF into XCIF...~n",[]),
	transform:xcif(Cifs,Xcifs),
	print_cif(Xcifs),nl,
	format("...transformed~n",[]),
	format("...fusing constraints in XCIF...~n",[]),
	fuse_it(Xcifs,Groups,Links),
	format("...constraints fused~n",[]),
	fuse:print_groups(Groups),
	format("...refreshing variable names in fused constraints...~n",[]),
	fuse:refresh_vars_in_group(Groups,Links,Refreshed),
	format("...refreshed~n",[]),
	fuse:print_groups(Refreshed),
	nl,
	format("*** Links...~n",[]),
	write(Links),
	nl,
	solve_it(Mode,Refreshed,Links,Empty_slots,Result).

fuse_it(Xcif,Groups,Links) :-
	%
	% call fuser
	%
	format("*** transforming...~n",[]),
%	transform:xcif(Cif,Xcif),
	format("*** fusing...~n",[]),
	fuse:hfuse(Xcif,Groups,Links).

%
% given a list of fused groups & links
% solve the CSP
%
solve_it(Mode,Groups,Links,Empty_slots,Result) :-
	format("...filtering empty slots to form CSP...~n",[]),
	filter_empty_slot:filter_empty_slot(Groups,Empty_slots,Filtered),
	format("...empty slots filtered & CSP formed~n",[]),
	%
	% splitting
	%
	format("...decomposing CSP...~n",[]),
	split_csp:decompose_csp(multiple,Filtered,Csp,Dbq),
%	split_csp:split_csp(multiple,Filtered,Csp,Dbq),
	format("===DBQ===~n",[]),
	print_dbq(Dbq),
	nl,
	format("===sub-CSP===~n",[]),
	print_cif(Csp),
	nl,
	solve_it_part2(Mode,Dbq,Csp,Links,Result).



solve_it_part2(_,[],_,_,fail) :-
	format("*** internal ERROR: DBQ is empty~n",[]),
	!.

%
% no sub-CSP, just DBQ can solve it
%
solve_it_part2(_,Dbq,[],_,Result) :-
	%
	% DBQ gen
	%
	!,
	format("...generating DBQs...~n",[]),
	dbq_gen:generate_dbq(Dbq,Queries),
	dump_dbq(Queries),
	nl,
	ksl:get_config(name,Me),
	ksl:get_config(pfdm_wrapper,Pfdm_wrapper),


	findall(X,member([_:_,X],Queries),Temp_queries),
	flatten(Temp_queries,Dbq_cif),
	cif_util:filter_basic_types_generate(Dbq_cif,Temp_queries2),
	regroup_queries(Temp_queries2,All_queries),
	send_db_queries(Me,Pfdm_wrapper,All_queries),
	format("...waiting for query result...~n",[]),
	receive_db_tuples(Me,Pfdm_wrapper,Result).

/*
	format("...sending DB queries...~n",[]),
	send_db_queries(Me,Pfdm_wrapper,Queries),
	format("...waiting for query result...~n",[]),
	receive_db_tuples(Me,Pfdm_wrapper,Result).
*/

%
% both are not empty
%
solve_it_part2(Mode,Dbq,Csp,Links,Result) :-
	%
	% generate DBQ and retreive data from databases
	%
	!,
	format("...generating DBQs...~n",[]),
	dbq_gen:generate_dbq(Dbq,Query_groups),				%generate CIF DBQ
	dump_dbq(Query_groups),
	nl,
%	send_eclipse_solution_space(Me,Pfdm_wrapper,Eclipse,Queries),
%	send_eclipse_solution_space(Me,Eclipse,Queries),		%send tuples to ECLiPSe
%
%	send_db_queries(Me,Pfdm_wrapper,Queries),			%send DB query to P/FDM
%	receive_db_tuples(Me,Pfdm_wrapper,Tuples),			%receive data tuples
%	Dbq=[_:_,Cif_query],						%get original query in CIF
%	send_eclipse_solution_space(Me,Eclipse,Cif_query,Tuples),	%send tuples to ECLiPSe

	%
	% compose query from generators in sub-CSP
	%
	fuse:get_all_gens(Csp,U_gens,E_gens),				%find all generators
	cif_util:append_cif(U_gens,E_gens,All_gens),			%put into a list
	filter_out_non_generate(All_gens,Csp_queries_cif),		%get those generating vars
%	cif_util:find_var_types(Useful_gens,Vars_with_types),		%find list of vars & types
%	make_dbq_from_csp_gen(Useful_gens,Vars_with_types,Csp_queries),	%query from sub-CSP part
	%
	% combine & re-group all DBQs
	%
	findall(X,member([_:_,X],Query_groups),Temp_dbq_queries),
	flatten(Temp_dbq_queries,Dbq_queries_cif),			%queries from DBQ part
	append_cif(Dbq_queries_cif,Csp_queries_cif,Temp_queries_cif),	%put them into 1 lot
	cif_util:filter_basic_types_generate(Temp_queries_cif,Temp_queries_cif2),
	regroup_queries(Temp_queries_cif2,All_queries),			%group them if they're linked together

	%
	% form var domains using regrouped DBQs
	%
	ksl:get_config(name,Me),
	ksl:get_config(pfdm_wrapper,Pfdm_wrapper),
	ksl:get_config(eclipse,Eclipse),
	%
	%
	% code gen
	%
	format("...generating ECLiPSe code...~n",[]),
	generate_eclipse_code(Csp,Links,Codes),			%generate ECLiPSe codes
	format("...ECLiPSe code generated~n",[]),
	ksl:get_config(name,Me),				%get my name
	ksl:get_config(eclipse,Eclipse),			%get receiver's name
	format("...initializing ECLiPSe...~n",[]),
	init_eclipse(Me,Eclipse),
	format("...ECLiPSe initialized~n",[]),
	format("...sending DBQ...~n",[]),
	send_eclipse_solution_space(Me,Pfdm_wrapper,Eclipse,All_queries),
	format("...sending CLP program to ECLiPSe...~n",[]),
	tell_eclipse(Me,Eclipse,Codes),
	format("...CLP program sent~n",[]),
	format("...asking ECLiPSe to solve it...~n",[]),
	ask_eclipse(Me,Eclipse,Mode,solve(_),Result),
	format("...got result from ECLiPSe~n",[]).

send_eclipse_solution_space(_,_,_,[]).
send_eclipse_solution_space(Me,Pfdm,Eclipse,[Q|Qs]) :-
	send_db_queries(Me,Pfdm,Q),			%send DB query to P/FDM
	receive_db_tuples(Me,Pfdm,Result),		%receive data tuples
	Q=[_:_,Cif_query],						%get original query in CIF
%	send_eclipse_solution_space(Me,Eclipse,Cif_query,Tuples),	%send tuples to ECLiPSe
	%
	% compose ECLiPSe domains
	%
	format("...composing variable domains & relationship instances...~n",[]),
	Result=[Vs:Ts,Tuples],
	compose_domains(Vs,Ts,Cif_query,Tuples,Domains),
	format("...sending tuples to ECLiPSe...~n",[]),
	ask_eclipse(Me,Eclipse,ask_one,add_tuples(Domains),_),		%assert into ECLiPSe
	format("...one set of tuple sent~n",[]),
	send_eclipse_solution_space(Me,Pfdm,Eclipse,Qs).


%
% initialise ECLiPSe
%
init_eclipse(Me,Eclipse) :-
	format("... sending initialisation message to ECLiPSe...~n",[]),
%	ask_eclipse(Me,Eclipse,ask_one,clean_up),
	send_ask(Me,Eclipse,ask_one,clean_up),
	format("...init message sent...~n",[]),
	format("...waiting for initialization reply...~n",[]),
	receive_message(Me,_),
	format("... remote ECLiPSe initialised~n",[]).





%
% an empty list
%
tell_eclipse(_,_,[]) :-
	!.

%
% a list
%
tell_eclipse(Me,Eclipse,[X|Xs]) :-
	!,
	tell_eclipse(Me,Eclipse,X),
	tell_eclipse(Me,Eclipse,Xs).

%
% something other than a list
%
tell_eclipse(Sender,Receiver,Something) :-
	%
	% make-up a reply-with
	%
	messaging:generate_id('comeon_',Reply_with),		%generate a reply-with
	%
	%
	%
	Ontology=shared,			%using the shared ontology
	Language=eclipse,			%language eclipse
	Perf=tell,				%performative TELL
	Content=Something,
	Params=[	sender:Sender,
			receiver:Receiver,
			reply_with:Reply_with,
			ontology:Ontology,
			language:Language,
			content:Content
		],
	compose_message(Sender,Receiver,Perf,Params,Message),
	send_message(Message).


%
% ASK ECLiPSe something
% either ASK_ONE or ASK_ALL
%
ask_eclipse(Me,Eclipse,One_or_all,Something,Result) :-
	send_ask(Me,Eclipse,One_or_all,Something),
	receive_result(Me,Eclipse,Result),
	write(Result),nl.

send_ask(Sender,Receiver,One_or_all,Something) :-
	member(One_or_all,[ask_one,ask_all]),
	%
	% make-up a reply-with
	%
	messaging:generate_id('comeon_',Reply_with),	%generate a reply-with
	%
	%
	%
	Ontology=shared,				%using the shared ontology
	Language=eclipse,				%language eclipse
	Perf=One_or_all,				%performative TELL
	Content=Something,
	Params=[	sender:Sender,
			receiver:Receiver,
			reply_with:Reply_with,
			ontology:Ontology,
			language:Language,
			content:Content
		],
	compose_message(Sender,Receiver,Perf,Params,Message),
	send_message(Message).

receive_result(Me,Eclipse,Result) :-
	messaging:compose_message(Eclipse,Me,tell,Param,Message),	%compose template
	messaging:receive_message(Message),
	messaging:get_argument(Param,content:Result).			%extract content


%
% send query messages to databases
%

send_db_queries(Sender,Receiver,Q) :-
	Q=[_:_,_],
	!,
	%
	% compose DBQ
	%
	messaging:generate_id('dbq_',Reply_with),	%generate a reply-with
	Ontology=shared,				%using the shared ontology
	Language=cif,					%language eclipse
	Perf=ask_all,					%performative TELL
	Content=Q,
	Params=[	sender:Sender,
			receiver:Receiver,
			reply_with:Reply_with,
			ontology:Ontology,
			language:Language,
			content:Content
		],
	compose_message(Sender,Receiver,Perf,Params,Message),
	%
	% send to P/FDM vendor databases
	%
	send_message(Message),
	write(Message),nl,
	format("...query sent~n",[]).

send_db_queries(_,_,[]).
send_db_queries(Sender,Receiver,[Q|Qs]) :-
	send_db_queries(Sender,Receiver,Q),
	send_db_queries(Sender,Receiver,Qs).

receive_db_tuples(Sender,Receiver,Result) :-
	%
	% receive result as tuples
	%
	format("...waiting for reply...~n",[]),
	messaging:compose_message(Receiver,Sender,tell,Reply_params,Reply),	%compose receive template
	messaging:receive_message(Reply),
	messaging:get_argument(Reply_params,content:Result),			%extract content
	format("...reply received~n",[]).




compose_domains(Vars,_,Query,Tuples,All_fnval) :-
	get_generate(Query,Generates),		%get all generate/2 & generate/5
	create_template(Generates,Template,Replacements),
	make_matching_list(Vars,Replacements,Matching_list),
	make_fnval_template(Template,Fnval_template),
	findall(Fnval_template,member(Matching_list,Tuples),All_fnval).

%
% extract all generate/2 & generate/5
%
get_generate([],[]).
get_generate([C|Cs],[C|Gs]) :-
	C=generate(_,_),
	!,
	get_generate(Cs,Gs).
get_generate([C|Cs],[C|Gs]) :-
	C=generate(_,_,_,_,_),
	!,
	get_generate(Cs,Gs).

get_generate([C|Cs],All_gen) :-
	C=restrict_subquery(_,Gen,Pred),
	!,
	get_generate(Gen,G_in_gen),
	get_generate(Pred,G_in_pred),
	get_generate(Cs,G_in_cs),
	append_cif(G_in_gen,G_in_pred,Temp_gen),
	append_cif(Temp_gen,G_in_cs,All_gen).


get_generate([_|Cs],Gs) :-
	get_generate(Cs,Gs).

make_matching_list([],_,[]).
make_matching_list([V|Vs],Replacements,[X|Xs]) :-
	member(V:X,Replacements),
	!,
	make_matching_list(Vs,Replacements,Xs).

make_matching_list(_,_,_) :-
	format("*** internal ERROR in make_matching_list/3~n",[]).

make_fnval_template([],[]).
make_fnval_template([G|Gs],[T|Ts]) :-
	G=generate(Class,Var),
	!,
	T=object(Class,Var),
	make_fnval_template(Gs,Ts).
make_fnval_template([G|Gs],[T|Ts]) :-
	G=generate(Fname,Types,Vars,Result_type,Result),
	!,
	T=fnval(Fname,Types,Vars,Result_type,Result),
	make_fnval_template(Gs,Ts).
make_fnval_template(_,_) :-
	format("*** internal ERROR in make_matching_list/3~n",[]).

%
% filter out those which is not a generate/2/5
%
filter_out_non_generate([],[]).
filter_out_non_generate([X|Xs],[X|Fs]) :-
	functor(X,generate,_),
	!,
	filter_out_non_generate(Xs,Fs).
filter_out_non_generate([_|Xs],Fs) :-
	filter_out_non_generate(Xs,Fs).


make_dbq_from_csp_gen(Useful_gens,Vars_with_types,Dbq_from_csp) :-
	split_var_type_list(Vars_with_types,Vars,Types),
	cif_util:filter_basic_types_generate(Useful_gens,Cif_query),
	Dbq_from_csp=[Vars:Types,Cif_query].

split_var_type_list([],[],[]).
split_var_type_list([V:T|Rest],[V|Vs],[T|Ts]) :-
	split_var_type_list(Rest,Vs,Ts).


%
% given a list of CIF representing a list of DBQs,
% re-group them so that related one falls into a group
%

regroup_queries([],[]).

regroup_queries([C|Cs],[Q|Qs]) :-
	cif_util:find_vars(C,Vars_in_c),			%find all vars in this construct
	cif_util:cif_about_var(Vars_in_c,[C|Cs],About,Remain),	%split tail into 2 parts
	find_var_types(About,Vars_with_type),
	split_var_type_list(Vars_with_type,Vars,Types),
	reorder_query_cif(About,Query_cif),
	Q=[Vars:Types,Query_cif],
	regroup_queries(Remain,Qs).

%
% reorder query cif so that generate/2 go 1st, then generate/5
%
reorder_query_cif(Cif,Q) :-
	reorder_query_cif2(Cif,Gen2,Gen3,Rest),
	append_cif(Gen2,Gen3,Temp),
	append_cif(Temp,Rest,Q).

reorder_query_cif2([],[],[],[]).

reorder_query_cif2([C|Cs],[C|Gen2],Gen3,Rest) :-
	C=generate(_,_),
	!,
	reorder_query_cif2(Cs,Gen2,Gen3,Rest).

reorder_query_cif2([C|Cs],Gen2,[C|Gen3],Rest) :-
	C=generate(_,_,_,_,_),
	!,
	reorder_query_cif2(Cs,Gen2,Gen3,Rest).

reorder_query_cif2([C|Cs],Gen2,Gen3,[C|Rest]) :-
	reorder_query_cif2(Cs,Gen2,Gen3,Rest).


%
% auto-start upon loading
%
:- go.
