:- module(autobuild_database, 
    [
    add_naive_dependency/1,
    remove_naive_dependency/1,
    clear_naive_dependencies/0,
    add_cluster/4,
    update_cluster/4,
    get_cluster/2,
    remove_cluster/1,
    clear_clusters/0,
    get_single_quantity_from_each_cluster/1,
    add_super_cluster/4,
    update_super_cluster/4,
    get_super_cluster/2,
    remove_super_cluster/1,
    clear_super_clusters/0,
    get_single_quantity_from_each_super_cluster/1,
    clear_all/0,
    naive_dependency/1,
    cluster/5,
    get_naive_cluster_dependencies/3,
    get_all_clusterIDs/1,
    add_external_actuator/1,
    delete_external_actuator/1,
    clear_external_actuators/0,
    get_all_super_clusterIDs/1,
    super_cluster_input_quantity/2,
    cluster_input_quantity/2,
    super_cluster_output_quantity/2,
    cluster_output_quantity/2,
    super_cluster/5,
    causal_group/2,
    add_causal_group/1,
    clear_causal_groups/0,
    actuators/2,
    clear_actuators/0,
    derived_influence/1,
    get_all_super_cluster_quantities/2
    ]).

/** <module> Database : Automatic model building

@author Jochem Liem

 * Automatic model building algorithm
 * Inspired by Hylke Buisman's algorithm

*/

%% naive_dependency(-NaiveDependency)
% NaiveDependency is on of the following type of dependencies:
% inf_pos_by(B,A),
% inf_neg_by(B,A),
% prop_pos(B,A),
% prop_neg(B,A),
% q_correspondence(B,A),
% mirror_q_correspondence(B,A)
% where A and B are internal quantity names
:- dynamic(naive_dependency/1).

%% cluster(-ClusterID, -QuantityNames, -ClusterInstantiation, -UserGenerated, -Actuated)
%  ClusterID is a unique id for a specific cluster
%  QuantityNames is a list of quantity names
%  ClusterInstantiation is a set of naive dependencies that represent a specific instantiation of the cluster
%  UserGenerated indicates if the cluster instantiation was chosen by the modeller
%  Actuated indicates whether the cluster is gets a value
:- dynamic(cluster/5).

%% super_cluster(-SuperClusterID, -ClusterIDs, -SuperClusterInstantiation, -UserGenerated, -Actuated)
%  SuperClusterID is a unique id for a specific super cluster
%  ClusterIDs is a list of cluster IDs
%  Super ClusterInstantiation is a set of naive dependencies that represent a specific instantiation of the super cluster
%  UserGenerated indicates if the super cluster instantiation was chosen by the modeller
%  Actuated indicates whether the super cluster is gets a value
:- dynamic(super_cluster/5).

%% causal_group(-CausalGroupID, -CausalGroup:list)
% CausalGroupID is a unique ID for a causal group
% CausalGroup is a set of superclusters forming a causal group
:- dynamic(causal_group/2).

%% actuators(-CausalGroupID, -Actuators:list)
% CausalGroupID is a unique ID for a causal group
% Actuators is a list of influences actuating the causal group
:- dynamic(actuators/2).

%% derived_influence(?Influence)
% Data structure which stores the influences derived by derive/2
:- dynamic derived_influence(1).

%% add_naive_dependency(+Dependency)
%  Adds a naive dependency to the database
add_naive_dependency(Dependency) :-
    assert(naive_dependency(Dependency)).

%% remove_naive_dependency(+Dependency)
%  removes a specific dependency from the database
remove_naive_dependency(Dependency) :-
    retractall(naive_dependency(Dependency)).

%% clear_naive_dependencies
%  Removes all naive dependencies from the database
clear_naive_dependencies :-
    retractall(naive_dependency(_)).

%% get_naive_cluster_dependencies(+Quantity1, +Quantity2, -ClusterDependencies:list)
%  Get the cluster dependencies  between two quantities  
get_naive_cluster_dependencies(Quantity1, Quantity2, ClusterDependencies) :-
    get_cluster_dependency_types(ClusterDependencyTypes),
    findall(ClusterDependency,
	(
	    member(DependencyType, ClusterDependencyTypes),
	    ClusterDependency =.. [DependencyType, Quantity2, Quantity1],
	    naive_dependency(ClusterDependency)
	),
	ClusterDependencies
    ).

