/*
 * 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
 */
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package fr.gouv.finances.cp.xemelios.plugin.signerfichier;

import java.io.FileOutputStream;
import java.io.FileInputStream;

import java.security.Security;

import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;

import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.transforms.Transforms;

import org.apache.xml.security.transforms.params.XPath2FilterContainer;

import org.apache.xml.security.utils.resolver.implementations.ResolverFragment;

import org.apache.xml.security.utils.HelperNodeList;
import javax.xml.namespace.NamespaceContext;


import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.xml.security.utils.Constants;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;

import java.util.StringTokenizer;


/**
 *
 * @author laurent.meckert
 */
public class APXSFileSigner extends FileSigner {

    PrivateKey privateKey = null;
    KeyStore ks = null;
    Transforms transforms = null;
    org.w3c.dom.Document doc = null;
    Element root = null;
    static NamespaceContext staticNsCtx = null;

    public void loadKeystore() throws Exception {
        setLoadKsOK(false);

        ks = KeyStore.getInstance(getKeystoreType());
//System.out.println("Attempt to load " + keystoreFile.getAbsolutePath());
        FileInputStream fis = new FileInputStream(keystoreFile);
//System.out.println("using password = " + keystorePassword);
        //load the keystore
        ks.load(fis, keystorePassword.toCharArray());
        setLoadKsOK(true);
    }

    public void loadInputFile() throws Exception {
        javax.xml.parsers.DocumentBuilderFactory dbf =
                javax.xml.parsers.DocumentBuilderFactory.newInstance();

        //XML Signature needs to be namespace aware
        dbf.setNamespaceAware(true);
        javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder();
        db.setErrorHandler(new org.apache.xml.security.utils.IgnoreAllErrorHandler());


        //    if (schemaValidate) {
        //      db.setEntityResolver(new org.xml.sax.EntityResolver() {
        //
        //        public org.xml.sax.InputSource resolveEntity(
        //                String publicId, String systemId)
        //                throws org.xml.sax.SAXException {
        //
        //          if (systemId.endsWith("xmldsig-core-schema.xsd")) {
        //            try {
        //              return new org.xml.sax.InputSource(
        //                      new FileInputStream("signme.xml"));
        //            } catch (FileNotFoundException ex) {
        //              throw new org.xml.sax.SAXException(ex);
        //            }
        //          } else {
        //            return null;
        //          }
        //        }
        //      });
        //    }
        doc = db.parse(new java.io.FileInputStream(inputFile));
        root = doc.getDocumentElement();
        setParseOK(true);
    }

    public Enumeration getAliases() throws Exception {
        return ks.aliases();
    }

    static void fixNameSpaces(XPath2FilterContainer c) throws Exception {

        if (staticNsCtx != null) {
            String URI = staticNsCtx.getNamespaceURI("n");
            if (URI != null) {
                c.setXPathNamespaceContext("n", URI);
            }
        }
    }

    /**
     * Creates a NodeList for a Transforms
     * @param doc
     * @param filters
     * @return
     * @throws Exception
     */
    public static NodeList newInstances(Document doc, String[][] filters)
            throws Exception {

        HelperNodeList nl = new HelperNodeList();
        nl.appendChild(doc.createTextNode("\n"));

        for (int i = 0; i < filters.length; i++) {
            String type = filters[i][0];
            String xpath = filters[i][1];

            if (type.equals(XPath2FilterContainer.INTERSECT)) {
                XPath2FilterContainer c = XPath2FilterContainer.newInstanceIntersect(doc, xpath);

                fixNameSpaces(c);
                nl.appendChild(c.getElement());
                nl.appendChild(doc.createTextNode("\n"));
            }
            if (type.equals(XPath2FilterContainer.SUBTRACT)) {
                XPath2FilterContainer c = XPath2FilterContainer.newInstanceSubtract(doc, xpath);
                fixNameSpaces(c);
                //c.setXPathNamespaceContext( "bla", "http://www.bla.com" );
                nl.appendChild(c.getElement());
                nl.appendChild(doc.createTextNode("\n"));
            }
            if (type.equals(XPath2FilterContainer.UNION)) {
                XPath2FilterContainer c = XPath2FilterContainer.newInstanceUnion(doc, xpath);
                fixNameSpaces(c);
                //c.setXPathNamespaceContext( "bla", "http://www.bla.com" );
                nl.appendChild(c.getElement());
                nl.appendChild(doc.createTextNode("\n"));
            }
        }

        return nl;
    }

