# firstly import the necessary libraries :
import cv2
import matplotlib.pyplot as plt
import numpy as np
import os
import zipfile
from os import listdir
from PIL import Image
from numpy import asarray,expand_dims
from matplotlib import pyplot
from keras.models import load_model
from keras_facenet import FaceNet
import pickle
from mtcnn import MTCNN
import math

# we are going to use harr cacade first 
HaarCascade = cv2.CascadeClassifier(cv2.samples.findFile(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'))
# if harr cascade is unable to detect we will keep mtcnn for that case
# Initialize the MTCNN detector
mtcnn = MTCNN()
# we are going to use Facenet architecture for creating the embeddings from faces
model_face = FaceNet()


def process_image(image_path):
    image = cv2.imread(image_path,cv2.IMREAD_UNCHANGED)
    # for this example we are not resizing the image dimensions :
    resized=image
    image_rgb = cv2.cvtColor(resized, cv2.COLOR_BGR2RGB)

    # we need to adjust the size of window in cv 2 to display the image


    face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
    gray_image = cv2.cvtColor(resized, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray_image, scaleFactor=1.1, minNeighbors=5)

    # cv2.namedWindow("output", cv2.WINDOW_NORMAL)       
    # cv2.resizeWindow("output", resized.shape[0],resized.shape[1])    
    for (x, y, w, h) in faces:
        cv2.rectangle(image_rgb, (x, y), (x+w, y+h), (0, 255, 0), 2)
        # cv2.imshow("output", image_rgb)
    #     cv2.waitKey(0)
    # cv2.destroyAllWindows()

    # convert the image back to RGB format and adjust the brighness and contrast after processing

    final = cv2.cvtColor(image_rgb, cv2.COLOR_BGR2RGB)
    final = cv2.convertScaleAbs(final, alpha=1, beta=0)  # Adjust alpha and beta as needed

    # save the image with bounding boxes as image_detected.jpg
    cv2.imwrite('image_detected.jpg',final)
    
    folder_name = 'attendance_folder'
    if not os.path.exists(folder_name):
        os.mkdir(folder_name)
    # List all files in the folder
    file_list = os.listdir(folder_name)

    face_images = []

    # Iterate through the files and remove them
    for file in file_list:
        file_path = os.path.join(folder_name, file)
        if os.path.isfile(file_path):
            os.remove(file_path)
            
    # Save the cropped photos in the folder named attendance_class
    for (x, y, w, h) in faces:
        face_crop = resized[y:y+h, x:x+w]
        face_images.append(face_crop)
        face_filename = os.path.join(folder_name, f'face_{x}_{y}.jpg')
        cv2.imwrite(face_filename, face_crop)

    
    # we need to adjust the size of window in cv 2 to display the image
    # folder_name = 'attendance_folder'
    # if not os.path.exists(folder_name):
    #     os.mkdir(folder_name)
    # List all files in the folder
    # file_list = os.listdir(folder_name)

    # face_images = []

    # # Iterate through the files and remove them
    # for file in file_list:
    #     file_path = os.path.join(folder_name, file)
    #     if os.path.isfile(file_path):
    #         os.remove(file_path)

    # cv2.namedWindow("output", cv2.WINDOW_NORMAL)       
    # cv2.resizeWindow("output", resized.shape[0],resized.shape[1])   
    
    # for face in faces:
    #     x, y, w, h = face['box']
    #     cv2.rectangle(image_rgb, (x, y), (x+w, y+h), (0, 255, 0), 2)
    #     cv2.imshow("output", image_rgb)
    #     # cv2.waitKey(0)
    #     face_crop = resized[y:y+h, x:x+w]
    #     face_images.append(face_crop)
    #     face_filename = os.path.join(folder_name, f'face_{x}_{y}.jpg')
    #     cv2.imwrite(face_filename, face_crop)
    # cv2.destroyAllWindows()

    # # convert the image back to RGB format and adjust the brighness and contrast after processing

    # final = cv2.cvtColor(image_rgb, cv2.COLOR_BGR2RGB)
    # final = cv2.convertScaleAbs(final, alpha=1, beta=0)  # Adjust alpha and beta as needed

    # # save the image with bounding boxes as image_detected.jpg
    # cv2.imwrite('image_detected.jpg',final)


def intermediate_process(gbr1):
    # detect the face in the cropped photo :
    harr = HaarCascade.detectMultiScale(gbr1,1.1,4)
    
    # if the face is detected then get the width and height
    if len(harr)>0:
        x1, y1, width, height = harr[0]
    
    # if harr cascade is unable to detect the face use mtcnn 
    else:
        faces_mtcnn = mtcnn.detect_faces(gbr1)
        if len(faces_mtcnn)>0:
            x1, y1, width, height = faces_mtcnn[0]['box']
        else :
            # if no face is detected in the image just use the top left 10x10 pixels
            x1, y1, width, height = 1, 1, 10, 10
    
    
    x1, y1 = abs(x1), abs(y1)
    x2, y2 = x1 + width, y1 + height

    #convert from bgr to rgb
    gbr = cv2.cvtColor(gbr1, cv2.COLOR_BGR2RGB)
    gbr = Image.fromarray(gbr)  # Convert from OpenCV to PIL
    # convert image as numpy array 
    gbr_array = asarray(gbr)

    # crop the face , resize it and store in face 
    face = gbr_array[y1:y2, x1:x2]
    face = Image.fromarray(face)
    face = face.resize((160, 160))
    face = asarray(face)
    return gbr, face


def generate_embeddings(zip_path):
    
    folder_name = os.path.splitext(zip_path)[0]
    
    # Create the directory if it does not exist
    if not os.path.exists(folder_name):
        os.makedirs(folder_name)
    
    # Unzip the file
    with zipfile.ZipFile(zip_path, 'r') as zip_ref:
        zip_ref.extractall(folder_name)
    folder=folder_name+'/'
    # now generate the embeddings :
        # intialize empty dictionary in which we will store the embeddings with name of the person
    database = {}

    # iterate through all the images in the training images folder 
    for filename in listdir(folder):
        path = folder + filename
        gbr1 = cv2.imread(folder + filename)        

        gbr, face = intermediate_process(gbr1)

        # facenet takes as input 4 dimensional array so we expand dimension 
        face = expand_dims(face, axis=0)
        signature = model_face.embeddings(face)

        # store the array in the database
        database[os.path.splitext(filename)[0]] = signature

    # cv2.destroyAllWindows()
        # make a file named data_processed.pkl and store the database in it
    myfile = open("embeddings.pkl", "wb")
    pickle.dump(database, myfile)
    myfile.close()

def recognize_faces(embeddigns_path):
    myfile = open(embeddigns_path, "rb")
    database = pickle.load(myfile)
    myfile.close()
    # same procedure as training 
    folder = 'attendance_folder/'
    file_list = os.listdir(folder)
    predicted=[]
    # Set up the plot
    num_images = len(file_list)  
    num_rows = math.ceil(num_images / 4) if math.ceil(num_images / 4)>0 else 1 # Ceiling division to calculate the number of rows
    fig, axes = plt.subplots(num_rows, 4, figsize=(16, 4*num_rows))
    if(num_rows==1):
        axes=axes.reshape(1,4)
    for i,filename in enumerate(file_list):
        path = os.path.join(folder, filename)
        gbr1 = cv2.imread(folder + filename)

        gbr,face = intermediate_process(gbr1)
        
        face = expand_dims(face, axis=0)
        signature = model_face.embeddings(face)
        
        min_dist=100
        identity=' '
        for key, value in database.items() :
            dist = np.linalg.norm(value-signature)
            if dist < min_dist:
                min_dist = dist
                identity = key
        # Plot the image with the identity text
        row = i // 4
        col = i % 4
        axes[row, col].imshow(gbr)
        axes[row, col].set_title(f"Identity: {identity}", fontsize=25)
        axes[row, col].axis('off')
        # print(identity)
        # cv2.namedWindow("output", cv2.WINDOW_NORMAL)       
        # cv2.resizeWindow("output", gbr1.shape[0],gbr1.shape[1]) 
        # cv2.putText(gbr1,identity, (100,100),cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 0), 2, cv2.LINE_AA)
        # cv2.rectangle(gbr1,(x1,y1),(x2,y2), (0,255,0), 2)
        # cv2.imshow("output",gbr1)
        # cv2.waitKey(0)
        predicted.append(identity)
        # Hide any remaining empty subplots
    for i in range(num_images, num_rows * 4):
        row = i // 4
        col = i % 4
        axes[row, col].axis('off')

    plt.tight_layout()
    fig.savefig('image_grid.jpg')
            
    # cv2.destroyAllWindows()
    # store the name of people present in a text file 
    attendance = [name for name in predicted if name != 'unknown']

    file_name = "Attendance.txt"

    with open(file_name, 'w') as file:
        for item in attendance:
            file.write(str(item) + '\n')