import cv2
import dlib
import numpy as np

import math
import openface
import openface.helper
from openface.data import iterImgs
from matplotlib import pyplot as plt

import sys
import os
import glob
import argparse
from PIL import Image as pilimage

import random
import time
import shutil as sh

from sklearn import preprocessing
from numpy.lib.scimath import logn

#Porpis
import vector_face as vecFace

#Crear Classe per tenir un objecte tipos imatge amb totes les seves dades
class ImageFace:
    def __init__(self,path):
        self.path = path
        self.vecFace = None
        self.vecClothes = None
        self.vecChannels = [[], [], []]
        self.vecFaceClothes = None
        self.sex = None
        self.name = path.split('/')[-1]
        
        coords = self.getValuesInPath(('X','Y','W','H'))
        self.coords = coords
        
    def setVecFace(self,vec):
        self.vecFace = vec
        
    def setVecClothes(self,vec):
        self.vecClothes = np.concatenate((vec[0], vec[1], vec[2]), axis=1)
        self.vecChannels = [[int(x) for x in vec[0]],[int(x) for x in vec[1]],[int(x) for x in vec[2]]]
        
    def setSex(self,sex):
        self.sex = sex
        
    def setVecChannels(self,vec):
        for i in range(0,len(vec)):
            self.vecChannels[i] = vec[i]
            
    def generateVecFaceClothes(self):
        if (self.vecFace is None) or (self.vecClothes is None):
            salida = None
        else:
            salida = []
            salida.extend(self.vecFace)
            i = 0
            while i < len(self.vecChannels):
                if self.vecChannels[i] is not None:
                    salida.extend(self.vecChannels[i])
                else:
                    salida = None
                    break
                i += 1
        
        if salida is not None:
            salida = preprocessing.normalize(salida)[0]
        
        self.vecFaceClothes = salida
            
    def setCoords(self,coords):
        self.coords = coords

    def getVecFace(self):
        return self.vecFace
    
    def getVecClothes(self):
        return self.vecClothes
    
    def getSex(self):
        return self.sex
    
    def getVecChannels(self):
        return self.vecChannles
    
    def getVecFaceClothes(self):
        return self.vecFaceClothes
    
    def getPath(self):
        return self.path
    
    def getCoords(self):
        return self.coords
    
    def getValuesInPath(self,values):
        n = len(values)
        i = 0
        posValues = []
        text = self.name.split('--')[1]
        while i < n:
            if values[i] in text:
                posValues.append(text.index(values[i]))
            else:
                self.setCoords(None)
                return None
            i += 1
        
        return [int(text[1:posValues[1]]), int(text[posValues[1]+1:posValues[2]]), int(text[posValues[2]+1:posValues[3]]), int(text[posValues[3]+1:])]
    
    def isEqualClothes(self,other, limit = 0.20):
        if self.vecClothes is not None:
            if bhattacharyya(self.vecClothes,other) < limit:
                return True
            else:
                return False
        else:
            return None


#Distancia de Bhattacharyya
def bhattacharyya(h1, h2):
    '''Calculates the Byattacharyya distance of two histograms.'''
    return 1 - np.sum(np.sqrt(np.multiply(normalize(h1), normalize(h2))))

def normalize(h):
    return h / np.sum(h)

def meanBhatt( hist ):
    mean = 0.0;
    for i in hist:
        mean += i;
    mean/= len(hist);
    return mean;

def bhattaVect( vec1,  vec2):
    #vec1 , vec2 = preprocessing.normalize(vec1, norm='l2'), preprocessing.normalize(vec2, norm='l2')
    vec1, vec2 = normalize(vec1), normalize(vec2)
    # calculate mean of hist1
    h1_ = meanBhatt(vec1)

    # calculate mean of hist2
    h2_ = meanBhatt(vec1)
    
    # calculate score
    score = 0.0;
    for i in range(len(vec1)):
        score += math.sqrt( vec1[i] * vec2[i] )
    # print h1_,h2_,score;
    score = math.sqrt( 1 - ( 1 / math.sqrt(h1_*h2_*len(vec1)*len(vec2))) * score )
    return score


