# clustering dataset
# determine k using elbow method
 
from sklearn.cluster import KMeans
from sklearn import metrics
from scipy.spatial.distance import cdist
import numpy as np
import matplotlib.pyplot as plt

import Image
import random

from sklearn.preprocessing import StandardScaler
import pandas as pd

def elbow(X, limit = 20, corteDiff = 0.80, corteMin = 0.01, aviso = False):
    cluster_values = []
    
    if limit > len(X):
        limit = len(X)-1
    
    cluster_range = range(1,limit)
    for k in cluster_range:
        clusters = KMeans(n_clusters = k)
        clusters.fit(X)
        cluster_values.append(clusters.inertia_)
    
    #clusters_df = pd.DataFrame( { "num_clusters":cluster_range, "cluster_errors": cluster_values } )
    
    if aviso:
        # Plot the elbow
        plt.plot(cluster_range, cluster_values, 'bx-')
        plt.xlabel('k')
        plt.ylabel('Distortion')
        plt.title('The Elbow Method showing the optimal k')
        plt.show()
    
    n = len(cluster_values)
    diff = cluster_values[0]-cluster_values[n-1]
    k = i = 2
    porAnt = float('Inf')

    while i < (n-1):
        porT = (cluster_values[0]-cluster_values[i])/float(diff)
        porP = (cluster_values[i-1]-cluster_values[i])/float(diff)
        pordiff = porAnt-porP
        if porT > corteDiff or pordiff < corteMin:
            k = i
            break

        i += 1
        porAnt = porP

    return k

class Cluster(object):

    def __init__(self):
        self.pixels = []
        self.centroid = None

    def addPoint(self, pixel):
        self.pixels.append(pixel)

    def setNewCentroid(self):

        R = [colour[0] for colour in self.pixels]
        G = [colour[1] for colour in self.pixels]
        B = [colour[2] for colour in self.pixels]
        
        if len(R) > 0:
            R = sum(R) / len(R)
        else:
            R = 0
        if len(G) > 0:
            G = sum(G) / len(G)
        else:
            G = 0
        if len(B) > 0:
            B = sum(B) / len(B)
        else:
            B = 0
        
        self.centroid = (R, G, B)
        self.pixels = []

        return self.centroid


class Kmeans(object):

    def __init__(self, k=3, max_iterations=5, min_distance=5.0, size=200):
        self.k = k
        self.max_iterations = max_iterations
        self.min_distance = min_distance
        self.size = (size, size)

    def run(self, image):
        self.image = Image.fromarray(image)
        self.image.thumbnail(self.size)
        self.pixels = np.array(self.image.getdata(), dtype=np.uint8)

        self.clusters = [None for i in range(self.k)]
        self.oldClusters = None

        randomPixels = random.sample(self.pixels, self.k)

        for idx in range(self.k):
            self.clusters[idx] = Cluster()
            self.clusters[idx].centroid = randomPixels[idx]

        iterations = 0

        while self.shouldExit(iterations) is False:

            self.oldClusters = [cluster.centroid for cluster in self.clusters]

            #qprint iterations

            for pixel in self.pixels:
                self.assignClusters(pixel)

            for cluster in self.clusters:
                cluster.setNewCentroid()

            iterations += 1

        return [cluster.centroid for cluster in self.clusters]

    def assignClusters(self, pixel):
        shortest = float('Inf')
        for cluster in self.clusters:
            distance = self.calcDistance(cluster.centroid, pixel)
            if distance < shortest:
                shortest = distance
                nearest = cluster

        nearest.addPoint(pixel)

    def calcDistance(self, a, b):

        result = np.sqrt(sum((a - b) ** 2))
        return result

    def shouldExit(self, iterations):

        if self.oldClusters is None:
            return False

        for idx in range(self.k):
            dist = self.calcDistance(
                np.array(self.clusters[idx].centroid),
                np.array(self.oldClusters[idx])
            )
            if dist < self.min_distance:
                return True

        if iterations <= self.max_iterations:
            return False

        return True

    # ############################################
    # The remaining methods are used for debugging
    def showImage(self):
        self.image.show()
        
    def valuesClustersCentroid(self):

        return [cluster.centroid for cluster in self.clusters]
        

    def showCentroidColours(self):
        for cluster in self.clusters:
            image = Image.new("RGB", (200, 200), cluster.centroid)
            image.show()

    def showClustering(self):
        localPixels = [None] * len(self.image.getdata())

        for idx, pixel in enumerate(self.pixels):
                shortest = float('Inf')
                for cluster in self.clusters:
                    distance = self.calcDistance(
                        cluster.centroid,
                        pixel
                    )
                    if distance < shortest:
                        shortest = distance
                        nearest = cluster

                localPixels[idx] = nearest.centroid

        w, h = self.image.size
        localPixels = np.asarray(localPixels)\
            .astype('uint8')\
            .reshape((h, w, 3))

        colourMap = Image.fromarray(localPixels)
        colourMap.show()
        
