package util;

public class Sorter {
    public void sort(int[] array) {
        if (array == null || array.length <= 1) return;

        int n = array.length;
        int[] temp = new int[n];

        while (true) {
            int i = 0;
            int merges = 0;

            while (i < n) {
                // Find first run
                int left = i;
                while (i + 1 < n && array[i] <= array[i + 1]) i++;
                int mid = i;
                i++;

                if (i >= n) break;

                // Find second run
                while (i + 1 < n && array[i] <= array[i + 1]) i++;
                int right = i;
                i++;

                // Merge both runs
                merge(array, temp, left, mid, right);
                merges++;
            }

            if (merges == 0) break;
        }
    }

    private void merge(int[] array, int[] temp, int left, int mid, int right) {
        System.arraycopy(array, left, temp, left, right - left + 1);

        int i = left;
        int j = mid + 1;
        int k = left;

        while (i <= mid && j <= right) {
            if (temp[i] <= temp[j]) {
                array[k++] = temp[i++];
            } else {
                array[k++] = temp[j++];
            }
        }
        while (i <= mid) array[k++] = temp[i++];
        while (j <= right) array[k++] = temp[j++];
    }

    public void mergeSort(int[] array) {
        if (array == null || array.length <= 1) return;
        int[] temp = new int[array.length];
        mergeSortRecursive(array, temp, 0, array.length - 1);
    }

    private void mergeSortRecursive(int[] array, int[] temp, int left, int right) {
        if (left >= right) return;
        int mid = (left + right) >>> 1;
        mergeSortRecursive(array, temp, left, mid);
        mergeSortRecursive(array, temp, mid + 1, right);
        mergeDescending(array, temp, left, mid, right);
    }

    private void mergeDescending(int[] array, int[] temp, int left, int mid, int right) {
        System.arraycopy(array, left, temp, left, right - left + 1);

        int i = left;
        int j = mid + 1;
        int k = left;

        while (i <= mid && j <= right) {
            if (temp[i] >= temp[j]) {
                array[k++] = temp[i++];
            } else {
                array[k++] = temp[j++];
            }
        }
        while (i <= mid) array[k++] = temp[i++];
        while (j <= right) array[k++] = temp[j++];
    }

    public void insertionSort(int[] array) {
        if (array == null || array.length <= 1) return;
        for (int i = 1; i < array.length; i++) {
            int key = array[i];
            int j = i - 1;
            while (j >= 0 && array[j] < key) {
                array[j + 1] = array[j];
                j--;
            }
            array[j + 1] = key;
        }
    }

    public void optimizedMergeSort(int[] array) {
        if (array == null || array.length <= 1) return;
        int[] temp = new int[array.length];
        optimizedMergeSortRecursive(array, temp, 0, array.length - 1);
    }

    private void optimizedMergeSortRecursive(int[] array, int[] temp, int left, int right) {
        if (left >= right) return;
        if (right - left + 1 <= 10) {
            insertionSortRange(array, left, right);
            return;
        }
        int mid = (left + right) >>> 1;
        optimizedMergeSortRecursive(array, temp, left, mid);
        optimizedMergeSortRecursive(array, temp, mid + 1, right);
        mergeDescending(array, temp, left, mid, right);
    }

    private void insertionSortRange(int[] array, int left, int right) {
        for (int i = left + 1; i <= right; i++) {
            int key = array[i];
            int j = i - 1;
            while (j >= left && array[j] < key) {
                array[j + 1] = array[j];
                j--;
            }
            array[j + 1] = key;
        }
    }

    public void testMergeSort() {
        int[] array = new int[100];
        Util util = new Util();
        util.fillArrayRandom(array, 100);
        Sorter mySorter = new Sorter();
        mySorter.mergeSort(array);
        util.printArray(array);
    }

    public void testOptimizedMergeSort() {
        int[] array = new int[100];
        Util util = new Util();
        util.fillArrayRandom(array, 100);
        Sorter mySorter = new Sorter();
        mySorter.optimizedMergeSort(array);
        util.printArray(array);
    }

    public static final void main(String[] args) {
        int[] array = new int[100000000];
        Util util = new Util();
        util.fillArrayRandom(array, 10000000);
        Sorter mySorter = new Sorter();
        mySorter.sort(array);

    }
}
