package be.msyben.vinylManager.controller;

import be.msyben.vinylManager.data.Data;
import be.msyben.vinylManager.entities.Album;
import be.msyben.vinylManager.entities.Collection;
import be.msyben.vinylManager.entities.Disque;
import be.msyben.vinylManager.entities.Image;
import be.msyben.vinylManager.exceptions.VinylManagerException;
import be.msyben.vinylManager.gui.TableExportGui;
import be.msyben.vinylManager.pdf.ReportGenerator;
import be.msyben.vinylManager.properties.Language;
import be.msyben.vinylManager.properties.Preferences;
import be.msyben.vinylManager.tools.SerialAndZip;
import be.msyben.vinylManager.tools.Tools;
import be.msyben.vinylManager.tools.importexport.*;
import be.msyben.vinylManager.tools.log.VinylManagerLogger;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Le controller pour les données.
 *
 * @author Mathieu Syben
 */
public class Controller {

    private static final int NBR_RECENT_FILES = 10;
    /**
     * Les données
     */
    private Data data;
    /**
     * Le fichier ouvert
     */
    private File openFile = null;
    /**
     * Indique si la collection en cours a été modifiée
     */
    private boolean modified;

    /**
     * Constructeur
     */
    public Controller() {
        data = Data.getInstance();
    }

    /**
     * Chargement du dernier fichier.
     */
    public void lastFileLoad() {
        VinylManagerLogger.log("Chargement du dernier fichier");
        modified = false;
        if (Preferences.getInstance().get(Preferences.OPEN_LAST_FILE, "N").equals("Y")) {
            String file = Preferences.getInstance().get(Preferences.LAST_OPENED_FILE);
            try {
                open(file);
            } catch (VinylManagerException ex) {
                data.setCollection(new Collection());
            }

        } else {
            data.setCollection(new Collection());
        }
    }

    /**
     * Faire une copie d'un fichier dans un autre avec le même nom + .bak
     *
     * @param f le fichier à copier
     * @param bckName le nom à ajouter pour retrouver le fichier (open ou save).
     */
    private void makeCopy(File f, String bckName) {
        InputStream is = null;
        OutputStream os = null;
        try {
            is = new FileInputStream(f);
            os = new FileOutputStream(f.getAbsolutePath() + "." + bckName + ".bak");

            Tools.copy(is, os);
            os.flush();
        } catch (Exception e) {
            VinylManagerLogger.log(e);
        } finally {
            Tools.closeQuietly(is);
            Tools.closeQuietly(os);
        }
    }

    /**
     * Permet de récupérer le nom que la fenêtre principale doit avoir
     *
     * @return le titre à afficher
     */
    public String getFrameTitle() {
        String title = Language.getInstance().get("main.window.title");
        boolean showNbr = Preferences.getInstance().get(Preferences.SHOW_NBR_ALBUMS, "N").equals("Y");
        if (openFile != null) {
            title += " (" + openFile.getName();
            if (modified) {
                title += "*";
            }
            title += ")";
        } else {
            if (modified) {
                title += "*";
            }
        }

        if (showNbr) {
            title += " (" + data.getCollection().getAlbums().size() + " " + Language.getInstance().get("name.albums").toLowerCase() + ")";
        }

        return title;
    }

    /**
     * Permet de savoir si la collection en cours est modifiée.
     *
     * @return true si la collection a été modifiée, false sinon
     */
    public boolean isModified() {
        return modified;
    }

    /**
     * Indique si la collection est ouverte depuis un fichier (ou nouvelle
     * collection).
     *
     * @return true si la collection vient d'un fichier, false sinon
     */
    public boolean isCollectionOpen() {
        return (openFile != null);
    }

