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

%require-types
%enable-all-warnings
%new-style
%no-child-restrictions
%allow-weak-references

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

our int calls_constructor = 0;
our int calls_destructor  = 0;
our int calls_hello  = 0;

class C1 {
    private {
        hash<auto> h;
    }

    constructor() {
        test(\self.h);
    }

    *int get() {
        return h.a;
    }

    static test(reference<hash<auto>> h) {
        h.a = 1;
    }
}

class PrivTest {
    constructor(int i) {}
    private constructor(string str) {}
}

class Test {
    private { any data; }
    public { any a; }
    constructor(any a) {
        data = a;
        calls_constructor++;
    }
    destructor() {
        calls_destructor++;
    }
    any getData() {
        return data;
    }
    string getType() {
        return get_class_name(self);
    }
    private p1() {
    }
    private p2() {
        p1();
    }
    hello() {
        p2();
        ++calls_hello;
    }
}

class Other {
    constructor(Parent p) {
    }
}

class Parent {
}

class Child inherits Parent, Other {
    constructor() : Other(self) {
    }
}

class MyClass {
    public {
        int i = 1;
    }

    get() {
    }
}

sub et(Test t) {
    t.a = "123";
}

class Issue3596Base {
    public {
        Issue3596_1 obj();
    }
}

class Issue3596_1 {
    int method() {
        return 1;
    }
}

class Issue3596_2 {
    private {

    }

    int test() {
        return method(1);
    }

    private:internal int method(int i) {
        return issue_3596.method();
    }
}

our auto issue_3596;

const Methods = ('p2', 'getData', 'hello', 'destructor', 'getType', 'p1', 'constructor');

%exec-class ObjectTest

class ObjectTest inherits QUnit::Test {
    public {
    }

    private {
        const CreateObjectSource = "
object sub test1() { return create_object(\"Mutex\");}
object sub test2() { return create_object(\"PrivTest\", \"str\");}
object sub test3() { return create_object_args(\"PrivTest\", 1);}
";
    }

    constructor() : QUnit::Test("Object test", "1.0") {
        issue_3596 = new Issue3596_1();

        addTestCase("object perm test", \objPermTest());
        addTestCase("issue 3596", \issue3596Test());
        addTestCase("instanceof Test", \instanceofTest());
        addTestCase("Test", \testObj());
        addTestCase("create_object", \testCreateObject());
        addTestCase("inheritance", \inheritance());
        addTestCase("weak refs", \weakRefTest());
        set_return_value(main());
    }

    objPermTest() {
        C1 c();
        assertEq(1, c.get());
    }

    issue3596Test() {
        Issue3596_2 i();
        assertEq(1, i.test());
    }

    instanceofTest() {
        Mutex m();
        assertTrue(m instanceof AbstractSmartLock);
        Mutex n = m;
        assertTrue(n instanceof AbstractSmartLock);
        delete n;
        assertFalse(m instanceof AbstractSmartLock);

        {
            Program p(PO_NEW_STYLE);
            *hash<ExceptionInfo> warn = p.parse("Mutex m; bool b = m instanceof int; delete b;", "", -1);
            on_error printf("%N\n", warn);
            assertEq("hash<ExceptionInfo>", warn.fullType());
        }

        {
            Program p(PO_NEW_STYLE);
            *hash<ExceptionInfo> warn = p.parse("float f; bool b = f instanceof int; delete b;", "", -1);
            on_error printf("%N\n", warn);
            assertEq("hash<ExceptionInfo>", warn.fullType());
        }

        {
            Program p(PO_NEW_STYLE);
            *hash<ExceptionInfo> warn = p.parse("Mutex m(); bool b = m instanceof Sequence; delete b;", "", -1);
            on_error printf("%N\n", warn);
            assertNothing(warn);
        }

        {
            Program p(PO_NEW_STYLE);
            *hash<ExceptionInfo> warn = p.parse("float f = 1; bool b = f instanceof float; delete b;", "", -1);
            on_error printf("%N\n", warn);
            assertNothing(warn);
        }
    }

    testObj() {
        Test t = new Test(1);

        assertEq(1, calls_constructor);
        (new Test(3)).hello();
        assertEq(2, calls_constructor);
        assertEq(1, calls_hello);
        assertEq("Test", t.getType());
        t.hello();
        assertEq(2, calls_hello);
        et(t);
        assertEq("123", t.a, "variable changed in method");

        # test fails on OSX, method return order is not guaranteed across platforms
        #unit.cmp(getMethodList(t), Methods, 'got method list');

        # test each method separately
        list ml = get_method_list(t);
        assertEq(Methods.size(), ml.size(), "method list size");
        map assertEq(True, inlist_hard($1, Methods), "method list " + $1), ml;

        # unless we explicitly delete, we get the following exception on OSX (but not on Linux):
        #   DESTRUCTOR-ERROR: illegal variable assignment after second phase of variable destruction
        delete t;
    }

    testCreateObject() {
        assertEq("Mutex", create_object("Mutex").className());
        assertEq("Mutex", create_object_args("Mutex").className());

        Program p(PO_NEW_STYLE | PO_NO_THREAD_CLASSES);
        p.importClass("PrivTest");
        p.parse(CreateObjectSource, "create-object");
        assertThrows("CREATE-OBJECT-ERROR", \p.callFunction(), "test1");
        assertThrows("RUNTIME-OVERLOAD-ERROR", \p.callFunction(), "test2");
        assertEq("PrivTest", p.callFunction("test3").className());
    }

    inheritance() {
        # causes a crash without the fix for #1169
        Child c();
    }

    weakRefTest() {
        MyClass m;

        {
            MyClass m1();

            m := m1;

            assertEq(True, m == m1);
            assertEq(False, m != m1);
            assertEq(True, m instanceof MyClass);
            assertEq(True, boolean(m));
            assertEq(Type::Object, m.type());
            assertEq(1, m1.i);
            assertEq(1, m.i);
            m.i = 2;
            assertEq(2, m1.i);
            assertEq(2, m.i);

            our MyClass m2 := m1;
            assertEq(True, m2 == m1);
            assertEq(False, m2 != m1);
            assertEq(True, m2 instanceof MyClass);
            assertEq(True, boolean(m2));
            assertEq(Type::Object, m2.type());
            assertEq(2, m2.i);
            m2.i = 3;
            assertEq(3, m1.i);
            assertEq(3, m2.i);

            hash h.m := m1;
            assertEq(True, h.m == m1);
            assertEq(False, h.m != m1);
            assertEq(True, h.m instanceof MyClass);
            assertEq(True, boolean(h.m));
            assertEq(Type::Object, h.m.type());
            assertEq(3, h.m.i);
            h.m.i = 4;
            assertEq(4, m1.i);
            assertEq(4, h.m.i);

            {
                Program p(PO_NEW_STYLE);
                assertThrows("PARSE-EXCEPTION", "weak", \p.parse(), ("any x := 1;", ""));
            }
        }

        assertThrows("OBJECT-ALREADY-DELETED", \m.get());
    }
}
