import os
import shutil
from PIL import Image, ImageEnhance, ImageFilter
import cv2
import numpy as np
from datos.Config import ConfigManager
from concurrent.futures import ThreadPoolExecutor, as_completed
import logica.controladores.image_preprocessing_controller as controller

config = ConfigManager()

def copy_images_to_output():
    if config.is_output_folder_set() and config.is_images_folder_set():
        
        if not os.path.exists(config.get_output_folder()) or not os.path.exists(config.get_images_folder()):
            controller.set_output("Source folder or output folder does not exist.")
            return
        
        source_folder = config.get_images_folder()
        output_folder = config.get_output_folder()

        files_output = os.listdir(output_folder)
        files = os.listdir(source_folder)

        if len(files_output) > 0:
            controller.set_output("Images already exist in the output folder.")
            return

        if len(files) == 0:
            controller.set_output("No images in the source folder.")
            return

        def copy_file(file):
            file_path = os.path.join(source_folder, file)
            output_path = os.path.join(output_folder, file)

            if os.path.isfile(file_path) and file_path.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff')):
                try:
                    shutil.copy(file_path, output_path)
                    controller.set_output(f"Image {file} copied successfully.")
                except Exception as e:
                    controller.set_output(f"Error copying image {file}: {str(e)}")
            else:
                controller.set_output(f"{file} is not an image file.")

        with ThreadPoolExecutor() as executor:
            executor.map(copy_file, files)

        controller.set_output(controller.load_images(output_folder))

def exist_path():
    output_folder = config.get_output_folder()

    # Check if the folder exists
    if not os.path.isdir(output_folder):
        controller.set_output("Specified output folder does not exist.")
        return -1
    
    files = os.listdir(output_folder)

    if len(files) == 0:
        controller.set_output("No images in the output folder.")
        return -1
    
    return files

def resize_image(file, output_folder, controller, config):
    file_path = os.path.join(output_folder, file)

    # Check if it's an image file
    if os.path.isfile(file_path) and file_path.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff')):
        try:
            # Open the image
            img = Image.open(file_path)
            # Resize the image
            resized_img = img.resize((config.IMAGE_GLOBAL_WIDTH, config.IMAGE_GLOBAL_HEIGHT))
            # Save the resized image, overwriting the original file
            resized_img.save(file_path)
            
            controller.set_output(f"Image {file} resized successfully.")

        except Exception as e:
            controller.set_output(f"Error resizing image {file}: {str(e)}")

    else:
        controller.set_output(f"{file} is not an image file.")
        return

def adjust_image(file_path, brightness_factor, contrast_factor, color_factor):
    """
    Adjusts the brightness, contrast, and color of an image.
    """
    try:
        # Open the image
        img = Image.open(file_path)
        # Adjust brightness, contrast, and color
        adjusted_img = ImageEnhance.Brightness(img).enhance(brightness_factor)
        adjusted_img = ImageEnhance.Contrast(adjusted_img).enhance(contrast_factor)
        adjusted_img = ImageEnhance.Color(adjusted_img).enhance(color_factor)
        # Save the image
        adjusted_img.save(file_path)
        return f"Image {os.path.basename(file_path)} adjusted successfully."
    except Exception as e:
        return f"Error adjusting image {os.path.basename(file_path)}: {str(e)}"

