import cv2
import cv2 as cv
import dlib
import numpy as np

from matplotlib import pyplot as plt
import networkx as nx

from scipy.cluster.hierarchy import dendrogram, linkage, fcluster, fclusterdata, leaders

from sklearn.cluster import MeanShift, estimate_bandwidth
from sklearn import preprocessing
from sklearn.datasets.samples_generator import make_blobs

from sklearn.cluster import SpectralClustering
from sklearn import metrics


#propia
import utils
import vector_face as vecFace
import kmean as km


# Agrupamiento no jerarquico Kmeans
def kmeans(data,labels,k=2):
    #print "Generant el clustering"
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
    ret,label,center=cv2.kmeans(data,k,criteria,10,cv2.KMEANS_RANDOM_CENTERS)
    
    # Now separate the data, Note the flatten()
    A = data[label.ravel()==0]
    B = data[label.ravel()==1]
    C = data[label.ravel()==2]
    #D = data[label.ravel()==3]
    

    # Plot the data
    plt.scatter(A[:,0],A[:,1])
    plt.scatter(B[:,0],B[:,1],c = 'red')
    plt.scatter(C[:,0],C[:,1],c = 'blue')
    #plt.scatter(D[:,0],D[:,1],c = 'green')
    #plt.scatter(center[:,0],center[:,1],center[:,2],center[:,3])
    plt.scatter(center[:,0],center[:,1],center[:,2])
    plt.xlabel('X'),plt.ylabel('Y')
    plt.show()


def clusterMeanShift(data,paths):
    #Es guarden els agrupaments ja definits per tipus dins el dict
    agrupacio = {} 
    agrupacio['images'] = []
    agrupacio['reps'] = []
    agrupacio['descarts'] = []
    agrupacio['desReps'] = []
    agrupacio['errors'] = [] 

    #Es preparen les dades a escala per utilitzar MeanShift
    X = preprocessing.scale(np.array(data))
    
    # Compute clustering with MeanShift

    # The following bandwidth can be automatically detected using
    bandwidth = estimate_bandwidth(X,quantile=0.2, n_samples = 500)
    
    ms = MeanShift(bandwidth = bandwidth, bin_seeding=False)
    ms.fit(X)
    labels = ms.labels_
    cluster_centers = ms.cluster_centers_
    
    clusters = {}
    for i in range(0,len(labels)):
        if not labels[i] in clusters.keys():
            clusters[labels[i]] = {}
            clusters[labels[i]]['images'] = []
            clusters[labels[i]]['reps'] = []
        
        clusters[labels[i]]['images'].append(paths[i])
        clusters[labels[i]]['reps'].append(data[i])

    for key in clusters.keys():
        agrupacio['images'].append(clusters[key]['images'])
        agrupacio['reps'].append(clusters[key]['reps'])

    labels_unique = np.unique(labels)
    n_clusters_ = len(labels_unique)
    
    return agrupacio


def clusterSpectral(data, paths, args):
    #Es guarden els agrupaments ja definits per tipus dins el dict
    agrupacio = {} 
    agrupacio['images'] = []
    agrupacio['reps'] = []
    agrupacio['descarts'] = []
    agrupacio['desReps'] = []
    agrupacio['errors'] = [] 
    
    gt = np.array(data)
    
    #print ' Mostrar elbow'
    knum = km.elbow(data,aviso = False)
    #print str(knum)
    
    # Cluster Spectral
    # - eigen_solver = {None, 'arpack', 'lobpcg', or 'amg'}  default -> None 'lobpcg' i 'amg' No funcionen
    # - affinity = {'nearest_neighbors', 'precomputed', 'rbf'} default -> rbf  'precomputed' No funciona
    # - n_init default -> 10
    sc = SpectralClustering(knum, eigen_solver = None ,affinity = 'nearest_neighbors', n_init=500)
    sc.fit(gt)
    #sc.fit_predict(gt)

    # clustering-results
    print('spectral clustering')
    print(sc.labels_)
    
    labels = sc.labels_
    
    clusters = {}
    for i in range(0,len(labels)):
        if not labels[i] in clusters.keys():
            clusters[labels[i]] = {}
            clusters[labels[i]]['images'] = []
            clusters[labels[i]]['reps'] = []
        
        clusters[labels[i]]['images'].append(paths[i])
        clusters[labels[i]]['reps'].append(data[i])

    for key in clusters.keys():
        agrupacio['images'].append(clusters[key]['images'])
        agrupacio['reps'].append(clusters[key]['reps'])

    labels_unique = np.unique(labels)
    n_clusters_ = len(labels_unique)
    
    return agrupacio
    

