import cv2
import dlib
import numpy as np

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

#from keras.models import load_model
#from keras.preprocessing import image

import random
import threading
import thread
import multiprocessing as mp

from scipy.misc import imresize
import utils
#import gender


# Extreure cares de les imatges
def imageFaces(image, outputDir,  mutex = None, 
               face_cascade_paths=['haarcascades/haarcascade_frontalface_alt2.xml', 'haarcascades/haarcascade_profileface.xml'], 
               scale_factor=1.1, min_neighbors=13,
               exts=['*.jpg','*.jpeg','*.png'],
               min_size=-1, max_size=-1, flags = cv2.CASCADE_SCALE_IMAGE, width_view = 0.9, marc=False):
    
    # Comprovar si estan los directorios salida y crearlos en su defecto
    if not os.path.exists(outputDir):
        os.mkdir(outputDir)
    
    # Dades sobre el nom de l'arxiu imatge
    img_basename = image.split('/')[-1].split('.')[0]
    img_ext = image.split('/')[-1].split('.')[1]
    
    # Possar diferents filtres pel classificador
    face_cascades = [cv2.CascadeClassifier(os.path.expanduser(path)) for path in face_cascade_paths]
    
    # Carrega de la imatge
    img =cv2.imread(image)
    
    if img is None:
        # Imprimeix en color vermell
        sys.stdout.write("\x1B[31m")
        print "Imagen " + image + " no cargada"
        sys.stdout.write("\x1B[37m")

    else:
        
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        #Graduar el tamany de forma automatica
        w,h = gray.shape
        if max_size == -1:
            size_max = int(max([w*0.35,h*0.35]))
        else:
            size_max = min([w,h])
            
        if min_size == -1:
            size_min = int(min([w*0.05,h*0.05]))
        else:
            size_min = min_size
            
        
        #Equalizar Histograma per millorar qualitat per lluminositat
        cv2.equalizeHist(gray, gray);
        
        faces = []
        for cascade in face_cascades:
            f = cascade.detectMultiScale(gray, scaleFactor = scale_factor,
                                        minNeighbors = min_neighbors, flags = flags, minSize = (size_min,size_min), maxSize = (size_max,size_max))
            f = list(f)
            if f: faces += f
        
        centros = []
        for i, ( x, y, w, h ) in enumerate(faces):
            
            unico =True # Controla que no haya recuedros repetidos

            # Per controlar que els diferents tipus de haarcascades no dupliquin resultats
            for cx,cy in centros:
                if cx > x and cy > y and cx < (x+w) and cy < (y+h):
                        unico = False # Recuadro repetido
            
            if unico: 
                #Expandir recorte para ver cuerpo
                exp_view = utils.expandView(img,x,y,w,h, width_view) #width_view para indicar el porcentaje de abertura entre 0.0 y 1.0
                rx,ry,rw,rh = exp_view[0]
                centros.append(exp_view[1])
                
                # Guardar recorte expandido sin recuadro
                img_crop_ext = img[ry:ry+rh,rx:rx+rw]
                
                # Es redimensiona la imatge per no tenir tan volum de dades
                width, height = img_crop_ext.shape[:2]
                reduccio = 1
                x_new, y_new = x-rx, y-ry
                w_new, h_new = w, h
                if width > 256:
                    reduccio = 256/float(width)
                    x_new, y_new = int((x-rx)*reduccio), int((y-ry)*reduccio)
                    w_new, h_new = int(w*reduccio), int(h*reduccio)
                    img_save=imresize(img_crop_ext,reduccio)
                    width, height = img_save.shape[:2]
                else:
                    img_save =img_crop_ext
                    
                # Bloquejar el thread per poder guardar la foto
                if mutex is not None:
                    mutex.acquire()
                
                if marc:
                    cv2.rectangle(img_save, (x_new, y_new), (x_new + w_new, y_new + h_new), (0,0,255), 2)
                
                # Es guarda sempre la mateixa proporcio de dimencions i es guarda en el nom el punt de tall per la roba   
                cv2.imwrite(outputDir+'/'+img_basename +'--X'+ str(x_new) + 'Y'+ str(y_new) + 'W'+ str(w_new) + 'H'+ str(h_new) + '--'+str(i)+'.png',img_save)
                
                # Desbloquejar tots els threads
                if mutex is not None:
                    mutex.release()
                    
