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

public class Main {

    public static int[][] generateMatrix(int size) {
        int[][] matrix = new int[size][size];
        Random rnd = new Random();
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                matrix[i][j] = rnd.nextInt(200);
            }
        }
        return matrix;
    }

    public static void writeMatrix(int[][] matrix, String filename) throws IOException {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
            for (int[] row : matrix) {
                for (int value : row) {
                    writer.write(value + " ");
                }
                writer.newLine();
            }
        }
    }

    public static int[][] readMatrix(String filename, int size) throws IOException {
        int[][] matrix = new int[size][size];
        try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
            for (int i = 0; i < size; i++) {
                String[] values = reader.readLine().trim().split(" ");
                for (int j = 0; j < size; j++) {
                    matrix[i][j] = Integer.parseInt(values[j]);
                }
            }
        }
        return matrix;
    }

    public static double singleThreaded(int[][] matrix) {
        int len = matrix.length;
        if (len == 0) {
            return 0;
        }

        double totalSum = 0;
        int totalCount = 0;

        for (int i = 0; i < len; i++) {
            for (int j = 0; j < len; j++) {
                if (i > j) {
                    totalSum += matrix[i][j];
                    totalCount++;
                }
            }
        }

        return totalCount == 0 ? 0 : totalSum / totalCount;
    }

    private static class PartialResult {
        double sum;
        int count;

        public PartialResult(double sum, int count) {
            this.sum = sum;
            this.count = count;
        }
    }

    public static double ThreadPoolExecutor(int[][] matrix, int numThreads) throws InterruptedException {
        int len = matrix.length;
        if (len == 0) {
            return 0;
        }

        ExecutorService executor = Executors.newFixedThreadPool(numThreads);
        List<Future<PartialResult>> futures = new ArrayList<>();
        for (int i = 0; i < len; i++) {
            final int row = i;
            Callable<PartialResult> task = () -> {
                double localSum = 0;
                int localCount = 0;
                for (int j = 0; j < len; j++) {
                    if (row > j) {
                        localSum += matrix[row][j];
                        localCount++;
                    }
                }
                return new PartialResult(localSum, localCount);
            };
            futures.add(executor.submit(task));
        }
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
        double totalSum = 0;
        int totalCount = 0;
        for (Future<PartialResult> future : futures) {
            try {
                PartialResult result = future.get();
                totalSum += result.sum;
                totalCount += result.count;
            } catch (ExecutionException e) {
                e.printStackTrace();
                return 0;
            }
        }
        return totalCount == 0 ? 0 : totalSum / totalCount;
    }

    public static double ForkJoinPool(int[][] matrix) {
        int rows = matrix.length;
        if (rows == 0) {
            return 0.0;
        }

        ForkJoinPool pool = new ForkJoinPool();
        PartialResult result = pool.invoke(new ForkJoinTask(matrix, 0, rows));

        return result.count == 0 ? 0 : result.sum / result.count;
    }

    private static class ForkJoinTask extends RecursiveTask<PartialResult> {
        private final int[][] matrix;
        private final int startRow;
        private final int endRow;

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

        @Override
        protected PartialResult compute() {
            if (endRow - startRow <= 100) {
                double sum = 0;
                int count = 0;
                for (int i = startRow; i < endRow; i++) {
                    for (int j = 0; j < i; j++) {
                        sum += matrix[i][j];
                        count++;
                    }
                }
                return new PartialResult(sum, count);
            } else {
                int mid = (startRow + endRow) / 2;
                ForkJoinTask left = new ForkJoinTask(matrix, startRow, mid);
                ForkJoinTask right = new ForkJoinTask(matrix, mid, endRow);
                left.fork();
                PartialResult rightResult = right.compute();
                PartialResult leftResult = left.join();

                double totalSum = leftResult.sum + rightResult.sum;
                int totalCount = leftResult.count + rightResult.count;

                return new PartialResult(totalSum, totalCount);
            }
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        Scanner scanner = new Scanner(System.in);
        int size;

        try {
            System.out.print("Введите размер матрицы (квадратной): ");
            if (!scanner.hasNextInt()) {
                throw new IllegalArgumentException("Ошибка: Вы должны ввести целое число.");
            }
            size = scanner.nextInt();

            if (size <= 0) {
                throw new IllegalArgumentException("Ошибка: Размер матрицы должен быть положительным числом.");
            }
        } catch (IllegalArgumentException e) {
            System.err.println(e.getMessage());
            return;
        }

        int[][] matrix = generateMatrix(size);
        writeMatrix(matrix, "Matrix.txt");

        int[][] matrixRead = readMatrix("Matrix.txt", size);

        long startTime = System.currentTimeMillis();
        double singleThreadedResult = singleThreaded(matrixRead);
        long endTime = System.currentTimeMillis();
        System.out.println("Однопоточный алгоритм: " + (endTime - startTime) + " мс, Среднее арифметическое: " + singleThreadedResult);

        startTime = System.currentTimeMillis();
        double threadPoolResult = ThreadPoolExecutor(matrixRead, 4);
        endTime = System.currentTimeMillis();
        System.out.println("Многопоточный алгоритм с использованием ThreadPoolExecutor: " + (endTime - startTime) + " мс, Среднее арифметическое: " + threadPoolResult);

        startTime = System.currentTimeMillis();
        double ForkJoinResult = ForkJoinPool(matrixRead);
        endTime = System.currentTimeMillis();

        System.out.println("Многопоточный алгоритм с использованием ForkJoinPool: " + (endTime - startTime) + " мс, Среднее арифметическое: " + ForkJoinResult);

    }
}