def bhattaNega(vec1,vec2):
    o1, o2 = varianza(vec1), varianza(vec2)
    
    return np.log(((o2/o1)+(o1/o2)+2)/4)/4

def varianza(datos):
    n = len(datos)
    mean = sum(datos)/float(n)
    return (sum([(x-mean)**2 for x in datos]))/float(n)

#Este metodo implementa el cuadrado de la distancia (p_vec: primer vector,q_vec: segundo vector)
#return: La distancia euclidiana al cuadrado de los vectores uno y dos
def squareEuclideanDist(self, p_vec, q_vec):
    diff = p_vec - q_vec
    return max(np.sum(diff ** 2), self.e)

#El metodo implementa la metrica de la distancia euclidiana (p_vec: vector uno,q_vec: vector dos)
#return: La distancia entre vector uno y dos
def euclideanDistance(self, p_vec, q_vec):
    return max(math.sqrt(self.squareEuclideanDist(p_vec, q_vec)), self.e)


def euclidean(x, y): 
    return math.sqrt( sum( [ (xi - yi)**2 for xi, yi in zip(x, y) ] ) )
    

# Para que todas las imagenes tengan las mismas dimensiones por defecto es 96x96
def resize(img,w,h,size=96):
    rows,cols = img.shape[:2]
    
    pts1 = np.float32([[0,0],[w,0],[0,h],[w,h]])
    pts2 = np.float32([[0,0],[size,0],[0,size],[size,size]])
     
    M = cv2.getPerspectiveTransform(pts1,pts2)
     
    return cv2.warpPerspective(img,M,(size,size)) #devuelve la imagen con la dimension deseada
 
# valor entrada por es entre 0 a 1 para la proporcion
def expandView(img,x,y,w,h, porH = 2.0, porW = 1.5):
    rows,cols = img.shape[:2]
    
    #print "x: " + str(x) + " y: " + str(y) +" w: " + str(w) + " h: " + str(h) + " rows " + str(rows) + " cols " + str(cols)
    extraW = int(w*porW) # Valor extra para coguer el cuerpo
    extraH = int(h*porH) # Valor extra para coguer el cuerpo
    
    fx = x-int(extraW/2)
    if fx < 0:
        fx = 0
        
    fy = y-int(extraH/2)
    if fy < 0:
        fy = 0
        
    fw = w+int(extraW)
    if (fw+fx) > cols:
        fw = (cols - fx)
    
    fh = h+int(extraH*2.1)
    if (fh+fy) > rows:
        fh = (rows - fy)
    
    return [(fx,fy,fw,fh),(x+int(w/2),y+int(h/2))] #Retorna un quadre mes gran amb el punt central

# Mover las imagenes juntas
def moveImg(images,outDir):
    
    # Si no existeix es crea el directori
    if not os.path.exists(outDir):
        os.mkdir(outDir)
    
    i ,ok = 0 , False
    
    if isinstance(images,dict):
        # Si es un diccionari es tracta diferent a una llista
        for image in images.keys():
            # Per no sobre escriure carpetes es crea una nova
            while not ok:
                directori = str(time.strftime("%Y%m%d"))+'_'+str(time.strftime("%H%M%S")) +'_'+str(i)
                if not os.path.exists(outDir+'/'+ directori ):
                    os.mkdir(outDir+'/'+ directori )
                    ok = True
                else:
                    i += 1
            
            for img in images[image]:            
                # moure les imatges al lloc per clasificar-les  
                os.system('cp '+ img +'  '+ outDir+'/'+ directori +'/')
            # Posar un delay perque es puguin copia be totes les imatges
            time.sleep(1)
            i = 0
            ok = False
    # En cas de se una llista
    else:
        for image in images:
            # Per no sobre escriure carpetes es crea una nova
            while not ok:
                directori = str(time.strftime("%Y%m%d"))+'_'+str(time.strftime("%H%M%S")) +'_'+str(i)
                if not os.path.exists(outDir+'/'+ directori ):
                    os.mkdir(outDir+'/'+ directori )
                    ok = True
                else:
                    i += 1
            
            for img in image:            
                # moure les imatges al lloc per clasificar-les  
                os.system('cp '+ img +'  '+ outDir+'/'+ directori +'/')
            # Posar un delay perque es puguin copia be totes les imatges
            time.sleep(1)
            i = 0
            ok = False

    

