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

%new-style
%enable-all-warnings
%require-types
%strict-args
%no-child-restrictions

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

%exec-class OverloadTest

hashdecl MyHash {
    int x = 1;
}

sub o0(...) {
}

int sub sl_test_1(int i, softlist<*Mutex> l) {
    delete argv;
    return 1;
}

int sub sl_test_2(int i, *softlist<*Mutex> l) {
    delete argv;
    return 1;
}

string sub f1_test(string x) {
    return type(x);
}

string sub f1_test(float x) {
    return type(x);
}

string sub f_test(int x) {
    return type(x);
}

string sub f_test(float x) {
    return type(x);
}

int sub f2_test(*int i) {
    return -1;
}

int sub f2_test() {
    return 0;
}

int sub f3_test(*int i, *int j) {
    return -1;
}

int sub f3_test() {
    return 0;
}

int sub f4_test() {
    delete argv;
    return -1;
}

int sub f4_test(list l) {
    return 0;
}

int sub f5_test(softlist l, *object o) {
    return -1;
}

int sub f5_test(string str) {
    return 0;
}

int sub f6(int i, *string s1, *int i1, *hash h1) {
    return 4;
}

int sub f6(int i, *string s1, *int i1) {
    return 3;
}

int sub f6(int i, *string s1) {
    return 2;
}

int sub f6(int i) {
    return 1;
}

int sub f7(hash h) {
    return 1;
}

int sub f7(hash<string, int> h) {
    return 2;
}

int sub f7(hash<MyHash> h) {
    return 3;
}

int sub f8(any i) {
    return 0;
}

int sub f8(hash h) {
    return 1;
}

int sub f8(hash<string, int> h) {
    return 2;
}

int sub f9(any x) {
    return 0;
}

int sub f9(softint i) {
    return 1;
}

int sub f10(auto x) {
    return 0;
}

int sub f10(softint i) {
    return 1;
}

sub issue_4419_t0(auto x) {
    throw "X";
}

sub issue_4419_t0(Mutex x) {
}

sub issue_4419_t1(hash<auto> x) {
    throw "X";
}

sub issue_4419_t1(hash<string, Mutex> x) {
}

sub issue_4419_t2(list<auto> x) {
    throw "X";
}

sub issue_4419_t2(list<Mutex> x) {
}

public class OverloadTest inherits QUnit::Test {
    public {
        const Api = "hash sub p() { return {}; }*hash sub p(string a) {return {};}any sub p(string a, string b) { return 1; }";
    }

    constructor() : Test("Overload test", "1.0") {
        addTestCase("param type err", \paramTypeErrorTest());
        addTestCase("varargs", \varargsTest());
        addTestCase("reference matching", \referenceMatching());
        addTestCase("issue 4419", \issue4419());
        addTestCase("softlist overload test", \softlistOverloadTest());
        addTestCase("issue 3861", \issue3861());
        addTestCase("issue 3441", \issue3441());
        addTestCase("Values test", \testValues());
        addTestCase("Variables test", \testVariables());
        addTestCase("issue 1928", \issue1928());
        addTestCase("Overload test", \testOverload());

        # Return for compatibility with test harness that checks return value.
        set_return_value(main());
    }

