#!/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 ReferenceTest

class T {
    public {
        static hash<auto> h();
        static reference<auto> rs;
        reference<auto> r;
    }

    constructor() {
        r = \h;
        rs = \h;
    }

    *hash<auto> test() {
        return testCall(r);
    }

    *hash<auto> testStatic() {
        return testCall(rs);
    }

    static private hash<auto> testCall(hash<auto> h) {
        return h;
    }
}

class A {
    private {
        reference<hash> x;
    }

    constructor(reference<hash> xx) {
        x = \xx;
    }

    bool test(string str) {
        return x.hasKey(str);
    }
}

class ReferenceTest inherits QUnit::Test {
    constructor() : Test("Reference Test", "1.0") {
        addTestCase("member ref", \memberReferenceTest());
        addTestCase("issue 3417", \issue3417());
        addTestCase("issue 2891", \issue2891());
        addTestCase("Complex reference test", \testComplexReferences());
        addTestCase("Reference test", \testReference());
        addTestCase("Assignment test", \testReferenceAssignments());

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

    memberReferenceTest() {
        T t();
        assertEq({}, t.test());
        assertEq({}, t.testStatic());
    }

    issue3417() {
        hash<auto> h = {};
        A a(\h);
        assertEq(False, a.test("str"));
    }

    issue2891() {
        hash h;
        reference<hash> r = \h.b;
        assertEq(NOTHING, keys h);
        delete r;
    }

    testComplexReferences() {
        {
            *int i = 1;
            testRef(i, \i);
            assertEq(0, i);
        }

        {
            int i = 0;
            reference<int> r1 = \i;
            assertEq(0, r1);
            reference<softint> r2 = \r1;
            assertEq(0, r2);
            r2 = 1;
            assertEq(1, r1);
            assertEq(1, r2);
            assertEq(1, i);
            reference r3 = \r1;
            assertEq(1, r3);
            r3 = 2;
            assertEq(2, r1);
            assertEq(2, r2);
            assertEq(2, r3);
            assertEq(2, i);
        }

        {
            *int i = 0;
            reference<int> r1 = \i;
            assertEq(0, r1);
            reference<softint> r2 = \r1;
            assertEq(0, r2);
            r2 = 1;
            assertEq(1, r1);
            assertEq(1, r2);
            assertEq(1, i);
            *reference<int> r3 = \r1;
            r3 = 2;
            assertEq(2, r1);
            assertEq(2, r2);
            assertEq(2, r3);
            assertEq(2, i);
        }

        {
            any x;
            reference<list> r1 = \x;
            r1 = ();
            assertEq((), x);
            delete x;
            *reference<int> r2 = \x;
            r2 = 3;
            assertEq(3, x);
        }

        {
            code c0 = sub (*reference<*list> t) {
                t = ();
            };
            code c1 = sub (*reference<*list> t) {
                c0(\t);
            };

            c1();
        }

        {
            code c0 = string sub (reference<hash> h) {
                hash h1 = h;
                delete h1;
                h = ("a": "a");
                return h.fullType();
            };

            code c1 = string sub (reference<list> l) {
                list l1 = l;
                delete l1;
                l = ("a",);
                return l.fullType();
            };

            code c2 = string sub (reference h) {
                hash h1 = h;
                delete h1;
                h.a = "a";
                return h.fullType();
            };

            code c3 = string sub (reference l) {
                list l1 = l;
                delete l1;
                l += ("a",);
                return l.fullType();
            };

            any v = new hash<string, int>();
            assertEq("hash", c0(\v));
            assertEq("a", v.a);

            v = new list<int>();
            assertEq("list", c1(\v));
            assertEq("a", v[0]);

            v = new hash<string, int>();
            assertEq("hash", c2(\v));
            assertEq("a", v.a);

            v = new list<int>();
            assertEq("list", c3(\v));
            assertEq("a", v[0]);
        }

        {
            assertThrows("RUNTIME-TYPE-ERROR", sub () {any x = "1"; backquote("", \x);});
        }

        {
            code c = int sub (reference<softint> i) {
                return i;
            };

            any str = "1";
            assertEq(1, c(\str));
            assertEq(1, str);
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", "reference<string>.*reference<int>", \p.parse(), ("sub t() {int i = 0; reference<string> r0 = \\i;}", ""));
            assertThrows("PARSE-TYPE-ERROR", "reference<\\*softint>.*reference<int>", \p.parse(), ("sub t() {int i = 0; reference<*softint> r0 = \\i;}", ""));
        }
    }

    testReference() {
        {
            int i = 0;
            reference r1 = \i;
            assertEq(0, r1);
            reference r2 = \r1;
            r2 = 1;
            assertEq(1, r1);
            assertEq(1, r2);
            assertEq(1, i);
        }

        list l = (
            ("val": 1),
            ("val", 2),
        );
        reference r1;
        reference r2;
        r1 = \l[0];
        r2 = \l[1];
        r1.val = 10;
        r2.val = 20;
        assertEq(10, l[0].val);
        assertEq(20, l[1].val);
        assertEq(10, r1.val);
        assertEq(20, r2.val);
        r2 = r1;
        assertEq(10, l[0].val);
        assertEq(10, l[1].val);
        assertThrows("REFERENCE-ERROR", sub () { r2 = \r1; });
        reference r3 = \r1;
        assertThrows("REFERENCE-ERROR", sub () { r2 = \r3; });
        reference r4 = \r3;
        assertThrows("REFERENCE-ERROR", sub () { r2 = \r4; });
    }

    testReferenceAssignments() {
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("sub t() {reference r = {};}", ""));
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("sub t() {*reference r = {};}", ""));
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("our reference r = {};", ""));
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("our *reference r = {};", ""));
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("sub t() {reference<int> r = 1;}", ""));
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), (
                "sub t () { (reference<int> r0, reference<int> r1) = (1, 2); }", "",
            ));
            p.parse("sub t1() {int a; reference r = \\a;}", "");
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            p.parse("sub t2() {int a; *reference r = \\a;}", "");
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            p.parse("sub t3(*reference r) {r = 1;}", "");
            int i;
            p.callFunction("t3", \i);
            p.callFunction("t3");
            assertEq(1, i);
            assertThrows("RUNTIME-OVERLOAD-ERROR", \p.callFunction(), ("t3", i));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            p.parse("sub t4(reference r) {r = 2;}", "");
            int i;
            p.callFunction("t4", \i);
            assertEq(2, i);
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            p.parse("sub t5(*reference r) {reference r1 = \\r; r1 = 1;}", "");
            int i;
            p.callFunction("t5", \i);
            assertEq(1, i);
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            p.parse("sub t6(reference r) {reference r1 = \\r; r1 = 2;}", "");
            int i;
            p.callFunction("t6", \i);
            assertEq(2, i);
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            # the following does not cause a parse error to be raised because the runtime type of the reference isn't known
            p.parse("sub t7(*reference r) {reference r1 = r; r1 = 3;}", "");
            int i = 2;
            assertThrows("RUNTIME-TYPE-ERROR", \p.callFunction(), ("t7", \i));
            assertEq(2, i);
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            # the following does not cause a parse error to be raised because the runtime type of the reference isn't known
            p.parse("sub t8(reference r) {reference r1 = r; r1 = 4;}", "");
            int i = 2;
            assertThrows("RUNTIME-TYPE-ERROR", \p.callFunction(), ("t8", \i));
            assertEq(2, i);
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            p.parse("sub _t9(reference r) {r = 5;} int sub t9() {int i; reference x = \\i; _t9(\\x); return i;}", "");
            assertEq(5, p.callFunction("t9"));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            p.parse("sub _t10(*reference r) {r = 6;} int sub t10() {int i; *reference x = \\i; _t10(\\x); return i;}", "");
            assertEq(6, p.callFunction("t10"));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            p.parse("sub _t11(reference r) {r = 7;} int sub t11() {int i; _t11(\\i); return i;}", "");
            assertEq(7, p.callFunction("t11"));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            p.parse("sub _t12(*reference r) {r = 8;} int sub t12() {int i; _t12(\\i); return i;}", "");
            assertEq(8, p.callFunction("t12"));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            p.parse("int sub t13(*reference r) {r = 9; return r;}", "");
            assertEq(9, p.callFunction("t13"));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            p.parse("int sub t14(*reference r) {_t14(\\r); r.a = 1; return r.size();} sub _t14(reference r) {r = {};}", "");
            assertEq(1, p.callFunction("t14"));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            p.parse("int sub t15(*reference r) {_t15(\\r); r[0] = 1; return r.size();} sub _t15(reference r) {r = ();}", "");
            assertEq(1, p.callFunction("t15"));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            p.parse("our int i; *int sub get() {return i;}", "");
            assertEq(NOTHING, p.callFunction("get"));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("class T { public {reference r = 1;}}", ""));
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("class T { public {*reference r = 1;}}", ""));
            p.parse("our int i; class T1 { public {reference r;} constructor() {r = \\i; r = 1;}} int sub t1() {T1 t(); return i;}", "");
            assertEq(1, p.callFunction("t1"));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            p.parse("our int i; class T2 {
    public {static reference r;}
    constructor() {
        r = \\i;
        r = 2;
    }
}