# Barra Porcentaje
def progressBar(per):
    # colors (37 -> Blanc , 31 -> vermell, 32 -> verd, 33 -> tronja, 34 -> blau fosc, 35 -> lila, 36 -> Blau Clar, 30 -> negre)
    #colors = ("\x1B[37m","\x1B[31m","\x1B[32m","\x1B[33m","\x1B[34m","\x1B[35m","\x1B[36m") # Per la consola
    colors = ("\x1B[37m","\x1B[31m","\x1B[34m","\x1B[32m","\x1B[33m","\x1B[35m","\x1B[36m")
    sys.stdout.write('\r')
    sys.stdout.write(colors[random.randint(3,6)]) # Canvia de color
    sys.stdout.write("#"*int(per/2) + " [ "+ str(per) + " %] ")
    sys.stdout.flush()
    
    
# La lectura i escriptura la fa linea a linea per llista
def ioFitxer(fitxer,mode,text=None):
    
    if mode == 'r' or mode == 'R':
        f = open(fitxer, "r")
        
        retorn = []
        linea = f.readline()
        while linea != "":
            retorn.append(linea)
            linea = f.readline()
    
        f.close
        return retorn
    
    if text is not None:
    
        if (mode == 'w' or mode == 'W'):
            f = open(fitxer,'w')
            
            f.write(text)
        
            f.close
        if (mode == 'a' or mode == 'A'):
            f = open(fitxer,'a')
        
            for tx in text:
                f.write(str(tx)+"\n")
    
            f.close
    
    return None


def writeCsv(fitxer,text=None):
        f = open(fitxer,'a')
    
        for tx in text:
            f.write(str(tx)+";")
        
        f.seek(f.tell()-1) # retornar una posicio anterior per eliminar el ; i posar el \n
        f.write("\n")
        
        f.close
        
# Lectura dels fitxers de referencia per rep
def readCsv(fitxer):

    out = []
    
    if os.path.isfile(fitxer):

        f = open(fitxer,'r')
        
        linea = f.readline()
        while linea != "":
            out.append([float(x) for x in linea[:-2].split(';')])
            linea = f.readline()
    
        f.close

     
    return out

#Comprovar que les imatges compleixen uns requisits de histograma per la llum o fusco
def filtreHist(images, data, tall , rangePos , equal=True, aviso=False):

    pini, pfin = rangePos[0],rangePos[1]
    descarts, desReps = [],[]
    retornImg, retornReps = [],[]
    
    i=0
    for im in images:
    
        img = cv2.imread(im,0)
    
        if equal:
            #Equalizar Histograma per millorar qualitat per lluminositat
            cv2.equalizeHist(img, img);
        
        hist = cv2.calcHist([img],[0],None,[256],[0,256])
        
        # Obtenir els valors de descart
        valorMax = max(hist)[0]
        pos = np.where(hist == valorMax)[0][0]
        
        # Es compleixen el terme de descart
        if valorMax > tall and pos > pini and pos < pfin:
            
            #afegida a la pila dels descarts 
            descarts.append(im)
            desReps.append(data[i])
            
            if aviso:
                # Mostrar imatge
                cv2.imshow(im,img)
                # Histograma visualitzat
                plt.title('Histograma de '+ im)
                plt.hist(img.ravel(),256,[0,256]); plt.show()
                # Esperar tecla
                cv2.waitKey(0)
                cv2.destroyAllWindows()
        else:
            retornImg.append(im)
            retornReps.append(data[i])
            
        i += 1
    
    # Retorna la criva
    return [retornImg, retornReps, descarts, desReps]

