#!/usr/bin/env qore
# -*- mode: qore; indent-tabs-mode: nil -*-

%new-style
%enable-all-warnings
%require-types
%strict-args

%requires ../../../../qlib/QUnit.qm

%exec-class SortTest

class SortTest inherits QUnit::Test {
    constructor() : QUnit::Test("SortTest", "1.0") {
        addTestCase("sort() test", \sortTest());
        addTestCase("sort_descending() test", \sortDescendingTest());
        addTestCase("sort_stable() test", \sortStableTest());
        addTestCase("sort_descending_stable() test", \sortDescendingStableTest());
        addTestCase("stability test", \stableTest());
        addTestCase("descending stability test", \descendingStableTest());

        set_return_value(main());
    }

    sortTest() {
        assertEq(list(), sort(list()));
        assertEq((NULL, NULL, NULL), sort((NULL, NULL, NULL)));
        assertEq((NOTHING, NOTHING, NOTHING), sort((NOTHING, NOTHING, NOTHING)));

        assertEq((1,), sort((1,)));
        assertEq((1,1,1,1,1), sort((1,1,1,1,1)));

        assertEq((1,2,3,4,5), sort((1,2,3,4,5)));
        assertEq((1,2,3,4,5), sort((5,4,3,2,1)));
        assertEq((1,2,3,4,5), sort((4,2,5,1,3)));

        assertEq((-5,-4,-3,-2,-1), sort((-5,-4,-3,-2,-1)));
        assertEq((-5,-4,-3,-2,-1), sort((-1,-2,-3,-4,-5)));
        assertEq((-5,-4,-3,-2,-1), sort((-4,-2,-5,-1,-3)));

        assertEq((-5,-3,0,2,5), sort((-5,-3,0,2,5)));
        assertEq((-5,-3,0,2,5), sort((5,2,0,-3,-5)));
        assertEq((-5,-3,0,2,5), sort((-3,2,-5,5,0)));

        assertEq(("abc", "def", "ghi", "xyz"), sort(("abc", "def", "ghi", "xyz")));
        assertEq(("abc", "def", "ghi", "xyz"), sort(("xyz", "ghi", "def", "abc")));
        assertEq(("abc", "def", "ghi", "xyz"), sort(("xyz", "abc", "ghi", "def")));

        assertEq(("a", "ab", "abc", "abcd"), sort(("a", "ab", "abc", "abcd")));
        assertEq(("a", "ab", "abc", "abcd"), sort(("abcd", "abc", "ab", "a")));
        assertEq(("a", "ab", "abc", "abcd"), sort(("ab", "abcd", "a", "abc")));

        assertEq(("abc", "abcd", "abcde", "abcdef"), sort(("abc", "abcd", "abcde", "abcdef")));
        assertEq(("abc", "abcd", "abcde", "abcdef"), sort(("abcdef", "abcde", "abcd", "abc")));
        assertEq(("abc", "abcd", "abcde", "abcdef"), sort(("abcd", "abcdef", "abc", "abcde")));

        assertEq(("1",2,"3",4,"5"), sort(("1",2,"3",4,"5")));
        assertEq(("1",2,"3",4,"5"), sort(("5",4,"3",2,"1")));
        assertEq(("1","2",3,4,5), sort((4,"2",5,"1",3)));

        # int+string comparisons are handled as string+string
        assertEq((1,"2",3,5,7,8,"A","x"), sort((1,"2",3,"A",5,"x",7,8)));
    }

    sortDescendingTest() {
        assertEq(list(), sort_descending(list()));
        assertEq((NULL, NULL, NULL), sort_descending((NULL, NULL, NULL)));
        assertEq((NOTHING, NOTHING, NOTHING), sort_descending((NOTHING, NOTHING, NOTHING)));

        assertEq((1,), sort_descending((1,)));
        assertEq((1,1,1,1,1), sort_descending((1,1,1,1,1)));

        assertEq((5,4,3,2,1), sort_descending((1,2,3,4,5)));
        assertEq((5,4,3,2,1), sort_descending((5,4,3,2,1)));
        assertEq((5,4,3,2,1), sort_descending((4,2,5,1,3)));

        assertEq((-1,-2,-3,-4,-5), sort_descending((-5,-4,-3,-2,-1)));
        assertEq((-1,-2,-3,-4,-5), sort_descending((-1,-2,-3,-4,-5)));
        assertEq((-1,-2,-3,-4,-5), sort_descending((-4,-2,-5,-1,-3)));

        assertEq((5,2,0,-3,-5), sort_descending((-5,-3,0,2,5)));
        assertEq((5,2,0,-3,-5), sort_descending((5,2,0,-3,-5)));
        assertEq((5,2,0,-3,-5), sort_descending((-3,2,-5,5,0)));

        assertEq(("xyz", "ghi", "def", "abc"), sort_descending(("abc", "def", "ghi", "xyz")));
        assertEq(("xyz", "ghi", "def", "abc"), sort_descending(("xyz", "ghi", "def", "abc")));
        assertEq(("xyz", "ghi", "def", "abc"), sort_descending(("xyz", "abc", "ghi", "def")));

        assertEq(("abcd", "abc", "ab", "a"), sort_descending(("a", "ab", "abc", "abcd")));
        assertEq(("abcd", "abc", "ab", "a"), sort_descending(("abcd", "abc", "ab", "a")));
        assertEq(("abcd", "abc", "ab", "a"), sort_descending(("ab", "abcd", "a", "abc")));

        assertEq(("abcdef", "abcde", "abcd", "abc"), sort_descending(("abc", "abcd", "abcde", "abcdef")));
        assertEq(("abcdef", "abcde", "abcd", "abc"), sort_descending(("abcdef", "abcde", "abcd", "abc")));
        assertEq(("abcdef", "abcde", "abcd", "abc"), sort_descending(("abcd", "abcdef", "abc", "abcde")));

        assertEq(("5",4,"3",2,"1"), sort_descending(("1",2,"3",4,"5")));
        assertEq(("5",4,"3",2,"1"), sort_descending(("5",4,"3",2,"1")));
        assertEq((5,4,3,"2","1"), sort_descending((4,"2",5,"1",3)));

        # int+string comparisons are handled as string+string
        assertEq(("x","A",8,7,5,3,"2",1), sort_descending((1,"2",3,"A",5,"x",7,8)));
    }

