/*
 * Copyright
 *   2008 axYus - www.axyus.com
 *   2008 L.Meckert - laurent.meckert.com
 *
 * This file is part of XEMELIOS.
 *
 * XEMELIOS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * XEMELIOS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with XEMELIOS; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package fr.gouv.finances.cp.xemelios.plugin.signerfichier;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import java.io.File;
import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter;
import org.apache.log4j.Logger;
import java.util.Enumeration;
import java.util.Collections;

import fr.gouv.finances.dgfip.xemelios.common.config.DocumentModel;
import fr.gouv.finances.dgfip.xemelios.common.config.DocumentsModel;
import fr.gouv.finances.dgfip.xemelios.common.config.Loader;
import java.util.ArrayList;
import java.util.Date;

/**
 *
 * @author laurent.meckert
 */
public class Actions {
    // classe Singleton

    private static Actions _instance = null;
    private File fileToSign = null;
    private File fileToGenerate = null;
    private File xemeliosCertFile = null;
    static Logger logger = Logger.getLogger(Actions.class);
    protected JFileChooser fc;
    IFileSigner signer = null;
    private boolean busy = false;
    private java.awt.Frame parentWindow;
    private DlgKeystore dialogKeystore = null;
    private String xemeliosCertPassword = "xemelios"; // à parametrer
    private String xemeliosCertFilename = "privatekeys";
    /**
     * The System property key for the user home directory.
     */
    private static final String USER_HOME_KEY = "user.home";
    public static final String USER_HOME = getSystemProperty(USER_HOME_KEY);

    private class OptionsPaneStatusReporter implements IStatusReporter {

        OptionsPaneStatusReporter() {
        }

        public void info(String statusMessage, String title) {
            show(statusMessage, title, JOptionPane.PLAIN_MESSAGE);
        }

        public void warning(String statusMessage, String title) {
            show(statusMessage, title, JOptionPane.WARNING_MESSAGE);
        }

        public void error(String statusMessage, String title) {
            show(statusMessage, title, JOptionPane.ERROR_MESSAGE);
        }

        private void show(String statusMessage, String title, int sever) {

            JOptionPane.showMessageDialog(parentWindow, statusMessage,
                    title, sever);

        }
    }
    private OptionsPaneStatusReporter statusReporter = null;

    private Actions() {
        _instance = this;
        statusReporter = new OptionsPaneStatusReporter();
        initCertFile();
        initSigner();
    }

    //-----------------------------------------------------------------------
    /**
     * <p>Gets a System property, defaulting to <code>null</code> if the property
     * cannot be read.</p>
     *
     * <p>If a <code>SecurityException</code> is caught, the return
     * value is <code>null</code> and a message is written to <code>System.err</code>.</p>
     *
     * @param property the system property name
     * @return the system property value or <code>null</code> if a security problem occurs
     */
    private static String getSystemProperty(String property) {
        try {
            return System.getProperty(property);
        } catch (SecurityException ex) {
            // we are not allowed to look at this property
            System.err.println(
                    "Caught a SecurityException reading the system property '" + property + "'; the SystemUtils property value will default to null.");
            return null;
        }
    }

    /**
     *
     * @return la seule instance de la classe
     */
    public static Actions getInstance() {
        if (_instance == null) {
            _instance = new Actions();
        }
        return _instance;
    }

    public void dispose() {
        if (signer != null) {
            signer.dispose();
            signer = null;
        }
        if (dialogKeystore != null) {
            dialogKeystore.dispose();
            dialogKeystore = null;
        }

    }

    /**
     *
     */
    public void ShowInputFileSelector() {
        if (getFileToSign() != null) {
            fc = new JFileChooser(getFileToSign());
        } else {
            fc = new JFileChooser(".");
        }
        fc.addChoosableFileFilter(new FileFilter() {

            public boolean accept(File f) {
                return f.isDirectory() || f.getName().toUpperCase().endsWith("XML");
            }

            public String getDescription() {
                return "Document XML";
            }
        });
        // Show open dialog; this method does not return until the dialog
        // is closed
        fc.showOpenDialog(null);
        setFileToSign(fc.getSelectedFile());

    }

