import java.util.Random;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;


public class DividingByAverage {
    public static void main(String[] args) {
        Random random = new Random();

        // Создание массива
        double[][] array = new double[8000][8000];
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array.length; j++) {
                array[i][j] = random.nextInt(101);
            }
        }

        // Создание копии исходного массива
        double[][] arraySave = new double[array.length][array.length];
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array.length; j++) {
                arraySave[i][j] = array[i][j];
            }
        }

        // Тест однопоточного алгоритма
        System.out.println("Однопоточный алгоритм:");
        long startTime1 = System.nanoTime();
        singleThreadedAlgorithm(array);
        long endTime1 = System.nanoTime();
        long duration1 = endTime1 - startTime1;
        System.out.println("Время выполнения: " + (duration1 / 1000000) + " мс");
        System.out.println(" ");

        // Восстановление массива
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array.length; j++) {
                array[i][j] = arraySave[i][j];
            }
        }

        // Тест многопоточного алгоритма с ThreadPoolExecutor
        System.out.println("Алгоритм с ThreadPoolExecutor:");
        long startTime2 = System.nanoTime();
        threadPoolExecutorAlgorithm(array);
        long endTime2 = System.nanoTime();
        long duration2 = endTime2 - startTime2;
        System.out.println("Время выполнения: " + (duration2 / 1000000) + " мс");
        System.out.println(" ");

        // Восстановление массива
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array.length; j++) {
                array[i][j] = arraySave[i][j];
            }
        }

        // Тест многопоточного алгоритма с ForkJoinPool
        System.out.println("Алгоритм с ForkJoinPool:");
        long startTime3 = System.nanoTime();
        forkJoinPoolAlgorithm(array);
        long endTime3 = System.nanoTime();
        long duration3 = endTime3 - startTime3;
        System.out.println("Время выполнения: " + (duration3 / 1000000) + " мс");
        System.out.println(" ");

        // Восстановление массива
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array.length; j++) {
                array[i][j] = arraySave[i][j];
            }
        }
    }

    // Однопоточный алгоритм
    public static void singleThreadedAlgorithm(double[][] array) {
        // Сумма всех элементов массива
        double sum = 0;
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array.length; j++) {
                sum += array[i][j];
            }
        }

        // Среднее арифметическое
        double average = (double) sum / (array.length * array.length);

        System.out.println("array[100][100] = " + array[100][100]);
        System.out.println("Среднее арифметическое: " + average);

        // Деление элементов массива на среднее
        for (int i = 0; i < array.length; i++) {
            for (int j = 0; j < array.length; j++) {
                array[i][j] = array[i][j] / average;
            }
        }

        System.out.println("array[100][100] = " + array[100][100]);
    }

    // Тест многопоточного алгоритма с ThreadPoolExecutor
    public static void threadPoolExecutorAlgorithm(double[][] array) {
        int size = array.length;
        // Количество потоков
        int threads = 4;
        // Создаем пул потоков
        ExecutorService executor = Executors.newFixedThreadPool(threads);
        // Используем AtomicLong для безопасного накопления суммы в потоках
        AtomicLong totalSum = new AtomicLong(0);

        // Считаем сумму элементов массива с использованием потоков
        for (int t = 0; t < threads; t++) {
            // Начало потока
            final int startRow = t * (size / threads);
            // Конец потока
            final int endRow = (t == threads - 1) ? size : (t + 1) * (size / threads);
            executor.submit(() -> {
                // Считаем сумму в пределах выделенного потоку диапазона строк
                double localSum = 0;
                for (int i = startRow; i < endRow; i++) {
                    for (int j = 0; j < size; j++) {
                        localSum += array[i][j];
                    }
                }
                // Добавляем локальную сумму в атомарную переменную.
                // Преобразуем в long для атомарного сложения
                totalSum.addAndGet((long) localSum);
            });
        }

        // Делим элементы массива на среднее значение
        for (int t = 0; t < threads; t++) {
            final int startRow = t * (size / threads);
            final int endRow = (t == threads - 1) ? size : (t + 1) * (size / threads);
            executor.submit(() -> {
                // Разделяем элементы массива на среднее значение
                for (int i = startRow; i < endRow; i++) {
                    for (int j = 0; j < size; j++) {
                        array[i][j] /= totalSum.get() / (double) (size * size);
                    }
                }
            });
        }

        // Закрываем пул после того, как все задачи были отправлены
        executor.shutdown();

        // Ожидаем завершения всех задач
        while (!executor.isTerminated()) {
            // Ждем завершения всех задач
        }

        double average = totalSum.get() / (double) (size * size);
        System.out.println("Среднее арифметическое: " + average);
        System.out.println("array[100][100] = " + array[100][100]);
    }

    // Порог разбиения
    private static final int THRESHOLD = 50000;

    // Многопоточный алгоритм с ForkJoinPool
    public static void forkJoinPoolAlgorithm(double[][] array) {
        int size = array.length;
        ForkJoinPool forkJoinPool = new ForkJoinPool();

        // Подсчет суммы
        SumTask sumTask = new SumTask(array, 0, size);
        double totalSum = forkJoinPool.invoke(sumTask);

        // Вычисление среднего
        double average = totalSum / (size * size);
        System.out.println("Среднее арифметическое: " + average);

        // Деление массива
        forkJoinPool.invoke(new NormalizeTask(array, 0, size, average));

        forkJoinPool.shutdown();

        System.out.println("array[100][100] = " + array[100][100]);
    }

    // Подсчет суммы
    static class SumTask extends RecursiveTask<Double> {
        private final double[][] array;
        private final int startRow, endRow;

        SumTask(double[][] array, int startRow, int endRow) {
            this.array = array;
            this.startRow = startRow;
            this.endRow = endRow;
        }

        @Override
        protected Double compute() {
            // Если строк меньше порога, считаем напрямую
            if (endRow - startRow <= THRESHOLD) {
                double sum = 0;
                for (int i = startRow; i < endRow; i++) {
                    for (int j = 0; j < array[i].length; j++) {
                        sum += array[i][j];
                    }
                }
                return sum;
            } else {
                // Разделяем массив на две части
                int midRow = (startRow + endRow) / 2;

                SumTask top = new SumTask(array, startRow, midRow);
                SumTask bottom = new SumTask(array, midRow, endRow);

                // Запускаем верхнюю часть в новом потоке
                top.fork();
                // Вычисляем нижнюю часть в текущем потоке
                double bottomResult = bottom.compute();
                // Ждём завершения верхней части и суммируем
                return top.join() + bottomResult;
            }
        }
    }

    // Деление массива
    static class NormalizeTask extends RecursiveAction {
        private final double[][] array;
        private final int startRow, endRow;
        private final double average;

        NormalizeTask(double[][] array, int startRow, int endRow, double average) {
            this.array = array;
            this.startRow = startRow;
            this.endRow = endRow;
            this.average = average;
        }

        @Override
        protected void compute() {
            if (endRow - startRow <= THRESHOLD) {
                for (int i = startRow; i < endRow; i++) {
                    for (int j = 0; j < array[i].length; j++) {
                        array[i][j] /= average;
                    }
                }
            } else {
                int midRow = (startRow + endRow) / 2;
                invokeAll(new NormalizeTask(array, startRow, midRow, average),
                        new NormalizeTask(array, midRow, endRow, average));
            }
        }
    }
}