# Extraccion de caras de las imagenes
def extractFaces(inputDir,outputDir, scale_factor=1.1, min_neighbors=13,
                 face_cascade_paths=['haarcascades/haarcascade_frontalface_alt2.xml', 'haarcascades/haarcascade_profileface.xml'],
                 exts=['*.jpg','*.jpeg','*.png'],
                 min_size=-1, max_size=-1 , flags=cv2.CASCADE_SCALE_IMAGE, aviso = True):

    #Comprovar directorio donde estan las fotografias
    if os.path.exists(inputDir):
        
        # Comprovar si estan los directorios salida y crearlos en su defecto
        if not os.path.exists(outputDir):
            os.mkdir(outputDir)
        
        # objecte locker per controlar els threads
        mutex = threading.Lock() #Per a threading
        ncpu = mp.cpu_count() # Numero de cpus del ordinador
        
        #ncpu = 1 #Test per calcular temps amb un CPU
        
        # Iniciar variables
        threads, images, run_th = [], [], []
        m, per = 0, -1
        
        #poner distintos filtros para el classificador
        #face_cascades = [cv2.CascadeClassifier(os.path.expanduser(path)) for path in face_cascade_paths]

        # read list of images
        for ext in exts:    
            images.extend(glob.glob(inputDir+'/'+ext))
        
        #numero de fotos
        n=len(images)
        
        if aviso:
            print ""
            print " Processant " + str(n) + " Imatge(s) "
            if min_size == -1:
                print " Parametres: scale_factor " + str(scale_factor) + " min_neighbors " + str(min_neighbors) + " min_size ( Automatic a 0.05 per imatge ) max_size ( Automatic a 0.35 per imatge )"
            else:
                print " Parametres: scale_factor " + str(scale_factor) + " min_neighbors " + str(min_neighbors) + " min_size ( " + str(min_size) + " )  max_size ( " +str(max_size) + " )"
            print " Filtres xml utilitzats:"
            for fil in face_cascade_paths:
                print " |--> " + fil
            print "------------------------------------------------------------------------------"
        
        for image in images:
            try:
                #imageFaces(image, outputDir, None, face_cascade_paths, scale_factor, min_neighbors, exts, flags) # funcio sense utilitzar threads
                # Es crea un fil perque un thread cerqui les cares per cada imatge, aixi es paral-lelitza amb millor eficiencia
                threads.append(threading.Thread(target=imageFaces, 
                            args=(image, outputDir, mutex, face_cascade_paths, scale_factor, min_neighbors, exts, min_size, max_size, flags,)))
            except:
                print "Error desconocido del thread"
            
                
        #Preperar els treballs en grups del numero de cpus 
        while len(threads) > 0:
            while len(run_th) < (ncpu+1) and len(threads) > 0:
                # Treure el thread de la llista per incorporar-lo en una de treball
                run_th.append(threads.pop())
            
            # Iniciar els grups de threads
            for th in run_th:
                th.start()
            # Esperar que acabin els threads    
            for th in run_th:
                if per < int((m/float(n))*100):
                    per = int((m/float(n))*100)
                    if aviso:
                        progressBar(per)
                # Esperar a finalitzar el proces
                th.join()
                m += 1
            # Vuidar la llista per incorporar nous threads    
            run_th =[]
        
        # Visualizar el final del proceso
        if aviso:
            progressBar(100)
            sys.stdout.write("\x1B[37m") # Canvia de color a normal
            print('\n')
        
        
