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

public class Main {

    public static int[][] generateMatrix(int rows, int cols) {
        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] = random.nextInt(100);
            }
        }
        return matrix;
    }


    public static void writeMatrixToFile(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[][] readMatrixFromFile(String filename, int rows, int cols) throws IOException {
        int[][] matrix = new int[rows][cols];
        try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
            for (int i = 0; i < rows; i++) {
                String[] values = reader.readLine().trim().split(" ");
                for (int j = 0; j < cols; j++) {
                    matrix[i][j] = Integer.parseInt(values[j]);
                }
            }
        }
        return matrix;
    }


    public static void singleThreadedSort(int[][] matrix) {
        int cols = matrix[0].length;
        int rows = matrix.length;

        int[] columnSums = new int[cols];
        for (int j = 0; j < cols; j++) {
            for (int i = 0; i < rows; i++) {
                columnSums[j] += matrix[i][j];
            }
        }

        sortColumns(matrix, columnSums);
    }


    public static void multiThreadedSortWithThreadPool(int[][] matrix, int numThreads) throws InterruptedException {
        int cols = matrix[0].length;
        int rows = matrix.length;


        ExecutorService executor = Executors.newFixedThreadPool(numThreads);

        int[] columnSums = new int[cols];
        for (int j = 0; j < cols; j++) {
            final int col = j;

            executor.submit(() -> {
                for (int i = 0; i < rows; i++) {
                    columnSums[col] += matrix[i][col];
                }
            });
        }

        executor.shutdown();

        executor.awaitTermination(1, TimeUnit.MINUTES);

        sortColumns(matrix, columnSums);
    }

    public static void multiThreadedSortWithForkJoin(int[][] matrix) {
        int cols = matrix[0].length;
        int rows = matrix.length;

        int[] columnSums = new int[cols];
        ForkJoinPool pool = new ForkJoinPool();
        pool.invoke(new ColumnSumTask(matrix, columnSums, 0, cols - 1));

        sortColumns(matrix, columnSums);
    }

    static class ColumnSumTask extends RecursiveAction {
        private final int[][] matrix;
        private final int[] columnSums;
        private final int start;
        private final int end;

        public ColumnSumTask(int[][] matrix, int[] columnSums, int start, int end) {
            this.matrix = matrix;
            this.columnSums = columnSums;
            this.start = start;
            this.end = end;
        }

        @Override
        protected void compute() {
            if (end - start <= 10) {
                for (int j = start; j <= end; j++) {
                    for (int i = 0; i < matrix.length; i++) {
                        columnSums[j] += matrix[i][j];
                    }
                }
            } else {
                int mid = (start + end) / 2;
                ColumnSumTask leftTask = new ColumnSumTask(matrix, columnSums, start, mid);
                ColumnSumTask rightTask = new ColumnSumTask(matrix, columnSums, mid + 1, end);
                invokeAll(leftTask, rightTask);
            }
        }
    }

    private static void sortColumns(int[][] matrix, int[] columnSums) {
        int cols = matrix[0].length;
        int rows = matrix.length;

        Integer[] columnIndices = new Integer[cols];
        for (int j = 0; j < cols; j++) {
            columnIndices[j] = j;
        }

        Arrays.sort(columnIndices, Comparator.comparingInt(j -> columnSums[j]));

        int[][] sortedMatrix = new int[rows][cols];
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                sortedMatrix[i][j] = matrix[i][columnIndices[j]];
            }
        }

        for (int i = 0; i < rows; i++) {
            System.arraycopy(sortedMatrix[i], 0, matrix[i], 0, cols);
        }
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Input lines: ");
        int rows = scanner.nextInt();
        System.out.print("Input columns: ");
        int cols = scanner.nextInt();

        int[][] matrix = generateMatrix(rows, cols);
        writeMatrixToFile(matrix, "input_matrix.txt");

        int[][] matrix1 = readMatrixFromFile("input_matrix.txt", rows, cols);
        int[][] matrix2 = readMatrixFromFile("input_matrix.txt", rows, cols);
        int[][] matrix3 = readMatrixFromFile("input_matrix.txt", rows, cols);

        long startTime = System.currentTimeMillis();
        singleThreadedSort(matrix1);
        long endTime = System.currentTimeMillis();
        System.out.println("One thread: " + (endTime - startTime) + " mc");
        writeMatrixToFile(matrix1, "output_single_thread.txt");

        startTime = System.currentTimeMillis();
        multiThreadedSortWithThreadPool(matrix2, 4);
        endTime = System.currentTimeMillis();
        System.out.println("ThreadPoolExecutor: " + (endTime - startTime) + " mc");
        writeMatrixToFile(matrix2, "output_thread_pool.txt");

        startTime = System.currentTimeMillis();
        multiThreadedSortWithForkJoin(matrix3);
        endTime = System.currentTimeMillis();
        System.out.println("ForkJoinPool: " + (endTime - startTime) + " mc");
        writeMatrixToFile(matrix3, "output_fork_join.txt");
    }
}