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

public class MatrixSorter {

    public static char[][] generateMatrix(int rows, int cols) {
        char[][] matrix = new char[rows][cols];
        Random random = new Random();
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                matrix[i][j] = (char) random.nextInt(100);
            }
        }
        return matrix;
    }

    public static char[][] copyMatrix(char[][] matrix) {
        char[][] copy = new char[matrix.length][matrix[0].length];
        for (int i = 0; i < matrix.length; i++) {
            System.arraycopy(matrix[i], 0, copy[i], 0, matrix[i].length);
        }
        return copy;
    }

    public static void printMatrix(char[][] matrix) {
        for (char[] row : matrix) {
            for (char value : row) {
                System.out.print((int) value + " ");
            }
            System.out.println();
        }
    }

    public interface RowSumCalculator {
        int[] calculateRowSums(char[][] matrix) throws Exception;
    }

    public interface RowSorter {
        Integer[] sortIndices(int[] rowSums) throws Exception;
    }

    public static class SingleThreadRowSumCalculator implements RowSumCalculator {
        @Override
        public int[] calculateRowSums(char[][] matrix) {
            int rows = matrix.length;
            int[] sums = new int[rows];
            for (int i = 0; i < rows; i++) {
                int sum = 0;
                for (char value : matrix[i]) {
                    sum += value;
                }
                sums[i] = sum;
            }
            return sums;
        }
    }

    public static class ThreadPoolRowSumCalculator implements RowSumCalculator {
        private ExecutorService executor;

        public ThreadPoolRowSumCalculator(ExecutorService executor) {
            this.executor = executor;
        }

        @Override
        public int[] calculateRowSums(char[][] matrix) throws Exception {
            int rows = matrix.length;
            int[] sums = new int[rows];
            @SuppressWarnings("unchecked")
            Future<Integer>[] futures = new Future[rows];

            for (int i = 0; i < rows; i++) {
                final int row = i;
                futures[i] = executor.submit(() -> {
                    int sum = 0;
                    for (char value : matrix[row]) {
                        sum += value;
                    }
                    return sum;
                });
            }

            for (int i = 0; i < rows; i++) {
                sums[i] = futures[i].get();
            }
            return sums;
        }
    }

    public static class ForkJoinRowSumCalculator implements RowSumCalculator {
        private static final int THRESHOLD = 100;
        private ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());

        @Override
        public int[] calculateRowSums(char[][] matrix) {
            int rows = matrix.length;
            int[] sums = new int[rows];
            pool.invoke(new RowSumTask(matrix, 0, rows, sums));
            return sums;
        }

        private static class RowSumTask extends RecursiveAction {
            private char[][] matrix;
            private int start, end;
            private int[] sums;

            public RowSumTask(char[][] matrix, int start, int end, int[] sums) {
                this.matrix = matrix;
                this.start = start;
                this.end = end;
                this.sums = sums;
            }

            @Override
            protected void compute() {
                if (end - start <= THRESHOLD) {
                    for (int i = start; i < end; i++) {
                        int sum = 0;
                        for (char value : matrix[i]) {
                            sum += value;
                        }
                        sums[i] = sum;
                    }
                } else {
                    int mid = (start + end) / 2;
                    RowSumTask leftTask = new RowSumTask(matrix, start, mid, sums);
                    RowSumTask rightTask = new RowSumTask(matrix, mid, end, sums);
                    invokeAll(leftTask, rightTask);
                }
            }
        }
    }

    public static class SingleThreadRowSorter implements RowSorter {
        @Override
        public Integer[] sortIndices(int[] rowSums) {
            int n = rowSums.length;
            Integer[] indices = new Integer[n];
            for (int i = 0; i < n; i++) {
                indices[i] = i;
            }
            mergeSort(indices, rowSums, 0, n - 1);
            return indices;
        }

        private void mergeSort(Integer[] indices, int[] rowSums, int left, int right) {
            if (left < right) {
                int mid = left + (right - left) / 2;
                mergeSort(indices, rowSums, left, mid);
                mergeSort(indices, rowSums, mid + 1, right);
                merge(indices, rowSums, left, mid, right);
            }
        }

        private void merge(Integer[] indices, int[] rowSums, int left, int mid, int right) {
            int leftSize = mid - left + 1;
            int rightSize = right - mid;
            Integer[] leftArr = new Integer[leftSize];
            Integer[] rightArr = new Integer[rightSize];
            System.arraycopy(indices, left, leftArr, 0, leftSize);
            System.arraycopy(indices, mid + 1, rightArr, 0, rightSize);

            int i = 0, j = 0, k = left;
            while (i < leftSize && j < rightSize) {
                if (rowSums[leftArr[i]] >= rowSums[rightArr[j]]) {
                    indices[k++] = leftArr[i++];
                } else {
                    indices[k++] = rightArr[j++];
                }
            }
            while (i < leftSize) {
                indices[k++] = leftArr[i++];
            }
            while (j < rightSize) {
                indices[k++] = rightArr[j++];
            }
        }
    }

    public static class ForkJoinRowSorter implements RowSorter {
        private static final int THRESHOLD = 100;
        private ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());

        @Override
        public Integer[] sortIndices(int[] rowSums) {
            int n = rowSums.length;
            Integer[] indices = new Integer[n];
            for (int i = 0; i < n; i++) {
                indices[i] = i;
            }
            pool.invoke(new MergeSortTask(indices, rowSums, 0, n - 1));
            return indices;
        }

        private static class MergeSortTask extends RecursiveAction {
            private Integer[] indices;
            private int[] rowSums;
            private int left, right;

            public MergeSortTask(Integer[] indices, int[] rowSums, int left, int right) {
                this.indices = indices;
                this.rowSums = rowSums;
                this.left = left;
                this.right = right;
            }

            @Override
            protected void compute() {
                if (right - left <= THRESHOLD) {
                    Arrays.sort(indices, left, right + 1,
                            Comparator.comparingInt((Integer i) -> rowSums[i]).reversed());
                } else {
                    int mid = left + (right - left) / 2;
                    MergeSortTask leftTask = new MergeSortTask(indices, rowSums, left, mid);
                    MergeSortTask rightTask = new MergeSortTask(indices, rowSums, mid + 1, right);
                    invokeAll(leftTask, rightTask);
                    merge(indices, rowSums, left, mid, right);
                }
            }

            private void merge(Integer[] indices, int[] rowSums, int left, int mid, int right) {
                Integer[] temp = new Integer[right - left + 1];
                int i = left, j = mid + 1, k = 0;
                while (i <= mid && j <= right) {
                    if (rowSums[indices[i]] >= rowSums[indices[j]]) {
                        temp[k++] = indices[i++];
                    } else {
                        temp[k++] = indices[j++];
                    }
                }
                while (i <= mid) {
                    temp[k++] = indices[i++];
                }
                while (j <= right) {
                    temp[k++] = indices[j++];
                }
                System.arraycopy(temp, 0, indices, left, temp.length);
            }
        }
    }

    public static class ThreadPoolRowSorter implements RowSorter {
        private ExecutorService executor;
        private int THRESHOLD;
    
        public ThreadPoolRowSorter(ExecutorService executor, int threshold) {
            this.executor = executor;
            this.THRESHOLD = threshold;
        }
    
        @Override
        public Integer[] sortIndices(int[] rowSums) throws Exception {
            int n = rowSums.length;
            Integer[] indices = new Integer[n];
            for (int i = 0; i < n; i++) {
                indices[i] = i;
            }
            sort(indices, rowSums, 0, n - 1).get();
            return indices;
        }
    
        private CompletableFuture<Void> sort(Integer[] indices, int[] rowSums, int left, int right) {
            if (right - left <= THRESHOLD) {
                Arrays.sort(indices, left, right + 1,
                        Comparator.comparingInt((Integer i) -> rowSums[i]).reversed());
                return CompletableFuture.completedFuture(null);
            } else {
                int mid = left + (right - left) / 2;
                CompletableFuture<Void> leftFuture = sort(indices, rowSums, left, mid);
                CompletableFuture<Void> rightFuture = sort(indices, rowSums, mid + 1, right);
                return CompletableFuture.allOf(leftFuture, rightFuture)
                        .thenRunAsync(() -> merge(indices, rowSums, left, mid, right), executor);
            }
        }
    
        private void merge(Integer[] indices, int[] rowSums, int left, int mid, int right) {
            Integer[] temp = new Integer[right - left + 1];
            int i = left, j = mid + 1, k = 0;
            while (i <= mid && j <= right) {
                if (rowSums[indices[i]] >= rowSums[indices[j]]) {
                    temp[k++] = indices[i++];
                } else {
                    temp[k++] = indices[j++];
                }
            }
            while (i <= mid) {
                temp[k++] = indices[i++];
            }
            while (j <= right) {
                temp[k++] = indices[j++];
            }
            System.arraycopy(temp, 0, indices, left, temp.length);
        }
    }

    public static class CombinedMatrixSorter {
        private RowSumCalculator sumCalculator;
        private RowSorter rowSorter;

        public CombinedMatrixSorter(RowSumCalculator sumCalculator, RowSorter rowSorter) {
            this.sumCalculator = sumCalculator;
            this.rowSorter = rowSorter;
        }

        public long sort(char[][] matrix) throws Exception {
            long startTime = System.currentTimeMillis();
            int[] rowSums = sumCalculator.calculateRowSums(matrix);
            Integer[] sortedIndices = rowSorter.sortIndices(rowSums);
            long endTime = System.currentTimeMillis();
            char[][] sortedMatrix = reorderMatrix(matrix, sortedIndices);
            return endTime - startTime;
        }

        private char[][] reorderMatrix(char[][] matrix, Integer[] indices) {
            char[][] sortedMatrix = new char[matrix.length][];
            for (int i = 0; i < indices.length; i++) {
                sortedMatrix[i] = matrix[indices[i]];
            }
            return sortedMatrix;
        }
    }

    public static void main(String[] args) throws Exception {
        int[] rowCounts = {10, 100, 1000, 10000, 100000, 1000000};
        int[] colCounts = {1000000, 100000, 10000, 1000, 100, 10};

        ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

        Map<String, RowSumCalculator> sumCalculators = new LinkedHashMap<>();
        sumCalculators.put("Single", new SingleThreadRowSumCalculator());
        sumCalculators.put("ThreadPool", new ThreadPoolRowSumCalculator(executor));
        sumCalculators.put("ForkJoin", new ForkJoinRowSumCalculator());

        Map<String, RowSorter> rowSorters = new LinkedHashMap<>();
        rowSorters.put("Single", new SingleThreadRowSorter());
        rowSorters.put("ThreadPool", new ThreadPoolRowSorter(executor, 100));
        rowSorters.put("ForkJoin", new ForkJoinRowSorter());

        int trials = 10;

        for (int sizeIndex = 0; sizeIndex < rowCounts.length; sizeIndex++) {
            int rows = rowCounts[sizeIndex];
            int cols = colCounts[sizeIndex];
            System.out.printf("Matrix size: %d x %d%n", rows, cols);

            for (Map.Entry<String, RowSumCalculator> sumEntry : sumCalculators.entrySet()) {
                for (Map.Entry<String, RowSorter> sortEntry : rowSorters.entrySet()) {
                    long totalTime = 0;
                    for (int t = 0; t < trials; t++) {
                        char[][] matrix = generateMatrix(rows, cols);
                        CombinedMatrixSorter sorter = new CombinedMatrixSorter(sumEntry.getValue(), sortEntry.getValue());
                        long time = sorter.sort(matrix);
                        totalTime += time;
                    }
                    System.out.printf("Summation: %-10s | Sorting: %-10s -> Average Time: %d ms%n",
                            sumEntry.getKey(), sortEntry.getKey(), totalTime / trials);
                }
            }
            System.out.println();
        }

        executor.shutdown();
    }
}