# Fer la taula de similits manualment amb un metode propi per factor escalar
def similituts(data,labels, corte = 0.70 ,aviso=False, save = False, metode = 'dot'):
    # Vector inicial amb les posicions del headers i iniciar variables
    vec = [[x] for x in range(0,len(data))]
    n, per, m = len(vec)-2, -1, 0
    fin = False # Per indicar que acaba si arriba al minim valor de tall
    sortida = {}
    
    data = np.array(data) 
    
    #print "Llista de imates per posicio"
    #i = 0
    #for lb in labels:
    #    print str(i) + " --> " + lb
    #    i += 1
    
    if aviso:
        print " Obtenint similituds de les imatges per comparativa amb un tall inicial de " + str(corte)
        print '------------------------------------------------------------------'
    
    while len(vec) > 2 and fin == False:
        
        mat = np.zeros((len(vec), len(vec)))
        
        if metode == 'pearson':
            minValor = [float('-Inf'),-1,-1]
        else:
            minValor = [float('Inf'),-1,-1]
              
        for i in range(1, len(vec)):
            for j in range(0, i):

                vec1 = vecFace.promVecs([data[x] for x in vec[i]])
                vec2 = vecFace.promVecs([data[x] for x in vec[j]])
                
                if vec1 != None and vec2 != None:
                    
                    if metode in ['dot','averageOne']:
                        if metode == 'dot':
                            valor = vecFace.compareVectors(np.array(vec1),np.array(vec2))
                        if metode == 'averageOne':
                            vec1 = vecFace.vecsOnes([vec1], other = 0)
                            vec2 = vecFace.vecsOnes([vec2], other = 0)
                            valor = 1-(100*vecFace.compareVectorsAverage(vec1[0],vec2[0]))
                        
                        #mat[i,j] = valor
                        # Per deixar de agrupar segons un valor determinat
                        if minValor[0] > valor:
                            minValor[0] = valor
                            minValor[1] = i
                            minValor[2] = j
                    else:
                        if metode == 'pearson':
                            valor = utils.pearson(vec1,vec2)
                            mat[i,j] = valor
                            if minValor[0] < valor:
                                minValor[0] = valor
                                minValor[1] = i
                                minValor[2] = j
                else:
                    if aviso:
                        print "Promig de vectors amb error al no haver vectors per calcular"
                    #mat[i,j] = -1.0
        
        m +=1
        
        if per < int((m/float(n))*100):
            per = int((m/float(n))*100)
            if aviso:
                utils.progressBar(per)
        
        if metode == 'pearson':
            if minValor[0] > corte:
                vec[minValor[1]].extend(vec[minValor[2]])
                vec.pop(minValor[2])
                #print str(vec)
            else:
                fin = True
        else:
            if minValor[0] < corte:
                vec[minValor[1]].extend(vec[minValor[2]])
                vec.pop(minValor[2])
                #print str(vec)
            else:
                fin = True

        
    images, desImg = [],[] # On es guarden les agrupacions i les imatges no agrupades
    reps, desReps = [],[] # On es guarden les referencia de le simatges anteriors
    i = 0
    for ve in vec:
        # si hi ha mes de una imatge vol dir que es un grup
        if len(ve) > 1:
            images.append([])
            reps.append([])
            for v in ve:
                images[i].append(labels[v])
                reps[i].append(data[v])
            i += 1
        else: # Descart per tornar a recalcular
            if len(ve) > 0: 
                desImg.append(labels[ve[0]])
                desReps.append(data[ve[0]])
            
    if save:    
        utils.moveImg(images,'./data') #Per veure com guarda les imatges
    
    if aviso:
        utils.progressBar(100)
        print
        
    sortida['images'] = images
    sortida['reps'] = reps
    sortida['descarts'] = desImg
    sortida['desReps'] = desReps
    sortida['revImg'] = []
    sortida['revReps'] = []
    sortida['errors'] = []      
    
    #return [reps,images,desReps,desImg]
    return sortida

 