    /**
     * Permet de sauver la collection en cours.
     *
     * @throws VinylManagerException en cas d'erreur de sauvegarde
     */
    public void sauver() throws VinylManagerException {
        // Test : gc avant la sauvegarde pour dimiuer l'utilisation de la mémoire...
        System.gc();
        
        VinylManagerLogger.log("Début de la sauvegarde dans le fichier ouvert " + openFile.getAbsolutePath());
        SerialAndZip.save(data.getCollection(), openFile);
        VinylManagerLogger.log("Fin de la sauvegarde dans le fichier ouvert " + openFile.getAbsolutePath());
        
        makeCopy(openFile, "save");
        VinylManagerLogger.log("Fin de la copie de la sauvegarde");
        modified = false;
    }

    /**
     * Permet de sauver la collection en cours dans un fichier spécifié.
     *
     * @throws VinylManagerException en cas d'erreur de sauvegarde
     */
    public void sauver(File f) throws VinylManagerException {
        // Test : gc avant la sauvegarde pour dimiuer l'utilisation de la mémoire...
        System.gc();
        
        openFile = f;
        VinylManagerLogger.log("Début de la sauvegarde dans " + openFile.getAbsolutePath());
        SerialAndZip.save(data.getCollection(), f);
        VinylManagerLogger.log("Fin de la sauvegarde dans " + openFile.getAbsolutePath());
        makeCopy(openFile, "save");
        VinylManagerLogger.log("Fin de la copie de la sauvegarde");
        modified = false;
        Preferences.getInstance().set(Preferences.LAST_OPENED_FILE, openFile.getAbsolutePath());
        updateLastOpenedFiles(openFile.getAbsolutePath());
    }

    /**
     * Ouvrir un fichier.
     *
     * @param f le fichier à ouvrir
     * @throws VinylManagerException en cas d'erreur
     */
    public void open(String fileName) throws VinylManagerException {
        VinylManagerLogger.log("Ouverture du fichier avec le nom " + fileName);
        if (fileName == null) {
            throw new VinylManagerException(Language.getInstance().get("error.file.not.exists").replace("%1", "null"));
        }
        File f = new File(fileName);
        if (!f.exists() && !f.isFile()) {
            VinylManagerLogger.log("Le fichier n'existe pas " + fileName);
            throw new VinylManagerException(Language.getInstance().get("error.file.not.exists").replace("%1", f.getAbsolutePath()));
        }
        open(f);
    }

    /**
     * Ouvre un fichier.
     *
     * @param f le fichier à ouvrir
     * @throws VinylManagerException en cas d'erreur
     */
    public void open(File f) throws VinylManagerException {
        VinylManagerLogger.log("Ouverture du fichier  " + f.getAbsolutePath());
        Collection c = (Collection) SerialAndZip.load(f);
        VinylManagerLogger.log("Ouverture terminée du fichier  " + f.getAbsolutePath());

        checkCollectionVersion(c);
        checkCollection(c);
        setAlbumsIds(c);

        makeCopy(f, "open");
        modified = false;
        openFile = f;
        Preferences.getInstance().set(Preferences.LAST_OPENED_FILE, openFile.getAbsolutePath());
        updateLastOpenedFiles(openFile.getAbsolutePath());
        data.setCollection(c);
    }

    /**
     * Importe une collection CSV.
     *
     * @param f le fichier à ouvrir
     * @throws VinylManagerException en cas d'erreur
     */
    public void importCollectionCsv(File f) throws VinylManagerException {
        VinylManagerLogger.log("Import du fichier  " + f.getAbsolutePath());
        Collection c = CsvImport.importCsv(f);
        VinylManagerLogger.log("Import terminé du fichier  " + f.getAbsolutePath());
        checkCollection(c);
        modified = true;
        openFile = null;
        Preferences.getInstance().set(Preferences.LAST_OPENED_FILE, "");
        data.setCollection(c);
    }

    /**
     * Importe une collection XML.
     *
     * @param f le fichier à ouvrir
     * @throws VinylManagerException en cas d'erreur
     */
    public void importCollectionXml(File f) throws VinylManagerException {
        VinylManagerLogger.log("Import du fichier  " + f.getAbsolutePath());
        Collection c = XmlImport.importXml(f);
        VinylManagerLogger.log("Import terminé du fichier  " + f.getAbsolutePath());
        checkCollection(c);
        modified = true;
        openFile = null;
        Preferences.getInstance().set(Preferences.LAST_OPENED_FILE, "");
        data.setCollection(c);
    }

