/*
 * Copyright
 *   2007 axYus - www.axyus.com
 *   2008 L.Meckert - laurent.meckert@axyus.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 Lesser General Public License as published by
 * the Free Software Foundation; either version 3 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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.signature;

import java.io.File;
//import java.io.FileInputStream;
import java.io.StringWriter;
import javax.xml.transform.stream.StreamResult;

import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMSource;


import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import java.security.PublicKey;
import java.security.cert.X509Certificate;

import javax.xml.namespace.NamespaceContext;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.utils.Constants;
import org.apache.xml.security.utils.XMLUtils;
//import org.apache.xpath.XPathAPI;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;

/**
 *   classe pour encapsuler la verification d'une signature
 *  Implementation avec la librarie Apache Xml Security (d'ou le prefixe APXS)
 */
public class APXSGlobalSignatureVerifier extends GlobalSignatureVerifier {

    protected org.w3c.dom.Document doc;
    protected Element nscontext;
    protected Element sigElement;
    private NamespaceContext nsCtx;

    /**
     *  Constructeur sans parametre
     */
    public APXSGlobalSignatureVerifier(NamespaceContext nsCtx) {
        this.nsCtx=nsCtx;
    }

    /**
     *  Appelle au besoin les initialisations necessaires
     * @throws java.lang.Exception
     */
    public void initialize() throws Exception {
    }

    /**
     *  Verifie la signature de la chaine Xml passee en parametre
     *  le statut complet ( de type bitmask)  sera accessible par getStatus()
     *  si succes = la signature proprement dite sera recuperable
     *  en appelant getSignatureAsXmlString()
     *@return  true si le digest est OK false sinon
     *@throws java.lang.Exception
     * @param s une chaine xml signee
     */
    public boolean verifyXml(String s) throws Exception {
        return false;

    }

