package net.returnvoid.analytics;

import java.util.ArrayList;
import java.util.Arrays;

import net.returnvoid.color.ColorDifference;
import net.returnvoid.color.ColorDifferenceMeasure;
import net.returnvoid.color.ColorHelper;
import net.returnvoid.color.ColorSpace;
import net.returnvoid.color.RGBColor;
import net.returnvoid.color.RColor;
import net.returnvoid.graphics.grid.GridMaker;
import net.returnvoid.graphics.grid.TableGrid;
import net.returnvoid.tools.RMath;
import processing.core.PImage;

/*
 * This code is copyright (c) Diana Lange 2017
 *
 * The library is published under the Creative Commons license NonCommercial 4.0.
 * Please check https://creativecommons.org/licenses/by-nc/4.0/ for more information.
 * 
 * This program is distributed in the hope that it will be useful, but without any warranty.
 */

/**
 * This class provides basic clustering solutions to learn clusterings and to
 * evaluate these clusterings.
 * 
 * @author Diana Lange
 *
 */
public class ClusteringHelper {

	/*
	 * ------------------------------------------------------------------------
	 * | General |
	 * ------------------------------------------------------------------------
	 */

	/**
	 * No instances of this class are allowed to be built.
	 */
	private ClusteringHelper() {
	}

	// loss

	/**
	 * Computed the mean loss of the given <b>clusters</b>.
	 * 
	 * @param clusters
	 *            A set of clusters.
	 * @return The mean loss of all the clusters.
	 */
	static public Float computeLoss(Cluster[] clusters) {
		float loss = 0;

		for (Cluster c : clusters) {
			loss += c.getLoss();
		}

		loss /= clusters.length;

		return loss;
	}

	/**
	 * Calculates the loss for all the clusterings in the given set.
	 * 
	 * @param clustering
	 *            A set of clusterings.
	 * @return The set of losses for each given clustering.d
	 */
	static public float[] getLosses(Clustering[] clustering) {
		float[] losses = new float[clustering.length];
		for (int i = 0; i < losses.length; i++) {
			losses[i] = clustering[i].getLoss();
		}

		return losses;
	}

	// training

	/**
	 * Creates a randomly sampled subset of the give set <b>raw</b>.
	 * 
	 * @param raw
	 *            The original data.
	 * @param trainingSize
	 *            The number of elements the training data should consist of. If
	 *            the given set <b>raw</b> is smaller than trainingSize, the
	 *            returned set is not a subset, but a randomly sorted version of
	 *            the original set.
	 * @return The shuffled subset of the input data.
	 */
	static public Object[] getTrainingData(Object[] raw, Integer trainingSize) {
		if (raw.length <= trainingSize) {
			trainingSize = raw.length;
		}

		// don't touch the original data
		Object[] copyArray = new Object[raw.length];
		System.arraycopy(raw, 0, copyArray, 0, raw.length);

		// switch every element of the copy with a random chosen other element
		// from the data
		for (int i = 0; i < copyArray.length; i++) {
			int switchIndex = RMath.random(copyArray.length);

			Object tmp = copyArray[i];
			copyArray[i] = copyArray[switchIndex];
			copyArray[switchIndex] = tmp;
		}

		// no subset needed
		if (copyArray.length == trainingSize) {
			return copyArray;
		}

		// copy element into the subset
		Object[] shuffled = new Object[trainingSize];
		System.arraycopy(copyArray, 0, shuffled, 0, shuffled.length);

		return shuffled;

	}

	/*
	 * ------------------------------------------------------------------------
	 * | Color Clustering |
	 * ------------------------------------------------------------------------
	 */

	/**
	 * Builds new ColorClusters from a given set of colors.
	 * 
	 * @param means
	 *            The colors that should be the initial means of the new
	 *            clusters.
	 * @param m
	 *            A colorDifferenceMeasure.
	 * @return ColorClusters with means (= given <b>means</b>) but without
	 *         members.
	 */
	static public ColorCluster[] colorsToClusters(RColor[] means, ColorDifferenceMeasure m) {
		ColorCluster[] clusters = new ColorCluster[means.length];

		for (int i = 0; i < clusters.length; i++) {
			clusters[i] = new ColorCluster(means[i], m);
		}

		return clusters;
	}

