import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;

import static java.lang.Math.min;

public class Main {
    private static final int ROWS = 10000;
    private static final int COLS = 10000;
    private static final int THREADS = 4;

    public static final long MaxValue = 1000000;

    public static void main(String[] args) {
        long[][] matrix = generateMatrix(ROWS, COLS);

        long start = System.nanoTime();
        long minSingle = findMinSingleThread(matrix);
        long end = System.nanoTime();
        System.out.println("Single: Min number - " + minSingle + ", Time: " + (end - start) / 1000000 + " мс");

        start = System.nanoTime();
        long minThreadPool = findMinThreadPool(matrix);
        end = System.nanoTime();
        System.out.println("ThreadPoolExecutor: Min number - " + minThreadPool + ", Time: " + (end - start) / 1000000 + " мс");

        start = System.nanoTime();
        long minForkJoin = findMinForkJoin(matrix);
        end = System.nanoTime();
        System.out.println("ForkJoinPool: Min number - " + minForkJoin + ", Time: " + (end - start) / 1000000 + " мс");
    }

    private static long[][] generateMatrix(int rows, int cols) {
        Random rand = new Random();
        long[][] matrix = new long[rows][cols];
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                matrix[i][j] = rand.nextLong(MaxValue);
            }
        }
        return matrix;
    }

    private static long findMinSingleThread(long[][] matrix) {
        long my_min = matrix[0][0];
        for(int i = 0; i < matrix.length; i++){
            my_min = min(my_min, findMinInRaw(matrix[i]));
        }
        return my_min;
    }

    private static long findMinInRaw(long[] arr){
        long my_min = arr[0];
        for(int i = 1; i < arr.length; i++){
            my_min = min(my_min, arr[i]);
        }
        return my_min;
    }

    private static long findMinThreadPool(long[][] matrix) {
        ExecutorService executor = Executors.newFixedThreadPool(THREADS);
        List<Future<Long>> results = new ArrayList<>();

        for (long[] row : matrix) {
            results.add(executor.submit(() -> findMinInRaw(row)));
        }

        long min = MaxValue;
        for (Future<Long> future : results) {
            try {
                min = min(min, future.get());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        executor.shutdown();
        return min;
    }

    private static long findMinForkJoin(long[][] matrix) {
        ForkJoinPool pool = new ForkJoinPool();
        return pool.invoke(new MinTask(matrix, 0, matrix.length));
    }

    static class MinTask extends RecursiveTask<Long> {
        private static final int  MAX_ROWS = 1000;
        private final long[][] matrix;
        private final int startRow, endRow;

        MinTask(long[][] matrix, int startRow, int endRow) {
            this.matrix = matrix;
            this.startRow = startRow;
            this.endRow = endRow;
        }

        @Override
        protected Long compute() {
            if (endRow - startRow <= MAX_ROWS) {
                long my_min = Long.MAX_VALUE;
                for (int i = startRow; i < endRow; i++) {
                    my_min = min(my_min, findMinInRaw(matrix[i]));
                }
                return my_min;
            }
            int mid = (startRow + endRow) / 2;
            MinTask leftTask = new MinTask(matrix, startRow, mid);
            MinTask rightTask = new MinTask(matrix, mid, endRow);
            invokeAll(leftTask, rightTask);
            return min(leftTask.join(), rightTask.join());
        }
    }
}