    protected String nodeAsString(Element element) {
        try {
            DOMSource domSource = new DOMSource(element);
            StringWriter writer = new StringWriter();
            StreamResult result = new StreamResult(writer);
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            transformer.transform(domSource, result);
            return writer.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

    /**
     * Verifie la signature du fichier passee en parametre
     *@return  true si le digest est OK false sinon
     *@throws java.lang.Exception
     *@param signatureFileName le fichier a verifier
     *
     */
    public boolean verifyFile(String signatureFileName) throws Exception {
        boolean isSignatureValid = false;

        javax.xml.parsers.DocumentBuilderFactory dbf =
                javax.xml.parsers.DocumentBuilderFactory.newInstance();

        if (doSchemaValidation) {
            dbf.setAttribute("http://apache.org/xml/features/validation/schema",
                    Boolean.TRUE);
            dbf.setAttribute(
                    "http://apache.org/xml/features/dom/defer-node-expansion",
                    Boolean.TRUE);
            dbf.setValidating(true);
            dbf.setAttribute("http://xml.org/sax/features/validation",
                    Boolean.TRUE);
        }

        dbf.setNamespaceAware(true);
        dbf.setAttribute("http://xml.org/sax/features/namespaces", Boolean.TRUE);


        File f = new File(signatureFileName);

        System.out.println("Try to verify " + f.toURL().toString());

        javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder();

        //Set the error handler
        Validator handler = new Validator();
        db.setErrorHandler(handler);



        doc = db.parse(new java.io.FileInputStream(f));


        if (doc != null) {
            setParseOK(true);
        }

        if (doSchemaValidation) {
            setSchemaOK(!handler.validationError);
        }
// TODO: utiliser la définition des namespaces située dans la config du document
        nscontext = XMLUtils.createDSctx(doc, "ds",
                Constants.SignatureSpecNS);
        // Ici on devra peut etre doute avoir le xpath precis de la signature
        // Par defaut je boucle sur touts les signatures (xpath="//ds:Signature")cela dit la premiere
        // version part du principe qu'on rencontrera  juste une signature globale
        // et les bordereaux ne seront pas signes
// TODO: ne pas utiliser les API xalan directement, car Xalan ne sera pas utilisé
//        NodeList nl = XPathAPI.selectNodeList(doc, getSignatureXPath(), nscontext);
        XPathFactory xpf = XPathFactory.newInstance();
        XPath xp = xpf.newXPath();
        xp.setNamespaceContext(nsCtx);
        NodeList nl = (NodeList)xp.evaluate(getSignatureXPath(),doc,XPathConstants.NODESET);

        if (nl != null && nl.getLength() > 0) {
            logger.info("" + nl.getLength() + " signature(s) detectee(s))");
            setDigestValid(true);
            for (int i = 0; i < nl.getLength(); ++i) {

                //   ancienne version sans boucle  sigElement = (Element) XPathAPI.selectSingleNode(doc,
                //    "//ds:Signature[1]", nscontext);

                sigElement = (Element) nl.item(i);

                if (sigElement != null) {
                    setSignatureFound(true);
                    incrFoundSignatures();
                }

                XMLSignature signature = new XMLSignature(sigElement,
                        f.toURL().toString());

                //  XMLUtils.outputDOMc14nWithComments(doc, System.out);


                addSignatureAsXmlString(nodeAsString(sigElement));

                //System.out.println(nodeAsString(doc.getDocumentElement()));

                // APXSGSVDebug.debugDoc(doc);
                // XMLUtils.outputDOMc14nWithComments(signature.getElement(), System.out);
                KeyInfo ki = signature.getKeyInfo();

                if (ki != null) {
                    if (ki.containsX509Data()) {
                        System.out.println("Could find a X509Data element in the KeyInfo");
                    }

                    X509Certificate certif = signature.getKeyInfo().getX509Certificate();

                    addCert(certif); // on les ajoute meme null pour que le tableau
                    // des certificats soit homogene a celiui des signatures
                    if (certif != null) {
                        setIsX509Cert(true);
                        
                        //    System.out.println(
                        //    "I try to verify the signature using the X509 Certificate: "
                        //    + cert);
                        isSignatureValid = signature.checkSignatureValue(certif); //
//                        System.out.println("The XML signature in file " + f.toURL().toString() + " is " + (isSignatureValid
//                                ? "valid (good)"
//                                : "invalid !!!!! (bad)"));
                        if (isSignatureValid) {
                            incrValidatedSignatures();
                        } else {
                            setDigestValid(false);
                        }
                    } else {
                        System.out.println("Did not find a Certificate");
                        addCert(null);
                        PublicKey pk = signature.getKeyInfo().getPublicKey();

                        if (pk != null) {

                            isSignatureValid = signature.checkSignatureValue(pk); //
                    
                            if (isSignatureValid) {
                                incrValidatedSignatures();
                            } else {
                                setDigestValid(false);
                            }
                        } else {
                            System.out.println(
                                    "Did not find a public key, so I can't check the signature");
                            setDigestValid(false);
                        }
                    }
                } else {
                    System.out.println("Did not find a KeyInfo");
                }
            }
        }
        if (!signatureFound()) {
            setDigestValid(false);
        }
        return digestValid();
    }

    private class Validator extends DefaultHandler {

        public boolean validationError = false;
        public SAXParseException saxParseException = null;

        @Override
        public void error(SAXParseException exception)
                throws SAXException {
            System.err.println(exception);
            logger.error(exception);
            validationError = true;
            saxParseException = exception;
        }

        @Override
        public void fatalError(SAXParseException exception)
                throws SAXException {
            System.err.println(exception);
            logger.error(exception);
            //  exception.print();
            validationError = true;
            saxParseException = exception;
        }

        @Override
        public void warning(SAXParseException exception)
                throws SAXException {
        }
    }

    /**
     * Procede au besoin aux nettoyages apres execution
     * @throws java.lang.Exception
     */
    public void terminate() throws Exception {

    }

    static {
        org.apache.xml.security.Init.init();
    }

    /**
     *
     * @param args
     */
//    public static void main(String args[]) {
//        // check args
//        String fileToVerify = null;
//        String optionalDTD = null;
//        if (args.length > 0) {
//            fileToVerify = args[0];
//        } else {
//
//            System.err.println("Usage java APXSGlobalSignatureVerifier <fichier_a_verifier>");
//            System.exit(1);
//        }
//
//        APXSGlobalSignatureVerifier verifier = new APXSGlobalSignatureVerifier(nsCtx);
//
//        try {
//            verifier.initialize();
//            verifier.verifyFile(fileToVerify);
//            for (int i = 0; i < verifier.getCertVectorSize(); ++i) {
//                X509Certificate cert = verifier.getCert(i);
//                if (cert != null) {
//                    System.out.println("PRINTING CERT=");
//                    System.out.println(cert);
//                }
//            }
//            System.out.println(verifier.getStatusAsString());
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
//    }
}