    /**
     * Fusionne la collection du fichier avec celle en cours.
     *
     * @param f Le fichier contenant la collection à fusionner.
     * @throws VinylManagerException en cas d'erreur
     */
    public void fusion(File f) throws VinylManagerException {
        VinylManagerLogger.log("Fusion du fichier  " + f.getAbsolutePath());
        Collection toAdd = (Collection) SerialAndZip.load(f);
        Collection orig = data.getCollection();
        if (orig == null) {
            throw new VinylManagerException(Language.getInstance().get("error.fusion.no.open.collection"));
        }
        checkCollectionVersion(toAdd);
        checkCollection(toAdd);
        for (Album a : toAdd.getAlbums()) {
            data.addAlbumWithoutListeners(a);
        }
        VinylManagerLogger.log("Fusion terminée du fichier  " + f.getAbsolutePath());
        setModified();
    }

    /**
     * Supprime un album.
     *
     * @param album l'album
     * @throws VinylManagerException en cas d'erreur
     */
    public void deleteAlbum(Album album) throws VinylManagerException {
        VinylManagerLogger.log("Suppression d'un album " + album);
        modified = true;
        if (album != null) {
            data.deleteAlbum(album);
        } else {
            throw new VinylManagerException(Language.getInstance().get("error.choice.album"));
        }

    }

    /**
     * Supprime un disque.
     *
     * @param album l'album
     * @param disque le disque
     * @throws VinylManagerException en cas d'erreur
     */
    public void deleteDisque(Album album, Disque disque) throws VinylManagerException {
        VinylManagerLogger.log("Ajout d'un disque " + disque + " dans l'album " + album);
        modified = true;
        if (album != null) {
            data.deleteDisc(album, disque);
            disque = null;
        } else {
            throw new VinylManagerException(Language.getInstance().get("error.choice.disque"));
        }
    }

    /**
     * Crée une nouvelle collection.
     */
    public void nouveau() {
        VinylManagerLogger.log("Nouvelle collection.");
        modified = false;
        openFile = null;
        Preferences.getInstance().set(Preferences.LAST_OPENED_FILE, "");
        data.clear();
    }

    /**
     * Ajoute un album.
     *
     * @param a l'album à ajouter
     */
    public void addAlbum(Album a) throws VinylManagerException {
        VinylManagerLogger.log("Ajout d'un album " + a);
        modified = true;
        data.addAlbum(a);
    }

    /**
     * Edition d'un album.
     *
     * @param a l'album édité
     */
    public void editedAlbum(Album a) throws VinylManagerException {
        VinylManagerLogger.log("Edition d'un album " + a);
        modified = true;
        data.editAlbum(a);
    }

    /**
     * Ajout d'un disque.
     *
     * @param album l'album édité
     * @param d Le disque à ajouter
     */
    public void addDisque(Album album, Disque d) {
        VinylManagerLogger.log("Ajout d'un disque " + d + " dans l'album " + album);
        modified = true;
        data.addDisque(album, d);

    }

    /**
     * Edition d'un disque.
     *
     * @param album l'album édité
     * @param disque Le disque édité
     */
    public void editDisque(Album album, Disque disque) {
        VinylManagerLogger.log("Edition d'un disque " + disque + " dans l'album " + album);
        modified = true;
        data.editDisque(album, disque);

    }

    /**
     * Fixe la cote d'un album.
     *
     * @param album l'album
     * @param cote la cote
     */
    public void setCote(Album album, int cote, boolean demi) {
        VinylManagerLogger.log("Set core " + cote + (demi?".5":"") + " pour l'album " + album);
        if (album != null) {
            modified = true;
            data.setAlbumCote(album, cote, demi);
        }
    }

