/*
 * 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.products.ov.mixnet.commons.concurrent;

import com.scytl.cryptolib.api.exceptions.CryptoLibException;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;
import com.scytl.products.ov.mixnet.commons.concurrent.operations.AggregateOperation;
import com.scytl.products.ov.mixnet.commons.tools.LoopTools.Range;

import static java.util.Collections.singletonList;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveTask;

/**
 * This class is in charge of: split the tasks one by Range object, execute the base case (passed on the constructor as
 * a {@code ParallelProcessTask} class) and once all executions are finished, execute the aggregation operation (passed
 * on the constructor as a {@code AggregateOperation} class)
 */
@SuppressWarnings("serial")
public final class LoopParallelizerTask<T> extends RecursiveTask<T> {
    private final transient ParallelProcessTask<T> process;

    private final transient List<Range> ranges;

    private final transient AggregateOperation<T, LoopParallelizerTask<T>> operation;

    /**
     * Constructor.
     * 
     * @param process
     */
    public LoopParallelizerTask(final ParallelProcessTask<T> process, final List<Range> ranges,
            final AggregateOperation<T, LoopParallelizerTask<T>> operation) {
        this.process = process;
        this.ranges = ranges;
        this.operation = operation;
    }

    @Override
    protected T compute() {
        if (ranges.size() == 1) {
            try {
                return process.run(ranges.get(0));
            } catch (GeneralCryptoLibException e) {
                throw new CryptoLibException(e);
            }
        } 

        List<LoopParallelizerTask<T>> tasks = new ArrayList<>(ranges.size());
        LoopParallelizerTask<T> myTask = 
                new LoopParallelizerTask<>(process, ranges.subList(0, 1), operation); 
        tasks.add(myTask);
        for (Range range : ranges.subList(1, ranges.size())) {
            LoopParallelizerTask<T> task = 
                    new LoopParallelizerTask<>(process, singletonList(range), operation);
            task.fork();
            tasks.add(task);
        }
        
        myTask.complete(myTask.compute());
        for (int i = tasks.size() - 1; i > 0; i--) {
            LoopParallelizerTask<T> task = tasks.get(i);
            if (task.tryUnfork()) {
                task.complete(task.compute());
            } else {
                task.join();
            }
        }
        return operation.execute(tasks);
    }
}
