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

import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore.PasswordProtection;
import java.security.Security;
import java.util.ArrayList;
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.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.scytl.keystore.ScytlKeyStoreServiceAPI;
import com.scytl.cryptolib.scytl.keystore.cryptoapi.CryptoAPIScytlKeyStore;

/**
 * Multithreaded tests of {@link ScytlKeyStoreService}.
 */
@Ignore
public class MultithreadScytlKeyStoreServiceIT {
    private static final char[] PASSWORD =
        "01234567890abcdefghijk".toCharArray();

    private static final String ALIAS = "myaliassymmetric-2_";

    @BeforeClass
    public static void setup() {
        Security.addProvider(new BouncyCastleProvider());
    }

    @AfterClass
    public static void tearDown() {

        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

    private static void assertServiceIsThreadSafe(
            ScytlKeyStoreServiceAPI 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(ScytlKeyStoreServiceAPI service,
            int count) throws GeneralCryptoLibException, IOException {
        for (int i = 0; i < count; i++) {
            CryptoAPIScytlKeyStore store;
            try (InputStream stream =
                MultithreadScytlKeyStoreServiceIT.class
                    .getResourceAsStream("/keystoreSymmetric.sks")) {
                store = service.loadKeyStore(stream,
                    new PasswordProtection(PASSWORD));
            }
            for (int j = 0; j < 10; j++) {
                store.getSecretKeyEntry(ALIAS, PASSWORD);
            }
        }
        return null;
    }

    @Test
    public void okWhenThreadSafeServicesTest()
            throws GeneralSecurityException, IOException,
            GeneralCryptoLibException {

        ScytlKeyStoreServiceAPI service = new ScytlKeyStoreService();
        assertServiceIsThreadSafe(service);
    }

    @Test
    public void okWhenThreadSafeServicesUsedPoolTest()
            throws GeneralSecurityException, IOException,
            GeneralCryptoLibException {

        ScytlKeyStoreServiceAPI service =
            new PollingScytlKeyStoreServiceFactory().create();
        assertServiceIsThreadSafe(service);
    }

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