    /**
     * Exporte un fichier en xml
     *
     * @param f le fichier dans lequel la collection doit être exportée
     */
    public void exporterXML(File f) {
        VinylManagerLogger.log("export en XML dans " + f.getAbsolutePath());
        XmlExport.exporterXML(f, data.getCollection());
    }

    /**
     * Exporte un fichier en csv
     *
     * @param f le fichier dans lequel la collection doit être exportée
     */
    public void exporterCSV(File f) {
        VinylManagerLogger.log("export en CSV dans " + f.getAbsolutePath());
        CsvExport.exporterCSV(f, data.getCollection());
    }

    /**
     * Exporte un fichier en vm lite
     *
     * @param f le fichier dans lequel la collection doit être exportée
     */
    public void exporterVmLite(File f) throws VinylManagerException {
        VinylManagerLogger.log("export en VM lite dans " + f.getAbsolutePath());
        VmAndroidExport.exportVmAndroid(f, data.getCollection());
    }

    /**
     * Exporte un fichier en html
     *
     * @param f le répertoire dans lequel la collection doit être exportée
     */
    public void exporterHtml(File f) throws VinylManagerException {
        VinylManagerLogger.log("export en HTML dans " + f.getAbsolutePath());
        HtmlExport.htmlExport(f, data.getCollection());
    }

    /**
     * Exporte en un fichier pdf court
     *
     * @param f le fichier dans lequel le pdf sera généré
     * @throws VinylManagerException en cas d'erreur
     */
    public void exportPdfCourt(File f) throws VinylManagerException {
        VinylManagerLogger.log("export en PDF court dans " + f.getAbsolutePath());
        byte[] contenu = ReportGenerator.genererRapportCourt(data.getCollection());
        InputStream is = null;
        OutputStream os = null;
        try {
            is = new ByteArrayInputStream(contenu);
            os = new FileOutputStream(f);
            Tools.copy(is, os);
        } catch (IOException ex) {
            throw new VinylManagerException(ex);
        } finally {
            Tools.closeQuietly(is);
            Tools.closeQuietly(os);
        }
    }

    /**
     * Exporte en un fichier pdf long
     *
     * @param f le fichier dans lequel le pdf sera généré
     * @throws VinylManagerException en cas d'erreur
     */
    public void exportPdfLong(File f) throws VinylManagerException {
        VinylManagerLogger.log("export en PDF long dans " + f.getAbsolutePath());
        byte[] contenu = ReportGenerator.genererRapportLong(data.getCollection());
        InputStream is = null;
        OutputStream os = null;
        try {
            is = new ByteArrayInputStream(contenu);
            os = new FileOutputStream(f);
            Tools.copy(is, os);
        } catch (IOException ex) {
            throw new VinylManagerException(ex);
        } finally {
            Tools.closeQuietly(is);
            Tools.closeQuietly(os);
        }
    }
    
    public void exportTable(File f, List<TableFormatExportField> fields, boolean csv) throws VinylManagerException {
        if (csv) {
            VinylManagerLogger.log("export en CSV dans " + f.getAbsolutePath());
        } else {
            VinylManagerLogger.log("export en XLSX dans " + f.getAbsolutePath());
        }
        TableFormatExport.exportTable(f, data.getCollection(), fields, csv);
    }

    /**
     * Permet d'indiquer que la collection a été modifiée.
     */
    public void setModified() {
        modified = true;
        data.notifyChange();
    }

    /**
     * Récupère la liste des 10 derniers fichiers ouverts.
     *
     * @return
     */
    public List<String> getLastOpenedFiles() {
        List<String> ret = new ArrayList<String>(NBR_RECENT_FILES);
        for (int i = 1; i <= NBR_RECENT_FILES; i++) {
            String val = Preferences.getInstance().get("file.history." + i);
            if (val == null || val.isEmpty()) {
                return ret;
            }
            ret.add(val);
        }
        return ret;
    }

