% Demonstrate caching and mirroring

:- eval(ex011()).

r(1).
r(2).    % This data will not be accessed because
         %    cached access assumes there is only one data item at a given key.
s(3).

ex011() :-
	access_data(test,r,RA),
	println(["Result1A=",RA]),
	% Accessed from cache
	access_data(test,r,RB),
	println(["Result1B=",RB]),
	access_data(test,s,S),
	println(["Result2=",S]).

location(test,r,r).
location(test,s,s).

retrieve_data(test,R,Data,URL|Xs) :-
	URL(Data).

%%% 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),
	!.
retrieve_data_general(Type,ID,Data) :-
	location(Type,ID,URL|Xs),
	retrieve_data(Type,ID,Data,URL|Xs),
	!,
	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,DBName,Username,Password),
%println([DS,Username,Password]," "),
	DB = DS.getConnection(),
%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),
	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(),
	element(ID,KeySet),
	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
	bound(ID),
	retrieve_data_general(Type,ID,Data),
	update_cache(Type,ID,Data,CacheData).

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

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) :-
println(["cache_capacity_unique",0]," "),
	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",Size,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
println(["cache history called for ",Type]),
	cache_history(Type,CacheHistory),		% history
println(["cache history",CacheHistory]),
	FirstID = CacheHistory.get(0),			% the oldest entry
println(["cache oldest",FirstID]),
	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
println(["cache store",ID]),
	CacheData.put(ID,Data),				% put the data
	cache_history(Type,CacheHistory),		% history
	CacheHistory.add(ID).				% add the latest ID entry
