package com.hithomelabs.princeton1.module6;

import com.hithomelabs.princeton1.module5.ComparableSort;
import com.hithomelabs.princeton1.module5.MeasurableSort;
import com.hithomelabs.princeton1.module5.SortingMetaData;

import java.util.Arrays;
import java.util.Comparator;

public class Merge<E> implements MeasurableSort<E> {
    @Override
    public void sort(E[] arr) {
        coreSortLogic(arr, null, null);
    }

    private void coreSortLogic(E[] arr, Comparator<E> cmp, SortingMetaData metaData) {
        int N = arr.length;
        // * * aux is a helper array required for merge
        E[] aux = Arrays.copyOf(arr, N);
        mergesort(arr, aux, 0, N - 1, cmp, metaData);
    }

    private void mergesort(E[] arr, E[] aux, int lo, int hi, Comparator<E> cmp, SortingMetaData metaData) {

        if (hi <= lo) return;
        int mid = lo + (hi - lo) / 2;
        mergesort(arr, aux, lo, mid, cmp, metaData);
        mergesort(arr, aux, mid + 1, hi, cmp, metaData);
        merge(arr, aux, lo, mid, hi, cmp, metaData);

    }

    private void merge(E[] arr, E[] aux, int lo, int mid, int hi, Comparator<E> cmp, SortingMetaData metaData) {

        // * * creating backup of original array
        for (int i = lo; i <= hi; i++)
            aux[i] = arr[i];

        int i = lo;
        int j = mid + 1;
        for (int k = lo; k <= hi; k++) {
            // * If i has already reached mid, no need to compare we insert at pointer k
            if (i > mid) {
                arr[k] = aux[j++];
                if (metaData != null)
                    metaData.incrementCompares();
            } else if (j > hi) {
                arr[k] = aux[i++];
                if (metaData != null)
                    metaData.incrementCompares();
            } else if (less( aux[i], aux[j], cmp, metaData)) {
                arr[k] = aux[i++];
            }
            else arr[k] = aux[j++];
        }
    }

    @Override
    public void sort(E[] arr, Comparator<E> cmp, SortingMetaData metaData) {
        if (metaData != null)
            metaData.startTime();
        coreSortLogic(arr, cmp, metaData);
        if (metaData != null)
            metaData.endTime();
    }

    @Override
    public void sort(E[] arr, Comparator<E> cmp) {
        coreSortLogic(arr, cmp, null);
    }
}