    /**
     * Selection d'un fichier contenant des certificats
     * @return Selected certificate File
     */
    public File SelectCertFile() {
        if (getXemeliosCertFile() != null) {
            fc = new JFileChooser(getXemeliosCertFile());
        } else {
            fc = new JFileChooser(".");
        }
        fc.addChoosableFileFilter(new FileFilter() {

            public boolean accept(File f) {
                return f.isDirectory() || f.getName().toUpperCase().endsWith("KS") || f.getName().toUpperCase().endsWith("JKS") || f.getName().toUpperCase().endsWith("JCE") || f.getName().toUpperCase().endsWith("P12") || f.getName().toUpperCase().endsWith("PFX");
            }

            public String getDescription() {
                return "Magasins de clés (.ks, .jks, .jce, .p12, .pks)";
            }
        });
        // Show open dialog; this method does not return until the dialog is closed
        fc.showOpenDialog(null);
        return fc.getSelectedFile();
    }

    /**
     * Montre un sélecteur de fichiers de certificats (keystores)
     *
     */
    public void ShowCertFileSelector() {
        if (getXemeliosCertFile() != null) {
            fc = new JFileChooser(getXemeliosCertFile());
        } else {
            fc = new JFileChooser(".");
        }
        fc.addChoosableFileFilter(new FileFilter() {

            public boolean accept(File f) {
                return f.isDirectory() || f.getName().toUpperCase().endsWith("KS") || f.getName().toUpperCase().endsWith("JKS") || f.getName().toUpperCase().endsWith("JCE") || f.getName().toUpperCase().endsWith("P12") || f.getName().toUpperCase().endsWith("PFX");
            }

            public String getDescription() {
                return "Magasins de clés (.ks, .jks, .jce, .p12, .pks)";
            }
        });
        // Show open dialog; this method does not return until the dialog is closed
        fc.showOpenDialog(null);
        setXemeliosCertFile(fc.getSelectedFile());
    }

    public void fillFromFile(File f) throws Exception {
        fillFromFile(f, null);
    }

    public void fillFromFile(File f, String forcedPassword) throws Exception {
        KeystoreFiller ksf = null;

        if (xemeliosCertFile.exists()) {
            ksf = new KeystoreFiller(xemeliosCertFile, xemeliosCertPassword);
        } else {
            ksf = new KeystoreFiller();
        }
        ksf.setStatusReporter(statusReporter);
        if (f == null) {
            f = SelectCertFile();
        }
        if (f != null && f.exists()) {

            //  saisie du password
            DlgKeystore tmpDlgK = new DlgKeystore(this.parentWindow, true);
            tmpDlgK.setValider(ksf);
            ksf.setDlgKeystore(tmpDlgK);
            ksf.setKeystoreFile(f);
            ksf.setKeystorePassword(xemeliosCertPassword);// le meme par defaut; dera redemande
            tmpDlgK.setMode(DlgKeystore.MODE_KEYSTORE);
            openCertFile(f, tmpDlgK, ksf);

            if (ksf.loadKsOK()) {
                ksf.importAliases(forcedPassword);
                ksf.saveKeystore(xemeliosCertFile, xemeliosCertPassword);
            }

        }
    }

    /**
     * Remplit a partir du magasin de certificats Windows
     *
     * @param parent fenetre parent passee a jna pour les
     * boites de dialogues venant de DLL native Windows
     * @param tous couramment toujours false;
     *         true importe tous les certificats Windows
     *         false importe un seul certificat choisi
     * @throws java.lang.Exception
     */
    public void fillFromWindows(java.awt.Window parent, boolean tous) throws Exception {
        W32CryptExporter wce = new W32CryptExporter(parent);
        if (tous) {
            wce.exportAll(xemeliosCertPassword);
        } else {
            wce.exportSelected(xemeliosCertPassword);
        }
        if (wce.getGeneratedFiles() != null) {
            logger.info("wce.getGeneratedFiles().size()="+wce.getGeneratedFiles().size());
            for (File f : wce.getGeneratedFiles()) {
                DlgDefinePassword dlgDefinePassword = new DlgDefinePassword(parentWindow, true);
                dlgDefinePassword.setVisible(true);

                fillFromFile(f, dlgDefinePassword.getPassword());
                f.delete();
            }
        }
    }

