/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.cxf.fediz.core.util;

import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.security.auth.DestroyFailedException;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;

import org.w3c.dom.Document;

import org.apache.wss4j.common.crypto.Crypto;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class SignatureUtils {

    private static final Logger LOG = LoggerFactory.getLogger(SignatureUtils.class);

    private static final XMLSignatureFactory XML_SIGNATURE_FACTORY = XMLSignatureFactory.getInstance("DOM");

    private SignatureUtils() {
    }

    public static Document signMetaInfo(Crypto crypto, String keyAlias, String keyPassword,
                                              Document doc, String referenceID) throws Exception {
        if (keyAlias == null || "".equals(keyAlias)) {
            keyAlias = crypto.getDefaultX509Identifier();
        }
        X509Certificate cert = CertsUtils.getX509CertificateFromCrypto(crypto, keyAlias);
//    }

/*    public static ByteArrayOutputStream signMetaInfo(FederationContext config, InputStream metaInfo,
        String referenceID)
        throws Exception {

        KeyManager keyManager = config.getSigningKey();
        String keyAlias = keyManager.getKeyAlias();
        String keypass = keyManager.getKeyPassword();

        // in case we did not specify the key alias, we assume there is only one key in the keystore ,
        // we use this key's alias as default.
        if (keyAlias == null || "".equals(keyAlias)) {
            //keyAlias = getDefaultX509Identifier(ks);
            keyAlias = keyManager.getCrypto().getDefaultX509Identifier();
        }
        CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
        cryptoType.setAlias(keyAlias);
        X509Certificate[] issuerCerts = keyManager.getCrypto().getX509Certificates(cryptoType);
        if (issuerCerts == null || issuerCerts.length == 0) {
            throw new ProcessingException(
                    "No issuer certs were found to sign the metadata using issuer name: "
                            + keyAlias);
        }
        X509Certificate cert = issuerCerts[0];
*/
        final String signatureMethod;
        if ("SHA1withDSA".equals(cert.getSigAlgName())) {
            signatureMethod = SignatureMethod.DSA_SHA1;
        } else if ("SHA1withRSA".equals(cert.getSigAlgName())) {
            signatureMethod = SignatureMethod.RSA_SHA1;
        } else if ("SHA256withRSA".equals(cert.getSigAlgName())) {
            signatureMethod = SignatureMethod.RSA_SHA1;
        } else {
            LOG.error("Unsupported signature method: " + cert.getSigAlgName());
            throw new RuntimeException("Unsupported signature method: " + cert.getSigAlgName());
        }

        List<Transform> transformList = new ArrayList<>();
        transformList.add(XML_SIGNATURE_FACTORY.newTransform(Transform.ENVELOPED, (TransformParameterSpec)null));
        transformList.add(XML_SIGNATURE_FACTORY.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE,
                                                             (C14NMethodParameterSpec)null));

        // Create a Reference to the enveloped document (in this case,
        // you are signing the whole document, so a URI of "" signifies
        // that, and also specify the SHA1 digest algorithm and
        // the ENVELOPED Transform.
        Reference ref = XML_SIGNATURE_FACTORY.newReference(
            "#" + referenceID,
            XML_SIGNATURE_FACTORY.newDigestMethod(DigestMethod.SHA1, null),
            transformList,
            null, null);

        // Create the SignedInfo.
        SignedInfo si = XML_SIGNATURE_FACTORY.newSignedInfo(
            XML_SIGNATURE_FACTORY.newCanonicalizationMethod(
                CanonicalizationMethod.EXCLUSIVE, (C14NMethodParameterSpec)null),
            XML_SIGNATURE_FACTORY.newSignatureMethod(
                signatureMethod, null), Collections.singletonList(ref));

        // step 2
        // Load the KeyStore and get the signing key and certificate.

        PrivateKey keyEntry = crypto.getPrivateKey(keyAlias, keyPassword);

        // Create the KeyInfo containing the X509Data.
        KeyInfoFactory kif = XML_SIGNATURE_FACTORY.getKeyInfoFactory();
        List<Object> x509Content = new ArrayList<>();
        x509Content.add(cert.getSubjectX500Principal().getName());
        x509Content.add(cert);
        X509Data xd = kif.newX509Data(x509Content);
        KeyInfo ki = kif.newKeyInfo(Collections.singletonList(xd));

        // step3

        // Create a DOMSignContext and specify the RSA PrivateKey and
        // location of the resulting XMLSignature's parent element.
        //DOMSignContext dsc = new DOMSignContext(keyEntry.getPrivateKey(), doc.getDocumentElement());
        DOMSignContext dsc = new DOMSignContext(keyEntry, doc.getDocumentElement());
        dsc.setIdAttributeNS(doc.getDocumentElement(), null, "ID");
        dsc.setNextSibling(doc.getDocumentElement().getFirstChild());

        // Create the XMLSignature, but don't sign it yet.
        XMLSignature signature = XML_SIGNATURE_FACTORY.newXMLSignature(si, ki);

        // Marshal, generate, and sign the enveloped signature.
        signature.sign(dsc);
        
        // Clean the private key from memory when we're done
        try {
            keyEntry.destroy();
        } catch (DestroyFailedException ex) {
            // ignore
        }

        // step 4
        // Output the resulting document.

        return doc;
    }

}
