package ru.kuznec;

import java.util.*;
import java.util.concurrent.*;

public class MatrixMinFinder {

    public static void main(String[] args) {
        int n = 10000;

        int[][] matrix = generateMatrix(n);
        System.out.println("Сгенерирована матрица размером " + n + "x" + n);

        // Однопоточный вариант
        long startTime = System.nanoTime();
        int minSingle = findMinSingleThread(matrix);
        long duration = System.nanoTime() - startTime;
        System.out.printf("Однопоточный вариант: min = %d, время = %.3f мс%n",
                minSingle, duration / 1_000_000.0);

        // Параллельный вариант с использованием ThreadPoolExecutor
        startTime = System.nanoTime();
        int minThreadPool = findMinThreadPool(matrix);
        duration = System.nanoTime() - startTime;
        System.out.printf("ThreadPoolExecutor: min = %d, время = %.3f мс%n",
                minThreadPool, duration / 1_000_000.0);

        // Параллельный вариант с использованием ForkJoinPool
        startTime = System.nanoTime();
        int minForkJoin = findMinForkJoin(matrix);
        duration = System.nanoTime() - startTime;
        System.out.printf("ForkJoinPool: min = %d, время = %.3f мс%n",
                minForkJoin, duration / 1_000_000.0);
    }

    /**
     * Генерация квадратной матрицы случайных чисел в диапазоне [-10000; 10000]
     */
    private static int[][] generateMatrix(int n) {
        int[][] matrix = new int[n][n];
        Random random = new Random();
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                matrix[i][j] = random.nextInt(20001) - 10000;
            }
        }
        return matrix;
    }

    /**
     * Однопоточный вариант поиска минимума элементов ниже главной диагонали.
     * Для каждой строки i, начиная с i=1 (так как в строке 0 нет элементов ниже диагонали),
     * перебираются столбцы от 0 до i-1.
     */
    public static int findMinSingleThread(int[][] matrix) {
        int n = matrix.length;
        int min = Integer.MAX_VALUE;
        for (int i = 1; i < n; i++) {
            for (int j = 0; j < i; j++) {
                if (matrix[i][j] < min) {
                    min = matrix[i][j];
                }
            }
        }
        return min;
    }

    /**
     * Параллельный вариант с использованием ThreadPoolExecutor.
     * Матрица делится на блоки строк, для каждого блока создаётся задача.
     */
    public static int findMinThreadPool(int[][] matrix) {
        int n = matrix.length;
        int numThreads = Runtime.getRuntime().availableProcessors();
        ExecutorService executor = Executors.newFixedThreadPool(numThreads);
        List<Future<Integer>> futures = new ArrayList<>();

        int blockSize = (n + numThreads - 1) / numThreads;
        for (int t = 0; t < numThreads; t++) {
            int startRow = t * blockSize;
            int endRow = Math.min(n, startRow + blockSize);
            Future<Integer> future = executor.submit(() -> {
                int localMin = Integer.MAX_VALUE;
                for (int i = Math.max(startRow, 1); i < endRow; i++) {
                    for (int j = 0; j < i; j++) {
                        if (matrix[i][j] < localMin) {
                            localMin = matrix[i][j];
                        }
                    }
                }
                return localMin;
            });
            futures.add(future);
        }

        int min = Integer.MAX_VALUE;
        try {
            for (Future<Integer> future : futures) {
                int result = future.get();
                if (result < min) {
                    min = result;
                }
            }
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        executor.shutdown();
        return min;
    }

    /**
     * Параллельный вариант с использованием ForkJoinPool.
     * Создаётся рекурсивная задача, которая делит диапазон строк, если он превышает порог.
     */
    public static int findMinForkJoin(int[][] matrix) {
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        MatrixMinTask task = new MatrixMinTask(matrix, 1, matrix.length);
        int min = forkJoinPool.invoke(task);
        forkJoinPool.shutdown();
        return min;
    }

    /**
     * Класс задачи для ForkJoinPool.
     * Если количество строк в диапазоне меньше порогового значения, производится последовательное вычисление,
     * иначе диапазон делится на две части.
     */
    static class MatrixMinTask extends RecursiveTask<Integer> {
        private static final int THRESHOLD = 5;
        private int[][] matrix;
        private int startRow;
        private int endRow;

        public MatrixMinTask(int[][] matrix, int startRow, int endRow) {
            this.matrix = matrix;
            this.startRow = startRow;
            this.endRow = endRow;
        }

        @Override
        protected Integer compute() {
            if (endRow - startRow <= THRESHOLD) {
                int localMin = Integer.MAX_VALUE;
                for (int i = startRow; i < endRow; i++) {
                    for (int j = 0; j < i; j++) {
                        if (matrix[i][j] < localMin) {
                            localMin = matrix[i][j];
                        }
                    }
                }
                return localMin;
            } else {
                int mid = (startRow + endRow) / 2;
                MatrixMinTask leftTask = new MatrixMinTask(matrix, startRow, mid);
                MatrixMinTask rightTask = new MatrixMinTask(matrix, mid, endRow);
                leftTask.fork();
                int rightResult = rightTask.compute();
                int leftResult = leftTask.join();
                return Math.min(leftResult, rightResult);
            }
        }
    }
}