%% add_cluster(+Quantities, +InstantiationNaiveDependenciesList, +UserGenerated, +Actuated) 
%  Add a cluster to the database 
add_cluster(Quantities, InstantiationNaiveDependenciesList, UserGenerated, Actuated) :-
    gensym(cluster, ClusterID),
    assert(cluster(ClusterID, Quantities, InstantiationNaiveDependenciesList, UserGenerated, Actuated)).

%% update_cluster(+ClusterID, +InstantiationNaiveDependenciesList, +UserGenerated, +Actuated) 
%  update_fields of a certain cluster
update_cluster(ClusterID, NewInstantiationNaiveDependenciesList, NewUserGenerated, NewActuated) :-
    cluster(ClusterID, Quantities, _InstantiationNaiveDependenciesList, _UserGenerated, _Actuated),
    remove_cluster(ClusterID),
    assert(cluster(ClusterID, Quantities, NewInstantiationNaiveDependenciesList, NewUserGenerated, NewActuated)).

%% get_cluster(+Quantity, -ClusterID)
%  Get the cluster the quantity belongs to
get_cluster(Quantity, ClusterID) :-
    get_all_clusterIDs(ClusterIDs),
    member(ClusterID, ClusterIDs),
    cluster(ClusterID, ClusterQuantities, _, _, _),
    member(Quantity, ClusterQuantities).

%% get_single_quantity_from_each_cluster(-Quantities:list)
%  Creates a list of one quantity from each cluster
get_single_quantity_from_each_cluster(Quantities) :-
    get_all_clusterIDs(ClusterIDs),
    findall(Quantity,
	(
	    member(ClusterID, ClusterIDs),
	    cluster(ClusterID, ClusterQuantities, _, _, _),
	    nth1(1, ClusterQuantities, Quantity)
	),
	Quantities
    ).

%% remove_cluster(+ClusterID)
%  removes cluster ClusterID from the database
remove_cluster(ClusterID) :-
    retractall(cluster(ClusterID,_,_,_,_)).

%% clear_clusters
%  Removes all clusters from the database
clear_clusters :-
    retractall(cluster(_,_,_,_,_)).

%% get_all_clusterIDs(-ClusterIDs:list)
%  Returns all the IDs of the clusters
get_all_clusterIDs(ClusterIDs) :-
    findall(ClusterID,
	cluster(ClusterID, _, _, _, _),
	ClusterIDs
    ).

/* Super Clusters */
%% add_super_cluster(+ClusterIDs, +InstantiationNaiveDependenciesList, +UserGenerated, +Actuated) 
%  Add a super cluster to the database 
add_super_cluster(ClusterIDs, InstantiationNaiveDependenciesList, UserGenerated, Actuated) :-
    gensym(super_cluster, SuperClusterID),
    assert(super_cluster(SuperClusterID, ClusterIDs, InstantiationNaiveDependenciesList, UserGenerated, Actuated)).

%% update_super_cluster(+SuperClusterID, +InstantiationNaiveDependenciesList, +UserGenerated, +Actuated) 
%  update_fields of a certain super cluster
update_super_cluster(SuperClusterID, NewInstantiationNaiveDependenciesList, NewUserGenerated, NewActuated) :-
    super_cluster(SuperClusterID, ClusterIDs, _InstantiationNaiveDependenciesList, _UserGenerated, _Actuated),
    remove_super_cluster(SuperClusterID),
    assert(super_cluster(SuperClusterID, ClusterIDs, NewInstantiationNaiveDependenciesList, NewUserGenerated, NewActuated)).

%% get_super_cluster(+Quantity, -ClusterID)
%  Get the cluster the quantity belongs to
get_super_cluster(Quantity, SuperClusterID) :-
    get_all_super_clusterIDs(SuperClusterIDs),
    member(SuperClusterID, SuperClusterIDs),
    super_cluster(SuperClusterID, Clusters, _, _, _),
    member(ClusterID, Clusters), 
    cluster(ClusterID, ClusterQuantities, _, _, _),
    member(Quantity, ClusterQuantities).