    public void loadAlias() {
        try {
            if (ks.isKeyEntry(alias)) {
//                System.out.println("KEY_ALIAS (" + alias + ") is a key entry");
                //get the private key for signing.
                privateKey = (PrivateKey) ks.getKey(alias,
                        aliasPassword.toCharArray());

                setLoadPkOK(true);
            } else {
//                System.out.println("Alias inconnu : " + alias);
                setLoadPkOK(false);
                return;
            }
        } catch (Exception e) {
//            e.printStackTrace();
            setLoadPkOK(false);
        }
    }

    private static String prettyPrint(X509Certificate c) {

        String result = "Pour:\t" +  c.getSubjectDN();
        result = result + "\nEmis par:\t" + c.getIssuerDN();
        result = result + "\nValidité:\tdu " + c.getNotBefore()
                + "\n\tau " + c.getNotAfter();
        result = result + "\nAlgorithme:\t" + c.getSigAlgName();
        result = result + "\nNuméro:\t" + c.getSerialNumber();

        return result;
    }

     private static String issuedFor(X509Certificate c) {

        String dn = c.getSubjectX500Principal().getName();
        String cn = null;
        String uid = null;
         StringTokenizer tokenizer = new StringTokenizer(dn,",");
            while(tokenizer.hasMoreTokens()) {
                    String tok = tokenizer.nextToken();
                    if(tok.startsWith("CN=")) cn= tok.substring(3);
                     if(tok.startsWith("UID=")) uid= tok.substring(4);
            }

        if (uid!= null) return uid;
         if(cn != null) return cn;
        return dn;
    }


