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

namespace T0 {
    int sub get_int() {
        return 1;
    }
}

class Base {
    public {
        static int statpub = 1;
        const conpub = 1;
        int mempub = 1;
    }

    private {
        static int statpriv = 1;
        const conpriv = 1;
        int mempriv = 1;
    }

    private:internal {
        static int statint = 1;
        const conint = 2;
        int memint = 1;
    }

    Base base() {
        return self;
    }

    int pubtest(int i) {
        return i;
    }

    string pubtest(string s) {
        return s;
    }

    int pubint() {
        return 1;
    }

    private int priv() {
        return 0;
    }

    private:internal int intern() {
        return 0;
    }

    private:internal int intern(int i) {
        return 1;
    }

    int getStatPriv() {
        return statpriv;
    }

    int getConPriv() {
        return conpriv;
    }

    public int getStatInt() {
        return statint;
    }

    public int getConInt() {
        return conint;
    }

    public int getMemInt() {
        return memint;
    }

    hash getHash() {
        return map {$1: self.$1}, keys self;
    }

    hash getSlice(list l) {
        return self{l};
    }

    code getCode() {
        return \intern();
    }
}

class PubChild inherits Base {
    public {
        static int statpub = 2;
        const conpub = 2;
    }

    private {
        static int statpriv = 2;
        const conpriv = 2;
    }

    private:internal {
        static int statint = 2;
        const conint = 3;
        int memint = 2;
    }

    Base test() {
        return cast<Base>(self);
    }

    Base base() {
        return Base::base();
    }

    float pubtest(float f) {
        return f + 1.0;
    }

    int getStatPub() {
        return statpub;
    }

    int getStatPriv() {
        return statpriv;
    }

    int getConPub() {
        return conpub;
    }

    int getConPriv() {
        return conpriv;
    }

    private int priv() {
        return 0;
    }

    private:internal int pubint() {
        return 1;
    }

    private:internal int intern() {
        return 0;
    }

    public int getStatInt() {
        return statint;
    }

    public int getConInt() {
        return conint;
    }

    public int getMemInt() {
        return memint;
    }

    hash getSlice(list l) {
        return self{l};
    }
}

class PrivChild inherits private Base {
    public {
        int memint = 3;
    }

    Base test() {
        return cast<Base>(self);
    }

    Base base() {
        return Base::base();
    }

    private int priv() {
        return 0;
    }

    private:internal int intern() {
        return 0;
    }

    hash getSlice(list l) {
        return self{l};
    }
}

class IntChild inherits private:internal Base {
    Base test() {
        return cast<Base>(self);
    }

    Base base() {
        return Base::base();
    }

    private int priv() {
        return Base::priv();
    }

    int getInt() {
        return pubint();
    }

    code getCode() {
        return \pubint();
    }
}

class BI1 {
    private:internal {
        int i = 2;
    }

    private int get() {
        return i;
    }

    private static int getStatic() {
        return 1;
    }
}

class CI1 inherits private:internal BI1 {
    constructor() : BI1() {
    }

    int get() {
        return BI1::get();
    }
}

class BIBase {
    private int get() {
        return 0;
    }

    private static int getStatic() {
        return 0;
    }
}

class BI2 inherits private:internal BIBase {
    private:internal {
        int i;
    }

    private:internal constructor() {
    }

    private constructor(int n_i) {
        i = n_i;
    }

    private int get() {
        return i;
    }

    private static int getStatic() {
        return 1;
    }
}

class CI2 inherits private:internal BI2 {
    constructor(int i) : BI2(i) {
    }

    int get() {
        return BI2::get();
    }
}

class BI3 {
    private:internal {
        int i;
    }

    private:internal constructor() {
    }

    private constructor(int i) {
        self.i = i;
    }

    private int get() {
        return i;
    }
}

class CI3 inherits private:internal BI2 {
    constructor(int i) : BI2(i) {
    }

    int get() {
        return BI2::get();
    }

    code getRef(string name) {
        return self.getCallReference(name);
    }

    static int getStatic() {
        return 1;
    }
}

class RefTest {
    private {
        code c;
    }

    constructor() {
        c = \RefTest::priv();
    }

    int get() {
        return call_function_args(c);
    }

    private static int priv() {
        return 1;
    }
}

class Base1 {
    int m() {
        return m1();
    }

