import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
from PIL import Image, ImageTk
import numpy as np
from cryptography.fernet import Fernet
import base64
import hashlib
import sys, os
from datetime import datetime

# --- Fonctions de chiffrement/déchiffrement ---
def generate_key_from_password(password: str) -> bytes:
    """Génère une clé Fernet à partir d'un mot de passe."""
    digest = hashlib.sha256(password.encode()).digest()
    return base64.urlsafe_b64encode(digest)

def encrypt_text(text: str, key: bytes) -> bytes:
    """Chiffre le texte avec Fernet."""
    f = Fernet(key)
    return f.encrypt(text.encode('utf-8'))

def decrypt_text(encrypted_text: bytes, key: bytes) -> str:
    """Déchiffre le texte avec Fernet."""
    f = Fernet(key)
    return f.decrypt(encrypted_text).decode('utf-8')

# --- Fonctions de stéganographie ---
def text_to_bin(text: str) -> str:
    """Convertit du texte en binaire."""
    return ''.join(format(ord(c), '08b') for c in text)

def bin_to_text(binary: str) -> str:
    """Convertit du binaire en texte."""
    return ''.join(chr(int(binary[i:i+8], 2)) for i in range(0, len(binary), 8))

def insert_text_in_image(image_path: str, output_path: str, text: str, password: str) -> tuple:
    try:
        key = generate_key_from_password(password)
        encrypted_text = encrypt_text(text, key)
        binary_text = text_to_bin(encrypted_text.decode('utf-8'))
        binary_text += '1111111111111110'  # Délimiteur de fin

        img = Image.open(image_path)
        if img.mode != "RGB":
            img = img.convert("RGB")  # Force RGB
        pixels = np.array(img)
        flat_pixels = pixels.flatten()

        if len(binary_text) > len(flat_pixels):
            return (False, "L'image est trop petite pour contenir le texte.")

        for i in range(len(binary_text)):
            flat_pixels[i] = (flat_pixels[i] & 0xFE) | int(binary_text[i])

        new_pixels = flat_pixels.reshape(pixels.shape)
        new_img = Image.fromarray(new_pixels.astype('uint8'))  # Assure-toi que le type est uint8
        new_img.save(output_path, format="PNG")  # Sauvegarde en PNG
        return (True, f"Texte inséré avec succès dans {output_path}")
    except Exception as e:
        return (False, f"Erreur : {str(e)}")

def extract_text_from_image(image_path: str, password: str) -> tuple:
    """Extrait le texte chiffré d'une image."""
    try:
        img = Image.open(image_path)
        pixels = np.array(img)
        flat_pixels = pixels.flatten()

        binary_text = ''
        for pixel in flat_pixels:
            binary_text += str(pixel & 1)

        # Trouver le délimiteur de fin
        delimiter = '1111111111111110'
        end_index = binary_text.find(delimiter)
        if end_index == -1:
            return (False, "Aucun texte trouvé dans l'image.")

        binary_text = binary_text[:end_index]
        encrypted_text = bin_to_text(binary_text).encode('utf-8')

        key = generate_key_from_password(password)
        decrypted_text = decrypt_text(encrypted_text, key)
        return (True, decrypted_text)
    except Exception as e:
        return (False, f"Erreur : {str(e)}")

def generate_output_filename(input_path: str) -> str:
    """Génère un nom de fichier de sortie."""
    base, ext = os.path.splitext(input_path)
    return f"{base}_stego{ext}"

def resource_path(relative_path):
    """ Gère les chemins pour PyInstaller """
    if hasattr(sys, '_MEIPASS'):
        return os.path.join(sys._MEIPASS, relative_path)
    return os.path.join(os.path.abspath("."), relative_path)