%% get_single_quantity_from_each_super_cluster(-Quantities:list)
%  Creates a list of one quantity from each super cluster
get_single_quantity_from_each_super_cluster(Quantities) :-
    get_all_super_clusterIDs(SuperClusterIDs),
    findall(Quantity,
	(
	    member(SuperClusterID, SuperClusterIDs),
	    super_cluster(SuperClusterID, ClusterIDs, _, _, _),
	    once(
		(
		member(ClusterID, ClusterIDs),
		cluster(ClusterID, ClusterQuantities, _, _, _),
		nth1(1, ClusterQuantities, Quantity)
	    )
	    )
	),
	Quantities
    ).

%% remove_super_cluster(+SuperClusterID)
%  Removes super cluster ClusterID from the database
remove_super_cluster(SuperClusterID) :-
    retractall(super_cluster(SuperClusterID,_,_,_,_)).

%% clear_clusters
%  Removes all clusters from the database
clear_super_clusters :-
    retractall(super_cluster(_,_,_,_,_)).

%% get_all_super_clusterIDs(-SuperClusterIDs:list)
%  Returns all the IDs of the super clusters
get_all_super_clusterIDs(SuperClusterIDs) :-
    findall(SuperClusterID,
	super_cluster(SuperClusterID, _, _, _, _),
	SuperClusterIDs
    ).

/* External Actuators */

%% add_external_actuator(+Actuator)
add_external_actuator(Actuator) :-
    assert(external_actuator(Actuator)).

%% delete_external_actuator(+Actuator)
delete_external_actuator(Actuator) :-
    retractall(external_actuator(Actuator)).

%% clear_external_actuators
clear_external_actuators :-
    retractall(external_actuator(_)).

%% clear_all
%  Clear the entire model building database
clear_all :-
    clear_naive_dependencies,
    clear_clusters,
    clear_super_clusters,
    clear_external_actuators,
    clear_causal_groups,
    clear_actuators,
    clear_derived_influences.

%% super_cluster_input_quantity(?InputQuantity, ?SuperCluster)
% Input quantity of a super cluster.
super_cluster_input_quantity(InputQuantity, SuperCluster) :-
    super_cluster(SuperCluster, Clusters, _, _, _),
    nth1(1, Clusters, InputCluster),
    cluster_input_quantity(InputQuantity, InputCluster).

%% cluster_input_quantity(?InputQuantity, Cluster)
% Input quantity of a cluster.
cluster_input_quantity(InputQuantity, Cluster) :-
    cluster(Cluster, Quantities, _, _, _),
    nth1(1, Quantities, InputQuantity).

%% super_cluster_output_quantity(?OutputQuantity, ?SuperCluster)
% Output quantity of a super cluster.
super_cluster_output_quantity(OutputQuantity, SuperCluster) :-
    super_cluster(SuperCluster, Clusters, _, _, _),
    last(Clusters, OutputCluster),
    cluster_output_quantity(OutputQuantity, OutputCluster).

%% cluster_output_quantity(?InputQuantity, ?Cluster)
% Output quantity of a cluster.
cluster_output_quantity(OutputQuantity, Cluster) :-
    cluster(Cluster, Quantities, _, _, _),
    last(Quantities, OutputQuantity).

%% add_causal_group(+CausalGroup, +CausalGroupInstantiation)
% Adds a causal group to the database.
add_causal_group(CausalGroup) :-
    gensym(causal_group, CausalGroupID),
    assert(causal_group(CausalGroupID, CausalGroup)).

%% clear_causal_groups
clear_causal_groups :-
    retractall(causal_group(_,_)).

%% clear_actuators
clear_actuators :-
    retractall(actuators(_,_)).

%%clear_derived_influences
clear_derived_influences :-
    retractall(derived_influences(_)).

get_all_super_cluster_quantities(SuperClusterID, Quantities) :-
    super_cluster(SuperClusterID, Clusters, _, _, _),
    findall(
        ClusterQuantities,
	(
	    member(ClusterID, Clusters),
	    cluster(ClusterID, ClusterQuantities, _, _, _)
	),
	SuperClusterQuantities
    ),
    flatten(SuperClusterQuantities, Quantities).
