/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package colorsegmentation;

import baseclasses.Point;
import java.util.Random;

/**
 * Implementation of K means clustering.
 * @author Alex
 */
public class ClusterAnalysis {
    
    /**
     * Max number of iterations for cluster analysis.
     */
    private final int maxIter;
    
    /**
     * Points to analyze.
     */
    private final Point[] points;
    
    /**
     * Cluster analysis object.
     * @param pts Points to analyze. 
     * @param max max number of iterations.
     */
    public ClusterAnalysis(Point[] pts, int max) {
        this.maxIter = max;
        this.points = pts;
    }
    
    /**
     * Cluster Points into number specified.
     * @param clustNum number of clusters.
     * @return lists of clustered points.
     */
    public Point[][] clusterPoints(int clustNum) {
        Point[][] clusteredPoints = new Point[clustNum][];
        int iter = 0;
        clusteredPoints[0] = this.points;
        for (int i = 1; i < clusteredPoints.length; i++) {
            clusteredPoints[i] = new Point[0];
        }
        Cluster[] clust = new Cluster[clustNum];
        Point[] centroid = this.randomCentroids(clustNum);
        do {
            for (int i = 0; i < clust.length; i++) {
                if (clust[i] != null) {
                    clusteredPoints[i] = clust[i].getPoints();
                }
                clust[i] = new Cluster(centroid[i]);
            }
            for (int i = 0; i < this.points.length; i++) {
                int clustInd = 0;
                double min = clust[clustInd].distance(this.points[i]);
                for (int j = 1; j < clust.length; j++) {
                    double tMin = clust[j].distance(this.points[i]);
                    if (tMin < min) {
                        min = tMin;
                        clustInd = j;
                    }
                }
                clust[clustInd].addPoint(this.points[i]);
            }
            for (int i = 0; i < clustNum; i++) {
                centroid[i] = clust[i].newCent();
            }
            iter++;
        } while(!this.converged(clust, clusteredPoints) && iter < this.maxIter);
        System.out.println("Iterations to convergence: " + iter);
        return clusteredPoints;
    }
    
    /**
     * Generate random centroids for beginning cluster analysis.
     * @param num number of random centroids to create.
     * @return array of random centroids.
     */
    public Point[] randomCentroids(int num) {
        Point[] centroid = new Point[num];
        Random rand = new Random();
        for (int i = 0; i < centroid.length; i++) {
            int randInd = rand.nextInt(this.points.length);
            centroid[i] = this.points[randInd];
        }
        return centroid;
    }
    
    /**
     * If the cluster analysis has converged. 
     * @param clusters collection of cluster objects.
     * @param prev previous points.
     * @return true if converged.
     */
    public boolean converged(Cluster[] clusters, Point[][] prev) {
        for (int i = 0; i < clusters.length; i++) {
            Point[] pClust = prev[i];
            for (int j = 0; j < pClust.length; j++) {
                if (!clusters[i].contains(pClust[j])) {
                    return false;
                }
            }
        }
        System.out.println("Cluster analysis has converged.");
        return true;
    }
    
    
}