# Funcion para distorsionar las caras blur_lvl comprente entre 0.0 a 1.0
def imageSmooth(image,outputDir, mutex=None, blur_lvl=0.3, scale_factor=1.1, min_neighbors=1,
                face_cascade_paths=['haarcascades/haarcascade_frontalface_alt2.xml', 'haarcascades/haarcascade_profileface.xml'],
                exts=['*.jpg','*.jpeg','*.png'],
                min_size=-1, max_size=-1,
                flags=cv2.CASCADE_SCALE_IMAGE):
        
    # Comprovar si estan los directorios salida y crearlos en su defecto
    if not os.path.exists(outputDir):
        os.mkdir(outputDir)
        
    # Dades sobre el nom de l'arxiu imatge
    img_basename = image.split('/')[-1].split('.')[0]
    img_ext = image.split('/')[-1].split('.')[1]

    #poner distintos filtros para el classificador
    face_cascades = [cv2.CascadeClassifier(os.path.expanduser(path)) for path in face_cascade_paths]
        
    # Carrega de la imatge
    img =cv2.imread(image)
    if img is None:
        print "\rImagen " + image + " no cargada"
    else:
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        w,h = gray.shape
        if max_size == -1:
            size_max = int(max([w*0.35,h*0.35]))
        else:
            size_max = min([w,h])
            
        if min_size == -1:
            size_min = int(min([w*0.05,h*0.05]))
        else:
            size_min = min_size
        
        #Equalizar Histograma per millorar qualitat per lluminositat
        cv2.equalizeHist(gray, gray);
        
        faces = []
        for cascade in face_cascades:
            f = cascade.detectMultiScale(gray, scaleFactor = scale_factor, 
                                        minNeighbors = min_neighbors, flags = flags, minSize = (size_min,size_min), maxSize = (size_max,size_max))
            f = list(f)
            if f: faces += f
        
        centros = []
        nfaces = 0
        for i, ( x, y, w, h ) in enumerate(faces):
            unico =True # Controla que no haya recuedros repetidos

            #Para Gesionar los recuadros repetidos pulir
            for cx,cy in centros:
                if cx > x and cy > y and cx < (x+w) and cy < (y+h):
                        unico = False # Recuadro repetido
            
            if unico:
                nfaces += 1
                # Guardar recorte expandido sin recuadro
                img_crop_ext = img[y:y+h,x:x+w]
                
                # Distorsionar cara se multiplica longuitud cuadrado cara por nivel
                img_blur = cv2.blur(img,(int(h*blur_lvl),int(h*blur_lvl)))
                
                # Possar la zona amb la cara distorsionada
                img[y:y+h,x:x+w] = img_blur[y:y+h,x:x+w]
        
        #outfname = outputDir+"/"+image.split('/')[-1]
        outfname = outputDir+'/'+img_basename+'-'+str(nfaces)+'.'+img_ext
        # Bloquejar el thread per poder guardar la foto
        if mutex is not None:
            mutex.acquire()
        cv2.imwrite(outfname, img)
        
        # Desbloquejar tots els threads
        if mutex is not None:
            mutex.release()


# Funcion para distorsionar las caras blur_lvl comprente entre 0.0 a 1.0
def smoothFaces(inputDir,outputDir, blur_lvl = 0.3, scale_factor=1.1, min_neighbors=1,
                face_cascade_paths=['haarcascades/haarcascade_frontalface_alt2.xml', 'haarcascades/haarcascade_profileface.xml'],
                exts=['*.jpg','*.jpeg','*.png'],
                min_size=-1, max_size=-1,
                flags=cv2.CASCADE_SCALE_IMAGE):

    #Comprovar directorio donde estan las fotografias
    if os.path.exists(inputDir):
        
        # Comprovar si estan los directorios salida y crearlos en su defecto
        if not os.path.exists(outputDir):
            os.mkdir(outputDir)
            
        # objecte locker per controlar els threads
        mutex = threading.Lock() #Per a threading
        ncpu = mp.cpu_count()
            
        # Iniciar variables
        threads, images, run_th = [],[],[]
        m, per = 0, -1 

        # read list of images
        for ext in exts:    
            images.extend(glob.glob(inputDir+'/'+ext))
        
        #numero de fotos
        n=len(images)
        
        print ""
        print " Distorsionant cares de " + str(n) + " Imatge(s) "
        print " Parametres: scale_factor " + str(scale_factor) + " min_neighbors " + str(min_neighbors)
        print " Filtres xml utilitzats:"
        for fil in face_cascade_paths:
            print " |--> " + fil
        print "------------------------------------------------------------------------------"
        
        for image in images:
            #imageSmooth(image,outputDir, None ,blur_lvl = blur_lvl ,scale_factor = scale_factor , min_neighbors = min_neighbors,face_cascade_paths=face_cascade_paths, exts=exts,flags=flags)
            try:
                # Es crea un fil perque un thread cerqui les cares per cada imatge, aixi es paral-lelitza amb millor eficiencia
                threads.append(threading.Thread(target=imageSmooth, 
                        args=(image,outputDir, mutex ,blur_lvl , scale_factor , min_neighbors,face_cascade_paths, exts, min_size, max_size, flags,)))
            except:
                print "Error desconocido del thread"
            
        #Preperar els treballs en grups del numero de cpus -1
        while len(threads) > 0:
            while len(run_th) < (ncpu+1) and len(threads) > 0:
                # Treure el thread de la llista per incorporar-lo en una de treball
                run_th.append(threads.pop())
            
            # Iniciar els grups de threads
            for th in run_th:
                th.start()
            # Esperar que acabin els threads    
            for th in run_th:
                if per < int((m/float(n))*100):
                    per = int((m/float(n))*100)
                    progressBar(per)
                th.join()
                m += 1
            # Vuidar la llista per incorporar nous threads    
            run_th =[]
                
        # Visualizar el final del proceso        
        progressBar(100)
        sys.stdout.write("\x1B[37m") # Canvia de color a normal
        print('\n')


# 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()
    

