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

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
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 javax.crypto.SecretKey;

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

import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.cryptolib.api.symmetric.SymmetricServiceAPI;
import com.scytl.cryptolib.symmetric.utils.SymmetricTestDataGenerator;

/**
 * Multithreaded tests of {@link SymmetricService}.
 */
public class MultithreadSymmetricServiceIT {
    private static final byte[] DATA =
        "dataToEncrypt".getBytes(StandardCharsets.UTF_8);

    private static SecretKey _key;

    @BeforeClass
    public static void setup() throws GeneralCryptoLibException {
        Security.addProvider(new BouncyCastleProvider());
        _key = SymmetricTestDataGenerator.getSecretKeyForEncryption();
    }

    @AfterClass
    public static void tearDown() {
        Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
    }

    private static void assertServiceIsThreadSafe(
            SymmetricServiceAPI 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(SymmetricServiceAPI service,
            int count) throws GeneralCryptoLibException {
        for (int i = 0; i < count; i++) {
            byte[] encrypted = service.encrypt(_key, DATA);
            byte[] decrypted = service.decrypt(_key, encrypted);
            if (!Arrays.equals(DATA, decrypted)) {
                throw new GeneralCryptoLibException("Data is corrupted.");
            }
        }
        return null;
    }

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

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

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