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

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;

import com.scytl.cryptolib.api.digital.envelope.DigitalEnvelopeServiceAPI;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.asymmetric.utils.AsymmetricTestDataGenerator;
import com.scytl.cryptolib.digital.envelope.cryptoapi.CryptoAPIDigitalEnvelope;
import com.scytl.cryptolib.primitives.primes.utils.PrimitivesTestDataGenerator;

/**
 * Multithreaded tests of {@link DigitalEnvelopeService}.
 */
@Ignore
public class MultithreadDigitalEnvelopeServiceIT {

    private static final int DATA_BYTE_LENGTH = 100;

    private static final int NUM_ASYMMETRIC_KEY_PAIRS = 5;

    private static byte[] _data;

    private static PublicKey[] _publicKeys;

    private static PrivateKey[] _privateKeys;

    @BeforeClass
    public static final void setup() throws GeneralCryptoLibException {
        Security.addProvider(new BouncyCastleProvider());

        _data = PrimitivesTestDataGenerator.getByteArray(DATA_BYTE_LENGTH);

        KeyPair[] keyPairs = AsymmetricTestDataGenerator
            .getKeyPairsForEncryption(NUM_ASYMMETRIC_KEY_PAIRS);
        _publicKeys =
            AsymmetricTestDataGenerator.extractPublicKeys(keyPairs);
        _privateKeys =
            AsymmetricTestDataGenerator.extractPrivateKeys(keyPairs);
    }

    @AfterClass
    public static void tearDown() {

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

    private static void assertServiceIsThreadSafe(
            DigitalEnvelopeServiceAPI service) {
        int size = Runtime.getRuntime().availableProcessors();
        Collection<Callable<Void>> tasks = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            tasks.add(() -> invokeService(service, size * 2));
        }

        Collection<Future<Void>> futures;
        ExecutorService executor = Executors.newFixedThreadPool(size);
        try {
            futures = executor.invokeAll(tasks);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new AssertionError(e);
        } finally {
            executor.shutdown();
        }

        try {
            for (Future<Void> future : futures) {
                future.get();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new AssertionError(e);
        } catch (ExecutionException e) {
            throw new AssertionError(e.getCause());
        }
    }

    private static Void invokeService(DigitalEnvelopeServiceAPI service,
            int count) throws GeneralCryptoLibException {
        for (int i = 0; i < count; i++) {
            CryptoAPIDigitalEnvelope envelope =
                service.createDigitalEnvelope(_data, _publicKeys);
            for (PrivateKey privateKey : _privateKeys) {
                byte[] data =
                    service.openDigitalEnvelope(envelope, privateKey);
                if (!Arrays.equals(_data, data)) {
                    throw new GeneralCryptoLibException(
                        "Data is corrupted.");
                }
            }
        }
        return null;
    }

    @Test
    public void okWhenThreadSafeServicesUsedPoolOf1Test()
            throws GeneralSecurityException, IOException,
            InterruptedException {
        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        config.setMaxTotal(1);
        DigitalEnvelopeServiceAPI service =
            new PollingDigitalEnvelopeServiceFactory(config).create();
        assertServiceIsThreadSafe(service);
    }

    @Test
    public void usingHelperTest()
            throws GeneralSecurityException, IOException,
            InterruptedException, GeneralCryptoLibException {
        DigitalEnvelopeServiceAPI service =
            DigitalEnvelopeServiceFactoryHelper
                .getFactoryOfThreadSafeServices().create();
        assertServiceIsThreadSafe(service);
    }

    @Test
    public void usingHelperWithParamsTest()
            throws GeneralSecurityException, IOException,
            InterruptedException, GeneralCryptoLibException {
        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        config.setMaxTotal(1);
        DigitalEnvelopeServiceAPI service =
            DigitalEnvelopeServiceFactoryHelper
                .getFactoryOfThreadSafeServices(config).create();
        assertServiceIsThreadSafe(service);
    }
}
