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

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import com.scytl.cryptolib.api.asymmetric.utils.KeyPairConverterAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.keypair.configuration.ConfigSigningKeyPairAlgorithmAndSpec;
import com.scytl.cryptolib.commons.configuration.PolicyFromPropertiesHelper;
import com.scytl.cryptolib.commons.constants.Constants;
import com.scytl.cryptolib.primitives.primes.utils.PrimitivesTestDataGenerator;
import com.scytl.cryptolib.primitives.securerandom.constants.SecureRandomConstants;
import com.scytl.cryptolib.test.tools.utils.CommonTestDataGenerator;

/**
 * Tests of the asymmetric service API.
 */
public class AsymmetricServiceTest {

    static final private int MAXIMUM_DATA_ARRAY_LENGTH = 10;

    private static final String XML_FILES_TO_SIGN_PATH =
        "/data/xml_files_to_sign";

    private static final String SIGNED_XML_FILES_PATH =
        "target/signed_xml_files";

    private static final String SIGNED_XML_FILENAME_PREFIX = "signed_";

    private static final String XML_SIGNATURE_PARENT_NODE = "element1";

    private static PolicyFromPropertiesHelper _defaultPolicy;

    private static AsymmetricService _asymmetricServiceForDefaultPolicy;

    private static PublicKey _publicKeyForEncryption;

    private static PrivateKey _privateKeyForDecryption;

    private static PublicKey _publicKeyForVerification;

    private static PrivateKey _privateKeyForSigning;

    private static int _dataByteLength;

    private static byte[] _data;

    private static KeyPairConverterAPI _keyPairConverter;

    @BeforeClass
    public static void setUp()
            throws GeneralCryptoLibException {

        Security.addProvider(new BouncyCastleProvider());

        _defaultPolicy =
            new PolicyFromPropertiesHelper(
                Constants.CRYPTOLIB_POLICY_PROPERTIES_FILE_PATH);

        _asymmetricServiceForDefaultPolicy = new AsymmetricService();

        KeyPair keyPairForEncryption =
            _asymmetricServiceForDefaultPolicy.getKeyPairForEncryption();
        _publicKeyForEncryption = keyPairForEncryption.getPublic();
        _privateKeyForDecryption = keyPairForEncryption.getPrivate();

        KeyPair keyPairForSigning =
            _asymmetricServiceForDefaultPolicy.getKeyPairForSigning();
        _publicKeyForVerification = keyPairForSigning.getPublic();
        _privateKeyForSigning = keyPairForSigning.getPrivate();

        _dataByteLength =
            CommonTestDataGenerator.getInt(1,
                SecureRandomConstants.MAXIMUM_GENERATED_BYTE_ARRAY_LENGTH);

        _data = PrimitivesTestDataGenerator.getByteArray(_dataByteLength);

        _keyPairConverter =
            _asymmetricServiceForDefaultPolicy.getKeyPairConverter();
    }

