/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.cryptolib.interoperation.js.commands.certificates;

import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.util.Date;

import javax.script.ScriptException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.utils.AsymmetricTestDataGenerator;
import com.scytl.cryptolib.certificates.bean.CertificateData;
import com.scytl.cryptolib.certificates.bean.RootCertificateData;
import com.scytl.cryptolib.certificates.bean.X509DistinguishedName;
import com.scytl.cryptolib.certificates.constants.X509CertificateTestConstants;
import com.scytl.cryptolib.certificates.service.CertificatesService;
import com.scytl.cryptolib.certificates.utils.X509CertificateTestDataGenerator;
import com.scytl.cryptolib.interoperation.js.commands.Command;
import com.scytl.cryptolib.interoperation.js.commands.CommandException;
import com.scytl.cryptolib.interoperation.js.constants.CertificateConstants;
import com.scytl.cryptolib.interoperation.js.constants.CommonConstants;
import com.scytl.cryptolib.primitives.primes.utils.PemUtils;

/**
 * Tests the Java-JavaScript interoperation for certificate applications.
 */
public class TestCertificate extends Command {

    private static final Logger LOGGER =
        LoggerFactory.getLogger(TestCertificate.class);

    private final Object _namespace;

    private final CertificatesService _certificatesService;

    private static PrivateKey _rootPrivateKey;

    private static String _rootPrivateKeyPem;

    private static String _rootCertificatePem;

    private static String _privateKeyPem;

    private static String _publicKeyPem;

    private static CertificateData _certificateData;

    private static String[] _issuerDnFields;

    private static String[] _subjectDnFields;

    private static String _notBeforeStr;

    private static String _notAfterStr;

    /**
     * Default constructor.
     *
     * @throws CommandException
     *             if the script engine cannot evaluate the certificate
     *             interoperation tests implemented in JavaScript.
     */
    public TestCertificate() throws CommandException {
        super(CertificateConstants.JS_CERTIFICATE_TEST_PATH);

        _namespace = _engine
            .get(CertificateConstants.JS_CERTIFICATE_TEST_NAMESPACE);

        clearExceptionData();

        try {
            _certificatesService = new CertificatesService();

            generateInputData();
        } catch (GeneralCryptoLibException e) {
            throw new CommandException.Builder().addCause(e).build();
        }
    }

    /**
     * Generates a certificate using Java and opens the result using JavaScript.
     * If no exceptions are thrown, the certificate signature can be verified
     * and its content can be retrieved then the interoperation test for
     * certificate applications was successful.
     *
     * @throws CommandException
     *             if a JavaScript method cannot be found, a cryptographic
     *             operation fails, the certificate's signature cannot be
     *             verified or its content cannot be retrieved.
     */
    @Override
    public void execute() throws CommandException {

        String certificatePem = generateUsingJava();

        openUsingJavaScript(certificatePem);

        LOGGER.info(CommonConstants.LOGGER_PROMPT
            + "INTEROPERATION TEST FOR CERTIFICATE WAS SUCCESSFUL.");
    }

