from PIL import Image

import math
import random
def _color_distance(c1, c2):
    """
    Calcula a distância Euclidiana entre duas cores RGB (tuplas).
    """
    return math.sqrt((c1[0] - c2[0])**2 + (c1[1] - c2[1])**2 + (c1[2] - c2[2])**2)

def quantize(image_path, num_colors=8, min_distance=35):
    """
    Carrega a imagem de 'image_path' e cria uma paleta de até 'num_colors' cores
    onde cada cor está a pelo menos 'min_distance' de qualquer outra na paleta.
    
    Em seguida, recolore a imagem usando essa paleta. Retorna o objeto PIL.Image final.
    
    :param image_path: Caminho do arquivo da imagem.
    :param num_colors: Quantidade máxima de cores desejadas.
    :param min_distance: Distância mínima entre as cores selecionadas.
    :return: PIL.Image recolorida.
    """
    # 1. Carrega a imagem
    img = Image.open(image_path).convert("RGB")

    # Opcional: Redimensionar para acelerar a coleta de cores
    # Mantém proporção e limita a 200px no maior lado, por exemplo
    img_small = img.copy()
    img_small.thumbnail((200, 200))

    # 2. Extrai todos os pixels em uma lista
    pixels = list(img_small.getdata())

    # 3. Seleciona cores de forma iterativa
    random.shuffle(pixels)  # Embaralha para não pegar só da borda
    palette = []

    for color in pixels:
        # Se já atingimos o número de cores desejado, paramos.
        if len(palette) >= num_colors:
            break

        # Verifica se essa cor está "longe" de todas na palette
        too_close = False
        for p in palette:
            if _color_distance(color, p) < min_distance:
                too_close = True
                break

        # Se não está próxima de nenhuma, adiciona na paleta
        if not too_close:
            palette.append(color)

    # Se a paleta final tiver menos cores que o desejado, paciência.
    # É possível que tenhamos "esgotado" as cores disponíveis ou que min_distance seja alto.

    # 4. Recolore a imagem original usando a paleta
    #    Para cada pixel, encontramos a cor da paleta mais próxima e atribuímos.
    img_result = Image.new("RGB", img.size)
    pixels_original = img.load()
    pixels_result = img_result.load()

    for y in range(img.height):
        for x in range(img.width):
            original_color = pixels_original[x, y]
            # Acha cor mais próxima na palette
            closest_color = min(palette, key=lambda c: _color_distance(original_color, c))
            pixels_result[x, y] = closest_color

    return img_result



def bw(image_path, num_gray):
    """
    Lê uma imagem colorida, converte para escala de cinza e reduz a quantidade de tons
    para num_gray (limite máximo de 255).
    
    Retorna um objeto PIL.Image da imagem em tons de cinza quantizada.
    """
    if num_gray > 255:
        raise ValueError("num_gray deve ser no máximo 255.")

    # Abre a imagem
    img = Image.open(image_path)

    # Converte para escala de cinza (0 a 255)
    gray_img = img.convert("L")  # Modo "L" é 8-bit grayscale

    # Se quisermos usar a quantização interna do Pillow, poderíamos fazer:
    #
    #   # Converte a imagem em P com palette adaptativa e 'num_gray' cores
    #   quantized_gray = gray_img.quantize(colors=num_gray)
    #   # Converte novamente para "L" para manter um canal (grayscale)
    #   final_gray = quantized_gray.convert("L")
    #   return final_gray
    #
    # Mas se preferir um controle manual de quantização:
    
    # Carrega os pixels para manipulação direta
    pixels = gray_img.load()
    width, height = gray_img.size

    for y in range(height):
        for x in range(width):
            # Valor original (0..255)
            old_pixel = pixels[x, y]
            # Mapeia para 0..(num_gray-1)
            new_pixel = int((old_pixel / 255) * (num_gray - 1))
            # Escala de volta para 0..255
            new_pixel_255 = int((new_pixel / (num_gray - 1)) * 255) if (num_gray - 1) > 0 else 0
            pixels[x, y] = new_pixel_255

    return gray_img

def shuffle(image_path, num_colors=8, num_combinations=50):
    """
    1. Converte a imagem em modo 'P' (paleta) com num_colors usando ADAPTIVE.
    2. Para cada combinação, cria uma nova paleta aleatória (para as mesmas indices).
    3. Retorna um list de PIL.Image (cada imagem já em RGB).
    """
    # 1) Abrir imagem e quantizar
    img = Image.open(image_path).convert('RGB')
    quantized_img = img.convert('P', palette=Image.ADAPTIVE, colors=num_colors)

    # Copiamos a paleta original (é uma lista de até 256*3 valores)
    original_palette = quantized_img.getpalette()

    results = []
    for _ in range(num_combinations):
        # Copiamos a paleta original para alterar
        new_palette = original_palette.copy()

        # Para cada uma das cores (índices 0..num_colors-1), geramos cores aleatórias
        # Cada cor ocupa 3 posições (R, G, B) na paleta
        for i in range(num_colors):
            r = random.randint(0, 255)
            g = random.randint(0, 255)
            b = random.randint(0, 255)
            start = i * 3
            new_palette[start:start+3] = [r, g, b]

        # Criamos uma cópia do quantized_img para colocar a nova paleta
        new_img = quantized_img.copy()
        new_img.putpalette(new_palette)

        # Convertendo para RGB para simplificar exibição/gravação posterior
        new_img_rgb = new_img.convert("RGB")
        results.append(new_img_rgb)

    return results