    @AfterClass
    public static void cleanUp() {

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

    @Test
    public void testWhenCreateEncryptionCryptoKeyPairThenExpectedAlgorithm() {

        String publicKeyAlgorithm = _publicKeyForEncryption.getAlgorithm();
        ConfigSigningKeyPairAlgorithmAndSpec encryptionKeyPairAlgorithmAndSpec =
            ConfigSigningKeyPairAlgorithmAndSpec.valueOf(_defaultPolicy
                .getPropertyValue("asymmetric.encryptionkeypair"));

        Assert.assertEquals("The algorithm was not the expected one.",
            encryptionKeyPairAlgorithmAndSpec.getAlgorithm(),
            publicKeyAlgorithm);
    }

    @Test
    public void testWhenCreateSigningCryptoKeyPairThenExpectedAlgorithm() {

        String publicKeyAlgorithm =
            _publicKeyForVerification.getAlgorithm();
        ConfigSigningKeyPairAlgorithmAndSpec signingKeyPairAlgorithmAndSpec =
            ConfigSigningKeyPairAlgorithmAndSpec.valueOf(_defaultPolicy
                .getPropertyValue("asymmetric.signingkeypair"));

        Assert.assertEquals("The algorithm was not the expected one.",
            signingKeyPairAlgorithmAndSpec.getAlgorithm(),
            publicKeyAlgorithm);
    }

    @Test
    public void testWhenAsymmetricallyEncryptAndDecryptThenOk()
            throws GeneralCryptoLibException {

        byte[] encryptedData =
            _asymmetricServiceForDefaultPolicy.encrypt(
                _publicKeyForEncryption, _data);

        byte[] decryptedData =
            _asymmetricServiceForDefaultPolicy.decrypt(
                _privateKeyForDecryption, encryptedData);

        Assert.assertArrayEquals(decryptedData, _data);
    }

    @Test
    public void testWhenSignAndVerifySignatureThenOk()
            throws GeneralCryptoLibException {

        byte[] signature =
            _asymmetricServiceForDefaultPolicy.sign(_privateKeyForSigning,
                _data);

        boolean verified =
            _asymmetricServiceForDefaultPolicy.verifySignature(signature,
                _publicKeyForVerification, _data);

        Assert.assertTrue(verified);
    }

    @Test
    public void testWhenSignAndVerifySignatureForMultipleDataElementsThenOk()
            throws GeneralCryptoLibException {

        byte[][] dataArray =
            PrimitivesTestDataGenerator.getByteArrayArray(_dataByteLength,
                CommonTestDataGenerator.getInt(2,
                    MAXIMUM_DATA_ARRAY_LENGTH));

        byte[] signature =
            _asymmetricServiceForDefaultPolicy.sign(_privateKeyForSigning,
                dataArray);

        boolean verified =
            _asymmetricServiceForDefaultPolicy.verifySignature(signature,
                _publicKeyForVerification, dataArray);

        Assert.assertTrue(verified);
    }

    @Test
    public void testWhenSignAndVerifySignatureForDataInputStreamThenOk()
            throws GeneralCryptoLibException, IOException {

        InputStream dataInputStream = new ByteArrayInputStream(_data);

        byte[] signature =
            _asymmetricServiceForDefaultPolicy.sign(_privateKeyForSigning,
                dataInputStream);

        dataInputStream.reset();

        boolean verified =
            _asymmetricServiceForDefaultPolicy.verifySignature(signature,
                _publicKeyForVerification, dataInputStream);

        Assert.assertTrue(verified);
    }

    @Test
    public void testWhenSignAndVerifySignatureForXmlFileThenOk()
            throws GeneralCryptoLibException, FileNotFoundException,
            URISyntaxException {

        File signedFilesDir = new File(SIGNED_XML_FILES_PATH);
        if (!signedFilesDir.exists()) {
            signedFilesDir.mkdir();
        }

        File filesToSignDir =
            new File(getClass().getResource(XML_FILES_TO_SIGN_PATH)
                .toURI());
        File[] filesToSign = filesToSignDir.listFiles();
        for (File file : filesToSign) {
            InputStream xmlInStream = new FileInputStream(file);

            OutputStream xmlOutStream =
                new FileOutputStream(SIGNED_XML_FILES_PATH
                    + File.separatorChar + SIGNED_XML_FILENAME_PREFIX
                    + file.getName());

            _asymmetricServiceForDefaultPolicy.signXml(
                _privateKeyForSigning, null, xmlInStream, xmlOutStream,
                XML_SIGNATURE_PARENT_NODE);

            InputStream signedXmlInStream =
                new FileInputStream(SIGNED_XML_FILES_PATH
                    + File.separatorChar + "signed_" + file.getName());

            boolean signatureVerified =
                _asymmetricServiceForDefaultPolicy.verifyXmlSignature(
                    _publicKeyForVerification, signedXmlInStream,
                    XML_SIGNATURE_PARENT_NODE);

            Assert.assertTrue(signatureVerified);
        }
    }

    @Test
    public void testWhenConvertKeyPairForEncryptionToAndFromPemThenOk()
            throws GeneralCryptoLibException {

        String publicKeyPem =
            _keyPairConverter
                .exportPublicKeyForEncryptingToPem(_publicKeyForEncryption);
        PublicKey publicKey =
            _keyPairConverter
                .getPublicKeyForEncryptingFromPem(publicKeyPem);
        Assert.assertArrayEquals(publicKey.getEncoded(),
            _publicKeyForEncryption.getEncoded());

        String privateKeyPem =
            _keyPairConverter
                .exportPrivateKeyForEncryptingToPem(_privateKeyForDecryption);
        PrivateKey privateKey =
            _keyPairConverter
                .getPrivateKeyForEncryptingFromPem(privateKeyPem);
        Assert.assertArrayEquals(privateKey.getEncoded(),
            _privateKeyForDecryption.getEncoded());
    }

    @Test
    public void testWhenConvertKeyPairForSigningToAndFromPemThenOk()
            throws GeneralCryptoLibException {

        String publicKeyPem =
            _keyPairConverter
                .exportPublicKeyForEncryptingToPem(_publicKeyForVerification);
        PublicKey publicKey =
            _keyPairConverter
                .getPublicKeyForEncryptingFromPem(publicKeyPem);
        Assert.assertArrayEquals(publicKey.getEncoded(),
            _publicKeyForVerification.getEncoded());

        String privateKeyPem =
            _keyPairConverter
                .exportPrivateKeyForEncryptingToPem(_privateKeyForSigning);
        PrivateKey privateKey =
            _keyPairConverter
                .getPrivateKeyForEncryptingFromPem(privateKeyPem);
        Assert.assertArrayEquals(privateKey.getEncoded(),
            _privateKeyForSigning.getEncoded());
    }
}
