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

class TestC inherits Socket {
    private {
        int a;
        int b;
    }
    public {
        list data;
        hash t;
        int x;
        any key;
        any unique;
        any tnew;
        any barn;
        any asd;
    }

    constructor(any a, any b, any c) {
        a = 1;
        b = 2;
        data = (a, b, c);
    }
    any getData(int elem) {
        if (exists elem)
            return data[elem];
        return data;
    }
    string methodGate(string m) {
        return m;
    }
    string memberGate(string m) {
        return "memberGate-" + m;
    }
    memberNotification(string m) {
        t.m = self.m;
    }
    code closure(any x) {
        int a = 1;
        # return a closure encapsulating the state of the object
        return string sub (any y) {
            return sprintf("%s-%n-%n-%n", data[1], x, y, ++a);
        };
    }
    any argTest() {
        return argv;
    }
}

hashdecl T {
    int i;
}

hashdecl MyHash {
    hash<T> hd();
    hash<string, int> hi();
    list<int> l();
}

class HTTest {
    public {
        hash<MyHash> hd();
        hash<string, int> hi();
        list<int> l();

        static hash<MyHash> vhd();
        static hash<string, int> vhi();
        static list<int> vl();
    }
}

hashdecl MyHash0 {
    list<hash<MyHash>> l;
}

const H0 = <MyHash0>{
    "l": (<MyHash>{},),
};

const H1 = H0 + <MyHash0>{
    "l": H0.l + (<MyHash>{},),
};