    /**
     *
     */
    public void ShowOutputFileSelector() {
        // Show save dialog; this method does not return until the dialog is closed
        if (getFileToGenerate() != null) {
            fc = new JFileChooser(getFileToGenerate());
        } else {
            fc = new JFileChooser(".");
        }
        fc.addChoosableFileFilter(new FileFilter() {

            public boolean accept(File f) {
                return f.isDirectory() || f.getName().toUpperCase().endsWith("XML");
            }

            public String getDescription() {
                return "Document XML";
            }
        });
        fc.showSaveDialog(null);
        setFileToGenerate(fc.getSelectedFile());

        if (getFileToGenerate() == null) {
            return;
        }
        if (!fileToGenerate.getName().toUpperCase().endsWith("XML")) {
            setFileToGenerate(new File(getFileToGenerate().getPath() + ".xml"));
        }
        // possibilite d'ecraser ; demander si OK
        if (fileToGenerate.exists()) {
            int response = JOptionPane.showConfirmDialog(parentWindow, new String[]{
                        /* first line of the message */"Le fichier " + fileToGenerate.getName(),
                        /* second line of message    */ "existe et sera écrasé"},
                    /* dialog title              */ "Remplacer le fichier?",
                    /* what buttons to display   */ JOptionPane.YES_NO_OPTION,
                    /* icon type to display      */ JOptionPane.WARNING_MESSAGE);
            switch (response) {
                case JOptionPane.YES_OPTION:
                    return;
                case JOptionPane.NO_OPTION:
                    setFileToGenerate(null);
                    return;
                case JOptionPane.CLOSED_OPTION:
                    setFileToGenerate(null);
                    return;
            }
        }



    }

    /* Version simplifiee pour projet NetBeans
    protected void initSigner() {
    FileSignerFactory fsf = FileSignerFactory.newInstance();
    fsf.setAttribute(FileSignerFactory.USE_APACHEXMLSEC, true);
    //   fsf.setNsCtx(docModel.getNamespaces());

    try {
    signer = fsf.newFileSigner();
    // exmpel  signer.addIntersectFilter("//n:Bordereau");
    } catch (Exception e) {
    e.printStackTrace();
    }
    }  */
    protected void initCertFile() {
        // On recupere le fichier contenant les certificats
        // dans le sous repertoire xemelios du home de l'utilisateur

        if (USER_HOME != null) {
            File certDir = new File(USER_HOME, "xemelios");
            if (!certDir.exists()) {
                certDir.mkdirs();
            }
            setXemeliosCertFile(new File(certDir, xemeliosCertFilename));
        }
    }