    private void generateInputData() throws GeneralCryptoLibException {

        KeyPair rootKeyPair =
            AsymmetricTestDataGenerator.getKeyPairForSigning();
        _rootPrivateKey = rootKeyPair.getPrivate();
        _rootPrivateKeyPem = PemUtils.privateKeyToPem(_rootPrivateKey);
        putExceptionData("Root private key (PEM format) [Java]",
            _rootPrivateKeyPem);

        RootCertificateData rootCertificateData =
            X509CertificateTestDataGenerator
                .getRootCertificateData(rootKeyPair);
        Certificate rootCertificate =
            _certificatesService.createRootAuthorityX509Certificate(
                rootCertificateData, _rootPrivateKey).getCertificate();
        _rootCertificatePem = PemUtils.certificateToPem(rootCertificate);
        putExceptionData("Root certificate (PEM format) [Java]",
            _rootCertificatePem);

        KeyPair keyPair =
            AsymmetricTestDataGenerator.getKeyPairForSigning();
        PrivateKey privateKey = keyPair.getPrivate();
        _privateKeyPem = PemUtils.privateKeyToPem(privateKey);
        putExceptionData("Private key (PEM format) [Java]",
            _privateKeyPem);
        _publicKeyPem = PemUtils.publicKeyToPem(keyPair.getPublic());
        putExceptionData("Public key (PEM format) [Java]", _publicKeyPem);

        _certificateData =
            X509CertificateTestDataGenerator.getCertificateData(keyPair);
        _issuerDnFields = getDnFields(_certificateData.getIssuerDn());
        _subjectDnFields = getDnFields(_certificateData.getSubjectDn());
        _notBeforeStr = getValidityDateAsString(
            _certificateData.getValidityDates().getNotBefore());
        _notAfterStr = getValidityDateAsString(
            _certificateData.getValidityDates().getNotAfter());

        putExceptionData("Issuer common name [Java]", _issuerDnFields[0]);
        putExceptionData("Issuer country [Java]", _issuerDnFields[1]);
        putExceptionData("Issuer organizational unit [Java]",
            _issuerDnFields[2]);
        putExceptionData("Issuer organization [Java]", _issuerDnFields[3]);
        putExceptionData("Subject common name [Java]",
            _subjectDnFields[0]);
        putExceptionData("Subject country [Java]", _subjectDnFields[1]);
        putExceptionData("Subject organizational unit [Java]",
            _subjectDnFields[2]);
        putExceptionData("Subject organization", _subjectDnFields[3]);
        putExceptionData(
            "Starting date of validity (Epoch time format) [Java]",
            _notBeforeStr);
        putExceptionData(
            "Ending date of validity (Epoch time format) [Java]",
            _notAfterStr);
    }

    private String generateUsingJava() throws CommandException {

        LOGGER.info(CommonConstants.LOGGER_PROMPT
            + "Generating certificate, using Java...");

        try {
            return PemUtils.certificateToPem(
                _certificatesService.createSignX509Certificate(
                    _certificateData, _rootPrivateKey).getCertificate());
        } catch (GeneralCryptoLibException e) {
            throw buildExceptionWithData(
                "Could not generate certificate, using Java.", e);
        }
    }

    private void openUsingJavaScript(final String certificatePem)
            throws CommandException {

        LOGGER.info(CommonConstants.LOGGER_PROMPT
            + "Opening certificate, using JavaScript...");

        try {
            getInvocable().invokeMethod(_namespace,
                CertificateConstants.JS_CERTIFICATE_OPENING_FUNCTION,
                certificatePem, _rootCertificatePem, _publicKeyPem,
                _issuerDnFields, _subjectDnFields, _notBeforeStr,
                _notAfterStr);
        } catch (NoSuchMethodException e) {
            throw new CommandException.Builder()
                .addMessage(
                    "Attempt by script engine to call non-existent function "
                        + CertificateConstants.JS_CERTIFICATE_TEST_NAMESPACE
                        + "."
                        + CertificateConstants.JS_CERTIFICATE_OPENING_FUNCTION
                        + " in resources file "
                        + CertificateConstants.JS_CERTIFICATE_TEST_PATH)
                .addCause(e).build();
        } catch (ScriptException e) {
            throw buildExceptionWithData(
                "Could not open certificate, using JavaScript.", e);
        }
    }

    private String[] getDnFields(
            final X509DistinguishedName distinguishedName) {

        String[] dnFields = new String[4];
        dnFields[0] = distinguishedName.getCommonName();
        dnFields[1] = distinguishedName.getCountry();
        dnFields[2] = distinguishedName.getOrganizationalUnit();
        dnFields[3] = distinguishedName.getOrganization();

        return dnFields;
    }

    private String getValidityDateAsString(final Date validityDate) {

        return String.valueOf(validityDate.getTime()).substring(0,
            X509CertificateTestConstants.EPOCH_TIME_LENGTH);
    }
}
