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

public hashdecl Container {
    int i = 1;
    string str = "hi";
    number n;
}

public hashdecl Container2 {
    int i = 2;
    string str = "hi";
    number n;
    date d = now_us();
}

public namespace Test;

public hashdecl Test::Container3 {
    int i = 3;
}

public hashdecl R1 {
    hash<R1> r();
}

hashdecl Issue3518 {
    int i;
}

hashdecl Issue3654 {
    bool b = False;
}

const B = <Issue3654>{};

*string sub str_test() {}

class HashDeclTest inherits QUnit::Test {
    constructor() : QUnit::Test("HashDecl test", "1.0") {
        addTestCase("issue 3654", \issue3654());
        addTestCase("serialize imported", \serializeImportedTest());
        addTestCase("issue 3167", \issue3167());
        addTestCase("Test HashDecl", \testHashDecl());
        set_return_value(main());
    }

    issue3654() {
        assertEq(False, B.b);
    }

    serializeImportedTest() {
        Program p();
        p.importHashDecl("Issue3518");
        p.parse("binary sub t() { return Serializable::serialize(<Issue3518>{\"i\": 1}); }", "");
        assertEq("hash<Issue3518>", Serializable::deserialize(p.callFunction("t")).fullType());
    }

    issue3167() {
        assertThrows("RUNTIME-TYPE-ERROR", "key.*Container",
            sub () {
                hash<Container> h();
                h += {"str": str_test()};
                delete h;
            }
        );
        assertThrows("RUNTIME-TYPE-ERROR", "key.*Container",
            sub () {
                hash<Container> h({
                    "str": str_test(),
                });
                delete h;
            }
        );
    }

    testHashDecl() {
        # issue #2491: test implicit creation of typed hashes (explicit value type)
        {
            hash<string, int> c;
            c.a = 1;
            assertEq(Type::Hash, c.type());
            assertEq("hash<string, int>", c.fullType());
            assertEq(1, c.a);
            assertEq(True, c instanceof hash<string, int>);
        }

        # issue #2491: test implicit creation of typed hashes (hashdecl)
        assertThrows("HASHDECL-IMPLICIT-CONSTRUCTION-ERROR", "Container", sub () {hash<Container> c; c.n = M_PIn;});

        {
            hash<Container> c();
            assertEq(Type::Hash, c.type());
            assertEq(1, c.i);
            assertEq("hi", c.str);
            assertEq(NOTHING, c.n);
            assertEq("hash<Container>", c.fullType());
            assertEq(True, c instanceof hash<Container>);
        }

        {
            hash<Container> c(("i": 2, "str": "other", "n": 1n));
            assertEq(Type::Hash, c.type());
            assertEq(2, c.i);
            assertEq("other", c.str);
            assertEq(1n, c.n);
        }

        {
            hash<Container> c(("n": 1n));
            assertEq(Type::Hash, c.type());
            assertEq(1, c.i);
            assertEq("hi", c.str);
            assertEq(1n, c.n);
        }

        {
            hash<Container> c();
            hash h = c;
            assertEq(Type::Hash, h.type());
            assertEq(1, h.i);
            assertEq("hi", h.str);
            assertEq(NOTHING, h.n);

            assertEq(h, c);
            assertEq(c, h);
        }

        {
            hash<Container> c();
            hash h = c;
            c = cast<hash<Container>>(h);
            assertEq(Type::Hash, c.type());
            assertEq(1, c.i);
            assertEq("hi", c.str);
            assertEq(NOTHING, c.n);

            assertEq(h, c);
            assertEq(c, h);
        }

        {
            hash<Container> c1();
            hash<Container> c2 = new hash<Container>();
            assertEq(c1, c2);
        }

        {
            hash<Container> c1(("i": 2, "str": "other", "n": 1n));
            hash<Container> c2 = new hash<Container>(("i": 2, "str": "other", "n": 1n));
            assertEq(c1, c2);
            c2.n = 2n;
            assertEq(True, c1 != c2);
        }

        {
            hash<Container> c1();
            hash<Container2> c2;
            c2 = cast<hash<Container2>>(c1);
            assertEq(Type::Date, c2.d.type());
        }

        {
            hash<string, int> h(("i": 4));
            hash<Container> c1 = cast<hash<Container>>(h);
            assertEq(4, c1.i);
        }

        {
            # make sure that a runtime assignment from a hashdecl hash to an untyped hash results in an untyped hash assignment
            hash h = new hash<Container>();
            h.b = "str";
            assertEq("str", h.b);
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", "assignment", \p.parse(), ("sub t() {hash<string, int> h = new hash<Container>();}", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", "assignment", \p.parse(), ("sub t() {hash<Container> c = new hash<string, int>(('x': 3));}", ""));
        }

        {
            hash<Container> c1;
            hash<Container2> c2();
            c1 = cast<hash<Container>>(c2 - "d");
            assertEq(c1.i, 2);
        }

        {
            hash<Test::Container3> c3();
            assertEq(3, c3.i);
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() {hash<Container> c();}", "");
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("sub t() {hash<Container> c(); c = (\"i\": 2, \"str\": \"other\", \"n\": 1.1n);}", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("sub t() {hash<Container> c1; hash<Container2> c2(); c1 = cast<hash<Container2>>(c2);}", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("sub t() {hash<Container> c1; c1 = ('x1': 1);}", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("sub t() {hash<Container> c1; hash<Container2> c2(); c1 = c2;}", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() {hash h = ('x': 1); hash<Container2> c2(); c2 += h;}", "");
            assertThrows("HASHDECL-KEY-ERROR", \p.callFunction(), ("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() {hash<Container> c(); string k = 'x1'; c{k} = 2;}", "");
            assertThrows("INVALID-MEMBER", \p.callFunction(), ("t"));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() {hash<Container> c(); c.x1 = 2;}", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("sub tn(string str) {} sub t() {hash<Container> c(); tn(c.i);}", ""));
        }

        {
            Program p(PO_NEW_STYLE);
            assertThrows("PARSE-TYPE-ERROR", \p.parse(), ("sub t() {hash<Container> c(); c.i = 'str';}", ""));
        }

        if (HAVE_STACK_GUARD) {
            Program p(PO_NEW_STYLE);
            p.parse("sub t() { hash<R1> r(); }", "");
            assertThrows("STACK-LIMIT-EXCEEDED", \p.callFunction(), "t");
        }

        {
            Program p(PO_NEW_STYLE|PO_NO_INHERIT_USER_HASHDECLS);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() {hash<Container> c; delete c;}", ""));
        }

        {
            Program p(PO_NEW_STYLE|PO_NO_INHERIT_USER_HASHDECLS);
            p.importHashDecl("Container");
            p.parse("hash<Container> sub t() {return new hash<Container>();}", "");
            hash<Container> c = p.callFunction("t");
            assertEq(True, c instanceof hash<Container>);
        }

        {
            Program p(PO_NEW_STYLE|PO_NO_INHERIT_SYSTEM_HASHDECLS);
            assertThrows("PARSE-EXCEPTION", \p.parse(), ("sub t() {hash<ExceptionInfo> c; delete c;}", ""));
        }
    }
}
