package be.msyben.vinylManager.tools;

import be.msyben.vinylManager.exceptions.VinylManagerException;
import be.msyben.vinylManager.properties.Language;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * Classe pour sérialiser et zipper un objet dans un fichier.
 *
 * @author msyben
 */
public class SerialAndZip {

    /**
     * La taille du buffer.
     */
    private static final int BUFFER = 2048;

    /**
     * Sauve un objet sous forme serialisée et zippée dans un fichier
     *
     * @param o l'objet à sérialiser
     * @param f le fichier dans lequel l'objet sera sérialisé
     * @throws VinylManagerException en cas d'erreur
     */
    public static void save(Object o, File f) throws VinylManagerException {

        
        ByteArrayOutputStream baos = null;
        ObjectOutputStream oos = null;
        FileOutputStream dest = null;
        CheckedOutputStream checksum = null;
        BufferedOutputStream buff = null;
        ZipOutputStream out = null;
        ByteArrayInputStream fi = null;
        BufferedInputStream buffi = null;
        try {
            // SERIAL
            baos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(baos);
            oos.writeObject(o);
            oos.flush();
            baos.flush();

            // ZIP
            // création d'un flux d'écriture sur fichier
            dest = new FileOutputStream(f.getAbsolutePath());

            // calcul du checksum : Adler32 (plus rapide) ou CRC32
            checksum = new CheckedOutputStream(dest, new CRC32());

            // création d'un buffer d'écriture
            buff = new BufferedOutputStream(checksum);

            // création d'un flux d'écriture Zip
            out = new ZipOutputStream(buff);

            // spécification de la méthode de compression
            out.setMethod(ZipOutputStream.DEFLATED);

            // spécifier la qualité de la compression 0..9
            out.setLevel(Deflater.BEST_COMPRESSION);

            // buffer temporaire des données à écriture dans le flux de sortie
            byte data[] = new byte[BUFFER];

            // création d'un flux de lecture
            fi = new ByteArrayInputStream(baos.toByteArray());

            // création d'un tampon de lecture sur ce flux
            buffi = new BufferedInputStream(fi, BUFFER);

            // création d'en entrée Zip pour ce fichier
            ZipEntry entry = new ZipEntry("temp.file");

            // ajout de cette entrée dans le flux d'écriture de l'archive Zip
            out.putNextEntry(entry);

            // écriture du fichier par paquet de BUFFER octets
            // dans le flux d'écriture
            int count;
            while ((count = buffi.read(data, 0, BUFFER)) != -1) {
                out.write(data, 0, count);
            }

            // Close the current entry
            out.closeEntry();
        } catch (IOException ex) {
            throw new VinylManagerException(Language.getInstance().get("error.save.message"), ex);
        } catch (Exception ex) {
            throw new VinylManagerException(Language.getInstance().get("error.save.message"), ex);
        } finally {
            //fermeture des flux
            Tools.closeQuietly(buffi);
            Tools.closeQuietly(fi);
            Tools.closeQuietly(out);
            Tools.closeQuietly(buff);
            Tools.closeQuietly(checksum);
            Tools.closeQuietly(dest);
            Tools.closeQuietly(oos);
            Tools.closeQuietly(baos);
        }

    }

    /**
     * Charge un fichier contenant un objet sérialisé et le retourne
     *
     * @param f le fichier à charger
     * @return l'objet dézippé et désérialisé
     * @throws VinylManagerException en cas d'erreur
     */
    public static Object load(File f) throws VinylManagerException {
        BufferedOutputStream dest = null;
        FileInputStream fis = null;
        BufferedInputStream buffi = null;
        ZipInputStream zis = null;
        ByteArrayOutputStream baos = null;
        InputStream is = null;
        ObjectInputStream ois = null;
        try {
            Object ret = null;

            // fichier destination
            dest = null;

            // ouverture fichier entrée
            fis = new FileInputStream(f.getAbsolutePath());

            // ouverture fichier de buffer
            buffi = new BufferedInputStream(fis);

            // ouverture archive Zip d'entrée
            zis = new ZipInputStream(buffi);

            // entrée Zip
            ZipEntry entry = zis.getNextEntry();

            int count;
            byte data[] = new byte[BUFFER];

            // création ByteArrayOutputStream
            baos = new ByteArrayOutputStream();

            // affectation buffer de sortie
            dest = new BufferedOutputStream(baos, BUFFER);

            // écriture sur disque
            while ((count = zis.read(data, 0, BUFFER)) != -1) {
                dest.write(data, 0, count);
            }

            // vidage du tampon
            dest.flush();
            
            // Recuperation
            is = new ByteArrayInputStream(baos.toByteArray());
            ois = new ObjectInputStream(is);
            ret = ois.readObject();

            return ret;
        } catch (IOException ex) {
            throw new VinylManagerException(Language.getInstance().get("error.open.message"), ex);
        } catch (ClassNotFoundException ex) {
            throw new VinylManagerException(Language.getInstance().get("error.open.message"), ex);
        } catch (Exception ex) {
            throw new VinylManagerException(Language.getInstance().get("error.open.message"), ex);
        } finally {
            Tools.closeQuietly(ois);
            Tools.closeQuietly(is);
            Tools.closeQuietly(baos);
            Tools.closeQuietly(zis);
            Tools.closeQuietly(buffi);
            Tools.closeQuietly(fis);
            Tools.closeQuietly(dest);
        }
    }
}