    sortStableTest() {
        assertEq(list(), sort_stable(list()));
        assertEq((NULL, NULL, NULL), sort_stable((NULL, NULL, NULL)));
        assertEq((NOTHING, NOTHING, NOTHING), sort_stable((NOTHING, NOTHING, NOTHING)));

        assertEq((1,), sort_stable((1,)));
        assertEq((1,1,1,1,1), sort_stable((1,1,1,1,1)));

        assertEq((1,2,3,4,5), sort_stable((1,2,3,4,5)));
        assertEq((1,2,3,4,5), sort_stable((5,4,3,2,1)));
        assertEq((1,2,3,4,5), sort_stable((4,2,5,1,3)));

        assertEq((-5,-4,-3,-2,-1), sort_stable((-5,-4,-3,-2,-1)));
        assertEq((-5,-4,-3,-2,-1), sort_stable((-1,-2,-3,-4,-5)));
        assertEq((-5,-4,-3,-2,-1), sort_stable((-4,-2,-5,-1,-3)));

        assertEq((-5,-3,0,2,5), sort_stable((-5,-3,0,2,5)));
        assertEq((-5,-3,0,2,5), sort_stable((5,2,0,-3,-5)));
        assertEq((-5,-3,0,2,5), sort_stable((-3,2,-5,5,0)));

        assertEq(("abc", "def", "ghi", "xyz"), sort_stable(("abc", "def", "ghi", "xyz")));
        assertEq(("abc", "def", "ghi", "xyz"), sort_stable(("xyz", "ghi", "def", "abc")));
        assertEq(("abc", "def", "ghi", "xyz"), sort_stable(("xyz", "abc", "ghi", "def")));

        assertEq(("a", "ab", "abc", "abcd"), sort_stable(("a", "ab", "abc", "abcd")));
        assertEq(("a", "ab", "abc", "abcd"), sort_stable(("abcd", "abc", "ab", "a")));
        assertEq(("a", "ab", "abc", "abcd"), sort_stable(("ab", "abcd", "a", "abc")));

        assertEq(("abc", "abcd", "abcde", "abcdef"), sort_stable(("abc", "abcd", "abcde", "abcdef")));
        assertEq(("abc", "abcd", "abcde", "abcdef"), sort_stable(("abcdef", "abcde", "abcd", "abc")));
        assertEq(("abc", "abcd", "abcde", "abcdef"), sort_stable(("abcd", "abcdef", "abc", "abcde")));

        assertEq(("1",2,"3",4,"5"), sort_stable(("1",2,"3",4,"5")));
        assertEq(("1",2,"3",4,"5"), sort_stable(("5",4,"3",2,"1")));
        assertEq(("1","2",3,4,5), sort_stable((4,"2",5,"1",3)));

        # int+string comparisons are handled as string+string
        assertEq((1,"2",3,5,7,8,"A","x"), sort_stable((1,"2",3,"A",5,"x",7,8)));
    }

