namespace Nerfed.Runtime;

public static class SpanExtensions
{
    public static void QuickSort<T>(this Span<T> span, Comparison<T> comparison) {
        QuickSort(span, 0, span.Length - 1, comparison);
    }

    private static void QuickSort<T>(this Span<T> span, int leftIndex, int rightIndex, Comparison<T> comparison) {
        for(;;) {
            int i = leftIndex;
            int j = rightIndex;
            T pivot = span[leftIndex];

            while(i <= j) {
                while(comparison(span[i], pivot) < 0) {
                    i++;
                }

                while(comparison(span[j], pivot) > 0) {
                    j--;
                }

                if(i <= j) {
                    (span[i], span[j]) = (span[j], span[i]);
                    i++;
                    j--;
                }
            }

            if(leftIndex < j) {
                QuickSort(span, leftIndex, j, comparison);
            }

            if(i < rightIndex) {
                leftIndex = i;
                continue;
            }

            break;
        }
    }

    public static void HeapSort<T>(this Span<T> span, Comparison<T> comparison) {
        if(span.Length <= 1) {
            return;
        }

        for(int i = span.Length / 2 - 1; i >= 0; i--) {
            Heapify(span, span.Length, i, comparison);
        }

        for(int i = span.Length - 1; i >= 0; i--) {
            (span[0], span[i]) = (span[i], span[0]);
            Heapify(span, i, 0, comparison);
        }
    }

    private static void Heapify<T>(Span<T> span, int size, int index, Comparison<T> comparison) {
        for(;;) {
            int largestIndex = index;
            int leftChild = 2 * index + 1;
            int rightChild = 2 * index + 2;

            if(leftChild < size && comparison(span[leftChild], span[largestIndex]) > 0) {
                largestIndex = leftChild;
            }

            if(rightChild < size && comparison(span[rightChild], span[largestIndex]) > 0) {
                largestIndex = rightChild;
            }

            if(largestIndex != index) {
                (span[index], span[largestIndex]) = (span[largestIndex], span[index]);
                index = largestIndex;
                continue;
            }

            break;
        }
    }

    public static void MergeSort<T>(this Span<T> span, T[] leftArray, T[] rightArray, Comparison<T> comparison) {
        MergeSort(span, leftArray, rightArray, 0, span.Length - 1, comparison);
    }

    private static void MergeSort<T>(this Span<T> span, T[] leftArray, T[] rightArray, int left, int right, Comparison<T> comparison) {
        if(left < right) {
            int middle = left + (right - left) / 2;
            MergeSort(span, leftArray, rightArray, left, middle, comparison);
            MergeSort(span, leftArray, rightArray, middle + 1, right, comparison);
            MergeArray(span, leftArray, rightArray, left, middle, right, comparison);
        }
    }

    private static void MergeArray<T>(Span<T> span, T[] leftArray, T[] rightArray, int left, int middle, int right, Comparison<T> comparison) {
        int leftArrayLength = middle - left + 1;
        int rightArrayLength = right - middle;
        int i, j;

        for(i = 0; i < leftArrayLength; ++i) {
            leftArray[i] = span[left + i];
        }

        for(j = 0; j < rightArrayLength; ++j) {
            rightArray[j] = span[middle + 1 + j];
        }

        i = 0;
        j = 0;
        int k = left;

        while(i < leftArrayLength && j < rightArrayLength) {
            if(comparison(leftArray[i], rightArray[j]) <= 0) {
                span[k++] = leftArray[i++];
            }
            else {
                span[k++] = rightArray[j++];
            }
        }

        while(i < leftArrayLength) {
            span[k++] = leftArray[i++];
        }

        while(j < rightArrayLength) {
            span[k++] = rightArray[j++];
        }
    }

    public static void InsertionSort<T>(this Span<T> span, Comparison<T> comparison) {
        for(int i = 1; i < span.Length; i++) {
            T key = span[i];
            int flag = 0;
            for(int j = i - 1; j >= 0 && flag != 1;) {
                if(comparison(key, span[j]) < 0) {
                    span[j + 1] = span[j];
                    j--;
                    span[j + 1] = key;
                }
                else {
                    flag = 1;
                }
            }
        }
    }
}