import java.math.BigInteger;
import java.util.Random;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;

public class MatrixSumOfProducts {

    // Генерация матрицы
    public static int[][] generateMatrix(int rows, int cols, int minValue, int maxValue) {
        int[][] matrix = new int[rows][cols];
        Random random = new Random();
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                matrix[i][j] = minValue + random.nextInt(maxValue - minValue + 1);
            }
        }
        return matrix;
    }

    // Однопоточный алгоритм
    public static BigInteger singleThreadedSum(int[][] matrix) {
        BigInteger totalSum = BigInteger.ZERO;
        for (int[] row : matrix) {
            BigInteger rowProduct = BigInteger.ONE;
            for (int value : row) {
                rowProduct = rowProduct.multiply(BigInteger.valueOf(value));
            }
            totalSum = totalSum.add(rowProduct);
        }
        return totalSum;
    }

    // Параллельный алгоритм с ThreadPoolExecutor
    public static BigInteger multiThreadedSumWithThreadPool(int[][] matrix, int numThreads) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(numThreads);
        AtomicReference<BigInteger> totalSum = new AtomicReference<>(BigInteger.ZERO);

        int rowsPerThread = matrix.length / numThreads;
        for (int i = 0; i < numThreads; i++) {
            int startRow = i * rowsPerThread;
            int endRow = (i == numThreads - 1) ? matrix.length : startRow + rowsPerThread;

            executor.submit(() -> {
                BigInteger localSum = BigInteger.ZERO;
                for (int row = startRow; row < endRow; row++) {
                    BigInteger rowProduct = BigInteger.ONE;
                    for (int value : matrix[row]) {
                        rowProduct = rowProduct.multiply(BigInteger.valueOf(value));
                    }
                    localSum = localSum.add(rowProduct);
                }
                // Используем локальную копию переменной localSum
                BigInteger finalLocalSum = localSum;
                totalSum.updateAndGet(current -> current.add(finalLocalSum));
            });
        }

        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
        return totalSum.get();
    }

    // Параллельный алгоритм с ForkJoinPool
    public static BigInteger multiThreadedSumWithForkJoin(int[][] matrix) {
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        return forkJoinPool.invoke(new MatrixTask(matrix, 0, matrix.length));
    }

    private static class MatrixTask extends RecursiveTask<BigInteger> {
        private final int[][] matrix;
        private final int startRow;
        private final int endRow;
        private static final int THRESHOLD = 100; // Порог для разделения задачи

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

        @Override
        protected BigInteger compute() {
            if (endRow - startRow <= THRESHOLD) {
                BigInteger localSum = BigInteger.ZERO;
                for (int row = startRow; row < endRow; row++) {
                    BigInteger rowProduct = BigInteger.ONE;
                    for (int value : matrix[row]) {
                        rowProduct = rowProduct.multiply(BigInteger.valueOf(value));
                    }
                    localSum = localSum.add(rowProduct);
                }
                return localSum;
            } else {
                int mid = (startRow + endRow) / 2;
                MatrixTask leftTask = new MatrixTask(matrix, startRow, mid);
                MatrixTask rightTask = new MatrixTask(matrix, mid, endRow);
                leftTask.fork();
                BigInteger rightResult = rightTask.compute();
                BigInteger leftResult = leftTask.join();
                return leftResult.add(rightResult);
            }
        }
    }

    // Основной метод
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        int rows = 100;
        int cols = 100;
        int minValue = 1;
        int maxValue = 100;
        int numThreads = Runtime.getRuntime().availableProcessors(); // Используем все доступные ядра

        // Генерация матрицы
        int[][] matrix = generateMatrix(rows, cols, minValue, maxValue);

        // Однопоточный алгоритм
        long startTime = System.nanoTime();
        BigInteger singleThreadedResult = singleThreadedSum(matrix);
        long endTime = System.nanoTime();
        System.out.println("Single-threaded result: " + singleThreadedResult);
        System.out.println("Execution time (single-threaded): " + (endTime - startTime) / 1_000_000 + " ms");

        // Параллельный алгоритм с ThreadPoolExecutor
        startTime = System.nanoTime();
        BigInteger multiThreadedResultThreadPool = multiThreadedSumWithThreadPool(matrix, numThreads);
        endTime = System.nanoTime();
        System.out.println("Multi-threaded result (ThreadPoolExecutor): " + multiThreadedResultThreadPool);
        System.out.println("Execution time (ThreadPoolExecutor): " + (endTime - startTime) / 1_000_000 + " ms");

        // Параллельный алгоритм с ForkJoinPool
        startTime = System.nanoTime();
        BigInteger multiThreadedResultForkJoin = multiThreadedSumWithForkJoin(matrix);
        endTime = System.nanoTime();
        System.out.println("Multi-threaded result (ForkJoinPool): " + multiThreadedResultForkJoin);
        System.out.println("Execution time (ForkJoinPool): " + (endTime - startTime) / 1_000_000 + " ms");
    }
}