package fr.iutfbleau.sae;
import fr.iutfbleau.sae.mhuffman.*;
import fr.iutfbleau.sae.mpif.PIFWriter;
import fr.iutfbleau.sae.mpif.Pixel;
import fr.iutfbleau.sae.mpif.RGBImage;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import javax.imageio.ImageIO;
import javax.swing.JFileChooser;

/**
 * Controleur pour la conversion d'images au format PIF.
 * Gere le chargement des images, le calcul des frequences,
 * la generation des codes de Huffman et la sauvegarde au format PIF.
 */
public class ConverterController {
    
    /** Image convertie en RGBImage */
    private RGBImage image;

    /** Table de frequences pour chaque composante */
    private FrequencyTable frequencyTable;

    /** Codes de Huffman pour la composante rouge */
    private Map<Integer, String> abrHuffmanR;
    
    /** Codes de Huffman pour la composante verte */
    private Map<Integer, String> abrHuffmanG;
    
    /** Codes de Huffman pour la composante bleue */
    private Map<Integer, String> abrHuffmanB;

    /** Codes canoniques pour la composante rouge */
    private Map<Integer, String> canonRED;
    
    /** Codes canoniques pour la composante verte */
    private Map<Integer, String> canonGREEN;
    
    /** Codes canoniques pour la composante bleue */
    private Map<Integer, String> canonBLUE;

    /** La fenetre du convertisseur */
    private ConverterWindow fen;

    /** Chemin du fichier de sortie */
    String outputPath;
    
    /** Chemin du fichier d'entree */
    String inputPath;

    /**
     * Construit un nouveau controleur de conversion.
     * 
     * @param fen la fenetre de l'interface graphique
     * @param in le chemin du fichier d'entree (peut etre null)
     * @param out le chemin du fichier de sortie (peut etre null)
     */
    public ConverterController(ConverterWindow fen, String in, String out) {
        this.fen = fen;
        this.outputPath = out;
        this.inputPath = in;
    }

    /**
     * Charge une image depuis un fichier et la convertit en RGBImage.
     * Extrait les composantes RGB de chaque pixel et met a jour l'interface.
     * 
     * @param file le fichier image a charger
     */
    public void loadImage(File file) {
        try {
            // Lire l'image avec BufferedImage
            BufferedImage buffimage = ImageIO.read(file);
            if (buffimage == null) {
                throw new IllegalArgumentException("Le fichier specifie n'est pas une image valide.");
            }

            int w = buffimage.getWidth();
            int h = buffimage.getHeight();
            
            // Creer une RGBImage de la meme taille
            this.image = new RGBImage(w, h);
            
            // Remplir la RGBImage avec les pixels de BufferedImage
            for (int y = 0; y < h; y++) {
                for (int x = 0; x < w; x++) {
                    int rgb = buffimage.getRGB(x, y);
                    
                    // Extraire les composantes R, G, B
                    int r = (rgb >> 16) & 0xFF; 
                    int g = (rgb >> 8) & 0xFF;
                    int b = rgb & 0xFF;
                    
                    // Creer un pixel et l'ajouter a la RGBImage
                    this.image.setPixel(x, y, new Pixel(r, g, b));
                }
            }
            
            // Mettre a jour le GUI
            this.fen.setImagePreview(buffimage);
            
        } catch (IOException e) {
            GestionErreur.afficherErreur("Erreur lors du chargement : " + e.getMessage());
        }
    }

    /**
     * Calcule les frequences d'apparition de chaque valeur RGB dans l'image.
     * Met a jour l'interface avec les tables de frequences calculees.
     */
    public void computeFrequencies() {
        this.frequencyTable = new FrequencyTable();
        if (this.image == null) {
            System.out.println("Gros pepin");
        }
        this.frequencyTable.computeFromImage(this.image);

        // Mettre a jour le GUI avec les frequences
        int[] freqR = this.frequencyTable.getRed();
        int[] freqG = this.frequencyTable.getGreen();
        int[] freqB = this.frequencyTable.getBlue();
        this.fen.setFrequencyTable(freqR, freqG, freqB);
    }

    /**
     * Genere les arbres de Huffman pour chaque composante RGB.
     * Cree les codes binaires optimaux bases sur les frequences calculees.
     * Met a jour l'interface avec les codes de Huffman generes.
     */
    public void computeHuffman() {
        // Generation des arbres de Huffman pour chaque composante
        HuffmanTree arbreR = new HuffmanTree(this.frequencyTable.getRed());
        this.abrHuffmanR = arbreR.generateCodes();
        
        HuffmanTree arbreG = new HuffmanTree(this.frequencyTable.getGreen());
        this.abrHuffmanG = arbreG.generateCodes();
        
        HuffmanTree arbreB = new HuffmanTree(this.frequencyTable.getBlue());
        this.abrHuffmanB = arbreB.generateCodes();

        // Mettre a jour le GUI avec les codes de Huffman
        this.fen.setHuffmanTable(this.abrHuffmanR, this.abrHuffmanG, this.abrHuffmanB);
    }