	/**
	 * Assigns each color from given set of <b>colors</b> to one of the cluster
	 * of the given set <b>clusters</b>. The means of each cluster will be
	 * updated after all colors are assigned to their clusters. For any color
	 * the most similar cluster is chosen, i.e. the distance to the clusters
	 * mean is minimal. The distance is calculated based on the
	 * ColorDifferenceMeasure of the first cluster in <b>clusters</b>.
	 * 
	 * @param clusters
	 *            A set of clusters with already assigned means and
	 *            ColorDifferenceMeasure. The ColorDifferenceMeasure for all
	 *            clusters should be equal.
	 * @param colors
	 *            A set of colors. For better performance make sure that the
	 *            color space of all colors are equal and equal to the cluster
	 *            means color spaces as well as that the color space correspond
	 *            to the ColorDifferenceMeasure of the clusters.
	 * @return The updated clusters with new cluster members as well as updated
	 *         means.
	 */
	static public ColorCluster[] addColorsToClusters(ColorCluster[] clusters, RColor[] colors) {

		ColorDifferenceMeasure m = clusters[0].getColorDifferenceMeasure();

		// inital clustering - add all colors to one cluster

		Float minDiff;
		int minID;

		for (int j = 0; j < colors.length; j++) {

			minDiff = 1000000f;
			minID = 0;

			for (int i = 0; i < clusters.length; i++) {

				Float diff = ColorDifference.difference(colors[j], clusters[i].getMean(), m);

				if (diff < minDiff) {
					minDiff = diff;
					minID = i;
				}
			}

			clusters[minID].addElement(colors[j]);
		}

		// update the cluster means

		for (ColorCluster cluster : clusters) {
			cluster.updateMean();
		}

		return clusters;
	}

	// k-means implementation

	/**
	 * Chooses k colors from the given set of <b>colors</b>. These colors are as
	 * different to each other as possible. If <b>existingClusters</b> is not
	 * null, these clusters / the means of these clusters are used for the
	 * returned k colors. But at least one new color of the given set is added,
	 * even when existingClusters.length = k.
	 * 
	 * @param colors
	 *            A set of colors. The color space of these should correspond to
	 *            the given ColorDifferenceMeasure (e.g. RGBColor and
	 *            RGBEuclidean) to prevent unnecessary color space conversions.
	 * @param k
	 *            The number of colors which will be returned.
	 * @param existingClusters
	 *            Null or ColorClusters which will provide max. k - 1 colors to
	 *            the returned k colors from their means.
	 * @param m
	 *            A ColorDifferenceMeasure (not null).
	 * @return The k-coloes.
	 */
	static private RColor[] kmeansInitialClusterMeans(RColor[] colors, Integer k, ColorCluster[] existingClusters,
			ColorDifferenceMeasure m) {

		/*
		 * if (m == null) { m = ColorDifferenceMeasure.RGBEuclidean; }
		 * 
		 * k = k < 2 ? 2 : k;
		 */
		RColor[] clusterCenters;
		if (existingClusters == null) {

			RColor[] tmpCenters = new RColor[2];

			// find two first starting colors that are as different as possible
			// do that by starting with n (=20) random starting setups
			// and keep best result
			Float maxDiff = 0f;
			for (int i = 0; i < 15; i++) {
				RColor c1 = colors[RMath.random(colors.length)];
				RColor c2 = null;
				int id = ColorDifference.getMostDifferentColor(c1, colors, m);

				if (id == -1) {
					// no color found that isn't already a cluster center
					c2 = RGBColor.getRandomRGB();
				} else {
					c2 = colors[id];
				}

				Float diff = ColorDifference.difference(c1, c2, m);

				if (diff > maxDiff) {
					tmpCenters[0] = c1;
					tmpCenters[1] = c2;
				}
			}

			if (k == 2) {
				return tmpCenters;
			}

			existingClusters = ClusteringHelper.colorsToClusters(tmpCenters, m);
		}

		clusterCenters = new RColor[k];

		int numberOfOldClusters = existingClusters.length;

		// at least one new cluster center should be added
		if (numberOfOldClusters >= k) {
			numberOfOldClusters--;
		}

		// copy old clustercenters
		for (int i = 0; i < numberOfOldClusters; i++) {
			clusterCenters[i] = existingClusters[i].getMean();
		}

		// println("new clusters: " + (clusterCenters.length -
		// numberOfOldClusters) + " of " +clusterCenters.length);

		// add new cluster centers
		for (int i = numberOfOldClusters; i < clusterCenters.length; i++) {
			Integer id = ColorDifference.getMostDifferentColor(clusterCenters, colors, m);
			if (id == -1) {
				// no color found that isn't already a cluster center
				clusterCenters[i] = RGBColor.getRandomRGB();
			} else {
				clusterCenters[i] = colors[id];
			}
		}

		return clusterCenters;
	}