# Funcio per unir en una sola dimensio una llista de n dimensions amb un grau de profunditat
def unirLlista(llistes):
    
    sortida = []
    
    # Comprovar si es una llista
    if isinstance(llistes, list):

        for llista in llistes:
            sortida.extend(llista)
        
    return sortida

# Per obtenir el valor de desviacio d unes dades
def desviacio(dades,promig):
    
    n = len(dades)
    
    return math.sqrt(sum([(x-promig)**2 for x in dades])/float(n))

# Obtenir valor medi del vector
def mediana(vector):
    
    valor = float('inf')
    
    if len(vector) > 1:
        vec = sorted(vector)
        valor = float(vec[int(len(vector)/2)])
        
    return valor


def pearson(x, y):
    assert len(x) == len(y)
    n = len(x)
    assert n > 0
    avg_x = float(sum(x))/len(x)
    avg_y = float(sum(y))/len(y)
    diffprod = 0
    xdiff2 = 0
    ydiff2 = 0
    for idx in range(n):
        xdiff = x[idx] - avg_x
        ydiff = y[idx] - avg_y
        diffprod += xdiff * ydiff
        xdiff2 += xdiff * xdiff
        ydiff2 += ydiff * ydiff

    return diffprod / float(math.sqrt(xdiff2 * ydiff2))


# Obtenir dades de directori concret
def llistaDir(directori, values = ('-','0','1','2','3','4','5','6','7','8','9')):
    
    if os.path.exists(directori):
    
        llista = os.listdir(directori)
        directoris = sorted([x for x in llista if not os.path.isfile(os.path.join(directori,x))])
        
        persones = []
        
        for dire in directoris:
            i=0
            n = len(dire)
            bo = True
            while i < n:
                if not dire[i] in values:
                    bo=False
                    i = n
                i += 1
            if bo:
                persones.append(dire)
        
        
        return sorted(persones)
        
    else:
        return None
    

def movefiles(fitxers,directori, accio = 'move'):
    if accio in ('copy','move'):
        
        if not os.path.exists(directori):
            os.mkdir(directori)
        
        for fitxer in fitxers:    
            if accio == 'copy':
                sh.copy(fitxer,os.path.join(directori,fitxer.split('/')[-1]))
            if accio == 'move':
                sh.move(fitxer,os.path.join(directori,fitxer.split('/')[-1]))
    else:
        print 'Error en accio de seleccio '

# per obtenir la data, el valor div es el divisor        
def getDate(text,div='_', formatDate = '%Y%m%d', init_year = 2000):
    name = text.split('/')[-1]
    date = name.split(div)
    
    for d in date:
        if validateDateEs(d,formatDate, init_year):
            return d
    
    return None
    
    
def validateDateEs(date,formatDate, init_year):
    """
    Funcio per validar una data en format:
        ddmmyyyy, ddmmyy, dmyy, ddmmyyyy hhmmss, ddmmyy hhmmss, dmyy hms, yyyymmdd, yymmdd
    """
    #for format in ['%d%m%Y', '%d%m%y', '%d%m%Y %H%M%S', '%d%m%y %H%M%S', '%Y%m%d', '%y%m%d']:
    try:
        result = time.strptime(date, formatDate)
        if result.tm_year >= init_year:
            return True
    except:
        pass
    return False
  
def mitjaClusters(directori,fitxer = 'means.csv' ):
    if os.path.exists(directori):
        if os.path.exists(os.path.join(directori,fitxer)):
            os.remove(os.path.join(directori,fitxer))
        persones = llistaDir(directori)
        
        for persona in persones:
            repsPresona = readCsv(os.path.join(directori,persona,persona+'.csv'))
            vecMean = vecFace.promVecs(repsPresona)
            writeCsv(os.path.join(directori,fitxer),vecMean)
        
    else:
        print 'Directori ' + directori + ' inexistent'
