/*
 * 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.asymmetric;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;

import javax.script.ScriptException;

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

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.service.AsymmetricService;
import com.scytl.cryptolib.interoperation.js.commands.Command;
import com.scytl.cryptolib.interoperation.js.commands.CommandException;
import com.scytl.cryptolib.interoperation.js.constants.AsymmetricConstants;
import com.scytl.cryptolib.interoperation.js.constants.CommonConstants;
import com.scytl.cryptolib.primitives.primes.utils.PemUtils;

/**
 * Tests the Java-JavaScript interoperation for XML asymmetric signer
 * applications.
 */
public class TestAsymmetricXmlSigner extends Command {

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

    private final Object _namespace;

    private final AsymmetricService _asymmetricService;

    private PublicKey _publicKey;

    private String _publicKeyPem;

    private PrivateKey _privateKey;

    private String _privateKeyPem;

    /**
     * Default constructor.
     *
     * @throws CommandException
     *             if the script engine cannot evaluate the asymmetric XML
     *             signer interoperation tests implemented in JavaScript.
     */
    public TestAsymmetricXmlSigner() throws CommandException {
        super(AsymmetricConstants.JS_ASYMMETRIC_XML_SIGNER_TEST_PATH);

        _namespace = _engine.get(
            AsymmetricConstants.JS_ASYMMETRIC_XML_SIGNER_TEST_NAMESPACE);

        clearExceptionData();

        try {
            _asymmetricService = new AsymmetricService();

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

    /**
     * Digitally signs some data using Java, verifies the result using
     * JavaScript, digitally signs the same data using JavaScript and verifies
     * the result using Java. If no exceptions are thrown and the signatures can
     * verified , then the interoperation test for asymmetric signer
     * applications was successful.
     *
     * @throws CommandException
     *             if a JavaScript method cannot be found, a cryptographic
     *             operation fails or a signature verification operation yields
     *             a value of false.
     */
    @Override
    public void execute() throws CommandException {

        String signedXmlFileB64 = signXmlFileUsingJava();

        verifyXmlFileSignatureUsingJavaScript(signedXmlFileB64);
    }

    private void generateInputData() throws GeneralCryptoLibException {

        KeyPair keyPair = _asymmetricService.getKeyPairForSigning();

        _publicKey = keyPair.getPublic();
        _publicKeyPem = PemUtils.publicKeyToPem(_publicKey);
        putExceptionData("Public key (PEM format)", _publicKeyPem);

        _privateKey = keyPair.getPrivate();
        _privateKeyPem = PemUtils.privateKeyToPem(_privateKey);
        putExceptionData("Private key (PEM format)", _privateKeyPem);
    }

    private String signXmlFileUsingJava() throws CommandException {

        LOGGER.info(CommonConstants.LOGGER_PROMPT
            + "Digitally signing XML file, using Java...");

        try (InputStream inStream = getClass().getResourceAsStream(
            AsymmetricConstants.ASYMMETRIC_XML_SIGNER_TEST_FILE_TO_SIGN_PATH);
                ByteArrayOutputStream outStream =
                    new ByteArrayOutputStream()) {
            _asymmetricService.signXml(_privateKey, null, inStream,
                outStream,
                AsymmetricConstants.ASYMMETRIC_XML_SIGNER_TEST_FILE_SIGNATURE_PARENT_NODE);

            return Base64.getEncoder()
                .encodeToString(outStream.toByteArray());
        } catch (IOException e) {
            throw buildExceptionWithData(
                "Could not open resources file "
                    + AsymmetricConstants.ASYMMETRIC_XML_SIGNER_TEST_FILE_TO_SIGN_PATH,
                e);
        } catch (GeneralCryptoLibException e) {
            throw buildExceptionWithData(
                "Could not digitally sign XML file, using Java.", e);
        }
    }

    private void verifyXmlFileSignatureUsingJavaScript(
            final String signedXmlFileB64) throws CommandException {

        LOGGER.info(CommonConstants.LOGGER_PROMPT
            + "Verifying XML file digital signature, using JavaScript...");

        try {
            getInvocable().invokeMethod(_namespace,
                AsymmetricConstants.JS_ASYMMETRIC_XML_SIGNATURE_VERIFICATION_FUNCTION,
                _publicKeyPem, signedXmlFileB64,
                AsymmetricConstants.ASYMMETRIC_XML_SIGNER_TEST_FILE_SIGNATURE_PARENT_NODE);
        } catch (NoSuchMethodException e) {
            throw new CommandException.Builder()
                .addMessage(
                    "Attempt by script engine to call non-existent function "
                        + AsymmetricConstants.JS_ASYMMETRIC_XML_SIGNER_TEST_NAMESPACE
                        + "."
                        + AsymmetricConstants.JS_ASYMMETRIC_XML_SIGNATURE_VERIFICATION_FUNCTION
                        + " in resources file "
                        + AsymmetricConstants.JS_ASYMMETRIC_XML_SIGNER_TEST_PATH)
                .addCause(e).build();
        } catch (ScriptException e) {
            throw buildExceptionWithData(
                "Could not verify Java generated XML file digital signature, using JavaScript.",
                e);
        }
    }
}