class HashTest inherits QUnit::Test {
    constructor () : Test("Hash test", "1.0") {
        addTestCase("Hash test", \testHash());

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

    hash hash_return(any var) {
        return ( "gee" : "whiz",
	         "num" : test1(),
	         "var" : var );
    }

    int test1() { return 1; }
    int test2() { return 2; }

    testHash() {
        # hash tests
        hash b = ( "test" : 1, "gee" : 2, "well" : "string" );
        assertEq(2, b.gee, "object dereference");
        assertEq(3, elements b, "elements operator on hash before delete");
        delete b{"gee"};
        assertEq(2, elements b, "elements operator on hash after delete");
        b{"test"} = "there";
        hash d{"gee"}[25] = "I hope it works";
        assertEq("there", b.test, "hash dereference after assignment");
        assertEq("there", b.test, "object dereference after assignment");
        assertEq(NOTHING, b{"geez"}, "non-existent object dereference");
        assertEq(0, int(d.gee[25]), "hash dereference of list element");
        assertEq("I hope it works", d{"gee"}[25], "dereference of list member of hash");
        hash c = ( "hi" : "there", "gee" : "whillakers" );
        d = c;
        assertEq(True, d == c, "hash comparison");
        assertEq("whillakers", d.gee, "hash dereference after entire hash assignment");
        c{"gee"} = "roo";
        assertEq("roo", c{"gee"}, "original hash dereference after assignment to member of copied hash");
        assertEq("whillakers", d.gee, "hash dereference of member of copied hash");
        d = ( "gee" : test1(), "howdy" : test2());
        assertEq(1, d.gee, "hash dereference with evaluation");
        assertEq("whiz", hash_return(){"gee"}, "simple hash return and dereference");
        assertEq(1, hash_return(){"num"}, "hash return with function element result and dereference");
        assertEq("hi there", hash_return("hi there"){"var"}, "hash return with local variable result and dereference");

        hash a = ( "key" : 1, "unique" : 100, "asd" : "dasd" );
        b = ( "key" : 3, "tnew" : 45, "barn" : "door" );
        c = a + b;
        assertEq(3, c.key, "hash plus operator element override");
        assertEq(45, c."tnew", "hash plus operator new element");
        assertEq(100, c.unique, "hash plus operator unchanged element");
        a += b;
        assertEq(3, a.key, "hash plus equals operator element override");
        assertEq(45, a."tnew", "hash plus equals operator new element");
        assertEq(100, a.unique, "hash plus equals operator unchanged element");

        # test hash slice creation
        assertEq(("unique" : 100, "tnew" : 45), a.("unique", "tnew"), "hash slice creation");

        TestC ot(1, "two", 3.0);
        ot += a;
        assertEq(("unique" : 100, "tnew" : 45), ot.("unique", "tnew"), "hash slice creation from object");

        # use the foreach ... in (keys <hash>) specialization
        int cnt = 0;
        foreach my string k in (keys c) {
            # to avoid unused local var warning
            delete k;
            ++cnt;
        }
        assertEq(5, cnt, "foreach hash keys specialization");
        # do pseudo-method tests
        assertEq("key", c.firstKey(), "<hash>.firstKey()");
        assertEq("barn", c.lastKey(), "<hash>.lastKey()");
        assertEq(5, c.size(), "<hash>.size()");

        hash nch = c.("key", "barn");
        foreach my hash hi in (nch.pairIterator()) {
            if (!$#)
                assertEq("key", hi.key, "HashIterator::first()");
            else if ($# == 4)
                assertEq("barn", hi.key, "HashIterator::last()");
        }

        HashPairReverseIterator hi(nch);
        foreach my hash hiv in (hi) {
            if ($# == 4)
                assertEq("key", hiv.key, "HashReverseIterator::last()");
            else if (!$#)
                assertEq("barn", hiv.key, "HashReverseIterator::first()");
        }
        assertEq(False, hi.valid(), "HashReverseIterator::valid()");
        # restart iterator
        assertEq(True, hi.next(), "HashReverseIterator::next()");
        assertEq("barn", hi.getKey(), "HashReverseIterator::getKey()");
        hi.reset();
        assertEq(False, hi.valid(), "HashReverseIterator::valid() after reset");

        # delete 3 keys from the c hash
        b = c - "tnew" - "barn" - "asd";
        assertEq(( "key" : 3, "unique" : 100 ), b, "hash minus operator");
        b = c - ("tnew", "barn", "asd");
        assertEq(( "key" : 3, "unique" : 100 ), b, "hash minus operator with list argument");
        b -= "unique";
        assertEq(( "key" : 3 ), b, "hash minus-equals operator");
        c -= ( "tnew", "barn" );
        assertEq(( "key": 3, "unique" : 100, "asd" : "dasd" ), c, "hash minus-equals operator with list argument");
        hash nh += ( "new-hash" : 1 );
        assertEq(( "new-hash" : 1 ), nh, "hash plus-equals, lhs NOTHING");

        HTTest ht();
        assertEq(Type::Hash, ht.hd.type());
        assertEq(Type::Hash, ht.hi.type());
        assertEq(Type::List, ht.l.type());
        assertEq(Type::Hash, HTTest::vhd.type());
        assertEq(Type::Hash, HTTest::vhi.type());
        assertEq(Type::List, HTTest::vl.type());

        {
            hash<MyHash> hd();
            assertEq(Type::Hash, hd.hd.type());
            assertEq(Type::Hash, hd.hi.type());
            assertEq(Type::List, hd.l.type());
        }

        {
            auto hd = <MyHash>{};
            assertEq(Type::Hash, hd.hd.type());
            assertEq(Type::Hash, hd.hi.type());
            assertEq(Type::List, hd.l.type());
        }

        {
            auto hd = hash<MyHash>{};
            assertEq(Type::Hash, hd.hd.type());
            assertEq(Type::Hash, hd.hi.type());
            assertEq(Type::List, hd.l.type());
        }

        {
            auto hd = <MyHash>{"hd": <T>{"i": 3}};
            assertEq(3, hd.hd.i);
            assertEq(Type::Hash, hd.hd.type());
            assertEq(Type::Hash, hd.hi.type());
            assertEq(Type::List, hd.l.type());
        }

        {
            code t = auto sub(int i) { return <StatInfo>{}; };
            auto v = 1;
            hash<auto> h = {"a": 1, "b": t(v)};
            assertEq("hash<StatInfo>", h.b.fullType());
        }
    }
}