	/**
	 * Updates all given clusters until they are stable: The colors of all
	 * clusters may change their cluster if other clusters are more similar than
	 * the current one. After all colors are updated the cluster means are
	 * updated and the process starts over. The clusters are considered stable,
	 * when no color changed to another cluster in one iteration. Clusters
	 * should be initialized before. That means all clusters should have a
	 * reasonable cluster center and colors as clustered members.
	 * 
	 * @param clusters
	 *            The clusters with means, members and a ColorDifferenceMeasure
	 *            to define the similarity measure.
	 * @return The updated clusters.
	 */
	static private ColorCluster[] kmeansClusterColors(ColorCluster[] clusters) {

		Float minDiff;
		int minID;

		ColorDifferenceMeasure m = clusters[0].getColorDifferenceMeasure();

		// optimize the clusters until clusters are stabilized / try max (20)
		// times to stabilize
		boolean colorChangedCluster = true;
		int count = 0;
		while (colorChangedCluster && count < 15) {
			colorChangedCluster = false;

			// iterate over all clusters
			for (int j = 0; j < clusters.length; j++) {

				// iterate over all colors of that cluster
				for (int k = 0; k < clusters[j].getElements().size(); k++) {
					minDiff = 1000000f;
					minID = 0;

					RColor c = clusters[j].getElements().get(k);

					// find the best cluster for the current color
					for (int i = 0; i < clusters.length; i++) {

						Float diff = ColorDifference.difference(c, clusters[i].getMean(), m);

						if (diff < minDiff) {
							minDiff = diff;
							minID = i;
						}
					}

					// is false, when clusters[j] = clusters[minID]
					boolean changed = clusters[j].switchElement(c, clusters[minID]);
					if (changed) {
						colorChangedCluster = true;
					}
				}
			}

			// update means
			for (ColorCluster cluster : clusters) {
				cluster.updateMean();
			}

			count++;
		}

		// remove clusters that are empty
		// there shouldn't be any empty clusters when
		// the means are member of the input data - if not, there might be empty
		// ones
		// this is just kept in for savety,
		// but probably could be removed

		ArrayList<ColorCluster> noEmpties = new ArrayList<ColorCluster>();

		int numberOfRemovedClusters = 0;
		for (int i = 0; i < clusters.length; i++) {
			if (clusters[i].size() == 0) {
				numberOfRemovedClusters++;
			} else {
				noEmpties.add(clusters[i]);
			}
		}

		// System.out.println("k: " + clusters.length + ", empty clusters: " +
		// numberOfRemovedClusters);

		if (numberOfRemovedClusters == 0) {
			return clusters;
		} else {
			// System.out.println("reduced returned: " + noEmpties.size());
			return noEmpties.toArray(new ColorCluster[noEmpties.size()]);
		}
	}

	/**
	 * Applies the k-means algorithm to colors for a given k. The difference
	 * measure which will be applied is set by the given parameter <b>m</b>. The
	 * initial clusters are built by looking for the most different k colors in
	 * the given set of <b>colors</b>.
	 * 
	 * @param colors
	 *            The set of colors which will be clustered. To prevent
	 *            unnecessary color space transformations please make sure that
	 *            these colors are all members of the same color space and that
	 *            this color space correlates to the give
	 *            ColorDifferenceMeasure. You can convert the colors using the
	 *            ColorHelper.convert(ColorDifferenceMeasure) method.
	 * @param k
	 *            The number of clusters (>= 2).
	 * @param m
	 *            The ColorDifferencemeasure.
	 * @return The clustering of the given input set. The clustering might not
	 *         consists of k clusters but less (empty clusters are removed).
	 */
	static public ColorClustering kmeans(RColor[] colors, Integer k, ColorDifferenceMeasure m) {

		return ClusteringHelper.kmeans(colors, k, m, null);
	}

