:- eval(consult("utils.prova")).

% GeneStream Query A
:- solve(neighbours(term2term,[term1_id,2],[term2_id,ID2]|R)).

% GeneStream Query B
:- solve(term2term(3,[term1_id,"protein-nucleus import"],
	    [term2_id,Term],[relationship_type_id,5])).

% GeneStream Query C
:- solve(term2term(2,[term2_id,"protein-nucleus import"],
		[term1_id,Term],[relationship_type_id,5])).

% GeneStream Query D
:- solve(term2part(4,[term,"ribosomal protein-nucleus import"]|R)).

% GeneStream: Viva presentation example
:-	solve(term2term(1,[term1_id,"molecular_function"],
		[term2_id,Child],[relationship_type_id,5])).

% GeneStream: Transitive Redundancy query 
:- solve(term2term_transitive(1,[term1_id,T1],[term2_id,T2],[relationship_type_id,5])).

% Database locations
location(database,go,"jdbc:mysql://localhost/go","guest","guestdb").
location(database,scop,"jdbc:mysql://localhost/scop","guest","guestdb").

%%% Genestream D10

% Rule A0
neighbours(Table,[F1,ID1],[F2,ID2]|R) :-
	dbopen(go,DB),
	neighbours(DB,Table,[F1,ID1],[F2,ID2]|R).

% Rule A1
neighbours(DB,Table,[F1,ID1],[F2,ID2]|R) :-
   permute_bound([ID1,ID2],[IDA,IDB]),
   sql_select(DB,Table,[F1,IDA],[F2,IDB]|R).

% Rule A2
neighbours_recursive(DB,Table,N,[F1,ID1],[F2,ID2]|R) :-
   permute_bound([ID1,ID2],[IDA,IDB]),
   id_recursive(DB,Table,N,[F1,IDA],[F2,IDB]|R).

% Rule B
id_recursive(DB,Table,Integer.N,[F1,ID1],[F2,ID1]|R).
% Rule C
id_recursive(DB,Table,Integer.N,[F1,ID1],[F2,ID2]|R) :-
   Integer.N>0,
   Integer.N2=Integer.N-1,
   sql_select(DB,Table,[F1,ID1],[F2,ID3]|R),
   id_recursive(DB,Table,Integer.N2,[F1,ID3],[F2,ID2]|R).

% Rule D
term2term(Integer.N,[F1,T1],[F2,T2]|R) :-
	dbopen(go,DB),
	term2term(DB,Integer.N,[F1,T1],[F2,T2]|R).
term2term(DB,Integer.N,[F1,T1],[F2,T2]|R) :-
   sql_select(DB,term,[name,T1],[id,ID1]),
   id_recursive(DB,term2term,Integer.N,[F1,ID1],[F2,ID2]|R),
   ID1<>ID2,
   sql_select(DB,term,[name,T2],[id,ID2]).

% Rule E
term2part(Integer.N,[term,T1],[part,T2]) :-
	dbopen(go,DB),
	term2part(DB,Integer.N,[term,T1],[part,T2]).
term2part(java.sql.Connection.DB,Integer.N,[term,T1],[part,T2]) :-
   sql_select(java.sql.Connection.DB,term,[name,T1],[id,ID1]),
   id_recursive(java.sql.Connection.DB,term2term,Integer.N,
       [term2_id,ID1],[term1_id,ID3],[relationship_type_id,5]),
   id_recursive(java.sql.Connection.DB,term2term,Integer.N,
       [term1_id,ID3],[term2_id,ID2],[relationship_type_id,3]),
   ID3<>ID2,
   sql_select(java.sql.Connection.DB,term,[name,T2],[id,ID2]).

% Rule to establish Transitive Redundancy 
term2term_transitive(Integer.N,[F1,T1],[F2,T2]|R) :-
	dbopen(go,DB),
	term2term_transitive(DB,Integer.N,[F1,T1],[F2,T2]|R).
term2term_transitive(java.sql.Connection.DB,N,[F1,T1],[F2,T2]|R) :-
	N<2,
	!,
% 6572 8717 8733
   sql_select(java.sql.Connection.DB,term2term,[F1,ID1],[F2,ID2]|R),
   sql_select(java.sql.Connection.DB,term2term,[F1,IDX],[F2,ID2]|R),
	ID1<IDX,
	neighbours(java.sql.Connection.DB,term2term,[F1,ID1],[F2,IDX]|R),
   sql_select(java.sql.Connection.DB,term,[name,T1],[id,ID1]),
   sql_select(java.sql.Connection.DB,term,[name,T2],[id,IDX]).
term2term_transitive(java.sql.Connection.DB,N,[F1,T1],[F2,T2]|R) :-
   sql_select(java.sql.Connection.DB,term2term,[F1,ID1],[F2,ID2]|R),
   sql_select(java.sql.Connection.DB,term2term,[F1,IDX],[F2,ID2]|R),
	ID1<>IDX,
	neighbours_recursive(java.sql.Connection.DB,term2term,N,[F1,ID1],[F2,IDX]|R),
   sql_select(java.sql.Connection.DB,term,[name,T1],[id,ID1]),
   sql_select(java.sql.Connection.DB,term,[name,T2],[id,ID2]).

%%% 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(),
	DS = D.getDataSource(URL,DBName,Username,Password),
	DB = DS.getConnection(),
	!.

%%% 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 the data
	Data=CacheData.get(ID),
%println(["Data recovered from cache:",ID,Data]," "),
	% Success, Data (whatever object it is) is returned
	!.
access_data(Type,ID,Data,CacheData) :-
	% Retrieve the data from its location and update the cache
	retrieve_data_general(Type,ID,Data),
	update_cache(Type,ID,Data,CacheData).

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

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) :-
	Size=CacheData.size(),							% size of the cache
	cache_capacity(Type,MaxSize),					% check what is the maximum capacity
	!,														% only one match is used
	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
