%%% not
not([X|Args]) :-
	derive([X|Args]),
	!,
	fail().
not([X|Args]).


%%% Mirroring
% Only the first working connection is chosen
retrieve_data_general(Type,ID,Data) :-
	location_active(Type,ID,URL|Xs),
	retrieve_data(Type,ID,Data,URL|Xs),
%println(["aaa",ID]," "),
	!.
retrieve_data_general(Type,ID,Data) :-
%println(["aaa"]," "),
	location(Type,ID,URL|Xs),
%println(["aa:",URL]," "),
	retrieve_data(Type,ID,Data,URL|Xs),
	!,
%println(["a:",ID]," "),
	retractall(location_active(Type,ID|Ts)),
	assert(location_active(Type,ID,URL|Xs)).

retrieve_data(database,DBName,DB,URL,Username,Password) :-
	D = ws.prova.Database(),
%println([DS,Username,Password]," "),
	DS = D.getDataSource(URL),
%println([DS,Username,Password]," "),
	DB = DS.getConnection(Username, Password),
%println([DB,Username,Password]," "),
	!.


%%% Opening databases including Connection caching
dbopen(DBName,DB) :-
	access_data(database,DBName,DB).

access_data(Type,ID,Data) :-
	% retrieve or create the cache for Type
	cache_db(Type,CacheData),
	access_data(Type,ID,Data,CacheData).
access_data(Type,ID,Data,CacheData) :-
	% Attempt to retrieve bound data
	bound(ID),
%println(["ID:",ID]),
	Data=CacheData.get(ID),
%println(["Data recovered from cache:",CacheData,ID,Data]," "),
	% Success, Data (whatever object it is) is returned
	!.
access_data(Type,ID,Data,CacheData) :-
	% Attempt to retrieve possibly multiple data corresponding to unbound key
	KeySet = CacheData.keySet(),
%println(["KeySet:",KeySet]),
	element(ID,KeySet),
%println(["ID:",ID]),
	Data=CacheData.get(ID)
%println(["Data recovered from cache."]," ")
	% Success, Data (whatever object it is) is returned
	.
access_data(Type,ID,Data,CacheData) :-
	% Retrieve the data from its location and update the cache
%println(["+++++++",ID]," "),
	bound(ID),
	retrieve_data_general(Type,ID,Data),
%println(["-------",Type,ID,Data]," "),
	update_cache(Type,ID,Data,CacheData).

cache_capacity(database,0).
cache_capacity(pdb,3).
cache_capacity(location,1).
cache_capacity(_,5).

cache_capacity_unique(Type,MaxSize) :-
	cache_capacity(Type,MaxSize),
	!.						% only one match is used

cache_db(Type,CacheData) :-
	cache_data(Type,CacheData),			% cache for Type is available?
	!.						% yes, so we're done
cache_db(Type,CacheData) :-				% no, so create the cache for Type
	CacheData = java.util.HashMap(),		% maps ID to Data
	assert(cache_data(Type,CacheData)),		% persist the reference
	CacheHistory = java.util.ArrayList(),		% stores a list of ID's as they arrive
	assert(cache_history(Type,CacheHistory)).	% persist the reference

update_cache(Type,ID,Data,CacheData) :-
	cache_capacity_unique(Type,0),
	!.
update_cache(Type,ID,Data,CacheData) :-
	Size=CacheData.size(),				% size of the cache
	cache_capacity_unique(Type,MaxSize),			% check what is the maximum capacity
%println(["cache_capacity_unique",MaxSize]),
	Size<MaxSize,					% if within bounds
	!,						% cut: no more choices
	cache_store(Type,ID,Data,CacheData).		% call store the data
update_cache(Type,ID,Data,CacheData) :-			% only if capacity is full
	cache_history(Type,CacheHistory),		% history
	FirstID = CacheHistory.get(0),			% the oldest entry
	CacheHistory.remove(0),				% remove it
	CacheData.remove(FirstID),			% remove the corresponding data
	cache_store(Type,ID,Data,CacheData).		% call store the data

