const std = @import("std.zig");
const assert = std.debug.assert;
const testing = std.testing;
const mem = std.mem;
const math = std.math;
pub fn binarySearch(
    comptime T: type,
    key: T,
    items: []const T,
    context: anytype,
    comptime compareFn: fn (context: @TypeOf(context), lhs: T, rhs: T) math.Order,
) ?usize {
    var left: usize = 0;
    var right: usize = items.len;
    while (left < right) {
        
        const mid = left + (right - left) / 2;
        
        switch (compareFn(context, key, items[mid])) {
            .eq => return mid,
            .gt => left = mid + 1,
            .lt => right = mid,
        }
    }
    return null;
}
test "binarySearch" {
    const S = struct {
        fn order_u32(context: void, lhs: u32, rhs: u32) math.Order {
            _ = context;
            return math.order(lhs, rhs);
        }
        fn order_i32(context: void, lhs: i32, rhs: i32) math.Order {
            _ = context;
            return math.order(lhs, rhs);
        }
    };
    try testing.expectEqual(
        @as(?usize, null),
        binarySearch(u32, 1, &[_]u32{}, {}, S.order_u32),
    );
    try testing.expectEqual(
        @as(?usize, 0),
        binarySearch(u32, 1, &[_]u32{1}, {}, S.order_u32),
    );
    try testing.expectEqual(
        @as(?usize, null),
        binarySearch(u32, 1, &[_]u32{0}, {}, S.order_u32),
    );
    try testing.expectEqual(
        @as(?usize, null),
        binarySearch(u32, 0, &[_]u32{1}, {}, S.order_u32),
    );
    try testing.expectEqual(
        @as(?usize, 4),
        binarySearch(u32, 5, &[_]u32{ 1, 2, 3, 4, 5 }, {}, S.order_u32),
    );
    try testing.expectEqual(
        @as(?usize, 0),
        binarySearch(u32, 2, &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.order_u32),
    );
    try testing.expectEqual(
        @as(?usize, 1),
        binarySearch(i32, -4, &[_]i32{ -7, -4, 0, 9, 10 }, {}, S.order_i32),
    );
    try testing.expectEqual(
        @as(?usize, 3),
        binarySearch(i32, 98, &[_]i32{ -100, -25, 2, 98, 99, 100 }, {}, S.order_i32),
    );
}
pub fn insertionSort(
    comptime T: type,
    items: []T,
    context: anytype,
    comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool,
) void {
    var i: usize = 1;
    while (i < items.len) : (i += 1) {
        const x = items[i];
        var j: usize = i;
        while (j > 0 and lessThan(context, x, items[j - 1])) : (j -= 1) {
            items[j] = items[j - 1];
        }
        items[j] = x;
    }
}
pub fn insertionSortContext(len: usize, context: anytype) void {
    var i: usize = 1;
    while (i < len) : (i += 1) {
        var j: usize = i;
        while (j > 0 and context.lessThan(j, j - 1)) : (j -= 1) {
            context.swap(j, j - 1);
        }
    }
}
const Range = struct {
    start: usize,
    end: usize,
    fn init(start: usize, end: usize) Range {
        return Range{
            .start = start,
            .end = end,
        };
    }
    fn length(self: Range) usize {
        return self.end - self.start;
    }
};
const Iterator = struct {
    size: usize,
    power_of_two: usize,
    numerator: usize,
    decimal: usize,
    denominator: usize,
    decimal_step: usize,
    numerator_step: usize,
    fn init(size2: usize, min_level: usize) Iterator {
        const power_of_two = math.floorPowerOfTwo(usize, size2);
        const denominator = power_of_two / min_level;
        return Iterator{
            .numerator = 0,
            .decimal = 0,
            .size = size2,
            .power_of_two = power_of_two,
            .denominator = denominator,
            .decimal_step = size2 / denominator,
            .numerator_step = size2 % denominator,
        };
    }
    fn begin(self: *Iterator) void {
        self.numerator = 0;
        self.decimal = 0;
    }
    fn nextRange(self: *Iterator) Range {
        const start = self.decimal;
        self.decimal += self.decimal_step;
        self.numerator += self.numerator_step;
        if (self.numerator >= self.denominator) {
            self.numerator -= self.denominator;
            self.decimal += 1;
        }
        return Range{
            .start = start,
            .end = self.decimal,
        };
    }
    fn finished(self: *Iterator) bool {
        return self.decimal >= self.size;
    }
    fn nextLevel(self: *Iterator) bool {
        self.decimal_step += self.decimal_step;
        self.numerator_step += self.numerator_step;
        if (self.numerator_step >= self.denominator) {
            self.numerator_step -= self.denominator;
            self.decimal_step += 1;
        }
        return (self.decimal_step < self.size);
    }
    fn length(self: *Iterator) usize {
        return self.decimal_step;
    }
};
const Pull = struct {
    from: usize,
    to: usize,
    count: usize,
    range: Range,
};
pub fn sort(
    comptime T: type,
    items: []T,
    context: anytype,
    comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool,
) void {
    
    var cache: [512]T = undefined;
    if (items.len < 4) {
        if (items.len == 3) {
            
            if (lessThan(context, items[1], items[0])) mem.swap(T, &items[0], &items[1]);
            if (lessThan(context, items[2], items[1])) {
                mem.swap(T, &items[1], &items[2]);
                if (lessThan(context, items[1], items[0])) mem.swap(T, &items[0], &items[1]);
            }
        } else if (items.len == 2) {
            if (lessThan(context, items[1], items[0])) mem.swap(T, &items[0], &items[1]);
        }
        return;
    }
    
    
    
    var iterator = Iterator.init(items.len, 4);
    while (!iterator.finished()) {
        var order = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7 };
        const range = iterator.nextRange();
        const sliced_items = items[range.start..];
        switch (range.length()) {
            8 => {
                swap(T, sliced_items, context, lessThan, &order, 0, 1);
                swap(T, sliced_items, context, lessThan, &order, 2, 3);
                swap(T, sliced_items, context, lessThan, &order, 4, 5);
                swap(T, sliced_items, context, lessThan, &order, 6, 7);
                swap(T, sliced_items, context, lessThan, &order, 0, 2);
                swap(T, sliced_items, context, lessThan, &order, 1, 3);
                swap(T, sliced_items, context, lessThan, &order, 4, 6);
                swap(T, sliced_items, context, lessThan, &order, 5, 7);
                swap(T, sliced_items, context, lessThan, &order, 1, 2);
                swap(T, sliced_items, context, lessThan, &order, 5, 6);
                swap(T, sliced_items, context, lessThan, &order, 0, 4);
                swap(T, sliced_items, context, lessThan, &order, 3, 7);
                swap(T, sliced_items, context, lessThan, &order, 1, 5);
                swap(T, sliced_items, context, lessThan, &order, 2, 6);
                swap(T, sliced_items, context, lessThan, &order, 1, 4);
                swap(T, sliced_items, context, lessThan, &order, 3, 6);
                swap(T, sliced_items, context, lessThan, &order, 2, 4);
                swap(T, sliced_items, context, lessThan, &order, 3, 5);
                swap(T, sliced_items, context, lessThan, &order, 3, 4);
            },
            7 => {
                swap(T, sliced_items, context, lessThan, &order, 1, 2);
                swap(T, sliced_items, context, lessThan, &order, 3, 4);
                swap(T, sliced_items, context, lessThan, &order, 5, 6);
                swap(T, sliced_items, context, lessThan, &order, 0, 2);
                swap(T, sliced_items, context, lessThan, &order, 3, 5);
                swap(T, sliced_items, context, lessThan, &order, 4, 6);
                swap(T, sliced_items, context, lessThan, &order, 0, 1);
                swap(T, sliced_items, context, lessThan, &order, 4, 5);
                swap(T, sliced_items, context, lessThan, &order, 2, 6);
                swap(T, sliced_items, context, lessThan, &order, 0, 4);
                swap(T, sliced_items, context, lessThan, &order, 1, 5);
                swap(T, sliced_items, context, lessThan, &order, 0, 3);
                swap(T, sliced_items, context, lessThan, &order, 2, 5);
                swap(T, sliced_items, context, lessThan, &order, 1, 3);
                swap(T, sliced_items, context, lessThan, &order, 2, 4);
                swap(T, sliced_items, context, lessThan, &order, 2, 3);
            },
            6 => {
                swap(T, sliced_items, context, lessThan, &order, 1, 2);
                swap(T, sliced_items, context, lessThan, &order, 4, 5);
                swap(T, sliced_items, context, lessThan, &order, 0, 2);
                swap(T, sliced_items, context, lessThan, &order, 3, 5);
                swap(T, sliced_items, context, lessThan, &order, 0, 1);
                swap(T, sliced_items, context, lessThan, &order, 3, 4);
                swap(T, sliced_items, context, lessThan, &order, 2, 5);
                swap(T, sliced_items, context, lessThan, &order, 0, 3);
                swap(T, sliced_items, context, lessThan, &order, 1, 4);
                swap(T, sliced_items, context, lessThan, &order, 2, 4);
                swap(T, sliced_items, context, lessThan, &order, 1, 3);
                swap(T, sliced_items, context, lessThan, &order, 2, 3);
            },
            5 => {
                swap(T, sliced_items, context, lessThan, &order, 0, 1);
                swap(T, sliced_items, context, lessThan, &order, 3, 4);
                swap(T, sliced_items, context, lessThan, &order, 2, 4);
                swap(T, sliced_items, context, lessThan, &order, 2, 3);
                swap(T, sliced_items, context, lessThan, &order, 1, 4);
                swap(T, sliced_items, context, lessThan, &order, 0, 3);
                swap(T, sliced_items, context, lessThan, &order, 0, 2);
                swap(T, sliced_items, context, lessThan, &order, 1, 3);
                swap(T, sliced_items, context, lessThan, &order, 1, 2);
            },
            4 => {
                swap(T, sliced_items, context, lessThan, &order, 0, 1);
                swap(T, sliced_items, context, lessThan, &order, 2, 3);
                swap(T, sliced_items, context, lessThan, &order, 0, 2);
                swap(T, sliced_items, context, lessThan, &order, 1, 3);
                swap(T, sliced_items, context, lessThan, &order, 1, 2);
            },
            else => {},
        }
    }
    if (items.len < 8) return;
    
    while (true) {
        
        
        
        
        if (iterator.length() < cache.len) {
            
            
            
            if ((iterator.length() + 1) * 4 <= cache.len and iterator.length() * 4 <= items.len) {
                iterator.begin();
                while (!iterator.finished()) {
                    
                    var A1 = iterator.nextRange();
                    var B1 = iterator.nextRange();
                    var A2 = iterator.nextRange();
                    var B2 = iterator.nextRange();
                    if (lessThan(context, items[B1.end - 1], items[A1.start])) {
                        
                        mem.copy(T, cache[B1.length()..], items[A1.start..A1.end]);
                        mem.copy(T, cache[0..], items[B1.start..B1.end]);
                    } else if (lessThan(context, items[B1.start], items[A1.end - 1])) {
                        
                        mergeInto(T, items, A1, B1, context, lessThan, cache[0..]);
                    } else {
                        
                        if (!lessThan(context, items[B2.start], items[A2.end - 1]) and !lessThan(context, items[A2.start], items[B1.end - 1])) continue;
                        
                        mem.copy(T, cache[0..], items[A1.start..A1.end]);
                        mem.copy(T, cache[A1.length()..], items[B1.start..B1.end]);
                    }
                    A1 = Range.init(A1.start, B1.end);
                    
                    if (lessThan(context, items[B2.end - 1], items[A2.start])) {
                        
                        mem.copy(T, cache[A1.length() + B2.length() ..], items[A2.start..A2.end]);
                        mem.copy(T, cache[A1.length()..], items[B2.start..B2.end]);
                    } else if (lessThan(context, items[B2.start], items[A2.end - 1])) {
                        
                        mergeInto(T, items, A2, B2, context, lessThan, cache[A1.length()..]);
                    } else {
                        
                        mem.copy(T, cache[A1.length()..], items[A2.start..A2.end]);
                        mem.copy(T, cache[A1.length() + A2.length() ..], items[B2.start..B2.end]);
                    }
                    A2 = Range.init(A2.start, B2.end);
                    
                    const A3 = Range.init(0, A1.length());
                    const B3 = Range.init(A1.length(), A1.length() + A2.length());
                    if (lessThan(context, cache[B3.end - 1], cache[A3.start])) {
                        
                        mem.copy(T, items[A1.start + A2.length() ..], cache[A3.start..A3.end]);
                        mem.copy(T, items[A1.start..], cache[B3.start..B3.end]);
                    } else if (lessThan(context, cache[B3.start], cache[A3.end - 1])) {
                        
                        mergeInto(T, cache[0..], A3, B3, context, lessThan, items[A1.start..]);
                    } else {
                        
                        mem.copy(T, items[A1.start..], cache[A3.start..A3.end]);
                        mem.copy(T, items[A1.start + A1.length() ..], cache[B3.start..B3.end]);
                    }
                }
                
                
                _ = iterator.nextLevel();
            } else {
                iterator.begin();
                while (!iterator.finished()) {
                    var A = iterator.nextRange();
                    var B = iterator.nextRange();
                    if (lessThan(context, items[B.end - 1], items[A.start])) {
                        
                        mem.rotate(T, items[A.start..B.end], A.length());
                    } else if (lessThan(context, items[B.start], items[A.end - 1])) {
                        
                        mem.copy(T, cache[0..], items[A.start..A.end]);
                        mergeExternal(T, items, A, B, context, lessThan, cache[0..]);
                    }
                }
            }
        } else {
            
            
            
            
            
            
            
            
            
            
            var block_size: usize = math.sqrt(iterator.length());
            var buffer_size = iterator.length() / block_size + 1;
            
            
            var A: Range = undefined;
            var B: Range = undefined;
            var index: usize = 0;
            var last: usize = 0;
            var count: usize = 0;
            var find: usize = 0;
            var start: usize = 0;
            var pull_index: usize = 0;
            var pull = [_]Pull{
                Pull{
                    .from = 0,
                    .to = 0,
                    .count = 0,
                    .range = Range.init(0, 0),
                },
                Pull{
                    .from = 0,
                    .to = 0,
                    .count = 0,
                    .range = Range.init(0, 0),
                },
            };
            var buffer1 = Range.init(0, 0);
            var buffer2 = Range.init(0, 0);
            
            find = buffer_size + buffer_size;
            var find_separately = false;
            if (block_size <= cache.len) {
                
                
                find = buffer_size;
            } else if (find > iterator.length()) {
                
                find = buffer_size;
                find_separately = true;
            }
            
            
            
            
            
            iterator.begin();
            while (!iterator.finished()) {
                A = iterator.nextRange();
                B = iterator.nextRange();
                
                
                
                
                last = A.start;
                count = 1;
                while (count < find) : ({
                    last = index;
                    count += 1;
                }) {
                    index = findLastForward(T, items, items[last], Range.init(last + 1, A.end), context, lessThan, find - count);
                    if (index == A.end) break;
                }
                index = last;
                if (count >= buffer_size) {
                    
                    pull[pull_index] = Pull{
                        .range = Range.init(A.start, B.end),
                        .count = count,
                        .from = index,
                        .to = A.start,
                    };
                    pull_index = 1;
                    if (count == buffer_size + buffer_size) {
                        
                        
                        buffer1 = Range.init(A.start, A.start + buffer_size);
                        buffer2 = Range.init(A.start + buffer_size, A.start + count);
                        break;
                    } else if (find == buffer_size + buffer_size) {
                        
                        
                        buffer1 = Range.init(A.start, A.start + count);
                        find = buffer_size;
                    } else if (block_size <= cache.len) {
                        
                        buffer1 = Range.init(A.start, A.start + count);
                        break;
                    } else if (find_separately) {
                        
                        buffer1 = Range.init(A.start, A.start + count);
                        find_separately = false;
                    } else {
                        
                        buffer2 = Range.init(A.start, A.start + count);
                        break;
                    }
                } else if (pull_index == 0 and count > buffer1.length()) {
                    
                    buffer1 = Range.init(A.start, A.start + count);
                    pull[pull_index] = Pull{
                        .range = Range.init(A.start, B.end),
                        .count = count,
                        .from = index,
                        .to = A.start,
                    };
                }
                
                
                last = B.end - 1;
                count = 1;
                while (count < find) : ({
                    last = index - 1;
                    count += 1;
                }) {
                    index = findFirstBackward(T, items, items[last], Range.init(B.start, last), context, lessThan, find - count);
                    if (index == B.start) break;
                }
                index = last;
                if (count >= buffer_size) {
                    
                    pull[pull_index] = Pull{
                        .range = Range.init(A.start, B.end),
                        .count = count,
                        .from = index,
                        .to = B.end,
                    };
                    pull_index = 1;
                    if (count == buffer_size + buffer_size) {
                        
                        
                        buffer1 = Range.init(B.end - count, B.end - buffer_size);
                        buffer2 = Range.init(B.end - buffer_size, B.end);
                        break;
                    } else if (find == buffer_size + buffer_size) {
                        
                        
                        buffer1 = Range.init(B.end - count, B.end);
                        find = buffer_size;
                    } else if (block_size <= cache.len) {
                        
                        buffer1 = Range.init(B.end - count, B.end);
                        break;
                    } else if (find_separately) {
                        
                        buffer1 = Range.init(B.end - count, B.end);
                        find_separately = false;
                    } else {
                        
                        
                        if (pull[0].range.start == A.start) pull[0].range.end -= pull[1].count;
                        
                        buffer2 = Range.init(B.end - count, B.end);
                        break;
                    }
                } else if (pull_index == 0 and count > buffer1.length()) {
                    
                    buffer1 = Range.init(B.end - count, B.end);
                    pull[pull_index] = Pull{
                        .range = Range.init(A.start, B.end),
                        .count = count,
                        .from = index,
                        .to = B.end,
                    };
                }
            }
            
            pull_index = 0;
            while (pull_index < 2) : (pull_index += 1) {
                const length = pull[pull_index].count;
                if (pull[pull_index].to < pull[pull_index].from) {
                    
                    index = pull[pull_index].from;
                    count = 1;
                    while (count < length) : (count += 1) {
                        index = findFirstBackward(T, items, items[index - 1], Range.init(pull[pull_index].to, pull[pull_index].from - (count - 1)), context, lessThan, length - count);
                        const range = Range.init(index + 1, pull[pull_index].from + 1);
                        mem.rotate(T, items[range.start..range.end], range.length() - count);
                        pull[pull_index].from = index + count;
                    }
                } else if (pull[pull_index].to > pull[pull_index].from) {
                    
                    index = pull[pull_index].from + 1;
                    count = 1;
                    while (count < length) : (count += 1) {
                        index = findLastForward(T, items, items[index], Range.init(index, pull[pull_index].to), context, lessThan, length - count);
                        const range = Range.init(pull[pull_index].from, index - 1);
                        mem.rotate(T, items[range.start..range.end], count);
                        pull[pull_index].from = index - 1 - count;
                    }
                }
            }
            
            buffer_size = buffer1.length();
            block_size = iterator.length() / buffer_size + 1;
            
            
            
            
            iterator.begin();
            while (!iterator.finished()) {
                A = iterator.nextRange();
                B = iterator.nextRange();
                
                start = A.start;
                if (start == pull[0].range.start) {
                    if (pull[0].from > pull[0].to) {
                        A.start += pull[0].count;
                        
                        
                        
                        if (A.length() == 0) continue;
                    } else if (pull[0].from < pull[0].to) {
                        B.end -= pull[0].count;
                        if (B.length() == 0) continue;
                    }
                }
                if (start == pull[1].range.start) {
                    if (pull[1].from > pull[1].to) {
                        A.start += pull[1].count;
                        if (A.length() == 0) continue;
                    } else if (pull[1].from < pull[1].to) {
                        B.end -= pull[1].count;
                        if (B.length() == 0) continue;
                    }
                }
                if (lessThan(context, items[B.end - 1], items[A.start])) {
                    
                    mem.rotate(T, items[A.start..B.end], A.length());
                } else if (lessThan(context, items[A.end], items[A.end - 1])) {
                    
                    var findA: usize = undefined;
                    
                    var blockA = Range.init(A.start, A.end);
                    var firstA = Range.init(A.start, A.start + blockA.length() % block_size);
                    
                    var indexA = buffer1.start;
                    index = firstA.end;
                    while (index < blockA.end) : ({
                        indexA += 1;
                        index += block_size;
                    }) {
                        mem.swap(T, &items[indexA], &items[index]);
                    }
                    
                    
                    var lastA = firstA;
                    var lastB = Range.init(0, 0);
                    var blockB = Range.init(B.start, B.start + math.min(block_size, B.length()));
                    blockA.start += firstA.length();
                    indexA = buffer1.start;
                    
                    
                    if (lastA.length() <= cache.len) {
                        mem.copy(T, cache[0..], items[lastA.start..lastA.end]);
                    } else if (buffer2.length() > 0) {
                        blockSwap(T, items, lastA.start, buffer2.start, lastA.length());
                    }
                    if (blockA.length() > 0) {
                        while (true) {
                            
                            
                            if ((lastB.length() > 0 and !lessThan(context, items[lastB.end - 1], items[indexA])) or blockB.length() == 0) {
                                
                                const B_split = binaryFirst(T, items, items[indexA], lastB, context, lessThan);
                                const B_remaining = lastB.end - B_split;
                                
                                var minA = blockA.start;
                                findA = minA + block_size;
                                while (findA < blockA.end) : (findA += block_size) {
                                    if (lessThan(context, items[findA], items[minA])) {
                                        minA = findA;
                                    }
                                }
                                blockSwap(T, items, blockA.start, minA, block_size);
                                
                                mem.swap(T, &items[blockA.start], &items[indexA]);
                                indexA += 1;
                                
                                
                                
                                
                                if (lastA.length() <= cache.len) {
                                    mergeExternal(T, items, lastA, Range.init(lastA.end, B_split), context, lessThan, cache[0..]);
                                } else if (buffer2.length() > 0) {
                                    mergeInternal(T, items, lastA, Range.init(lastA.end, B_split), context, lessThan, buffer2);
                                } else {
                                    mergeInPlace(T, items, lastA, Range.init(lastA.end, B_split), context, lessThan);
                                }
                                if (buffer2.length() > 0 or block_size <= cache.len) {
                                    
                                    if (block_size <= cache.len) {
                                        mem.copy(T, cache[0..], items[blockA.start .. blockA.start + block_size]);
                                    } else {
                                        blockSwap(T, items, blockA.start, buffer2.start, block_size);
                                    }
                                    
                                    
                                    
                                    blockSwap(T, items, B_split, blockA.start + block_size - B_remaining, B_remaining);
                                } else {
                                    
                                    mem.rotate(T, items[B_split .. blockA.start + block_size], blockA.start - B_split);
                                }
                                
                                lastA = Range.init(blockA.start - B_remaining, blockA.start - B_remaining + block_size);
                                lastB = Range.init(lastA.end, lastA.end + B_remaining);
                                
                                blockA.start += block_size;
                                if (blockA.length() == 0) break;
                            } else if (blockB.length() < block_size) {
                                
                                
                                mem.rotate(T, items[blockA.start..blockB.end], blockB.start - blockA.start);
                                lastB = Range.init(blockA.start, blockA.start + blockB.length());
                                blockA.start += blockB.length();
                                blockA.end += blockB.length();
                                blockB.end = blockB.start;
                            } else {
                                
                                blockSwap(T, items, blockA.start, blockB.start, block_size);
                                lastB = Range.init(blockA.start, blockA.start + block_size);
                                blockA.start += block_size;
                                blockA.end += block_size;
                                blockB.start += block_size;
                                if (blockB.end > B.end - block_size) {
                                    blockB.end = B.end;
                                } else {
                                    blockB.end += block_size;
                                }
                            }
                        }
                    }
                    
                    if (lastA.length() <= cache.len) {
                        mergeExternal(T, items, lastA, Range.init(lastA.end, B.end), context, lessThan, cache[0..]);
                    } else if (buffer2.length() > 0) {
                        mergeInternal(T, items, lastA, Range.init(lastA.end, B.end), context, lessThan, buffer2);
                    } else {
                        mergeInPlace(T, items, lastA, Range.init(lastA.end, B.end), context, lessThan);
                    }
                }
            }
            
            
            
            
            
            
            
            
            insertionSort(T, items[buffer2.start..buffer2.end], context, lessThan);
            pull_index = 0;
            while (pull_index < 2) : (pull_index += 1) {
                var unique = pull[pull_index].count * 2;
                if (pull[pull_index].from > pull[pull_index].to) {
                    
                    var buffer = Range.init(pull[pull_index].range.start, pull[pull_index].range.start + pull[pull_index].count);
                    while (buffer.length() > 0) {
                        index = findFirstForward(T, items, items[buffer.start], Range.init(buffer.end, pull[pull_index].range.end), context, lessThan, unique);
                        const amount = index - buffer.end;
                        mem.rotate(T, items[buffer.start..index], buffer.length());
                        buffer.start += (amount + 1);
                        buffer.end += amount;
                        unique -= 2;
                    }
                } else if (pull[pull_index].from < pull[pull_index].to) {
                    
                    var buffer = Range.init(pull[pull_index].range.end - pull[pull_index].count, pull[pull_index].range.end);
                    while (buffer.length() > 0) {
                        index = findLastBackward(T, items, items[buffer.end - 1], Range.init(pull[pull_index].range.start, buffer.start), context, lessThan, unique);
                        const amount = buffer.start - index;
                        mem.rotate(T, items[index..buffer.end], amount);
                        buffer.start -= amount;
                        buffer.end -= (amount + 1);
                        unique -= 2;
                    }
                }
            }
        }
        
        if (!iterator.nextLevel()) break;
    }
}
pub fn sortContext(len: usize, context: anytype) void {
    return insertionSortContext(len, context);
}
fn mergeInPlace(
    comptime T: type,
    items: []T,
    A_arg: Range,
    B_arg: Range,
    context: anytype,
    comptime lessThan: fn (@TypeOf(context), T, T) bool,
) void {
    if (A_arg.length() == 0 or B_arg.length() == 0) return;
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    var A = A_arg;
    var B = B_arg;
    while (true) {
        
        const mid = binaryFirst(T, items, items[A.start], B, context, lessThan);
        
        const amount = mid - A.end;
        mem.rotate(T, items[A.start..mid], A.length());
        if (B.end == mid) break;
        
        B.start = mid;
        A = Range.init(A.start + amount, B.start);
        A.start = binaryLast(T, items, items[A.start], A, context, lessThan);
        if (A.length() == 0) break;
    }
}
fn mergeInternal(
    comptime T: type,
    items: []T,
    A: Range,
    B: Range,
    context: anytype,
    comptime lessThan: fn (@TypeOf(context), T, T) bool,
    buffer: Range,
) void {
    
    
    var A_count: usize = 0;
    var B_count: usize = 0;
    var insert: usize = 0;
    if (B.length() > 0 and A.length() > 0) {
        while (true) {
            if (!lessThan(context, items[B.start + B_count], items[buffer.start + A_count])) {
                mem.swap(T, &items[A.start + insert], &items[buffer.start + A_count]);
                A_count += 1;
                insert += 1;
                if (A_count >= A.length()) break;
            } else {
                mem.swap(T, &items[A.start + insert], &items[B.start + B_count]);
                B_count += 1;
                insert += 1;
                if (B_count >= B.length()) break;
            }
        }
    }
    
    blockSwap(T, items, buffer.start + A_count, A.start + insert, A.length() - A_count);
}
fn blockSwap(comptime T: type, items: []T, start1: usize, start2: usize, block_size: usize) void {
    var index: usize = 0;
    while (index < block_size) : (index += 1) {
        mem.swap(T, &items[start1 + index], &items[start2 + index]);
    }
}
fn findFirstForward(
    comptime T: type,
    items: []T,
    value: T,
    range: Range,
    context: anytype,
    comptime lessThan: fn (@TypeOf(context), T, T) bool,
    unique: usize,
) usize {
    if (range.length() == 0) return range.start;
    const skip = math.max(range.length() / unique, @as(usize, 1));
    var index = range.start + skip;
    while (lessThan(context, items[index - 1], value)) : (index += skip) {
        if (index >= range.end - skip) {
            return binaryFirst(T, items, value, Range.init(index, range.end), context, lessThan);
        }
    }
    return binaryFirst(T, items, value, Range.init(index - skip, index), context, lessThan);
}
fn findFirstBackward(
    comptime T: type,
    items: []T,
    value: T,
    range: Range,
    context: anytype,
    comptime lessThan: fn (@TypeOf(context), T, T) bool,
    unique: usize,
) usize {
    if (range.length() == 0) return range.start;
    const skip = math.max(range.length() / unique, @as(usize, 1));
    var index = range.end - skip;
    while (index > range.start and !lessThan(context, items[index - 1], value)) : (index -= skip) {
        if (index < range.start + skip) {
            return binaryFirst(T, items, value, Range.init(range.start, index), context, lessThan);
        }
    }
    return binaryFirst(T, items, value, Range.init(index, index + skip), context, lessThan);
}
fn findLastForward(
    comptime T: type,
    items: []T,
    value: T,
    range: Range,
    context: anytype,
    comptime lessThan: fn (@TypeOf(context), T, T) bool,
    unique: usize,
) usize {
    if (range.length() == 0) return range.start;
    const skip = math.max(range.length() / unique, @as(usize, 1));
    var index = range.start + skip;
    while (!lessThan(context, value, items[index - 1])) : (index += skip) {
        if (index >= range.end - skip) {
            return binaryLast(T, items, value, Range.init(index, range.end), context, lessThan);
        }
    }
    return binaryLast(T, items, value, Range.init(index - skip, index), context, lessThan);
}
fn findLastBackward(
    comptime T: type,
    items: []T,
    value: T,
    range: Range,
    context: anytype,
    comptime lessThan: fn (@TypeOf(context), T, T) bool,
    unique: usize,
) usize {
    if (range.length() == 0) return range.start;
    const skip = math.max(range.length() / unique, @as(usize, 1));
    var index = range.end - skip;
    while (index > range.start and lessThan(context, value, items[index - 1])) : (index -= skip) {
        if (index < range.start + skip) {
            return binaryLast(T, items, value, Range.init(range.start, index), context, lessThan);
        }
    }
    return binaryLast(T, items, value, Range.init(index, index + skip), context, lessThan);
}
fn binaryFirst(
    comptime T: type,
    items: []T,
    value: T,
    range: Range,
    context: anytype,
    comptime lessThan: fn (@TypeOf(context), T, T) bool,
) usize {
    var curr = range.start;
    var size = range.length();
    if (range.start >= range.end) return range.end;
    while (size > 0) {
        const offset = size % 2;
        size /= 2;
        const mid = items[curr + size];
        if (lessThan(context, mid, value)) {
            curr += size + offset;
        }
    }
    return curr;
}
fn binaryLast(
    comptime T: type,
    items: []T,
    value: T,
    range: Range,
    context: anytype,
    comptime lessThan: fn (@TypeOf(context), T, T) bool,
) usize {
    var curr = range.start;
    var size = range.length();
    if (range.start >= range.end) return range.end;
    while (size > 0) {
        const offset = size % 2;
        size /= 2;
        const mid = items[curr + size];
        if (!lessThan(context, value, mid)) {
            curr += size + offset;
        }
    }
    return curr;
}
fn mergeInto(
    comptime T: type,
    from: []T,
    A: Range,
    B: Range,
    context: anytype,
    comptime lessThan: fn (@TypeOf(context), T, T) bool,
    into: []T,
) void {
    var A_index: usize = A.start;
    var B_index: usize = B.start;
    const A_last = A.end;
    const B_last = B.end;
    var insert_index: usize = 0;
    while (true) {
        if (!lessThan(context, from[B_index], from[A_index])) {
            into[insert_index] = from[A_index];
            A_index += 1;
            insert_index += 1;
            if (A_index == A_last) {
                
                mem.copy(T, into[insert_index..], from[B_index..B_last]);
                break;
            }
        } else {
            into[insert_index] = from[B_index];
            B_index += 1;
            insert_index += 1;
            if (B_index == B_last) {
                
                mem.copy(T, into[insert_index..], from[A_index..A_last]);
                break;
            }
        }
    }
}
fn mergeExternal(
    comptime T: type,
    items: []T,
    A: Range,
    B: Range,
    context: anytype,
    comptime lessThan: fn (@TypeOf(context), T, T) bool,
    cache: []T,
) void {
    
    var A_index: usize = 0;
    var B_index: usize = B.start;
    var insert_index: usize = A.start;
    const A_last = A.length();
    const B_last = B.end;
    if (B.length() > 0 and A.length() > 0) {
        while (true) {
            if (!lessThan(context, items[B_index], cache[A_index])) {
                items[insert_index] = cache[A_index];
                A_index += 1;
                insert_index += 1;
                if (A_index == A_last) break;
            } else {
                items[insert_index] = items[B_index];
                B_index += 1;
                insert_index += 1;
                if (B_index == B_last) break;
            }
        }
    }
    
    mem.copy(T, items[insert_index..], cache[A_index..A_last]);
}
fn swap(
    comptime T: type,
    items: []T,
    context: anytype,
    comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool,
    order: *[8]u8,
    x: usize,
    y: usize,
) void {
    if (lessThan(context, items[y], items[x]) or ((order.*)[x] > (order.*)[y] and !lessThan(context, items[x], items[y]))) {
        mem.swap(T, &items[x], &items[y]);
        mem.swap(u8, &(order.*)[x], &(order.*)[y]);
    }
}
pub fn asc(comptime T: type) fn (void, T, T) bool {
    const impl = struct {
        fn inner(context: void, a: T, b: T) bool {
            _ = context;
            return a < b;
        }
    };
    return impl.inner;
}
pub fn desc(comptime T: type) fn (void, T, T) bool {
    const impl = struct {
        fn inner(context: void, a: T, b: T) bool {
            _ = context;
            return a > b;
        }
    };
    return impl.inner;
}
test "stable sort" {
    try testStableSort();
    comptime try testStableSort();
}
fn testStableSort() !void {
    var expected = [_]IdAndValue{
        IdAndValue{ .id = 0, .value = 0 },
        IdAndValue{ .id = 1, .value = 0 },
        IdAndValue{ .id = 2, .value = 0 },
        IdAndValue{ .id = 0, .value = 1 },
        IdAndValue{ .id = 1, .value = 1 },
        IdAndValue{ .id = 2, .value = 1 },
        IdAndValue{ .id = 0, .value = 2 },
        IdAndValue{ .id = 1, .value = 2 },
        IdAndValue{ .id = 2, .value = 2 },
    };
    var cases = [_][9]IdAndValue{
        [_]IdAndValue{
            IdAndValue{ .id = 0, .value = 0 },
            IdAndValue{ .id = 0, .value = 1 },
            IdAndValue{ .id = 0, .value = 2 },
            IdAndValue{ .id = 1, .value = 0 },
            IdAndValue{ .id = 1, .value = 1 },
            IdAndValue{ .id = 1, .value = 2 },
            IdAndValue{ .id = 2, .value = 0 },
            IdAndValue{ .id = 2, .value = 1 },
            IdAndValue{ .id = 2, .value = 2 },
        },
        [_]IdAndValue{
            IdAndValue{ .id = 0, .value = 2 },
            IdAndValue{ .id = 0, .value = 1 },
            IdAndValue{ .id = 0, .value = 0 },
            IdAndValue{ .id = 1, .value = 2 },
            IdAndValue{ .id = 1, .value = 1 },
            IdAndValue{ .id = 1, .value = 0 },
            IdAndValue{ .id = 2, .value = 2 },
            IdAndValue{ .id = 2, .value = 1 },
            IdAndValue{ .id = 2, .value = 0 },
        },
    };
    for (cases) |*case| {
        insertionSort(IdAndValue, (case.*)[0..], {}, cmpByValue);
        for (case.*) |item, i| {
            try testing.expect(item.id == expected[i].id);
            try testing.expect(item.value == expected[i].value);
        }
    }
}
const IdAndValue = struct {
    id: usize,
    value: i32,
};
fn cmpByValue(context: void, a: IdAndValue, b: IdAndValue) bool {
    return asc_i32(context, a.value, b.value);
}
const asc_u8 = asc(u8);
const asc_i32 = asc(i32);
const desc_u8 = desc(u8);
const desc_i32 = desc(i32);
test "sort" {
    const u8cases = [_][]const []const u8{
        &[_][]const u8{
            "",
            "",
        },
        &[_][]const u8{
            "a",
            "a",
        },
        &[_][]const u8{
            "az",
            "az",
        },
        &[_][]const u8{
            "za",
            "az",
        },
        &[_][]const u8{
            "asdf",
            "adfs",
        },
        &[_][]const u8{
            "one",
            "eno",
        },
    };
    for (u8cases) |case| {
        var buf: [8]u8 = undefined;
        const slice = buf[0..case[0].len];
        mem.copy(u8, slice, case[0]);
        sort(u8, slice, {}, asc_u8);
        try testing.expect(mem.eql(u8, slice, case[1]));
    }
    const i32cases = [_][]const []const i32{
        &[_][]const i32{
            &[_]i32{},
            &[_]i32{},
        },
        &[_][]const i32{
            &[_]i32{1},
            &[_]i32{1},
        },
        &[_][]const i32{
            &[_]i32{ 0, 1 },
            &[_]i32{ 0, 1 },
        },
        &[_][]const i32{
            &[_]i32{ 1, 0 },
            &[_]i32{ 0, 1 },
        },
        &[_][]const i32{
            &[_]i32{ 1, -1, 0 },
            &[_]i32{ -1, 0, 1 },
        },
        &[_][]const i32{
            &[_]i32{ 2, 1, 3 },
            &[_]i32{ 1, 2, 3 },
        },
    };
    for (i32cases) |case| {
        var buf: [8]i32 = undefined;
        const slice = buf[0..case[0].len];
        mem.copy(i32, slice, case[0]);
        sort(i32, slice, {}, asc_i32);
        try testing.expect(mem.eql(i32, slice, case[1]));
    }
}
test "sort descending" {
    const rev_cases = [_][]const []const i32{
        &[_][]const i32{
            &[_]i32{},
            &[_]i32{},
        },
        &[_][]const i32{
            &[_]i32{1},
            &[_]i32{1},
        },
        &[_][]const i32{
            &[_]i32{ 0, 1 },
            &[_]i32{ 1, 0 },
        },
        &[_][]const i32{
            &[_]i32{ 1, 0 },
            &[_]i32{ 1, 0 },
        },
        &[_][]const i32{
            &[_]i32{ 1, -1, 0 },
            &[_]i32{ 1, 0, -1 },
        },
        &[_][]const i32{
            &[_]i32{ 2, 1, 3 },
            &[_]i32{ 3, 2, 1 },
        },
    };
    for (rev_cases) |case| {
        var buf: [8]i32 = undefined;
        const slice = buf[0..case[0].len];
        mem.copy(i32, slice, case[0]);
        sort(i32, slice, {}, desc_i32);
        try testing.expect(mem.eql(i32, slice, case[1]));
    }
}
test "another sort case" {
    var arr = [_]i32{ 5, 3, 1, 2, 4 };
    sort(i32, arr[0..], {}, asc_i32);
    try testing.expect(mem.eql(i32, &arr, &[_]i32{ 1, 2, 3, 4, 5 }));
}
test "sort fuzz testing" {
    var prng = std.rand.DefaultPrng.init(0x12345678);
    const random = prng.random();
    const test_case_count = 10;
    var i: usize = 0;
    while (i < test_case_count) : (i += 1) {
        try fuzzTest(random);
    }
}
var fixed_buffer_mem: [100 * 1024]u8 = undefined;
fn fuzzTest(rng: std.rand.Random) !void {
    const array_size = rng.intRangeLessThan(usize, 0, 1000);
    var array = try testing.allocator.alloc(IdAndValue, array_size);
    defer testing.allocator.free(array);
    
    for (array) |*item, index| {
        item.id = index;
        item.value = rng.intRangeLessThan(i32, 0, 100);
    }
    sort(IdAndValue, array, {}, cmpByValue);
    var index: usize = 1;
    while (index < array.len) : (index += 1) {
        if (array[index].value == array[index - 1].value) {
            try testing.expect(array[index].id > array[index - 1].id);
        } else {
            try testing.expect(array[index].value > array[index - 1].value);
        }
    }
}
pub fn argMin(
    comptime T: type,
    items: []const T,
    context: anytype,
    comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool,
) ?usize {
    if (items.len == 0) {
        return null;
    }
    var smallest = items[0];
    var smallest_index: usize = 0;
    for (items[1..]) |item, i| {
        if (lessThan(context, item, smallest)) {
            smallest = item;
            smallest_index = i + 1;
        }
    }
    return smallest_index;
}
test "argMin" {
    try testing.expectEqual(@as(?usize, null), argMin(i32, &[_]i32{}, {}, asc_i32));
    try testing.expectEqual(@as(?usize, 0), argMin(i32, &[_]i32{1}, {}, asc_i32));
    try testing.expectEqual(@as(?usize, 0), argMin(i32, &[_]i32{ 1, 2, 3, 4, 5 }, {}, asc_i32));
    try testing.expectEqual(@as(?usize, 3), argMin(i32, &[_]i32{ 9, 3, 8, 2, 5 }, {}, asc_i32));
    try testing.expectEqual(@as(?usize, 0), argMin(i32, &[_]i32{ 1, 1, 1, 1, 1 }, {}, asc_i32));
    try testing.expectEqual(@as(?usize, 0), argMin(i32, &[_]i32{ -10, 1, 10 }, {}, asc_i32));
    try testing.expectEqual(@as(?usize, 3), argMin(i32, &[_]i32{ 6, 3, 5, 7, 6 }, {}, desc_i32));
}
pub fn min(
    comptime T: type,
    items: []const T,
    context: anytype,
    comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool,
) ?T {
    const i = argMin(T, items, context, lessThan) orelse return null;
    return items[i];
}
test "min" {
    try testing.expectEqual(@as(?i32, null), min(i32, &[_]i32{}, {}, asc_i32));
    try testing.expectEqual(@as(?i32, 1), min(i32, &[_]i32{1}, {}, asc_i32));
    try testing.expectEqual(@as(?i32, 1), min(i32, &[_]i32{ 1, 2, 3, 4, 5 }, {}, asc_i32));
    try testing.expectEqual(@as(?i32, 2), min(i32, &[_]i32{ 9, 3, 8, 2, 5 }, {}, asc_i32));
    try testing.expectEqual(@as(?i32, 1), min(i32, &[_]i32{ 1, 1, 1, 1, 1 }, {}, asc_i32));
    try testing.expectEqual(@as(?i32, -10), min(i32, &[_]i32{ -10, 1, 10 }, {}, asc_i32));
    try testing.expectEqual(@as(?i32, 7), min(i32, &[_]i32{ 6, 3, 5, 7, 6 }, {}, desc_i32));
}
pub fn argMax(
    comptime T: type,
    items: []const T,
    context: anytype,
    comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool,
) ?usize {
    if (items.len == 0) {
        return null;
    }
    var biggest = items[0];
    var biggest_index: usize = 0;
    for (items[1..]) |item, i| {
        if (lessThan(context, biggest, item)) {
            biggest = item;
            biggest_index = i + 1;
        }
    }
    return biggest_index;
}
test "argMax" {
    try testing.expectEqual(@as(?usize, null), argMax(i32, &[_]i32{}, {}, asc_i32));
    try testing.expectEqual(@as(?usize, 0), argMax(i32, &[_]i32{1}, {}, asc_i32));
    try testing.expectEqual(@as(?usize, 4), argMax(i32, &[_]i32{ 1, 2, 3, 4, 5 }, {}, asc_i32));
    try testing.expectEqual(@as(?usize, 0), argMax(i32, &[_]i32{ 9, 3, 8, 2, 5 }, {}, asc_i32));
    try testing.expectEqual(@as(?usize, 0), argMax(i32, &[_]i32{ 1, 1, 1, 1, 1 }, {}, asc_i32));
    try testing.expectEqual(@as(?usize, 2), argMax(i32, &[_]i32{ -10, 1, 10 }, {}, asc_i32));
    try testing.expectEqual(@as(?usize, 1), argMax(i32, &[_]i32{ 6, 3, 5, 7, 6 }, {}, desc_i32));
}
pub fn max(
    comptime T: type,
    items: []const T,
    context: anytype,
    comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool,
) ?T {
    const i = argMax(T, items, context, lessThan) orelse return null;
    return items[i];
}
test "max" {
    try testing.expectEqual(@as(?i32, null), max(i32, &[_]i32{}, {}, asc_i32));
    try testing.expectEqual(@as(?i32, 1), max(i32, &[_]i32{1}, {}, asc_i32));
    try testing.expectEqual(@as(?i32, 5), max(i32, &[_]i32{ 1, 2, 3, 4, 5 }, {}, asc_i32));
    try testing.expectEqual(@as(?i32, 9), max(i32, &[_]i32{ 9, 3, 8, 2, 5 }, {}, asc_i32));
    try testing.expectEqual(@as(?i32, 1), max(i32, &[_]i32{ 1, 1, 1, 1, 1 }, {}, asc_i32));
    try testing.expectEqual(@as(?i32, 10), max(i32, &[_]i32{ -10, 1, 10 }, {}, asc_i32));
    try testing.expectEqual(@as(?i32, 3), max(i32, &[_]i32{ 6, 3, 5, 7, 6 }, {}, desc_i32));
}
pub fn isSorted(
    comptime T: type,
    items: []const T,
    context: anytype,
    comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool,
) bool {
    var i: usize = 1;
    while (i < items.len) : (i += 1) {
        if (lessThan(context, items[i], items[i - 1])) {
            return false;
        }
    }
    return true;
}
test "isSorted" {
    try testing.expect(isSorted(i32, &[_]i32{}, {}, asc_i32));
    try testing.expect(isSorted(i32, &[_]i32{10}, {}, asc_i32));
    try testing.expect(isSorted(i32, &[_]i32{ 1, 2, 3, 4, 5 }, {}, asc_i32));
    try testing.expect(isSorted(i32, &[_]i32{ -10, 1, 1, 1, 10 }, {}, asc_i32));
    try testing.expect(isSorted(i32, &[_]i32{}, {}, desc_i32));
    try testing.expect(isSorted(i32, &[_]i32{-20}, {}, desc_i32));
    try testing.expect(isSorted(i32, &[_]i32{ 3, 2, 1, 0, -1 }, {}, desc_i32));
    try testing.expect(isSorted(i32, &[_]i32{ 10, -10 }, {}, desc_i32));
    try testing.expect(isSorted(i32, &[_]i32{ 1, 1, 1, 1, 1 }, {}, asc_i32));
    try testing.expect(isSorted(i32, &[_]i32{ 1, 1, 1, 1, 1 }, {}, desc_i32));
    try testing.expectEqual(false, isSorted(i32, &[_]i32{ 5, 4, 3, 2, 1 }, {}, asc_i32));
    try testing.expectEqual(false, isSorted(i32, &[_]i32{ 1, 2, 3, 4, 5 }, {}, desc_i32));
    try testing.expect(isSorted(u8, "abcd", {}, asc_u8));
    try testing.expect(isSorted(u8, "zyxw", {}, desc_u8));
    try testing.expectEqual(false, isSorted(u8, "abcd", {}, desc_u8));
    try testing.expectEqual(false, isSorted(u8, "zyxw", {}, asc_u8));
    try testing.expect(isSorted(u8, "ffff", {}, asc_u8));
    try testing.expect(isSorted(u8, "ffff", {}, desc_u8));
}