	/**
	 * Applies the k-means algorithm to colors for a given k. The difference
	 * measure which will be applied is set by the given parameter <b>m</b>. If
	 * <b>existingClusters</b> is given the initial cluster will be set by these
	 * and one or more new cluster centers will be added to create the first k
	 * clusters. If <b>existingClusters</b> is null, the initial clusters are
	 * built by looking for the most different k colors in the given set of
	 * <b>colors</b>.
	 * 
	 * @param colors
	 *            The set of colors which will be clustered. To prevent
	 *            unnecessary color space transformations please make sure that
	 *            these colors are all members of the same color space and that
	 *            this color space correlates to the give
	 *            ColorDifferenceMeasure. You can convert the colors using the
	 *            ColorHelper.convert(ColorDifferenceMeasure) method.
	 * @param k
	 *            The number of clusters (>= 2).
	 * @param m
	 *            The ColorDifferencemeasure.
	 * @param existingClusters
	 *            null or previously calculated clusters. The means of them will
	 *            serve as initial cluster centers for the new clustering.
	 * @return The clustering of the given input set. The clustering might not
	 *         consists of k clusters but less (empty clusters are removed).
	 */
	static public ColorClustering kmeans(RColor[] colors, Integer k, ColorDifferenceMeasure m,
			ColorCluster[] existingClusters) {

		// get first cluster centers
		RColor[] means = ClusteringHelper.kmeansInitialClusterMeans(colors, k, existingClusters, m);

		// build initial clusters from cluster centers
		ColorCluster[] clusters = ClusteringHelper.colorsToClusters(means, m);

		// add colors to clusters
		clusters = ClusteringHelper.addColorsToClusters(clusters, colors);

		// find optimal clustering
		clusters = ClusteringHelper.kmeansClusterColors(clusters);

		// save the current clustering
		return new ColorClustering(clusters, k);
	}

	/**
	 * Applies the k-means algorithm to colors for a range of k. The difference
	 * measure which will be applied is set by the given parameter <b>m</b>. For
	 * k=<b>minK</b> the initial clusters are built by looking for the most
	 * different k colors in the given set of <b>colors</b>. For k><b>minK</b>,
	 * the initial cluster centers are built from the cluster centers of k-1.
	 * This means, for k><b>minK</b> just one new cluster center is added. This
	 * new cluster is build again with a color that is the most different to all
	 * the existing cluster centers.
	 * 
	 * @param colors
	 *            The set of colors which will be clustered. To prevent
	 *            unnecessary color space transformations please make sure that
	 *            these colors are all members of the same color space and that
	 *            this color space correlates to the give
	 *            ColorDifferenceMeasure. You can convert the colors using the
	 *            ColorHelper.convert(ColorDifferenceMeasure) method.
	 * @param minK
	 *            The number of clusters for the first clustering (minK >= 2,
	 *            minK < maxK).
	 * @param maxK
	 *            The number of clusters for the last clustering (maxK <
	 *            colors.length, minK < maxK).
	 * @param m
	 *            The ColorDifferencemeasure.
	 * @return The Clusterings for minK to maxK.
	 */
	static public ColorClustering[] kmeansClusterings(RColor[] colors, Integer minK, Integer maxK,
			ColorDifferenceMeasure m) {
		ColorClustering[] clusterings = new ColorClustering[1 + maxK - minK];

		if (m == null) {
			m = colors[0].getColorSpace().toColorDifferenceMeasure();
		}

		for (int i = 0; i < clusterings.length; i++) {
			int k = i + minK;

			// use the cluster centers of k - 1 to start the new clustering for
			// k
			clusterings[i] = ClusteringHelper.kmeans(colors, k, m, i == 0 ? null : clusterings[i - 1].getClusters());
		}

		return clusterings;
	}

	// training

	/**
	 * Picks <b>trainingsSize</b> semi-random colors from the input image. The
	 * image is divided into a regular grid (table grid). Within the cells of
	 * the grid one sampler is chosen randomly. The picked colors are converted
	 * to a RGBColor
	 * 
	 * @param img
	 *            A image (Number of pixels should be higher then
	 *            trainingsSize).
	 * @param trainingsSize
	 *            The number of colors that should be sampled from the image.
	 * @return The sampled colors.
	 */
	static public RColor[] getTrainingColors(PImage img, Integer trainingsSize) {
		return ClusteringHelper.getTrainingColors(img, trainingsSize, ColorDifferenceMeasure.RGBEuclidean, 1);
	}

