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

import static java.util.concurrent.Executors.newFixedThreadPool;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

/**
 * Class to run all chosen commands, in the order that they are added to a list.
 */
public final class CommandRunner {

    private final Collection<Command> commands = new LinkedList<>();

    /**
     * Adds a command to the list of commands to be run.
     *
     * @param command
     *            the command to add to the list.
     */
    public void addCommand(final Command command) {

        commands.add(command);
    }

    /**
     * Runs all commands, in the order that they were added to the list.
     *
     * @throws CommandException
     *             if any of the commands cannot be executed.
     */
    public void runCommands() throws CommandException {

        Collection<Callable<Void>> tasks = newTasks();
        Collection<Future<Void>> futures = invokeTasks(tasks);
        checkResults(futures);
    }

    private Collection<Future<Void>> invokeTasks(
            final Collection<Callable<Void>> tasks)
            throws CommandException {

        Collection<Future<Void>> futures;
        ExecutorService executor = newExecutorService();
        try {
            futures = executor.invokeAll(tasks);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new CommandException.Builder()
                .addMessage("Failed to invoke the tasks.").addCause(e)
                .build();
        } finally {
            executor.shutdown();
        }
        return futures;
    }

    private ExecutorService newExecutorService() {

        return newFixedThreadPool(
            Runtime.getRuntime().availableProcessors());
    }

    private void checkResults(final Collection<Future<Void>> futures)
            throws CommandException {

        try {
            for (Future<Void> future : futures) {
                future.get();
            }
        } catch (ExecutionException e) {
            throw (CommandException) e.getCause();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new CommandException.Builder()
                .addMessage("Failed to check the results.").addCause(e)
                .build();
        }
    }

    private Collection<Callable<Void>> newTasks() {

        Collection<Callable<Void>> tasks =
            new ArrayList<>(commands.size());
        for (Command command : commands) {
            tasks.add(new CallableAdapter(command));
        }
        return tasks;
    }

    private static class CallableAdapter implements Callable<Void> {

        private final Command command;

        public CallableAdapter(final Command command) {
            this.command = command;
        }

        @Override
        public Void call() throws CommandException {
            command.execute();
            return null;
        }
    }
}