# Agrupamiento Jerarqico amb tall
def jerarquico(data,labels,corte,outDir, metode = 'average', save = False , showDen=False, aviso = False):
    # Iniciar variables
    x = np.array(data)
    reps, images= [], []
    max_d = corte
    
    # Crear tabla de proximidad en metodos --> single complete weighted centroid median ward
    # con metricas 
    
    if aviso:
        print ""
        print " Organitzant " + str(len(data)) + " Imatge(s) per la seva classificacio"
        print " Parametres: Metode d'organitacio: " + metode + " Valor de Tall " + str(corte)
        print "------------------------------------------------------------------------------"
    
    if len(data) > 1:

        plt.figure(figsize=(10,8))
        plt.title('Deotograma')
        plt.xlabel('X Indice Entrada')
        plt.ylabel('Y Distancia')
        
        Z = linkage(x, method = metode ,metric='euclidean')
        
        dn = dendrogram(Z,leaf_rotation=90.,leaf_font_size=10.,show_contracted=True,labels=labels)
        
        #per concretar si es busca un tall o es dona
        if corte < 0.1:
            # valor por defecto
            max_d = 0.6
            n=len(dn['dcoord'])            
            i = n
            while i > 0:
                if dn['dcoord'][i-1][0] == 0.0 and  dn['dcoord'][i-1][3] == 0.0:
                    while i < n:
                        if dn['dcoord'][i][0] != 0.0:
                            max_d = dn['dcoord'][i][1] + 0.01
                            break
                        i += 1
                    break
                i -= 1
        
        #print "Labels entrats"
        #for lb in labels:
        #    print lb
        
        # El diccionario tiene varias keys >> 'color_list' 'icoord'  'dcoord'  'ivl'  'leaves'    
        #print '--color_list ----------------'
        #print dn['color_list']
        
        #print '- ivl --------------------'
        #print dn['ivl'] # Etiquetas del dentograma
        
        #print '- leaves ------------------'
        #print dn['leaves'] # Muy util para lista de agrupados
        
        #print '- icoord---------------------'
        #for i in dn['icoord']:
        #    print i 
        #print '- dcoord-------------------'
        #for i in dn['dcoord']:
        #    print i
        
        # Organitzar les images i vectors segons clusters
        reps=[]
        images = organizarDento(dn,max_d)
        i = 0
        for img in images:
            reps.append([])
            for im in img:
                reps[i].append(data[labels.index(im)])
            i += 1
    else:
        if len(data) == 1:
            reps = [data]
            images = [labels]
    
    if save:
        utils.moveImg(images,outDir)
    
    if showDen:
        plt.axhline(y=max_d,c='k')
        plt.show()
        
    return [reps,images]