    sortDescendingStableTest() {
        assertEq(list(), sort_descending_stable(list()));
        assertEq((NULL, NULL, NULL), sort_descending_stable((NULL, NULL, NULL)));
        assertEq((NOTHING, NOTHING, NOTHING), sort_descending_stable((NOTHING, NOTHING, NOTHING)));

        assertEq((1,), sort_descending_stable((1,)));
        assertEq((1,1,1,1,1), sort_descending_stable((1,1,1,1,1)));

        assertEq((5,4,3,2,1), sort_descending_stable((1,2,3,4,5)));
        assertEq((5,4,3,2,1), sort_descending_stable((5,4,3,2,1)));
        assertEq((5,4,3,2,1), sort_descending_stable((4,2,5,1,3)));

        assertEq((-1,-2,-3,-4,-5), sort_descending_stable((-5,-4,-3,-2,-1)));
        assertEq((-1,-2,-3,-4,-5), sort_descending_stable((-1,-2,-3,-4,-5)));
        assertEq((-1,-2,-3,-4,-5), sort_descending_stable((-4,-2,-5,-1,-3)));

        assertEq((5,2,0,-3,-5), sort_descending_stable((-5,-3,0,2,5)));
        assertEq((5,2,0,-3,-5), sort_descending_stable((5,2,0,-3,-5)));
        assertEq((5,2,0,-3,-5), sort_descending_stable((-3,2,-5,5,0)));

        assertEq(("xyz", "ghi", "def", "abc"), sort_descending_stable(("abc", "def", "ghi", "xyz")));
        assertEq(("xyz", "ghi", "def", "abc"), sort_descending_stable(("xyz", "ghi", "def", "abc")));
        assertEq(("xyz", "ghi", "def", "abc"), sort_descending_stable(("xyz", "abc", "ghi", "def")));

        assertEq(("abcd", "abc", "ab", "a"), sort_descending_stable(("a", "ab", "abc", "abcd")));
        assertEq(("abcd", "abc", "ab", "a"), sort_descending_stable(("abcd", "abc", "ab", "a")));
        assertEq(("abcd", "abc", "ab", "a"), sort_descending_stable(("ab", "abcd", "a", "abc")));

        assertEq(("abcdef", "abcde", "abcd", "abc"), sort_descending_stable(("abc", "abcd", "abcde", "abcdef")));
        assertEq(("abcdef", "abcde", "abcd", "abc"), sort_descending_stable(("abcdef", "abcde", "abcd", "abc")));
        assertEq(("abcdef", "abcde", "abcd", "abc"), sort_descending_stable(("abcd", "abcdef", "abc", "abcde")));

        assertEq(("5",4,"3",2,"1"), sort_descending_stable(("1",2,"3",4,"5")));
        assertEq(("5",4,"3",2,"1"), sort_descending_stable(("5",4,"3",2,"1")));
        assertEq((5,4,3,"2","1"), sort_descending_stable((4,"2",5,"1",3)));

        # int+string comparisons are handled as string+string
        assertEq(("x","A",8,7,5,3,"2",1), sort_descending_stable((1,"2",3,"A",5,"x",7,8)));
    }

    stableTest() {
        hash values = {
            "1": 10,
            "2":  5,
            "3":  3,
            "4": -2,
            "5":  5,
            "6":  8,
            "7":  6,
            "8":  5,
            "9": -4,
            "10": 3,
            "11": 6,
            "12": 3,
        };

        list correctlySorted = (
            {"id": "9",  "value": -4},
            {"id": "4",  "value": -2},
            {"id": "3",  "value":  3},
            {"id": "10", "value":  3},
            {"id": "12", "value":  3},
            {"id": "2",  "value":  5},
            {"id": "5",  "value":  5},
            {"id": "8",  "value":  5},
            {"id": "7",  "value":  6},
            {"id": "11", "value":  6},
            {"id": "6",  "value":  8},
            {"id": "1",  "value": 10},
        );

        list unsorted;
        map unsorted += {"id": $1.key, "value": $1.value}, values.pairIterator();

        code sortFunc = int sub (hash l, hash r) { return l.value <=> r.value; };
        list stableSorted = sort_stable(unsorted, sortFunc);
        list unstableSorted = sort(unsorted, sortFunc);

        assertEq(correctlySorted, stableSorted);
        assertNeq(correctlySorted, unstableSorted);
    }

    descendingStableTest() {
        hash values = {
            "1": 10,
            "2":  5,
            "3":  3,
            "4": -2,
            "5":  5,
            "6":  8,
            "7":  6,
            "8":  5,
            "9": -4,
            "10": 3,
            "11": 6,
            "12": 3,
        };

        list correctlySorted = (
            {"id": "1",  "value": 10},
            {"id": "6",  "value":  8},
            {"id": "7",  "value":  6},
            {"id": "11", "value":  6},
            {"id": "2",  "value":  5},
            {"id": "5",  "value":  5},
            {"id": "8",  "value":  5},
            {"id": "3",  "value":  3},
            {"id": "10", "value":  3},
            {"id": "12", "value":  3},
            {"id": "4",  "value": -2},
            {"id": "9",  "value": -4},
        );

        list unsorted;
        map unsorted += {"id": $1.key, "value": $1.value}, values.pairIterator();

        code sortFunc = int sub (hash l, hash r) { return l.value <=> r.value; };
        list stableSorted = sort_descending_stable(unsorted, sortFunc);
        list unstableSorted = sort_descending(unsorted, sortFunc);

        assertEq(correctlySorted, stableSorted);
        assertNeq(correctlySorted, unstableSorted);
    }
}