# --- Interface graphique ---
class SteganographyApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Mirage")

        icon_path = resource_path("mirage.ico")
        try:
            self.root.iconphoto(True, tk.PhotoImage(file=icon_path))
        except:
            print(f"Icône non trouvée à {icon_path}")

        self.image_path = tk.StringVar()
        self.output_path = tk.StringVar()
        self.password = tk.StringVar()

        self.setup_ui()

    def setup_ui(self):
        # Frame principal
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        # Onglets
        notebook = ttk.Notebook(main_frame)
        notebook.pack(fill=tk.BOTH, expand=True)

        # Onglet pour insérer du texte
        insert_tab = ttk.Frame(notebook)
        notebook.add(insert_tab, text="Insérer du texte")

        # Onglet pour extraire du texte
        extract_tab = ttk.Frame(notebook)
        notebook.add(extract_tab, text="Extraire du texte")

        # Onglet Aide
        help_tab = ttk.Frame(notebook)
        notebook.add(help_tab, text="Aide")
        self.setup_help_tab(help_tab)

        # Widgets pour l'onglet Insérer
        ttk.Label(insert_tab, text="Image source:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        ttk.Entry(insert_tab, textvariable=self.image_path).grid(row=0, column=1, sticky=tk.EW, padx=5, pady=5)
        ttk.Button(insert_tab, text="Parcourir", command=self.browse_image_insert).grid(row=0, column=2, padx=5, pady=5)

        ttk.Label(insert_tab, text="Image de sortie:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
        ttk.Entry(insert_tab, textvariable=self.output_path).grid(row=1, column=1, sticky=tk.EW, padx=5, pady=5)
        ttk.Button(insert_tab, text="Parcourir", command=self.browse_output_insert).grid(row=1, column=2, padx=5, pady=5)
        ttk.Button(insert_tab, text="Générer un nom", command=self.generate_output_name).grid(row=1, column=3, padx=5, pady=5)

        ttk.Label(insert_tab, text="Texte à cacher:").grid(row=2, column=0, sticky=tk.W, padx=5, pady=5)
        self.text_to_hide = scrolledtext.ScrolledText(insert_tab, height=10)
        self.text_to_hide.grid(row=2, column=1, columnspan=3, sticky=tk.EW, padx=5, pady=5)

        ttk.Label(insert_tab, text="Mot de passe:").grid(row=3, column=0, sticky=tk.W, padx=5, pady=5)
        ttk.Entry(insert_tab, textvariable=self.password, show="*").grid(row=3, column=1, sticky=tk.EW, padx=5, pady=5)

        ttk.Button(insert_tab, text="Insérer le texte", command=self.insert_text).grid(row=4, column=1, pady=10)

        # Widgets pour l'onglet Extraire
        ttk.Label(extract_tab, text="Image avec texte caché:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        ttk.Entry(extract_tab, textvariable=self.image_path).grid(row=0, column=1, sticky=tk.EW, padx=5, pady=5)
        ttk.Button(extract_tab, text="Parcourir", command=self.browse_image_extract).grid(row=0, column=2, padx=5, pady=5)

        ttk.Label(extract_tab, text="Mot de passe:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
        ttk.Entry(extract_tab, textvariable=self.password, show="*").grid(row=1, column=1, sticky=tk.EW, padx=5, pady=5)

        ttk.Button(extract_tab, text="Extraire le texte", command=self.extract_text).grid(row=2, column=1, pady=10)

        ttk.Label(extract_tab, text="Texte extrait:").grid(row=3, column=0, sticky=tk.W, padx=5, pady=5)
        self.result_area = scrolledtext.ScrolledText(extract_tab, height=10)
        self.result_area.grid(row=3, column=1, columnspan=3, sticky=tk.EW, padx=5, pady=5)

        # Configuration des colonnes et lignes
        insert_tab.columnconfigure(1, weight=1)
        extract_tab.columnconfigure(1, weight=1)

    def setup_help_tab(self, help_tab):
        """Configure l'onglet Aide avec le contenu du README."""
        help_text = scrolledtext.ScrolledText(help_tab, height=20, wrap=tk.WORD)
        help_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        help_content = """Mirage - V1.00 - Outil de stéganographie et chiffrement

📌 DESCRIPTION
Ce projet est un outil graphique qui permet de :
- Chiffrer et déchiffrer du texte avec l'algorithme Fernet
- Cacher et extraire du texte dans des images (stéganographie)

🛠 PRÉREQUIS
- Python 3.8+
- Pillow 9.0.0+ (traitement des images)
- numpy 1.21.0+ (calculs numériques)
- cryptography 36.0.0+ (chiffrement Fernet)
- tkinter (inclus avec Python)

🚀 UTILISATION
1. Lancer l'application : python3 mirage.py
2. Onglet "Insérer du texte" :
   - Sélectionnez une image source
   - Spécifiez l'image de sortie
   - Entrez le texte à cacher
   - Entrez un mot de passe
   - Cliquez sur "Insérer le texte"
3. Onglet "Extraire du texte" :
   - Sélectionnez une image avec texte caché
   - Entrez le mot de passe
   - Cliquez sur "Extraire le texte"

🔐 SÉCURITÉ
- Le chiffrement utilise une clé générée à partir de votre mot de passe
- La clé est dérivée en SHA-256 et encodée en base64
- Utilisez des mots de passe forts pour une meilleure sécurité

⚠️ LIMITATIONS
- L'image doit être suffisamment grande pour contenir le texte
- Le mot de passe est nécessaire pour extraire le texte

📧 CONTACT
Pour toute question ou support : f4iyt@free.fr

📜 LICENCE
Ce projet est sous licence MIT."""
        help_text.insert("1.0", help_content)
        help_text.config(state=tk.DISABLED)

    def browse_image_insert(self):
        filename = filedialog.askopenfilename(filetypes=[("Images", "*.png *.jpg *.jpeg *.bmp")])
        if filename:
            self.image_path.set(filename)
            self.output_path.set(generate_output_filename(filename))

    def browse_output_insert(self):
        filename = filedialog.asksaveasfilename(defaultextension=".png", filetypes=[("Images", "*.png *.jpg *.jpeg *.bmp")])
        if filename:
            self.output_path.set(filename)

    def generate_output_name(self):
        input_path = self.image_path.get()
        if input_path:
            self.output_path.set(generate_output_filename(input_path))

    def browse_image_extract(self):
        filename = filedialog.askopenfilename(filetypes=[("Images", "*.png *.jpg *.jpeg *.bmp")])
        if filename:
            self.image_path.set(filename)

    # Fonctions pour insérer et extraire le texte
    def insert_text(self):
        image_path = self.image_path.get()
        output_path = self.output_path.get()
        text = self.text_to_hide.get("1.0", tk.END).strip()
        password = self.password.get()

        if not all([image_path, output_path, text, password]):
            messagebox.showerror("Erreur", "Tous les champs sont obligatoires !")
            return

        success, message = insert_text_in_image(image_path, output_path, text, password)
        if success:
            messagebox.showinfo("Succès", message)
        else:
            messagebox.showerror("Erreur", message)

    def extract_text(self):
        image_path = self.image_path.get()
        password = self.password.get()

        if not all([image_path, password]):
            messagebox.showerror("Erreur", "Tous les champs sont obligatoires !")
            return

        success, message = extract_text_from_image(image_path, password)
        if success:
            self.result_area.delete("1.0", tk.END)
            self.result_area.insert("1.0", message)
        else:
            messagebox.showerror("Erreur", message)

# --- Lancement de l'application ---
if __name__ == "__main__":
    root = tk.Tk()
    app = SteganographyApp(root)
    root.mainloop()