# Funcio per organitzar les imatges segons els clusters obtinguts sense tall        
def clustersJer(data,labels, corte ,outDir, metode='average',save = False , showDen=False, aviso = False):
    valorsTall = {}
    
    if len(data) > 1:
    
        #Preperar variables
        x = np.array(data)    
        clusters = {}
        
        #Funcio per obtenir els clusters
        #Z = fclusterdata(x,0, method = metode, metric='euclidean')
        X = linkage(x, method = metode, metric='euclidean')
        
        plt.title('Deotograma Metode '+ metode + ' Total Imatges: '+ str(len(labels)))
        
        # Se crea un dentograma donde los valores resultantes se guardan enun diccionario
        dn = dendrogram(X,leaf_rotation=90.,leaf_font_size=10.,show_contracted=True,labels=labels)
        
        pasats = []
        llista_colors=[]
        antColor = dn['color_list'][0]
        m = 1
        for col in dn['color_list']:
            if not col*m in pasats:
                if antColor == col:
                    llista_colors.append(col*m)
                else:
                    pasats.append(antColor*m)
                    antColor = col
                    llista_colors.append(col*m)
            else:
                m +=1
                llista_colors.append(col*m)
                antColor = col
        
        if corte is None:
            max_d = 0
        else:
            max_d = corte
        n=len(dn['ivl'])
        posImg = [i for i in range(5,n*10,10)]
        i , valor, m = 0 , 'nada', 1
        
        revisar = []
        
        #for color in dn['color_list']:
        for color in llista_colors:
            if not color in clusters.keys():
                clusters[color]=[]
            
            # Obtenir valors del labels
            x1, x2 = round(dn['icoord'][i][1],2), round(dn['icoord'][i][2],2)
            # Guardar cluster i posicio dels labels
            if x1 in posImg and dn['dcoord'][i][0] <= 0.0: #Retocat per evitar lineas que coincideixen
                clusters[color].append(int((x1-5)/10))
                valorsTall[dn['ivl'][int((x1-5)/10)]] = dn['dcoord'][i][1] # Valor y en convinar x1 i x2
                
            # Guardar cluster i posicio dels labels    
            if x2 in posImg and dn['dcoord'][i][3] <= 0.0: #Retocat per evitar lineas que coincideixen
                clusters[color].append(int((x2-5)/10))
                valorsTall[dn['ivl'][int((x2-5)/10)]] = dn['dcoord'][i][1] # Valor y en convinar x1 i x2
                #Marca els clusters que tenen un dels seus maxims que pasen d un valor per a revissar-lo

            #Marca els clusters que tenen un dels seus maxims que pasen d un valor de tall per a revissar-lo
            if corte is not None:
                if dn['dcoord'][i][1] > corte:
                    if not color in revisar:
                        revisar.append(color)
            i += 1
        i=0  
        
        descart, desReps = [],[]
        # Per descartar clusters on acaben les imatges que no coincideixen
        if 'b' in clusters.keys():
            if len(clusters['b']) == 0:
                del clusters['b'] # eliminar el cluster generic si no te dades
                if 'b' in revisar:
                    revisar.remove('b')
            else:
                # si el cluster es blau es per descartar si hi ha altres clusters
                if len(clusters.keys()) > 1:
                    #for valor in clusters['b']:
                    for key in [v for v in clusters.keys() if v[0]=='b']:
                        for valor in clusters[key]:
                            # Preperar els vetors de descarte amb les posicions correctes de les dades
                            desReps.append(data[dn['leaves'][valor]])
                            descart.append(dn['ivl'][valor]) 
                        del clusters[key] # Una vegada compiats els valors de descart eliminar-los del dict
                        if key in revisar:
                            revisar.remove(key)
        
        #Iniciar valors
        revImg , revReps = [],[]
        # Per extreure la imatges de cluster que s'ha de tornar a fer
        if len(revisar) > 0:
            for key in revisar:
                for valor in clusters[key]:
                    revReps.append(data[dn['leaves'][valor]])
                    revImg.append(dn['ivl'][valor]) 
                    
                del clusters[key] #Eliminar els valors ja guardats
        
        
        images, reps = [],[]
        desImg, desReps = [[]],[[]]
        i = 0
        for key in clusters.keys():
            reps.append([])
            images.append([])
            for valor in clusters[key]:
                # Preperar els vetors amb le sposicions correctes de les dades
                reps[i].append(data[dn['leaves'][valor]])
                images[i].append(dn['ivl'][valor])                    
            i += 1
        
        if save:
            utils.moveImg(images,outDir)
            if len(descart) > 0:
                utils.moveImg([descart],outDir+'/_descarts')
        
        if showDen:
            plt.axhline(y=max_d,c='k')
            plt.show()
    else:
        revImg , revReps, descart = [],[],[]
        if len(data) == 1:
            reps = [np.array(data)]
            images = [labels]
            valorsTall[labels[0]] = 0 # No hi ha tall preque no hi ha comparativa
        else:
            if aviso:
                print 'No hi ha imatges a processar'
            
            reps = [[]]
            images = [[]]
            
         
    return [reps,images,revReps,revImg,descart,valorsTall]


