package fr.iutfbleau.sae.mpif;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;

/**
 * Cette classe permet d'écrire une image au format PIF.
 * Le fichier PIF contient une en-tête avec la taille de l'image,
 * les longueurs des codes canoniques, puis l'encodage des pixels
 * en utilisant ces codes.
 *
 * Les méthodes de cette classe suivent l'ordre d'écriture du fichier :
 * 1) écrire l'en-tête
 * 2) écrire les tables de longueurs
 * 3) encoder les pixels un par un
 */
public class PIFWriter {

    /**
     * Sauvegarde une image au format PIF dans un fichier.
     * Le fichier est créé ou écrasé si il existe déjà.
     *
     * @param filepath chemin du fichier à écrire
     * @param image l'image RGB à encoder
     * @param canonR codes canoniques de la composante rouge
     * @param canonG codes canoniques de la composante verte
     * @param canonB codes canoniques de la composante bleue
     * @throws Exception si une erreur survient dans l'écriture
     */
    public void writeTOFile(String filepath, RGBImage image, 
            Map<Integer,String> canonR,
            Map<Integer,String> canonG,
            Map<Integer,String> canonB)
            throws Exception {
        
        // Création du flux de sortie binaire
        FileOutputStream fos = new FileOutputStream(filepath);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        BitOutputStream ecriveur = new BitOutputStream(bos);

        // Écriture de l'en-tête
        writeHeader(ecriveur, image.getWidth(), image.getHeight());

        // Écriture des tables de longueurs des codes canoniques
        writeTables(ecriveur, canonR, canonG, canonB);

        // Ecriture des pixels
        encodePixels(ecriveur, image, canonR, canonG, canonB);

        ecriveur.fermerFlux();
    }

    /**
     * Écrit l'en-tête du fichier PIF.  
     * L'en-tête contient uniquement la largeur et la hauteur,
     * chacune codée sur 16 bits.
     *
     * @param out flux de sortie binaire
     * @param width largeur de l'image
     * @param height hauteur de l'image
     */
    public void writeHeader(BitOutputStream out, int width, int height) {
        try {
            out.writeBits(width, 16);  
            out.writeBits(height, 16); 
        } catch (IOException e) {
            System.err.println("Erreur lors de l'écriture de l'en-tête du fichier PIF: " + e.getMessage());
        }
    }

    /**
     * Écrit dans le fichier les longueurs des codes canoniques.
     * Pour chaque composante (R, G, B), on parcourt les 256 valeurs possibles.
     * Si un symbole n'existe pas dans les codes, on écrit une longueur 0.
     *
     * @param out flux binaire vers le fichier
     * @param canonR longueurs pour la composante rouge
     * @param canonG longueurs pour la composante verte
     * @param canonB longueurs pour la composante bleue
     */
    public void writeTables(BitOutputStream out, Map<Integer, String> canonR, 
        Map<Integer, String> canonG, Map<Integer, String> canonB) {

        try {
            // Écriture des longueurs des codes canoniques pour chaque composante
            for (int i = 0; i < 256; i++) {
                int len;
                if (canonR.containsKey(i)) {
                    len = canonR.get(i).length();  
                } else {
                    len = 0;
                }
                out.writeBits(len, 8); 
            }
            
            for (int i = 0; i < 256; i++) {
                int len;
                if (canonG.containsKey(i)) {
                    len = canonG.get(i).length();  
                } else {
                    len = 0;
                }
                out.writeBits(len, 8);  
            }
            
            for (int i = 0; i < 256; i++) {
                int len;
                if (canonB.containsKey(i)) {
                    len = canonB.get(i).length();  
                } else {
                    len = 0;
                }
                out.writeBits(len, 8);
            }
        } catch (IOException e) {
            System.err.println("Erreur lors de l'écriture des tables de fréquences dans le fichier PIF");
        }
    }

    /**
     * Encode chaque pixel de l'image en utilisant les codes canoniques.
     * Pour chaque pixel, les composants R, G et B sont écrits
     * dans cet ordre en utilisant writeBitString().
     *
     * @param out flux d'écriture binaire
     * @param image l'image source
     * @param canonRED codes canoniques pour le rouge
     * @param canonGREEN codes canoniques pour le vert
     * @param canonBLUE codes canoniques pour le bleu
     */
    public void encodePixels(BitOutputStream out, RGBImage image, 
            Map<Integer, String> canonRED, 
            Map<Integer, String> canonGREEN, 
            Map<Integer, String> canonBLUE) {
        
        int width = image.getWidth();
        int height = image.getHeight();
        
        try {
            for (int y = 0; y < height; y++) {
                for (int x = 0; x < width; x++) {
                    // Récupérer le pixel une seule fois pour optimiser
                    Pixel pixel = image.getPixel(x, y);
                    int r = pixel.getR();
                    int g = pixel.getG();
                    int b = pixel.getB();

                    // OPTIMISATION : Utiliser writeBitString() directement
                    out.writeBitString(canonRED.get(r));
                    out.writeBitString(canonGREEN.get(g));
                    out.writeBitString(canonBLUE.get(b));
                }
            }
        } catch (IOException e) {
            System.err.println("Erreur lors de l'écriture des pixels dans le fichier PIF");
        }
    }
}