    @Override
     public String getIssuedFor(String rawalias) {
      try {
            X509Certificate cert =
                    (X509Certificate) ks.getCertificate(rawalias);
            if (cert != null) {
                return issuedFor(cert) ; //cert.toString();
            } else {
                return rawalias;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "Exception " + rawalias;
        }
    }

    @Override
    public String getDetails(String rawalias) {

        try {
            X509Certificate cert =
                    (X509Certificate) ks.getCertificate(rawalias);
            if (cert != null) {
                return prettyPrint(cert) ; //cert.toString();
            } else {
                return rawalias;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "Exception " + rawalias;
        }

    }

    public void sign() throws Exception {


        //The BaseURI is the URI that's used to prepend to relative URIs
        String BaseURI = outputFile.toURL().toString();
        //Create an XML Signature object from the document, BaseURI and
        //signature algorithm

        String pkAlgo = privateKey.getAlgorithm();
        setAlgorithm(pkAlgo);
        String algoId = null;
        if (pkAlgo.equals("RSA")) {
            algoId = XMLSignature.ALGO_ID_SIGNATURE_RSA;
        } else if (pkAlgo.equals("DSA")) {
            algoId = XMLSignature.ALGO_ID_SIGNATURE_DSA;
        }/* else if (pkAlgo.equals("EC")) {
        algoId = XMLSignature.ALGO_ID_SIGNATURE_ECDSA_SHA1;
        }*/
        if (algoId != null) {
            setAlgoIdOK(true);
        } else {
            System.err.println("Algorithme inconnu ou non supporté " + pkAlgo);
            setAlgoIdOK(false);
            return;

        }


        //   XPathFactory xpf = XPathFactory.newInstance();
        //          XPath xp = xpf.newXPath();
        //          xp.setNamespaceContext(nsCtx);

        //          NodeList nl = (NodeList)xp.evaluate(getSignatureXPath(),
        //  doc, XPathConstants.NODESET);


        XMLSignature sig = new XMLSignature(doc, BaseURI,
                algoId);




        //Append the signature element to the root element before signing because
        //this is going to be an enveloped signature.
        //This means the signature is going to be enveloped by the document.
        //Two other possible forms are enveloping where the document is inside the
        //signature and detached where they are seperate.

        root.appendChild(sig.getElement());
        // Add fragment resolver for uri=""
        ResolverFragment fragmentResolver = new ResolverFragment();
        sig.addResourceResolver(fragmentResolver);
        //create the transforms object for the Document/Reference
        transforms = new Transforms(doc);

        //First we have to strip away the signature element (it's not part of the
        //signature calculations). The enveloped transform can be used for this.
        if (true) {
            transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);

        }

        {
            // Filtrer par XPath

            // Attention faut absolument le name space dans nos fichies pes c'est  n:
            // exemple tout sauf Enveloppe
            //     String filters[][] = {  { XPath2FilterContainer.SUBTRACT, "//n:Enveloppe"} };
            // exemple = que Bordereau.
            // String filters[][] = {{XPath2FilterContainer.INTERSECT, "//n:Bordereau"}};

            staticNsCtx = nsCtx;

            if (subtractFilters.size() + intersectFilters.size() > 0) {
                String filters[][] = new String[subtractFilters.size() + intersectFilters.size()][2];
                int i = 0;
                for (String subs : subtractFilters) {
                    filters[i][0] = XPath2FilterContainer.SUBTRACT;
                    filters[i][1] = subs;
                    i++;
                }
                for (String inters : intersectFilters) {
                    filters[i][0] = XPath2FilterContainer.INTERSECT;
                    filters[i][1] = inters;
                    i++;
                }

                transforms.addTransform(Transforms.TRANSFORM_XPATH2FILTER,
                        //       XPath2FilterContainer.newInstances(doc,
                        newInstances(doc,
                        filters));

            }
        }
        //Part of the signature element needs to be canonicalized. It is a kind
        //of normalizing algorithm for XML. For more information please take a
        //look at the W3C XML Digital Signature webpage.
        transforms.addTransform(Transforms.TRANSFORM_C14N_WITH_COMMENTS);
        //Add the above Document/Reference
        sig.addDocument("", transforms, Constants.ALGO_ID_DIGEST_SHA1);


        {
            //Add in the KeyInfo for the certificate that we used the private key of
            X509Certificate cert =
                    (X509Certificate) ks.getCertificate(alias);

            sig.addKeyInfo(cert);
            sig.addKeyInfo(cert.getPublicKey());
            // System.out.println("Start signing");
            try {
                sig.sign(privateKey);
                setSignOK(true);
            } catch (Exception e) {
                e.printStackTrace();
                return;
            }
        //  System.out.println("Finished signing");
        }
        try {

            FileOutputStream f = new FileOutputStream(outputFile);
//         f.write("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>".getBytes());
//
//         XMLUtils.outputDOMc14nWithComments(doc, f);

            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer t = tf.newTransformer();
            t.setOutputProperty("encoding", "ISO-8859-1");
            t.setOutputProperty("method", "xml");
//         t.setOutputProperty("indent", "yes");
            t.transform(new DOMSource(doc), new StreamResult(f));

            f.flush();
            f.close();
            setWriteOK(true);
//            System.out.println("Wrote signature to " + BaseURI);
        } catch (Exception e) {
            e.printStackTrace();
//            System.out.println("APXSFileSigner ligne 340 Exception caught ");
            return;
        } catch (Error e) {
            e.printStackTrace();
//            System.out.println("APXSFileSigner ligne 344 Error caught ");
            return;
        }
    }

    @Override
    public void dispose() {

        doc = null;
        root = null;
    }




   static {
      org.apache.xml.security.Init.init();
      Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
   }

    public String getNomBonhomme(String rawAlias) {
        try {
            X509Certificate cert = (X509Certificate) ks.getCertificate(rawAlias);
            if (cert != null) {
                String dn = cert.getSubjectDN().getName();
                StringTokenizer tokenizer = new StringTokenizer(dn,",");
                while(tokenizer.hasMoreTokens()) {
                        String tok = tokenizer.nextToken().trim();
                        if(tok.startsWith("CN=")) return tok.substring(3);
                }
                return "<non définit>";
            } else {
                return rawAlias;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "Exception " + rawAlias;
        }
    }

    public String getEmetteur(String rawAlias) {
        try {
            X509Certificate cert = (X509Certificate) ks.getCertificate(rawAlias);
            if (cert != null) {
                String dn = cert.getIssuerX500Principal().getName();
                StringTokenizer tokenizer = new StringTokenizer(dn,",");
                while(tokenizer.hasMoreTokens()) {
                        String tok = tokenizer.nextToken().trim();
                        if(tok.startsWith("CN=")) return tok.substring(3);
                }
                return "<non définit>";
            } else {
                return rawAlias;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "Exception " + rawAlias;
        }
    }

    public Date getDebutValidite(String rawAlias) {
        try {
            X509Certificate cert = (X509Certificate) ks.getCertificate(rawAlias);
            if (cert != null) {
                return cert.getNotBefore();
            }
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public Date getFinValidite(String rawAlias) {
        try {
            X509Certificate cert = (X509Certificate) ks.getCertificate(rawAlias);
            if (cert != null) {
                return cert.getNotAfter();
            }
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

}