cache_store(Type,ID,Data,CacheData) :-			% store the data
	CacheData.put(ID,Data),				% put the data
	cache_history(Type,CacheHistory),		% history
	CacheHistory.add(ID).				% add the latest ID entry

%% sql_update %%
sql_update(DB,T|Pairs) :-
	Query = StringBuffer(),
	sql_update_prepare(Pairs,Query),
%println([Query]),
	sql_update_execute(DB,T,Query).

sql_update_where([[F,V]],Where) :-
	!,
	Where.append(F),
	Where.append('='),
	Where.append(V).
sql_update_where([[F,V]|Xs],Where) :-
	!,
	Where.append(F),
	Where.append('='),
	Where.append(V),
	Where.append(' and '),
	sql_update_where(Xs,Where).

sql_update_prepare([[where,String.V]],Query) :-
	!,
	Query.append(' where '),
	Query.append(V).
sql_update_prepare([[where,[F,V]]],Query) :-
	!,
%println([F]),
	Query.append(' where '),
	Query.append(F),
	Query.append('='),
	Query.append(V).
sql_update_prepare([[where|L]],Query) :-
	!,
%println([L]),
	Query.append(' where '),
	Where = StringBuffer(),
	sql_update_where(L,Where),
	Query.append(Where).
sql_update_prepare([[where,V]|Pairs],List,Result) :-
	Ex = java.sql.SQLException("Wrong Prova sql_update syntax."),
	raise(Ex).
sql_update_prepare([[F,V]],Query) :-
	!,
	sql_update_prefix(Query),
	Query.append(F),
	Query.append('='),
	Query.append(V).
sql_update_prepare([[F,V]|Pairs],Query) :-
	sql_update_prefix(Query),
	Query.append(F),
	Query.append('='),
	Query.append(V),
	sql_update_prepare(Pairs,Query).

sql_update_prefix(Query) :-
	S = Query.toString(),
	S.equals(''),
	!,
	Query.append(' set ').
sql_update_prefix(Query) :-
	Query.append(',').

% Process join is mostly implemented since Prova 2.0 RC2 in built-ins "init_join" and "join_test".
% It is now fully thread-safe so that parallel rule streams can be safely joined.

join(Me,XID,JoinID,Msg) :-
	join_test(Me,XID,JoinID,Msg,Complete),
	!,
	JoinID(Me,XID,Complete).
join(Me,XID,JoinID,Msg).

%%%%%%%%%%%%%%%%%%%%%%%%%%
% "Predicate join" rules %
%%%%%%%%%%%%%%%%%%%%%%%%%%

% TODO: All these should be mostly done in builtins to ensure thread safety

init_predicate_join(JoinID,XID,JoinPredicate,L) :-
	reset_predicate_join(JoinID,XID,JoinPredicate,L).

reset_predicate_join(JoinID,XID,JoinPredicate,L) :-
	Waiting = java.util.ArrayList(),
	Waiting.addAll(L),
	Complete = java.util.ArrayList(),
	assert(predicate_join_record(JoinID,XID,JoinPredicate,Waiting,Complete)).

stop_predicate_join(JoinID,XID) :-
	retractall(predicate_join_record(JoinID,XID,_,_,_)).

predicate_join(JoinID,XID,Msg,Params) :-
	predicate_join_record(JoinID,XID,JoinPredicate,Waiting,Complete),
	element(N,Msg,Waiting),
	!,
	Waiting.remove(N),
	Complete.add(Msg),
	predicate_join_ex(State,JoinID,XID,JoinPredicate,Waiting,Complete,Params).
predicate_join(JoinID,XID,Msg,Params).

predicate_join_ex(s(I),JoinID,XID,JoinPredicate,Waiting,Complete,Params) :-
	JoinPredicate(JoinID,XID,s(I),Msg,Waiting,Complete),
	JoinID(s(I),XID,Complete,Params).
predicate_join_ex(reset,JoinID,XID,JoinPredicate,Waiting,Complete,Params) :-
	JoinPredicate(JoinID,XID,reset,Msg,Waiting,Complete),
	JoinID(reset,XID,Complete,Params),
	!,
	Waiting.addAll(Complete),
	Complete.clear().