    paramTypeErrorTest() {
        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", "argument 'opts'", \p.parse(), ("sub t(int i, hash<auto> opts) {}\n"
                "t(1, 1);", ""));
        }
    }

    varargsTest() {
        assertNothing(o0("str"));
    }

    referenceMatching() {
        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("sub t(hash h, softstring str0, reference<bool> i) {}\n"
                "sub t(hash h, softstring str0, reference i) {} hash<auto> x = {}; softstring w = 1; t(x, w);", ""));
        }
    }

    issue4419() {
        Mutex m();
        {
            auto x = m;
            assertNothing(issue_4419_t0(x));
        }
        {
            hash<auto> x = {
                "a": m,
            };
            assertNothing(issue_4419_t1(x));
        }
        {
            list<auto> x = (m,);
            assertNothing(issue_4419_t2(x));
        }
    }

    softlistOverloadTest() {
        {
            Program p(PO_NEW_STYLE);
            p.importFunction("sl_test_1");
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("sl_test_1(1, new Sequence());", "test"));
            assertThrows("RUNTIME-OVERLOAD-ERROR", \sl_test_1(), (1, new Sequence()));
        }
        {
            Program p(PO_NEW_STYLE);
            p.importFunction("sl_test_2");
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("sl_test_2(1, new Sequence());", "test"));
            assertThrows("RUNTIME-OVERLOAD-ERROR", \sl_test_2(), (1, new Sequence()));
        }
    }

    issue3861() {
        {
            Program p(PO_NEW_STYLE);
            assertNothing(p.parse("namespace A { class A {} }
    namespace B { class A {} }
    string sub t(A::A a) {return 'A';}
    string sub t(B::A a) {return 'B';}
    string sub t() {return t(new A());}
    ", "issue 3861"));
            assertEq("A", p.callFunction("t"));
        }
        {
            Program p(PO_NEW_STYLE);
            assertThrows("DUPLICATE-SIGNATURE", \p.parse(), ("namespace A { class A {} }
    string sub t(A::A a) {return 'A';}
    string sub t(A a) {return 'A1';}
    ", "issue 3861"));
        }
        {
            Program p(PO_NEW_STYLE);
            assertNothing(p.parse("namespace A { class A {} }
    namespace B { class AA {} }
    string sub t(A a) {return 'A';}
    string sub t(AA a) {return 'B';}
    string sub t() {return t(new A());}
    ", "issue 3861"));
            assertEq("A", p.callFunction("t"));
        }
    }

    issue1928() {
        Program p(PO_NEW_STYLE);
        p.parsePending(Api, "");
        p.parse("sub t() {} int sub t1(){string vstr = ''; string p1 = vstr + ''; any x = ''; int rv = p(x, p1); return rv;} hash sub t2(){any a1 = ''; any a2; hash rv = p(a1, a2); return rv;}", "");
        assertEq(1, p.callFunction("t1"));
        assertEq({}, p.callFunction("t2"));
    }

    issue3441() {
        Program p(PO_NEW_STYLE);
        p.parse("sub t(hash<string, bool> h) {} sub t(int x) {} hash<auto> h = <StatInfo>{}; t(h);", "");
        assertThrows("RUNTIME-OVERLOAD-ERROR", "t\\(hash<.*StatInfo>\\)", \p.run());
    }

    testValues() {
        # test parse-time matching
        assertEq("integer", f_test(1));
        assertEq("float", f_test(1.1));
        assertEq("float", f1_test(1));
        assertEq("float", f1_test(1.1));
        assertEq("string", f1_test("str"));
        assertEq("string", f1_test("1.11"));

        # test runtime matching
        assertEq("integer", call_function(\f_test(), 1));
        assertEq("float", call_function(\f_test(), 1.1));
        assertEq("float", call_function(\f1_test(), 1));
        assertEq("float", call_function(\f1_test(), 1.1));
        assertEq("string", call_function(\f1_test(), "str"));
        assertEq("string", call_function(\f1_test(), "1.11"));

        assertThrows("RUNTIME-OVERLOAD-ERROR", \f1_test(), 123.456n);
    }

    testVariables() {
        int i = 1;
        float fi = 1.1;

        # test parse-time matching
        assertEq("integer", f_test(i));
        assertEq("float", f1_test(i));
        assertEq("float", f_test(fi));
        assertEq("float", f1_test(fi));

        # test runtime matching
        assertEq("integer", call_function(\f_test(), i));
        assertEq("float", call_function(\f1_test(), i));
        assertEq("float", call_function(\f_test(), fi));
        assertEq("float", call_function(\f1_test(), fi));
    }

    testOverload() {
        # test parse-time matching
        assertEq(0, f2_test());
        assertEq(0, f3_test());
        assertEq(0, f4_test(()));
        assertEq(0, f5_test("hi"));
        assertEq(1, f6(1));
        # make sure we catch and fix the behavior of corner cases
        any a;
        assertEq(2, f6(1, NOTHING));
        assertEq(2, f6(1, a));
        assertEq(2, f6(1, "hi"));
        assertEq(3, f6(1, NOTHING, 1));
        assertEq(3, f6(1, a, 1));
        assertEq(3, f6(1, NOTHING, NOTHING));
        assertEq(3, f6(1, a, 1));
        assertEq(4, f6(1, NOTHING, NOTHING, {}));
        assertEq(4, f6(1, a, a, {}));
        assertEq(4, f6(1, NOTHING, NOTHING, NOTHING));
        assertEq(4, f6(1, a, a, a));
        # check overload resolution with complex types
        assertEq(1, f7({}));
        assertEq(2, f7(("a": 1)));
        assertEq(2, f7(cast<hash<string, int>>(("a": 1))));
        assertEq(3, f7(new hash<MyHash>()));
        assertEq(0, f8());
        assertEq(1, f8({}));
        assertEq(2, f8(("a": 1)));
        assertEq(1, f8(("a": "1")));
        # check overload resolution against "any" and "auto"
        assertEq(0, f9());
        assertEq(1, f9("string"));
        assertEq(0, f10());
        assertEq(1, f10("string"));

        # test runtime matching
        assertEq(0, call_function(\f2_test()));
        assertEq(0, call_function(\f3_test()));
        assertEq(0, call_function(\f4_test(), ()));
        assertEq(0, call_function(\f5_test(), "hi"));
        assertEq(1, call_function(\f6(), 1));
        # make sure we catch and fix the behavior of corner cases
        assertEq(2, call_function(\f6(), 1, NOTHING));
        assertEq(2, call_function(\f6(), 1, a));
        assertEq(2, call_function(\f6(), 1, "hi"));
        assertEq(3, call_function(\f6(), 1, NOTHING, 1));
        assertEq(3, call_function(\f6(), 1, a, 1));
        assertEq(3, call_function(\f6(), 1, NOTHING, NOTHING));
        assertEq(3, call_function(\f6(), 1, a, a));
        assertEq(4, call_function(\f6(), 1, NOTHING, NOTHING, {}));
        assertEq(4, call_function(\f6(), 1, a, a, {}));
        assertEq(4, call_function(\f6(), 1, NOTHING, NOTHING, NOTHING));
        assertEq(4, call_function(\f6(), 1, a, a, a));
        assertEq(1, call_function(\f7(), {}));
        assertEq(2, call_function(\f7(), ("a": 1)));
        assertEq(2, call_function(\f7(), cast<hash<string, int>>(("a": 1))));
        assertEq(3, call_function(\f7(), new hash<MyHash>()));
        assertEq(0, call_function(\f8()));
        assertEq(1, call_function(\f8(), {}));
        assertEq(2, call_function(\f8(), ("a": 1)));
        assertEq(1, call_function(\f8(), ("a": "1")));
        assertEq(0, call_function(\f9()));
        assertEq(1, call_function(\f9(), "string"));
        assertEq(0, call_function(\f10()));
        assertEq(1, call_function(\f10(), "string"));
    }
}