int sub t2() {
    T2 t();
    return i;
}", "");
            assertEq(2, p.callFunction("t2"));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            p.parse("our int i; class T3 { public {*reference r;} constructor() {r = \\i; r = 3;}} int sub t3() {T3 t(); return i;}", "");
            assertEq(3, p.callFunction("t3"));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            p.parse("our int i; class T4 { public {static *reference r;} constructor() {r = \\i; r = 4;}} int sub t4() {T4 t(); return i;}", "");
            assertEq(4, p.callFunction("t4"));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            # the following does not cause a parse error to be raised because the runtime type of the reference isn't known
            p.parse("our int i; class T5 { public {reference r;} constructor() {r = 1;}} int sub t5() {T5 t(); return i;}", "");
            assertThrows("RUNTIME-TYPE-ERROR", \p.callFunction(), "t5");
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            # the following does not cause a parse error to be raised because the runtime type of the reference isn't known
            p.parse("our int i; class T6 { public {*reference r;} constructor() {r = 1;}} int sub t6() {T6 t(); return i;}", "");
            assertThrows("RUNTIME-TYPE-ERROR", \p.callFunction(), "t6");
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            # the following does not cause a parse error to be raised because the runtime type of the reference isn't known
            p.parse("our int i; class T7 { public {static reference r;} constructor() {r = 1;}} int sub t7() {T7 t(); return i;}", "");
            assertThrows("RUNTIME-TYPE-ERROR", \p.callFunction(), "t7");
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            # the following does not cause a parse error to be raised because the runtime type of the reference isn't known
            p.parse("our int i; class T8 { public {static *reference r;} constructor() {r = 1;}} int sub t8() {T8 t(); return i;}", "");
            assertThrows("RUNTIME-TYPE-ERROR", \p.callFunction(), "t8");
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS | PO_BROKEN_REFERENCES);
            p.parse("our int i; *int sub get() {return i;}", "");
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS | PO_BROKEN_REFERENCES);
            p.parse("int sub tb_0() {reference r = {}; return r.size();}", "");
            assertEq(0, p.callFunction("tb_0"));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS | PO_BROKEN_REFERENCES);
            p.parse("int sub tb_1() {*reference r = {}; return r.size();}", "");
            assertEq(0, p.callFunction("tb_1"));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS | PO_BROKEN_REFERENCES);
            p.parse("our reference r = {}; int sub tb_2(){return r.size();}", "");
            assertEq(0, p.callFunction("tb_2"));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS | PO_BROKEN_REFERENCES);
            p.parse("class TB_0 { public {reference r = 1;}} int sub tb_3(){TB_0 t(); return t.r.size();}", "");
            assertEq(0, p.callFunction("tb_3"));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS | PO_BROKEN_REFERENCES);
            p.parse("class TB_1 { public {*reference r = 1;}} int sub tb_4(){TB_1 t(); return t.r.size();}", "");
            assertEq(0, p.callFunction("tb_4"));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS | PO_BROKEN_REFERENCES);
            p.parse("class TB_2 { public {static reference r = 1;}} int sub tb_5(){TB_2::r.size();}", "");
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS | PO_BROKEN_REFERENCES);
            p.parse("class TB_3 { public {static *reference r = 1;}} int sub tb_6(){TB_3::r.size();}", "");
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS | PO_BROKEN_REFERENCES);
            p.parse("int sub tb_7(reference r) {r = {}; return r.size();}", "");
            assertEq(0, p.callFunction("tb_7"));
        }
        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS | PO_BROKEN_REFERENCES);
            p.parse("int sub tb_8(*reference r) {r = {}; return r.size();}", "");
            assertEq(0, p.callFunction("tb_8"));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS | PO_BROKEN_REFERENCES);
            p.parse("our *reference r = {}; int sub tb_0(){return r.size();}", "");
            assertEq(0, p.callFunction("tb_0"));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS | PO_BROKEN_REFERENCES);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("%correct-references\nour reference r = {};", ""));
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("%correct-references\nour *reference r = {};", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t(*reference r) {}", "");
            assertThrows("RUNTIME-OVERLOAD-ERROR", \p.callFunction(), ("t", {}));
        }

        {
            Program p(PO_NEW_STYLE | PO_STRICT_ARGS);
            p.parse("our int i = 1; sub t(reference r = \\i) { r = 3; } int sub get() { return i;}", "");
            p.run();
            int i = 2;
            p.callFunction("t", \i);
            assertEq(3, i);
            assertEq(1, p.callFunction("get"));
            p.callFunction("t");
            assertEq(3, p.callFunction("get"));
        }
    }

    static testRef(int i, *reference<*int> r) {
        r = 0;
    }
}