def unirClusters(reps, images, tall):
    n = len(reps)
    # Per unificar clusters anulat per fer test 
    if n > 1:
        i = 1
        m = 1
        union = []
        # Comprovar si es poden ajuntar un o mes clusters
        for grup in reps:
            i = m
            while i < n:
                vec1 = vecFace.promVecs(grup)
                vec2 = vecFace.promVecs(reps[i])
                valor = vecFace.compareVectors(np.array(vec1),np.array(vec2)) 
                if valor < tall:
                    union.append([m-1,i,valor])
                i += 1
            m += 1
        # Una vegada obtinguts els clusters, realitza una comparativa entre ells per veure si son molt similars o diferents
        rmA, rmB, rmValor = [],[],[]
        unir = []
        if len(union) > 0:
            for un in union:
                if not un[1] in rmB:
                    rmA.append(un[0])
                    rmB.append(un[1])
                    rmValor.append(un[2])
                else:
                    pos = rmB.index(un[1])
                    if un[2] < rmValor[pos]:
                        rmA.pop(pos)
                        rmB.pop(pos)
                        rmValor.pop(pos)
                        rmA.append(un[0])
                        rmB.append(un[1])
                        rmValor.append(un[2])
            i=0
            while i < len(rmA):
                unir.append([rmA[i],rmB[i]])
                i+=1
            
            for un in unir:
                reps[un[0]].extend(reps[un[1]])
                images[un[0]].extend(images[un[1]])

            # Eliminar les dades duplicades per unio
            rm = sorted(rmB) # Ordenar per fer millor la eliminacio
            i = 0
            for r in rm:
                #Al eliminar elements els index es desplacen les mateix vegades que s'elimina
                images.pop(r-i)
                reps.pop(r-i)
                i += 1
    
    return [reps,images]
    
# para organizar las imagenes por clusters    
def organizarDento(dn,corte):
    n=len(dn['ivl'])
    pila = []
    
    posImg = [i for i in range(5,n*10,10)]

    img = [(i*0)-1 for i in range(0,n)]
    grupo = 0
    max_x2 = round(dn['icoord'][0][2],2) #valor maxim x2 per el canvi de grup
    i = 0

    while i < (n-1):
        # Obtenir les dades del dentograma
        x1, x2 = round(dn['icoord'][i][1],2), round(dn['icoord'][i][2],2)
        y = dn['dcoord'][i][1]
        
        #print "Coordenadas X1 x2 y " + str(x1) + " - "+ str(x2) + ' - '+ str(y)
        #print 'iteracions ' + str(i)
        #print img
        #print 'Pila ' + str(pila)
        
        aux1 = None
        aux2 = None
        
        # pila on es guarden els nodes pare del dento i es busca si els actuals en tenen
        for p in pila:
            if x1 < (p[0]+0.1) and x1 > (p[0]-0.1):
                aux1 = p
            if x2 < (p[0]+0.1) and x2 > (p[0]-0.1):
                aux2 = p
                
        # Coordenades que entren dins el tall
        if y < corte:
            if aux1 is None and aux2 is None:
                if max_x2 < x2:
                    max_x2 = x2
                    if grupo in img:
                        grupo += 1
                
                img[int((x1-5)/10)] = grupo
                img[int((x2-5)/10)] = grupo
                pila.append([round(x1+((x2-x1)/float(2)),2),grupo])
            else:
                if aux1 is None:
                    img[int((x1-5)/10)] = aux2[1] #assignar grup
                    pila.remove(aux2)
                    pila.append([round(x1+((x2-x1)/float(2)),2),aux2[1]])
                else:
                    if aux2 is None:
                        img[int((x2-5)/10)] = aux1[1] #assignar grup
                        pila.remove(aux1)
                        pila.append([round(x1+((x2-x1)/float(2)),2),aux1[1]])
                    else:
                        # En aquest cas seran dos grups que fan unio a un de sol
                        if aux1[1] != aux2[1]:
                            m = 0
                            while m < len(img):
                                if img[m] == aux2[1]:
                                    img[m] = aux1[1] #reassignar grup al unir-los
                                m += 1
                        pila.remove(aux1)
                        pila.remove(aux2)
                        pila.append([round(x1+((x2-x1)/float(2)),2),aux1[1]])
        
        # Si supera el tall els nodes son descartats de la pila
        else:
            grupo = max(img)+1
            if aux1 is not None:
                pila.remove(aux1)
            if aux2 is not None:
                pila.remove(aux2)
                
            if x1 in posImg:
                img[int((x1-5)/10)] = grupo
                grupo += 1
                
            if x2 in posImg:
                img[int((x2-5)/10)] = grupo
                grupo += 1
        
        #if x1 not in posImg and x2 not in posImg:
        #    grupo += 1
        
        i += 1
    # Final del While

    valor= img[0]
    aux = []
    aux.append([])
    i=0
    pos=0
    for label in dn['ivl']:
        if valor != img[i]:
            aux.append([])
            pos += 1
            valor = img[i]
        aux[pos].append(label)
        i += 1
       
    
    return aux