    /* full version pour xemelios */
    protected void initSigner() {

        //later private TraitementExterneModel texModel = null;
        DocumentModel docModel = null;
        File modelBaseDirectory;
        DocumentsModel docModels = null;
        //docModels = MainWindow.instance.getDocModels();
        try {
            docModels = Loader.getDocumentsInfos(null);
            docModel = docModels.getDocumentById("PES_Aller");
            logger.info(docModel);
            modelBaseDirectory = new File(docModel.getBaseDirectory());
            // logger.info(docModel);
            // later texModel = docModel.getTraitementExterneById("signer-fichier-pes");

            FileSignerFactory fsf = FileSignerFactory.newInstance();
            fsf.setAttribute(FileSignerFactory.USE_APACHEXMLSEC, true);
            fsf.setNsCtx(docModel.getNamespaces());

            signer = fsf.newFileSigner();
        // exmpel  signer.addIntersectFilter("//n:Bordereau");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *
     */
    public void chooseInputFile() {
        ShowInputFileSelector();
        if (getFileToSign() == null) {
            return;
        }
        signer.setInputFile(getFileToSign());
        try {
            setBusy(true);
            signer.loadInputFile();
        } catch (Exception e) {
            e.printStackTrace();
        } catch (Error e) {
            e.printStackTrace();
        } finally {
            setBusy(false);
        }
        if (!signer.parseOK()) {
            showStatus();
        } else {
            //jButtonCertFile.setEnabled(true);
        }
    }

    public void openCertFile() {
        if (dialogKeystore == null) {
            JFrame frame = new JFrame("test");
            dialogKeystore = new DlgKeystore(frame, true);
        }
        signer.setKeystorePassword(xemeliosCertPassword);
        signer.setKeystoreFile(xemeliosCertFile);
        openCertFile(null, dialogKeystore, signer);
    }

    public void openCertFile(File someCertFile, DlgKeystore dlgKeystore, IKeystoreValider valider) {
        if (someCertFile == null) {
            if (!getXemeliosCertFile().exists()) {
                return;
            }
            someCertFile = getXemeliosCertFile();

        }
        if (valider == null) {

            valider = signer;
        }

        // maintenant on connait le mot de passe
        // on peut charger le fichier et presenter
        //la liste des alias
        try {

            valider.loadKeystore();
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (!valider.loadKsOK()) {

            if (dlgKeystore == null) {
                JFrame frame = new JFrame("test");
                dlgKeystore = new DlgKeystore(frame, true);
            }

            dlgKeystore.setValider(valider);
            dlgKeystore.setTitle("Ouverture du fichier " + someCertFile.getName());
            try {
                dlgKeystore.setMode(DlgKeystore.MODE_KEYSTORE);
            } catch (Exception e) {
                e.printStackTrace();
            }
            dlgKeystore.setVisible(true);
        }
    }

    /**
     * Appele un selecteur de fichier pour le fichier a generer
     */
    public void chooseOutputFile() {
        ShowOutputFileSelector();
        if (getFileToGenerate() == null) {
            return;
        }


        signer.setOutputFile(getFileToGenerate());
    }

    private class AliasComparator implements java.util.Comparator {

        public AliasComparator() {
        }

        public int compare(Object o1, Object o2) {
            //    Compares its two arguments for order.
            String a1 = getIssuedFor((String) o1);
            String a2 = getIssuedFor((String) o2);
            return a1.compareToIgnoreCase(a2);

        }

        public boolean equals(Object obj) {
            //Indicates whether some other object is "equal to" this Comparator.

            return false;

        }
    }

    /**
     *
     * @return liste triee des alias disponibles
     * @throws java.lang.Exception
     */
    public Enumeration getAliases() throws Exception {
        if (signer != null && signer.loadKsOK()) {
            //return signer.getAliases(); c'est qd meme mieux de trier
            ArrayList list = java.util.Collections.list(signer.getAliases());

            Collections.sort(list, new AliasComparator());

            return Collections.enumeration(list);


        } else {
            return null;
        }
    }

    /**
     *
     * @param alias
     * @return identite pour qui le certificat est emis
     */
    public String getIssuedFor(String alias) {
        if (signer != null && signer.loadKsOK()) {
            return signer.getIssuedFor(alias);
        } else {
            return "Echec Readable form de " + alias;
        }
    }

    /**
     *
     * @param alias
     * @return  texte avec details certificat et cles
     */
    public String getDetails(String alias) {
        if (signer != null && signer.loadKsOK()) {
            return signer.getDetails(alias);
        } else {
            return "Echec Readable form de " + alias;
        }
    }

    public String getNomBonhomme(String alias) {
        if (signer != null && signer.loadKsOK()) {
            return signer.getNomBonhomme(alias);
        } else {
            return "Echec Readable form de " + alias;
        }
    }

    public String getEmetteur(String alias) {
        if (signer != null && signer.loadKsOK()) {
            return signer.getEmetteur(alias);
        } else {
            return "Echec Readable form de " + alias;
        }
    }

    public Date getDateDebut(String alias) {
        if (signer != null && signer.loadKsOK()) {
            return signer.getDebutValidite(alias);
        } else {
            return null;
        }
    }

    public Date getDateFin(String alias) {
        if (signer != null && signer.loadKsOK()) {
            return signer.getFinValidite(alias);
        } else {
            return null;
        }
    }

    public boolean loadAlias(Object o) {

        if (signer != null) {
            try {
                signer.setAlias((String) (o));
                signer.setAliasPassword(signer.getKeystorePassword());
                // Premier essai avec le mot de passe generique du fichier
                signer.loadAlias();

                // Si ça ne marche pas on demande le mdp specifique de la cle
                if (!signer.loadPkOK()) {

                    if (dialogKeystore == null) {
                        JFrame frame = new JFrame("test");
                        dialogKeystore = new DlgKeystore(frame, true);
                    }
                    dialogKeystore.setValider(signer);
                    dialogKeystore.setTitle("Ouverture de la cle privée  " + signer.getAlias());
                    dialogKeystore.setMode(DlgKeystore.MODE_ALIAS);
                    dialogKeystore.setVisible(true);
                }
                return signer.loadPkOK();


            } catch (Exception e) {
                return false;
            }
        } else {
            return false;
        }
    }

    public String getAliasName() {
        return signer.getAlias();
    }

    public File getFileToSign() {
        return fileToSign;
    }

    public void setFileToSign(File fileToSign) {
        this.fileToSign = fileToSign;
        File parent = fileToSign.getParentFile();
        String fileName = fileToSign.getName();
        String newFileName = fileName.substring(0, fileName.lastIndexOf('.')) + ".signe.xml";
        setFileToGenerate(new File(parent, newFileName));
        signer.setOutputFile(getFileToGenerate());
    }

    public String getSignStatusString() {
        String bla;
        if (signer != null) {
            bla = signer.getStatusAsString();
        } else {
            bla = "Une erreur inattendue est survenue; veuillez consulter le log de l'application";
        }
        return bla;
    }

    /**
     * Montre le résultat de la vérification
     *
     */
    public void showStatus() {
        String bla;
        int sever;
        if (signer != null) {
            bla = signer.getStatusAsString();
            sever = signer.getStatusAsSeverity();
        } else {
            sever = FileSigner.SIGN_VERYBAD;
            bla = "Une erreur inattendue est survenue; veuillez consulter le log de l'application";
        }

        JOptionPane.showMessageDialog(getParentWindow(), bla,
                "Résultat de la vérification",
                sever == FileSigner.SIGN_VERYBAD ? JOptionPane.ERROR_MESSAGE : (sever == FileSigner.SIGN_BAD ? JOptionPane.WARNING_MESSAGE : JOptionPane.PLAIN_MESSAGE));
    }

    /**
     *
     * @return etat courant (true/false si opération en cours)
     */
    public boolean isBusy() {
        return this.busy;
    }

    void setBusy(boolean busy) {
        // only set if changing
        if (this.busy != busy) {
            this.busy = busy;
        }
    }

    /**
     *
     * @return febetre parent
     */
    public java.awt.Frame getParentWindow() {
        return parentWindow;
    }

    /**
     *
     * @param parentWindow
     */
    public void setParentWindow(java.awt.Frame parentWindow) {
        this.parentWindow = parentWindow;
    }

    /**
     *
     * @return
     */
    public File getXemeliosCertFile() {
        return xemeliosCertFile;
    }

    /**
     *
     * @param certFile
     */
    public void setXemeliosCertFile(File certFile) {
        this.xemeliosCertFile = certFile;
    }

    /**
     *
     * @return
     */
    public File getFileToGenerate() {
        return fileToGenerate;
    }

    /**
     *
     * @param fileToGenerate
     */
    public void setFileToGenerate(File fileToGenerate) {
        this.fileToGenerate = fileToGenerate;
    }

    /**
     *
     * @throws java.lang.Exception
     */
    public void doSign() throws Exception {
        signer.sign();
    }
}
