:- module(autobuild_clusters, 
    [
    find_clusters/0,
    grow_cluster/3,
    store_clusters/1,
    find_super_clusters/0,
    grow_super_cluster/3,
    store_super_clusters/1,
    test_cluster_overlap/1,
    consistent_cluster_pair/1
    ]).

/** <module> Find Clusters : Automatic model building

@author Jochem Liem

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

*/

/* Create clusters
*/

%% find_clusters
%  finds all clusters 
find_clusters :-
    get_all_quantity_names(QuantityNames),
    findall(Cluster,
	(
	    member(Quantity, QuantityNames),
	    once(grow_cluster([Quantity], QuantityNames, ClusterUnsorted)),
	    sort(ClusterUnsorted, Cluster)
	),
	ClustersBag
    ),
    setof(Cluster, member(Cluster, ClustersBag), Clusters),
    forall(member(Cluster, Clusters), format('Found cluster ~w\n', [Cluster])),
    test_cluster_overlap(Clusters),
    store_clusters(Clusters).

%% store_clusters(+Clusters:list)
%  Add the cluster to the database (creates a random instantiation of the cluster)
store_clusters(Clusters) :-
    forall(
	member(Cluster, Clusters),
	(
	    random_cluster_instantiation(Cluster, Instantiation),
	    add_cluster(Cluster, Instantiation, false, false)
	)
    ).
    
%% random_cluster_instantiation(+Cluster:list, -Instantiation:list)
%  Create a random instantiation for a cluster
random_cluster_instantiation(Cluster, Instantiation) :-
    random_cluster_instantiation2(Cluster, [], Instantiation).
random_cluster_instantiation2([_Quantity], Instantiation, Instantiation).
random_cluster_instantiation2([Quantity|Quantities], CurrentInstantiation, Instantiation) :-
    nth1(1, Quantities, NextQuantity),
    get_naive_cluster_dependencies(Quantity, NextQuantity, ClusterDependencies),
    append(CurrentInstantiation, ClusterDependencies, NextInstantiation),
    random_cluster_instantiation2(Quantities, NextInstantiation, Instantiation).

%% grow_cluster(+ClusterSeed:list, +Quantities, -Cluster:list)
%  Takes a list of cluster quantities as input and tries to extend them to a full cluster
grow_cluster(Cluster, [], Cluster).
grow_cluster(ClusterSeeds, [Quantity|Quantities], Cluster) :- % succeed clause
    member(ClusterSeed, ClusterSeeds),
    not(member(Quantity, ClusterSeeds)), % No doubles in the cluster
    get_quantity_owner(Quantity, Entity), % The quantity should belong to the same entity as ClusterSeed
    get_quantity_owner(ClusterSeed, Entity),
    consistent_cluster_pair([ClusterSeed, Quantity]),
    append([Quantity], ClusterSeeds, NewSeeds),
    grow_cluster(NewSeeds, Quantities, Cluster).
grow_cluster(ClusterSeeds, [_Quantity|Quantities], Cluster) :- % fail clause
    grow_cluster(ClusterSeeds, Quantities, Cluster).

/* SUPER CLUSTERS */

%% find_clusters
%  finds all super clusters 
find_super_clusters :-
    get_all_clusterIDs(ClusterIDs),
    findall(SuperCluster,
	(
	    member(ClusterID, ClusterIDs),
	    once(grow_super_cluster([ClusterID], ClusterIDs, UnsortedSuperCluster)),
	    sort(UnsortedSuperCluster, SuperCluster)
	),
	SuperClustersBag
    ),
    setof(SuperCluster, member(SuperCluster, SuperClustersBag), SuperClusters),
    forall(member(SuperCluster, SuperClusters), format('Found super cluster ~w\n', [SuperCluster])),
    test_cluster_overlap(SuperClusters),
    store_super_clusters(SuperClusters).