def adjust_brightness_contrast(brightness_factor=1.0, contrast_factor=1.0, color_factor=1.0):
    """
    Adjusts the brightness, contrast, and color of images in a folder.
    """
    controller.set_output("Adjusting brightness, contrast, and color of images...")
    output_folder = config.get_output_folder()
    files = exist_path()

    if files == -1:
        return

    # Filter image files
    image_files = [os.path.join(output_folder, file) for file in files if os.path.isfile(os.path.join(output_folder, file)) and file.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff'))]

    # Use ThreadPoolExecutor to adjust images concurrently
    with ThreadPoolExecutor() as executor:
        futures = [executor.submit(adjust_image, file_path, brightness_factor, contrast_factor, color_factor) for file_path in image_files]
        
        for future in futures:
            controller.set_output(future.result())

    controller.set_output(controller.cargar_imagenes(output_folder))

def process_denoise_image(file, output_folder, radius):
    file_path = os.path.join(output_folder, file)
    if os.path.isfile(file_path) and file_path.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff')):
        try:
            image = Image.open(file_path)
            denoised_img = image.filter(ImageFilter.GaussianBlur(radius))
            denoised_img.save(file_path)
            controller.set_output(f"Noise removed from {file} successfully.")
        except Exception as e:
            controller.set_output(f"Error removing noise from {file}: {str(e)}")

def denoise_image(radius=2):
    """
    Removes noise from an image using a blur filter.
    """
    output_folder = config.get_output_folder()
    files = exist_path()

    if files == -1:
        return

    with ThreadPoolExecutor() as executor:
        futures = [executor.submit(process_denoise_image, file, output_folder, radius) for file in files]
        for future in futures:
            future.result()

    controller.set_output(controller.cargar_imagenes(output_folder))

def calculate_factors(image_path):
    img = Image.open(image_path).convert('L')  # Convert to grayscale
    img_array = np.array(img)
    average_brightness = np.mean(img_array)

    img = Image.open(image_path).convert('L')  # Convert to grayscale
    img_array = np.array(img)
    average_contrast = np.std(img_array)

    img = Image.open(image_path)
    img_array = np.array(img)
    average_color_r = np.std(img_array[:,:,0])
    average_color_g = np.std(img_array[:,:,1])
    average_color_b = np.std(img_array[:,:,2])
    average_color = (average_color_r + average_color_g + average_color_b) / 3

    return average_brightness, average_contrast, average_color

def is_image_blurry(image_path, threshold=10):
    """
    Determines if an image is blurry using the Laplacian operator.
    """
    image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
    
    # Define the custom Laplacian kernel
    kernel = np.array([[1, 1, 1],
                       [1, -8, 1],
                       [1, 1, 1]], dtype=np.float32)
    
    # Apply the kernel to the image using cv2.filter2D
    laplacian = cv2.filter2D(image, cv2.CV_64F, kernel)
    
    # Calculate the variance of the Laplacian result
    laplacian_var = laplacian.var()
    # If the variance of the Laplacian operator is less than the threshold, the image is considered blurry
    return (laplacian_var / 10) < threshold

def remove_blurry_imagen(file_path, threshold=10):
    """
    Processes an image file to check if it's blurry and removes it if so.
    """
    try:
        # Check if the image is blurry
        if is_image_blurry(file_path, threshold):
            os.remove(file_path)
            controller.set_output(f"Image {os.path.basename(file_path)} removed for being blurry.")
    except Exception as e:
        controller.set_output(f"Error processing image {os.path.basename(file_path)}: {str(e)}")


def remove_blurry_images(threshold=10):
    """
    Removes blurry images in a folder.
    """
    output_folder = config.get_output_folder()

    files = exist_path()

    if files == -1:
        return
    
    # Create a ThreadPoolExecutor to parallelize file processing
    with ThreadPoolExecutor() as executor:
        futures = []
        for file in files:
            file_path = os.path.join(output_folder, file)
            # Check if it's an image file
            if os.path.isfile(file_path) and file_path.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff')):
                futures.append(executor.submit(remove_blurry_imagen, file_path, threshold))
        
        # Wait for all futures to complete
        for future in futures:
            future.result()

    controller.set_output(controller.cargar_imagenes(output_folder))

def paralized_resize_images_callback(width, height):
    output_folder = config.get_output_folder()

    files = exist_path()

    if files == -1:
        return

    # Modify the global size of the images to the new size
    config.set_image_global_size(width, height)

    with ThreadPoolExecutor() as executor:
        futures = {executor.submit(resize_image, file, output_folder, controller, config): file for file in files}
        
        for future in as_completed(futures):
            file = futures[future]
            try:
                future.result()
            except Exception as e:
                controller.set_output(f"Error processing file {file}: {str(e)}")

    controller.set_output(controller.cargar_imagenes(output_folder))

def process_add_noise_image(file, output_folder, noise_type, intensity):
    file_path = os.path.join(output_folder, file)

    if os.path.isfile(file_path) and file_path.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff')):
        try:
            image = cv2.imread(file_path)

            if noise_type == 'Gaussian':
                noise = np.random.normal(0, intensity, image.shape)
                noisy_image = image + noise

            elif noise_type == 'Salt and Pepper':
                noise = np.random.rand(*image.shape)
                noisy_image = image.copy()
                noisy_image[noise < intensity / 2] = 0
                noisy_image[noise > 1 - intensity / 2] = 255

            else:
                controller.set_output("Invalid noise type. Must be 'Gaussian' or 'Salt and Pepper'.")
                return
            
            noisy_image = np.clip(noisy_image, 0, 255).astype(np.uint8)

            cv2.imwrite(file_path, noisy_image)
            controller.set_output(f"Noise added to {file} successfully.")

        except Exception as e:
            controller.set_output(f"Error adding noise to {file}: {str(e)}")

def add_noise(noise_type, intensity):
    """
    Adds the specified type of noise with given intensity.
    """
    output_folder = config.get_output_folder()
    files = exist_path()

    if files == -1:
        return

    with ThreadPoolExecutor() as executor:
        futures = [executor.submit(process_add_noise_image, file, output_folder, noise_type, intensity) for file in files]

        for future in futures:
            future.result()

    controller.set_output(controller.cargar_imagenes(output_folder))