import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        int nThreads = 8;
        //100x100
        var matrix1 = Matrix(100);
        var matrix2 = Matrix(100);
        //Последовательный алгоритм
        long startTime = System.currentTimeMillis();
        int[][] matrixResult = SequentialMult(matrix1, matrix2, matrix1.length);
        long endTime = System.currentTimeMillis();
        System.out.println("Последовательно, 100x100, время выполнения: " + (endTime - startTime) + " миллисекунд");
        //Параллельный алгоритм
        startTime = System.currentTimeMillis();
        matrixResult = ParallelMult(matrix1, matrix2, matrix1.length, nThreads);
        endTime = System.currentTimeMillis();
        System.out.println("Параллельно, 100x100, (" + nThreads + " потоков), время выполнения : " + (endTime - startTime) + " миллисекунд\n");

        //300x300
        matrix1 = Matrix(300);
        matrix2 = Matrix(300);
        //Последовательный алгоритм
        startTime = System.currentTimeMillis();
        matrixResult = SequentialMult(matrix1, matrix2, matrix1.length);
        endTime = System.currentTimeMillis();
        System.out.println("Последовательно, 300x300, время выполнения: " + (endTime - startTime) + " миллисекунд");
        //Параллельный алгоритм
        startTime = System.currentTimeMillis();
        matrixResult = ParallelMult(matrix1, matrix2, matrix1.length, nThreads);
        endTime = System.currentTimeMillis();
        System.out.println("Параллельно, 300x300, (" + nThreads + " потоков), время выполнения : " + (endTime - startTime) + " миллисекунд\n");

        //500x500
        matrix1 = Matrix(500);
        matrix2 = Matrix(500);
        //Последовательный алгоритм
        startTime = System.currentTimeMillis();
        matrixResult = SequentialMult(matrix1, matrix2, matrix1.length);
        endTime = System.currentTimeMillis();
        System.out.println("Последовательно, 500x500, время выполнения: " + (endTime - startTime) + " миллисекунд");
        //Параллельный алгоритм
        startTime = System.currentTimeMillis();
        matrixResult = ParallelMult(matrix1, matrix2, matrix1.length, nThreads);
        endTime = System.currentTimeMillis();
        System.out.println("Параллельно, 500x500, (" + nThreads + " потоков), время выполнения : " + (endTime - startTime) + " миллисекунд\n");
    }

    public static int[][] Matrix(int size) {
        int[][] matrix = new int[size][size];
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                matrix[i][j] = (int) (Math.random() * 1000);
            }
        }
        return matrix;
    }

    public static int[][] SequentialMult(int[][] matrix1, int[][] matrix2, int size) {
        var matrixResult = new int[size][size];
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                for (int m = 0; m < size; m++) {
                    matrixResult[i][j] += matrix1[i][m] * matrix2[m][j];
                }
            }
        }
        return matrixResult;
    }

    public static int[][] ParallelMult(int[][] matrix1, int[][] matrix2, int size, int nThreads) throws InterruptedException {
        var matrixResult = new int[size][size];

        ExecutorService executorService = Executors.newFixedThreadPool(nThreads);
        int blockSize = size / nThreads;

        for (int i = 0; i < nThreads; i++) {
            int startRow = i * blockSize;
            int endRow = (i + 1) * blockSize;
            if (i == nThreads - 1) {
                endRow = size;
            }

            int finalEndRow = endRow;
            executorService.submit(() -> {
                for (int row = startRow; row < finalEndRow; row++) {
                    for (int col = 0; col < size; col++) {
                        for (int m = 0; m < size; m++) {
                            matrixResult[row][col] += matrix1[row][m] * matrix2[m][col];
                        }
                    }
                }
            });
        }

        executorService.shutdown();
        executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);

        return matrixResult;
    }
}