    private:internal int m1() {
        return 1;
    }
}

class Child1 inherits Base1;

class Issue3223Base {
    private:internal int myFunc() {
        return 1;
    }

    private int testImpl() {
        return myFunc();
    }

    private int testImpl2() {
        Queue q();
        background myFunc2(q);
        return q.get();
    }

    private:internal myFunc2(Queue q) {
        q.push(1);
    }
}

class Issue3223Child inherits private:internal Issue3223Base {
    int test() {
        return testImpl();
    }

    int test2() {
        return testImpl2();
    }
}

public class AccessTest inherits QUnit::Test {
    private {
        const Code = "
class B {
    private string t(int i) {return \"int\";}
    string t(string str) {return \"string\";}
    code get_t() {return \\t();}

    private string u(int i) {return \"int\";}
    private string u(string str) {return \"string\";}
    code get_u() {return \\u();}

    string v(int i) {return \"int\";}
    string v(string str) {return \"string\";}
    code get_v() {return \\v();}

    string w() {return \"none\";}
    code get_w() {return \\w();}

    private string x() {return \"none\";}
    code get_x() {return \\x();}
}
class C inherits B {}
class D inherits private B {}

string sub t1() { C t(); return t.t(False ? NOTHING : 1); }

string sub t2() { C t(); return t.t(False ? NOTHING : \"str\"); }

string sub t3() { object t = new D(); return t.t(False ? NOTHING : 1); }

string sub t4() { object t = new D(); return t.t(False ? NOTHING : \"str\"); }

string sub t5() { C t(); return t.get_t()(False ? NOTHING : 1); }
string sub t6() { C t(); return t.get_t()(False ? NOTHING : \"str\"); }

string sub t7() { C t(); return t.get_u()(False ? NOTHING : 1); }
string sub t8() { C t(); return t.get_u()(False ? NOTHING : \"str\"); }

string sub t9() { C t(); return t.get_v()(False ? NOTHING : 1); }
string sub t10() { C t(); return t.get_v()(False ? NOTHING : \"str\"); }

string sub t11() { C t(); return t.get_w()(); }
string sub t12() { C t(); return t.w(); }

string sub t13() { C t(); return t.get_x()(); }

string sub t14() { object t = new C(); return t.x(); }

our C ctg();
code sub t15() { return ctg.get_x(); }

code sub t16_17() { return \\ctg.t(); }

our object dtg = new D();
code sub t18() { return \\dtg.t(); }
";

const Class1 = "
class TestInheritA {
    public { string m_bar = 'A'; }
    string foo() { return 'A'; }
}

class TestInheritB {
    public { string m_bar = 'B'; }
    string foo() { return 'B'; }
}
";

        const Class2 = "class TestInheritAB inherits TestInheritA, TestInheritB {}";
        const Class3 = "class TestInheritBA inherits TestInheritB, TestInheritA {}";

        const Code2 = "class Base1 {int m() {return m1();} private:internal int m1() {return 1;}} class Child1 inherits Base1; Child1 sub test() {return new Child1();}";

        const Code3 = "class Child1 inherits Base1; int sub test() {Child1 c(); return c.m();}";
    }

    constructor() : Test("AccessTest", "1.0") {
        addTestCase("issue 3223", \issue3223());
        addTestCase("Test class access", \accessTestMethod());
        addTestCase("Test inheritance", \inheritanceTest());

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

    issue3223() {
        Issue3223Child c();
        assertEq(1, c.test());
        assertEq(1, c.test2());
    }

    accessTestMethod() {
        {
            Program p(PO_NEW_STYLE);
            p.parse(Code, "access");
            p.run();

            # this must fail: B::t(int) is private
            assertThrows("RUNTIME-OVERLOAD-ERROR",  \p.callFunction(), "t1");

            testAssertion("private-method-access-1", \p.callFunction(), ("t2",), new TestResultValue("string"));

            # this must fail: B::t(int) is private and also B is privately-inherited
            testAssertion("negative-private-method-access-2", \p.callFunction(), ("t3",), new TestResultExceptionRegexp('BASE-CLASS-IS-PRIVATE', "method of a privately-inherited class"));

            # this must fail: B::t(string) is a method of a privately-inherited class
            testAssertion("negative-private-method-access-3", \p.callFunction(), ("t4",), new TestResultExceptionRegexp('BASE-CLASS-IS-PRIVATE', "method of a privately-inherited class"));

            testAssertion("private-method-access-2", \p.callFunction(), ("t5",), new TestResultValue("int"));
            testAssertion("private-method-access-3", \p.callFunction(), ("t6",), new TestResultValue("string"));

            testAssertion("private-method-access-4", \p.callFunction(), ("t7",), new TestResultValue("int"));
            testAssertion("private-method-access-5", \p.callFunction(), ("t8",), new TestResultValue("string"));

            testAssertion("private-method-access-6", \p.callFunction(), ("t9",), new TestResultValue("int"));
            testAssertion("private-method-access-7", \p.callFunction(), ("t10",), new TestResultValue("string"));

            testAssertion("private-method-access-8", \p.callFunction(), ("t11",), new TestResultValue("none"));
            testAssertion("private-method-access-9", \p.callFunction(), ("t12",), new TestResultValue("none"));

            testAssertion("private-method-access-10", \p.callFunction(), ("t13",), new TestResultValue("none"));

            # this must fail: C::x() is private
            testAssertion("negative-private-method-access-8", \p.callFunction(), ("t14",), new TestResultExceptionRegexp('METHOD-IS-PRIVATE', "is private and cannot be accessed externally"));

            testAssertion("private-method-access-11", string sub () { return p.callFunction("t15")(); }, NOTHING, new TestResultValue("none"));

            # this must fail when calling the call reference with an integer and succeed when calling with a string arg
            assertThrows("RUNTIME-OVERLOAD-ERROR", string sub () { return p.callFunction("t16_17")(1); }, NOTHING, "negative-private-method-access-9");

            assertEq("string", p.callFunction("t16_17")("str"), "private-method-access-12");

            # this must fail because B is privately inherited in D
            testAssertion("negative-private-method-access-10", \p.callFunction(), ("t18",), new TestResultExceptionRegexp('ILLEGAL-CALL-REFERENCE', "cannot create a call reference"));

            # this must fail at parse time because B::t() is privately inherited in D, therefore it should not be possible to take a reference to the method of the privately-inherited class
            assertThrows("PARSE-ERROR", \p.parse(), ("code sub test() { D t(); return \\t.t(); }", "test"));
        }

        # check inheritance
        {
            Program p(PO_NEW_STYLE);
            testAssertion("negative-inheritance-1", \p.parse(), (Class1 + Class2, "test"), new TestResultExceptionRegexp('PARSE-ERROR', "cannot be combined with class"));
        }
        {
            Program p(PO_NEW_STYLE);
            testAssertion("negative-inheritance-2", \p.parse(), (Class1 + Class3, "test"), new TestResultExceptionRegexp('PARSE-ERROR', "cannot be combined with class"));
        }
    }

    inheritanceTest() {
        Base b();
        assertEq(1, b.getStatInt());
        assertEq(2, b.getConInt());
        assertEq(1, b.getStatPriv());
        assertEq(1, b.getConPriv());
        assertEq(("mempub": 1), hash(b));
        assertEq(("mempub": 1, "mempriv": 1, "memint": 1), b.getHash());
        assertEq(("mempub": 1, "memint": 1), b.getSlice(("mempub", "memint")));
        assertEq(0, b.getCode()());
        assertEq(0, call_function(b.getCode()));
        assertEq(0, call_function_args(b.getCode()));

        PubChild c1();
        assertEq(c1, cast<Base>(c1));
        assertEq(c1, c1.test());
        assertEq(2, c1.getStatPriv());
        assertEq(2, c1.getConPriv());
        assertEq(2, c1.getStatInt());
        assertEq(3, c1.getConInt());
        assertEq(1, c1.pubint());
        assertEq(1, call_object_method(c1, "pubint"));
        assertEq(2.0, c1.pubtest(1));
        assertEq(2.0, call_object_method(c1, "pubtest", 1));
        assertEq(2.0, c1.pubtest(1.0));
        assertEq(2.0, call_object_method(c1, "pubtest", 1.0));

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("Base sub () {PrivChild c2(); return cast<Base>(c2);}", ""));
        }

        PrivChild c2();
        assertEq(c2, c2.test());

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("Base sub () {IntChild c3(); return cast<Base>(c3);}", ""));
        }

        IntChild c3();
        assertEq(c3, c3.test());
        assertEq(1, c3.getInt());
        assertEq(1, c3.getCode()());
        assertEq(1, call_function(c3.getCode()));
        assertEq(1, call_function_args(c3.getCode()));
        assertThrows("METHOD-DOES-NOT-EXIST", \call_object_method(), (c3, "pubint"));

        CI1 c4();
        assertEq(2, c4.get());

        CI2 c5(2);
        assertEq(2, c5.get());

        CI3 c6(2);
        assertEq(2, c6.get());

        assertThrows("CALL-REFERENCE-ERROR", \c6.getRef(), "BI1::get");
        assertThrows("CALL-REFERENCE-ERROR", \c6.getRef(), "BIBase::get");
        assertEq(2, c6.getRef("BI2::get")());
        assertEq(2, c6.getRef("get")());

        assertThrows("CALL-REFERENCE-ERROR", \c6.getRef(), "BI1::getStatic");
        assertThrows("CALL-REFERENCE-ERROR", \c6.getRef(), "BIBase::getStatic");
        assertEq(1, c6.getRef("BI2::getStatic")());
        assertEq(1, c6.getRef("getStatic")());

        assertThrows("CALL-REFERENCE-ERROR", \get_call_reference(), "BIBase::get");
        assertThrows("CALL-REFERENCE-ERROR", \get_call_reference(), "_invalid_");
        assertThrows("CALL-REFERENCE-ERROR", \c6.getRef(), "_invalid_");
        assertEq(1, c6.getRef("get_int")());
        assertEq(1, get_call_reference("get_int")());

        RefTest rt();
        assertEq(1, rt.get());

        {
            Child1 child1();
            assertEq(1, child1.m());
            assertEq(1, call_object_method(child1, "m"));
            int i = child1.m();
            assertEq(1, i);

            {
                Program p(PO_NEW_STYLE|PO_REQUIRE_TYPES|PO_STRICT_ARGS);
                p.parse(Code2, "");
                object o = p.callFunction("test");
                assertEq(1, o.m());
                # issue 1551: test assignment of classes created accross Program boundaries
                child1 = p.callFunction("test");
                assertEq(1, child1.m());
            }

            {
                Program p(PO_NEW_STYLE|PO_REQUIRE_TYPES|PO_STRICT_ARGS);
                p.importClass("Base1");
                p.parse(Code3, "");
                assertEq(1, p.callFunction("test"));
            }
        }

        {
            Program p(PO_NEW_STYLE|PO_REQUIRE_TYPES|PO_STRICT_ARGS);
            *hash wh = p.parse("class B { int b() {} } class C inherits private B {} sub test() { C c(); c.b();}", "", WARN_ALL);
            assertEq("NON-EXISTENT-METHOD-CALL", wh.err);
            assertThrows("BASE-CLASS-IS-PRIVATE", \p.callFunction(), "test");
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-ERROR", \p.parse(), ("class B { int b() {} } class C inherits private B {} code sub test() { C c(); return \\c.b();}", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("class B { int b(int i) { return i; } } class C inherits B { private:internal int b(int i) { return i + 1; } string b(string s) { return s; }} sub test() { C c(); c.b(1n);}", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-ERROR", \p.parse(), ("class B { int b() {} } class C inherits private B {} sub test() { C c(); code cd = \\c.b();}", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-ERROR", \p.parse(), ("class B { int b() {} } class C inherits private:internal B {} sub test() { C c(); code cd = \\c.b();}", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", "cannot resolve.*B::b", \p.parse(), ("class B { private:internal b() {} } class C inherits B { b() { B::b(); } }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", "cannot be found", \p.parse(), ("class B { private:internal b() {} } class C inherits B { c() { b(); } }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", "cannot resolve.*statint", \p.parse(), ("class B { private:internal { static int statint = 1; }} class C inherits B { int test() { return statint; } }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", "cannot resolve.*conint", \p.parse(), ("class B { private:internal { const conint = 1; }} class C inherits B { int test() { return conint; } }", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("class B { private:internal constructor() {}} class C inherits B {} sub test() { C c(); }", "");
            assertThrows("RUNTIME-OVERLOAD-ERROR", \p.callFunction(), "test");
        }
    }
}