%% grow_cluster(+ClusterSeed:list, +Quantities, -Cluster:list)
%  Takes a list of cluster quantities as input and tries to extend them to a full cluster
grow_super_cluster(SuperCluster, [], SuperCluster).
grow_super_cluster(SuperClusterSeeds, [ClusterID|ClusterIDs], SuperCluster) :-
    member(SuperClusterSeedID, SuperClusterSeeds),
    autobuild_database:cluster(SuperClusterSeedID, SuperClusterQuantities, _, _, _),
    autobuild_database:cluster(ClusterID, ClusterQuantities, _, _, _),
    not(member(ClusterID, SuperClusterSeeds)), % No doubles in the cluster
    % Check if adding the cluster to the supercluster would result in a consistent supercluster
    forall(
	(
	    member(SuperClusterQuantity, SuperClusterQuantities),
	    member(ClusterQuantity, ClusterQuantities)
	),
	(
	    consistent_cluster_pair([ClusterQuantity, SuperClusterQuantity])
	)
    ),
    append([ClusterID], SuperClusterSeeds, NewSuperClusterSeeds),
    grow_super_cluster(NewSuperClusterSeeds, ClusterIDs, SuperCluster).
grow_super_cluster(SuperClusterSeeds, [_Cluster|Clusters], SuperCluster) :- % fail clause
    grow_super_cluster(SuperClusterSeeds, Clusters, SuperCluster).

%% random_super_cluster_instantiation(+SuperCluster:list, -Instantiation:list)
%  Create a random instantiation for a super cluster
random_super_cluster_instantiation(SuperCluster, Instantiation) :-
    once(random_super_cluster_instantiation2(SuperCluster, [], Instantiation)).
random_super_cluster_instantiation2([_Cluster], Instantiation, Instantiation).
random_super_cluster_instantiation2([ClusterID|ClusterIDs], CurrentInstantiation, Instantiation) :-
    nth1(1, ClusterIDs, NextClusterID),
    cluster(ClusterID, Quantities1, _, _, _),
    cluster(NextClusterID, Quantities2, _, _, _),
    % Get the naive dependencies between the last quantity of the first cluster and the first quantity of the second
    % cluster
    last(Quantities1, Quantity1),
    nth1(1, Quantities2, Quantity2),
    get_naive_cluster_dependencies(Quantity1, Quantity2, SuperClusterDependencies),
    % Append the dependencies to the instantiation
    append(CurrentInstantiation, SuperClusterDependencies, NextInstantiation),
    random_super_cluster_instantiation2(ClusterIDs, NextInstantiation, Instantiation).


%% store_super_clusters(+SuperClusters:list)
%  Add the super clusters to the database (creates a random instantiation of the supercluster)
store_super_clusters(SuperClusters) :-
    forall(
	member(SuperCluster, SuperClusters),
	(
	    random_super_cluster_instantiation(SuperCluster, Instantiation),
	    add_super_cluster(SuperCluster, Instantiation, false, false)
	)
    ).


/* GENERAL */

%% consistent_cluster_pair(+Pair:list)
%  Determines is the elements in the pair could be a consist cluster
consistent_cluster_pair(Pair) :-
    [A, B] = Pair,
    (
	naive_dependency(q_correspondence(A, B)),
	naive_dependency(q_correspondence(B, A)),
	naive_dependency(prop_pos(A, B)),
	naive_dependency(prop_pos(B, A))
    ;
	naive_dependency(mirror_q_correspondence(A,B)),
	naive_dependency(mirror_q_correspondence(B,A)),
	naive_dependency(prop_neg(A, B)),
	naive_dependency(prop_neg(B, A))
    ).

%% test_cluster_overlap(+Clusters:list)
%  Test if the clusters have overlap. Fails if they do.
test_cluster_overlap(Clusters) :-
    forall(
	member(Cluster, Clusters),
	(
	    select(Cluster, Clusters, RestClusters),
	    forall(
		member(OtherCluster, RestClusters),
		intersection(Cluster, OtherCluster, [])
	    )
	)
    ).

test_cluster_overlap(_Clusters) :-
    format('ERROR: Cluster overlap found! Model building stopped\n').