    private void updateLastOpenedFiles(String lastOpenedFile) {
        List<String> newFiles = new ArrayList<String>(NBR_RECENT_FILES);
        newFiles.add(lastOpenedFile);
        for (String s : getLastOpenedFiles()) {
            if (!(s.equalsIgnoreCase(lastOpenedFile))) {
                newFiles.add(s);
            }
        }
        int i = 1;
        int idx = 0;
        while (idx < newFiles.size() && i <= NBR_RECENT_FILES) {
            Preferences.getInstance().set("file.history." + i, newFiles.get(idx));
            i++;
            idx++;
        }
    }

    /**
     * Met à jour la collection en fonction de sa version.
     *
     * @param c La collection à controler.
     */
    private void checkCollectionVersion(Collection c) throws VinylManagerException {
        if (c.getVersion() == 0) {
            VinylManagerLogger.log("Changement vers version 1");
            change1(c);
        }
        if (c.getVersion() == 1) {
            VinylManagerLogger.log("Changement vers version 2");
            change2(c);
        }
        if (c.getVersion() == 2) {
            VinylManagerLogger.log("Changement vers version 3");
            change3(c);
        }
        if (c.getVersion() == 3 || c.getVersion() == 4) {
            // La version 4 ajoutait l'année de parution mais a été supprimée...
            /*VinylManagerLogger.log("Changement vers version 5");
            change5(c);*/
        }

    }

    /**
     * Effectuer le premier changement sur la collection. Modifie les images
     * pour être de type RGB.
     *
     * @param c La collection à modifier.
     */
    private void change1(Collection c) throws VinylManagerException {
        for (Album a : c.getAlbums()) {
            if (a.getImage() != null) {
                a.getImage().setContenu(Tools.convertToRGB(a.getImage().getContenu()));
            }
        }
        c.setVersion(1);
    }

    /**
     * Effectuer le deuxième changement sur la collection. Deplace vitesse,
     * taille et couleur vers disque.
     *
     * @param c La collection à modifier.
     */
    private void change2(Collection c) throws VinylManagerException {
        for (Album a : c.getAlbums()) {
            if (a != null) {
                for (Disque d : a.getDisques()) {
                    if (d != null) {
                        d.setVitesse(a.getVitesse());
                        d.setTaille(a.getTaille());
                        d.setCouleur(a.getCouleur());
                        a.setVitesse(0);
                        a.setTaille(null);
                        a.setCouleur("");
                    }
                }
            }
        }
        c.setVersion(2);
    }

    /**
     * Effectue le troisième changement. Ajoute l'id aux albums.
     *
     * @param c La collection à modifier.
     */
    private void change3(Collection c) {
        long id = 0;
        for (Album a : c.getAlbums()) {
            id++;
            a.setId(id);
        }
        c.setLastId(id);
        c.setVersion(3);
    }

    /**
     * Vérification de la collection dans un but de debug.
     *
     * @param c La collection.
     */
    private void checkCollection(Collection c) {
        long images = 0;
        int nbr = 0;
        for (Album a : c.getAlbums()) {
            if (a.getImage() != null) {
                images += a.getImage().getContenu().length;
                nbr++;
            }
            if (a.getOtherImages() != null) {
                for (Image i : a.getOtherImages()) {
                    images += i.getContenu().length;
                    nbr++;
                }
            }
        }
        if (nbr > 0) {
            VinylManagerLogger.log(nbr + " images pour une taille de " + images + " --> " + (images/nbr));
        } else {
            VinylManagerLogger.log("Pas d'image");
        }
        
    }

    private void setAlbumsIds(Collection c) {
        long id = 0;
        for (Album a : c.getAlbums()) {
            id++;
            a.setId(id);
        }
        c.setLastId(id);
    }
    
    public Set<String> getDiscogsIds() {
        Set<String> ret = new HashSet<>();
        for (Album a : data.getCollection().getAlbums()) {
            ret.add(a.getDiscogsId());
        }
        return ret;
    }
}