    /**
     * Genere les codes canoniques a partir des codes de Huffman.
     * Les codes canoniques sont une forme normalisee des codes de Huffman
     * qui facilite le stockage et le decodage.
     * Met a jour l'interface avec les codes canoniques generes.
     */
    public void computeCanonical() {
        CanonicalCode codeCanoniques = new CanonicalCode();
        this.canonRED = codeCanoniques.generateCodes(this.abrHuffmanR);
        this.canonGREEN = codeCanoniques.generateCodes(this.abrHuffmanG);
        this.canonBLUE = codeCanoniques.generateCodes(this.abrHuffmanB);

        // Mettre a jour le GUI avec les codes canoniques
        this.fen.setCanonicalTable(this.canonRED, this.canonGREEN, this.canonBLUE);
    }

    /**
     * Sauvegarde l'image au format PIF.
     * Verifie que l'image et les codes canoniques sont disponibles avant
     * d'effectuer la sauvegarde.
     * 
     * @param pathfile le chemin du fichier de sortie
     */
    public void saveAsPIF(String pathfile) {
        // Verifier que l'image et les codes canoniques sont disponibles
        if (this.image == null || this.canonRED == null) {
            GestionErreur.afficherErreur("Impossible de sauvegarder : image ou codes canoniques manquants.");
            return;
        }

        try {
            PIFWriter ecriveur = new PIFWriter();
            ecriveur.writeTOFile(pathfile, this.image, this.canonRED, this.canonGREEN, this.canonBLUE);
        } catch (Exception e) {
            GestionErreur.afficherErreur("Erreur lors de l'ecriture du fichier .pif : ");
        }
    }

    /**
     * Gere la sauvegarde via le bouton de l'interface.
     * Si un chemin de sortie est defini, sauvegarde directement.
     * Sinon, ouvre un selecteur de fichier pour que l'utilisateur choisisse
     * l'emplacement de sauvegarde.
     * La sauvegarde est effectuee dans un thread separe pour ne pas bloquer
     * l'interface graphique.
     */
    public void saveViaBtn() {
        if (outputPath != null) {
            saveAsPIF(outputPath);
            GestionErreur.afficherInfo("Fichier sauvegarde avec succes. Chemin : " + outputPath);
            return;
        }

        // Sinon JFileChooser
        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle("Enregistrer le fichier .pif");

        if (chooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
            File fichierSelectionne = chooser.getSelectedFile();
            ThreadSauvegardePIF thread = new ThreadSauvegardePIF(this, fichierSelectionne);
            thread.start();
        }
    }

    /**
     * Lance le processus complet de conversion d'une image au format PIF.
     * Etapes :
     * 1. Chargement de l'image (depuis un fichier ou via selecteur)
     * 2. Calcul des frequences
     * 3. Generation des codes de Huffman
     * 4. Generation des codes canoniques
     * 5. Sauvegarde (automatique ou via bouton selon les parametres)
     */
    public void conversionProcess() {
        // Chargement
        if (this.inputPath != null) {
            // Chargement direct depuis les arguments
            File file = new File(inputPath);
            if (!file.exists()) {
                GestionErreur.afficherErreur("Le fichier n'existe pas : " + inputPath);
                System.exit(1);
                return;
            }
            this.loadImage(file); 
        } else {
            // Sinon JFileChooser pour choisir l'image
            JFileChooser chooser = new JFileChooser();
            chooser.setDialogTitle("Choisissez une image");
            if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
                this.loadImage(chooser.getSelectedFile());
            } else {
                GestionErreur.afficherErreur("Aucune image choisie. Arret du programme.");
                System.exit(1);
                return;
            }
        }

        // Placer la logique de conversion dans l'ordre
        computeFrequencies();
        computeHuffman();
        computeCanonical();

        // Sauvegarder : si un second argument est donne, sauvegarde automatique
        if (this.outputPath != null) {
            this.saveAsPIF(this.outputPath);
            GestionErreur.afficherInfo("Fichier sauvegarde automatiquement : " + this.outputPath);
        } else {
            // Pas de deuxieme argument : ajouter un bouton pour choisir avec un JFileChooser
            fen.addSaveButton(this);
        }
    }

    /**
     * Retourne l'image actuellement chargee.
     * 
     * @return l'image RGB chargee, ou null si aucune image n'est chargee
     */
    public RGBImage getImage() {
        return this.image;
    }
}