	/**
	 * Picks <b>trainingsSize</b> semi-random colors from the input image. The
	 * image is divided into a regular grid (table grid). Within the cells of
	 * the grid the samplers are chosen randomly. The picked colors are
	 * converted to a RGBColor
	 * 
	 * @param img
	 *            A image (Number of pixels should be higher then
	 *            trainingsSize).
	 * @param trainingsSize
	 *            The number of colors that should be sampled from the image.
	 * @param cellSize
	 *            Sets the number of random chosen color samples per cell. If
	 *            cellSize=1, there are a lot of small cells and in each sell
	 *            one color is picked. If cellSize=2, there are bigger cells and
	 *            in each cell two colors are picked. In most cases cellSize=1
	 *            is the most convenient value.
	 * @return The sampled colors.
	 */
	static public RColor[] getTrainingColors(PImage img, Integer trainingsSize, Integer cellSize) {
		return ClusteringHelper.getTrainingColors(img, trainingsSize, ColorDifferenceMeasure.RGBEuclidean, 1);
	}

	/**
	 * Picks <b>trainingsSize</b> semi-random colors from the input image. The
	 * image is divided into a regular grid (table grid). Within the cells of
	 * the grid one sampler is chosen randomly. The picked colors are converted
	 * to a color space defined by the given ColorDifferenceMeasure (e.g.
	 * m=RGBEuclidean -> returned colors are RGBColors).
	 * 
	 * @param img
	 *            A image (Number of pixels should be higher then
	 *            trainingsSize).
	 * @param trainingsSize
	 *            The number of colors that should be sampled from the image.
	 *
	 * @param m
	 *            A ColorDifferenceMeasure (defines in what color space the
	 *            colors are returned).
	 * @return The sampled colors.
	 */
	static public RColor[] getTrainingColors(PImage img, Integer trainingsSize, ColorDifferenceMeasure m) {
		return ClusteringHelper.getTrainingColors(img, trainingsSize, m, 1);
	}

	/**
	 * Picks <b>trainingsSize</b> semi-random colors from the input image. The
	 * image is divided into a regular grid (table grid). Within the cells of
	 * the grid one sampler is chosen randomly. The picked colors are converted
	 * to the given color <b>space</b>.
	 * 
	 * @param img
	 *            A image (Number of pixels should be higher then
	 *            trainingsSize).
	 * @param space
	 *            A color space (defines in what color space the colors are
	 *            returned).
	 * @param cellSize
	 *            Sets the number of random chosen color samples per cell. If
	 *            cellSize=1, there are a lot of small cells and in each sell
	 *            one color is picked. If cellSize=2, there are bigger cells and
	 *            in each cell two colors are picked. In most cases cellSize=1
	 *            is the most convenient value.
	 * @return The sampled colors.
	 */
	static public RColor[] getTrainingColors(PImage img, Integer trainingsSize, ColorSpace space) {
		return ClusteringHelper.getTrainingColors(img, trainingsSize, space.toColorDifferenceMeasure(), 1);
	}

	/**
	 * Picks <b>trainingsSize</b> semi-random colors from the input image. The
	 * image is divided into a regular grid (table grid). Within the cells of
	 * the grid the samplers are chosen randomly. The picked colors are
	 * converted to the given color <b>space</b>.
	 * 
	 * @param img
	 *            A image (Number of pixels should be higher then
	 *            trainingsSize).
	 * @param trainingsSize
	 *            The number of colors that should be sampled from the image.
	 * @param space
	 *            A color space (defines in what color space the colors are
	 *            returned).
	 * @param cellSize
	 *            Sets the number of random chosen color samples per cell. If
	 *            cellSize=1, there are a lot of small cells and in each sell
	 *            one color is picked. If cellSize=2, there are bigger cells and
	 *            in each cell two colors are picked. In most cases cellSize=1
	 *            is the most convenient value.
	 * @return The sampled colors.
	 */
	static public RColor[] getTrainingColors(PImage img, Integer trainingsSize, ColorSpace space, int cellSize) {
		return ClusteringHelper.getTrainingColors(img, trainingsSize, space.toColorDifferenceMeasure(), cellSize);
	}

