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

public class MinDivisionTask {
    static double[][] mainArray = new double[4000][4000];
    static double[][] bufferArray = new double[mainArray.length][mainArray.length];
    static final int FRONTIER = 5000; // Порог разбиения
    static final int THREADS = 2; // Кол-во потоков
    static int countOftests = 20; // Кол-во тестов

    public static void main(String[] args) {
        DoTests();
    }
    public static void DoTests() {
        long SingleThreadedTimeSum = 0;
        long ThreadPoolExecutorTimeSum = 0;
        long ForkJoinPoolTimeSum = 0;
        int count = 0;
        Random random = new Random();
        while (count != countOftests) {
            count += 1;
            for (int i = 0; i < mainArray.length; i++) {
                for (int j = 0; j < mainArray.length; j++) {
                    mainArray[i][j] = random.nextInt(-100,100);
                    bufferArray[i][j] = mainArray[i][j];
                }
            }
            //PrintLogMatrix(mainArray); System.out.println();

            // Однопоточный алгоритм
            long SingleThreadedTime = ComputeAndPrintDuration("SingleThreadedAlgorithm");
            SingleThreadedTimeSum += SingleThreadedTime;
            // Многопоточный алгоритм
            long ThreadPoolExecutorTime = ComputeAndPrintDuration("ThreadPoolExecutorAlgorithm");
            ThreadPoolExecutorTimeSum += ThreadPoolExecutorTime;
            // ForkJoinPool-алгоритм
            long ForkJoinPoolTime = ComputeAndPrintDuration("ForkJoinPoolAlgorithm");
            ForkJoinPoolTimeSum += ForkJoinPoolTime;

            System.out.println("{Тест "+ count + "} " + "SingleThreadedTime: " + SingleThreadedTime +
                    " ThreadPoolExecutorTime: " + ThreadPoolExecutorTime + " ForkJoinPoolTime: " + ForkJoinPoolTime);
        }
        System.out.println("SingleThreadedTime: " + SingleThreadedTimeSum/countOftests);
        System.out.println("ThreadPoolExecutorTime: " + ThreadPoolExecutorTimeSum/countOftests);
        System.out.println("ForkJoinPoolTime: " + ForkJoinPoolTimeSum/countOftests);
    }
    public static void CopyArray() {
        for (int i = 0; i < mainArray.length; i++) {
            for (int j = 0; j < mainArray.length; j++) mainArray[i][j] = bufferArray[i][j];
        }
    }
    public static void PrintLogMatrix(double[][] mainArray) {
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                System.out.print(mainArray[i][j] + " ");
            }
            System.out.println();
        }
    }
    public static long ComputeAndPrintDuration(String typeOfAlgoritm) {
        long startTime = 0, endTime = 0, duration;
        switch (typeOfAlgoritm) {
            case "SingleThreadedAlgorithm":
                startTime = System.nanoTime();
                SingleThreadedAlgorithm(mainArray);
                endTime = System.nanoTime();
                break;
            case "ThreadPoolExecutorAlgorithm":
                startTime = System.nanoTime();
                ThreadPoolExecutorAlgorithm(mainArray);
                endTime = System.nanoTime();
                break;
            case "ForkJoinPoolAlgorithm":
                startTime = System.nanoTime();
                ForkJoinPoolAlgorithm(mainArray);
                endTime = System.nanoTime();
                break;
            default:
                break;
        }
        duration = (endTime - startTime) / 1000000;
        //System.out.println("Время выполнения: " + (duration) + " мс\n");
        CopyArray();
        return duration;
    }

    public static void SingleThreadedAlgorithm(double[][] mainArray) {
        //System.out.println("Однопоточный алгоритм");
        double min = Double.MAX_VALUE;
        for (int i = 0; i < mainArray.length; i++) {
            for (int j = 0; j < mainArray.length; j++) {
                if(min > mainArray[i][j]) min = mainArray[i][j];
            }
        }
        //System.out.println("Наименьший элемент: " + min);

        for (int i = 0; i < mainArray.length; i++) {
            for (int j = 0; j < mainArray.length; j++) {
                mainArray[i][j] = mainArray[i][j] / min;
            }
        }

        //PrintLogMatrix(mainArray);
    }
    public static void ThreadPoolExecutorAlgorithm(double[][] mainArray) {
        //System.out.println("ThreadPoolExecutorAlgorithm");
        int size = mainArray.length;
        ExecutorService executor = Executors.newFixedThreadPool(THREADS); // Создаем пул потоков

        AtomicLong globalMin = new AtomicLong(Long.MAX_VALUE);

        // Вычисление суммы элементов массива
        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 localMin = Double.MAX_VALUE;
                // Вычисление суммы в рамках выделенного потоку диапазона строк
                for (int i = startRow; i < endRow; i++) {
                    for (int j = 0; j < size; j++) {
                        if(localMin > mainArray[i][j]) localMin = mainArray[i][j];
                    }
                }
                // Вычисление наименьшего элемента глобально
                long finalLocalMin = (long)localMin;
                globalMin.updateAndGet(v -> Math.min(v, finalLocalMin));
            });
        }

        // Деление элементов массива на наименьшее значение
        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++) {
                        mainArray[i][j] /= globalMin.get();
                    }
                }
            });
        }
        executor.shutdown();
        while (!executor.isTerminated()) { /* Ожидание завершения всех задач */ }

        double min = globalMin.get();
        //System.out.println("Наименьший элемент: " + min);
        //PrintLogMatrix(mainArray);
    }
    public static void ForkJoinPoolAlgorithm(double[][] mainArray) {
        //System.out.println("ForkJoinPoolAlgorithm");
        int size = mainArray.length;
        ForkJoinPool forkJoinPool = new ForkJoinPool();

        // Вычисление наименьшего элемента
        MinTask minTask = new MinTask(mainArray, 0, size);
        double min = forkJoinPool.invoke(minTask);
        //System.out.println("Наименьший элемент: " + min);
        // Деление массива на наименьший элемент
        forkJoinPool.invoke(new DivisionTask(mainArray, 0, size, min));

        forkJoinPool.shutdown();
        //PrintLogMatrix(mainArray);
    }

    static class MinTask extends RecursiveTask<Double> {
        private final double[][] mainArray;
        private final int startRow, endRow;

        MinTask(double[][] mainArray, int startRow, int endRow) {
            this.mainArray = mainArray;
            this.startRow = startRow;
            this.endRow = endRow;
        }

        @Override
        protected Double compute() {
            if (endRow - startRow <= FRONTIER) {
                double min = Double.MAX_VALUE;
                for (int i = startRow; i < endRow; i++) {
                    for (int j = 0; j < mainArray[i].length; j++) {
                        if(min > mainArray[i][j]) min = mainArray[i][j];
                    }
                }
                return (Double) min;
            } else {
                int midRow = (startRow + endRow) / 2;

                MinTask top = new MinTask(mainArray, startRow, midRow);
                MinTask bottom = new MinTask(mainArray, midRow, endRow);

                top.fork();
                double bottomResult = bottom.compute();
                return (Double) Math.min(top.join(), (long)bottomResult);
            }
        }
    }
    static class DivisionTask extends RecursiveAction {
        private final double[][] mainArray;
        private final int startRow, endRow;
        private final double min;

        DivisionTask(double[][] mainArray, int startRow, int endRow, double min) {
            this.mainArray = mainArray;
            this.startRow = startRow;
            this.endRow = endRow;
            this.min = min;
        }

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