	/**
	 * Picks <b>trainingsSize</b> semi-random colors from the input image. The
	 * image is divided into a regular grid (table grid). Within the cells of
	 * the grid the samplers are chosen randomly. The picked colors are
	 * converted to a color space defined by the given ColorDifferenceMeasure
	 * (e.g. m=RGBEuclidean -> returned colors are RGBColors).
	 * 
	 * @param img
	 *            A image (Number of pixels should be higher then
	 *            trainingsSize).
	 * @param trainingsSize
	 *            The number of colors that should be sampled from the image.
	 * @param m
	 *            A ColorDifferenceMeasure (defines in what color space the
	 *            colors are returned).
	 * @param cellSize
	 *            Sets the number of random chosen color samples per cell. If
	 *            cellSize=1, there are a lot of small cells and in each sell
	 *            one color is picked. If cellSize=2, there are bigger cells and
	 *            in each cell two colors are picked. In most cases cellSize=1
	 *            is the most convenient value.
	 * @return The sampled colors.
	 */
	static public RColor[] getTrainingColors(PImage img, Integer trainingsSize, ColorDifferenceMeasure m,
			Integer cellSize) {
		RColor[] colors = new RColor[trainingsSize];
		ColorSpace space = m.toColorSpace();

		int elementsPerCell = cellSize < 1 ? 1 : cellSize;

		TableGrid grid = GridMaker.createTable(0, 0, img.width, img.height, trainingsSize / elementsPerCell);

		img.loadPixels();
		for (int i = 0; i < grid.size(); i++) {
			for (int j = 0; j < elementsPerCell; j++) {

				// random position within the grid's cell
				float x = grid.getX(i) + RMath.random(-0.5f, 0.5f) * grid.cellWidth();
				float y = grid.getY(i) + RMath.random(-0.5f, 0.5f) * grid.cellHeight();

				// calculate position within one-dimensional pixels array by
				// the given two-dimensional position
				int index = (int) y * img.width + (int) x;

				// get the color and store it and convert to set color space
				colors[i * elementsPerCell + j] = RGBColor.toRGB(img.pixels[index]);
				colors[i * elementsPerCell + j] = ColorHelper.convert(colors[i * elementsPerCell + j], space);
			}
		}

		// since grid.size() * elementsPerCell might be smaller than
		// trainingsSize, some empty space int the colors array might be left
		// over which needs to be filled

		for (int i = grid.size() * elementsPerCell; i < colors.length; i++) {
			int ci = RMath.random(img.pixels.length);
			colors[i] = RGBColor.toRGB(img.pixels[ci]);

			colors[i] = ColorHelper.convert(colors[i], space);

		}

		return colors;

	}

	/**
	 * Creates a randomly sampled subset of the give set of <b>colors</b>.
	 * 
	 * @param colors
	 *            The original data.
	 * @param trainingsSize
	 *            The number of elements the training data should consist of. If
	 *            the given set <b>colors</b> is smaller than trainingSize, the
	 *            returned set is not a subset, but a randomly sorted version of
	 *            the original set.
	 * @return The shuffled subset of the input data.
	 */
	static public RColor[] getTrainingColors(RColor[] colors, Integer trainingsSize) {
		return ClusteringHelper.getTrainingColors(colors, trainingsSize, ColorDifferenceMeasure.RGBEuclidean);
	}

	/**
	 * Creates a randomly sampled subset of the give set of <b>colors</b> and
	 * converts the returned colors to the given color <b>space</b>. If
	 * <b>space=null</b> the colors will not be converted.
	 * 
	 * @param colors
	 *            The original data.
	 * @param trainingsSize
	 *            The number of elements the training data should consist of. If
	 *            the given set <b>colors</b> is smaller than trainingSize, the
	 *            returned set is not a subset, but a randomly sorted version of
	 *            the original set.
	 * @param space
	 *            A color space or null. A ColorDifferenceMeasure or null.
	 * @return The shuffled subset of the input data.
	 */
	static public RColor[] getTrainingColors(RColor[] colors, Integer trainingsSize, ColorSpace space) {
		return ClusteringHelper.getTrainingColors(colors, trainingsSize, space.toColorDifferenceMeasure());
	}

	/**
	 * Creates a randomly sampled subset of the give set of <b>colors</b> and
	 * converts the returned colors to a color space defined by the given
	 * ColorDifferenceMeasure (e.g. m=RGBEuclidean -> returned colors are
	 * RGBColors). If <b>m=null</b> the colors will not be converted.
	 * 
	 * @param colors
	 *            The original data.
	 * @param trainingsSize
	 *            The number of elements the training data should consist of. If
	 *            the given set <b>colors</b> is smaller than trainingSize, the
	 *            returned set is not a subset, but a randomly sorted version of
	 *            the original set.
	 * @param m
	 *            A ColorDifferenceMeasure or null.
	 * @return The shuffled subset of the input data.
	 */
	static public RColor[] getTrainingColors(RColor[] colors, Integer trainingSize, ColorDifferenceMeasure m) {

		Object[] o = ClusteringHelper.getTrainingData(colors, trainingSize);

		RColor[] shuffled = Arrays.copyOf(o, o.length, RColor[].class);
		if (m != null) {
			ColorHelper.convert(shuffled, m.toColorSpace());
		}

		return shuffled;
	}
}
