# Copyright (c) 2023, NVIDIA CORPORATION.  All rights reserved.
#
# NVIDIA CORPORATION and its licensors retain all intellectual property
# and proprietary rights in and to this software, related documentation
# and any modifications thereto.  Any use, reproduction, disclosure or
# distribution of this software and related documentation without an express
# license agreement from NVIDIA CORPORATION is strictly prohibited.
#

import numpy as np
import omni.kit.test
from omni.ujitso import *

class TestBindings(omni.kit.test.AsyncTestCase):
    """Test bindings in this extension"""

    async def test_operation_result(self):
        """validate binding of OperationResult"""
        self.assertEqual(len(OperationResult.__members__), 11)

        self.assertEqual(int(OperationResult.SUCCESS), 0)
        self.assertEqual(int(OperationResult.FAILURE), 1)
        self.assertEqual(int(OperationResult.OVERFLOW_ERROR), 2)
        self.assertEqual(int(OperationResult.INVALIDHANDLE_ERROR), 3)
        self.assertEqual(int(OperationResult.NOPROCESSOR_ERROR), 4)
        self.assertEqual(int(OperationResult.NOTFOUND_ERROR), 5)
        self.assertEqual(int(OperationResult.NOTBUILT_ERROR), 6)
        self.assertEqual(int(OperationResult.INVALIDMETADATA_ERROR), 7)
        self.assertEqual(int(OperationResult.OUTOFMEMORY_ERROR), 8)
        self.assertEqual(int(OperationResult.DATAVALIDATION_ERROR), 10)
        self.assertEqual(int(OperationResult.INTERNAL), 0xffff)

    async def test_validation_type(self):
        """validate binding of ValidationType"""
        self.assertEqual(len(ValidationType.__members__), 3)

        self.assertEqual(int(ValidationType.MANDATORY), 0)
        self.assertEqual(int(ValidationType.DEFERRED), 1)
        self.assertEqual(int(ValidationType.NONE), 2)

    async def test_match_result(self):
        """validate binding of MatchResult"""
        self.assertEqual(len(MatchResult.__members__), 4)

        self.assertEqual(int(MatchResult.FAILURE), 0)
        self.assertEqual(int(MatchResult.LOWEST_PRIORITY), 1)
        self.assertEqual(int(MatchResult.NORMAL_PRIORITY), 1000)
        self.assertEqual(int(MatchResult.HIGHEST_PRIORITY), 2000)

    async def test_global_key_token(self):
        """validate binding of GlobalKeyToken"""
        self.assertEqual(len(IRegistry.GlobalKeyToken.__members__), 9)

        self.assertEqual(int(IRegistry.PATH), 0)
        self.assertEqual(int(IRegistry.VERSION), 1)
        self.assertEqual(int(IRegistry.TIME), 2)
        self.assertEqual(int(IRegistry.FLAGS), 3)
        self.assertEqual(int(IRegistry.PARAM0), 4)
        self.assertEqual(int(IRegistry.PARAM1), 5)
        self.assertEqual(int(IRegistry.PARAM2), 6)
        self.assertEqual(int(IRegistry.PARAM3), 7)
        self.assertEqual(int(IRegistry.CUSTOM_START), 1 << 16)

        self.assertEqual(int(IRegistry.GlobalKeyToken.PATH), 0)
        self.assertEqual(int(IRegistry.GlobalKeyToken.VERSION), 1)
        self.assertEqual(int(IRegistry.GlobalKeyToken.TIME), 2)
        self.assertEqual(int(IRegistry.GlobalKeyToken.FLAGS), 3)
        self.assertEqual(int(IRegistry.GlobalKeyToken.PARAM0), 4)
        self.assertEqual(int(IRegistry.GlobalKeyToken.PARAM1), 5)
        self.assertEqual(int(IRegistry.GlobalKeyToken.PARAM2), 6)
        self.assertEqual(int(IRegistry.GlobalKeyToken.PARAM3), 7)
        self.assertEqual(int(IRegistry.GlobalKeyToken.CUSTOM_START), 1 << 16)

    async def test_retrieve_flags(self):
        """validate binding of RetrieveFlags"""
        self.assertEqual(len(IDataStore.RetrieveFlags.__members__), 6)

        self.assertEqual(int(IDataStore.EN_RETRIEVEFLAG_NONE), 0)
        self.assertEqual(int(IDataStore.EN_RETRIEVEFLAG_SYNC), 1 << 0)
        self.assertEqual(int(IDataStore.EN_RETRIEVEFLAG_SIZE_ONLY), 1 << 1)
        self.assertEqual(int(IDataStore.EN_RETRIEVEFLAG_EXISTENCE_ONLY), 1 << 2)
        self.assertEqual(int(IDataStore.EN_RETRIEVEFLAG_LOCAL), 1 << 3)
        self.assertEqual(int(IDataStore.EN_RETRIEVEFLAG_CLUSTER), 1 << 4)

        self.assertEqual(int(IDataStore.RetrieveFlags.EN_RETRIEVEFLAG_NONE), 0)
        self.assertEqual(int(IDataStore.RetrieveFlags.EN_RETRIEVEFLAG_SYNC), 1 << 0)
        self.assertEqual(int(IDataStore.RetrieveFlags.EN_RETRIEVEFLAG_SIZE_ONLY), 1 << 1)
        self.assertEqual(int(IDataStore.RetrieveFlags.EN_RETRIEVEFLAG_EXISTENCE_ONLY), 1 << 2)
        self.assertEqual(int(IDataStore.RetrieveFlags.EN_RETRIEVEFLAG_LOCAL), 1 << 3)
        self.assertEqual(int(IDataStore.RetrieveFlags.EN_RETRIEVEFLAG_CLUSTER), 1 << 4)

    async def test_store_flags(self):
        """validate binding of StoreFlags"""
        self.assertEqual(len(IDataStore.StoreFlags.__members__), 10)

        self.assertEqual(int(IDataStore.EN_STOREFLAG_DEFAULT), 0)
        self.assertEqual(int(IDataStore.EN_STOREFLAG_NO_LOCAL), 1 << 0)
        self.assertEqual(int(IDataStore.EN_STOREFLAG_LOCAL_INCONSISTENT), 1 << 1)
        self.assertEqual(int(IDataStore.EN_STOREFLAG_NO_CLUSTER), 1 << 2)
        self.assertEqual(int(IDataStore.EN_STOREFLAG_CLUSTER_INCONSISTENT), 1 << 3)
        self.assertEqual(int(IDataStore.EN_STOREFLAG_NO_REMOTE), 1 << 4)
        self.assertEqual(int(IDataStore.EN_STOREFLAG_REMOTE_INCONSISTENT), 1 << 5)
        self.assertEqual(int(IDataStore.EN_STOREFLAG_PERSISTENCE_PRIORITY_LOW), 1 << 6)
        self.assertEqual(int(IDataStore.EN_STOREFLAG_PERSISTENCE_PRIORITY_MEDIUM), 2 << 6)
        self.assertEqual(int(IDataStore.EN_STOREFLAG_PERSISTENCE_PRIORITY_HIGH), 3 << 6)

        self.assertEqual(int(IDataStore.StoreFlags.EN_STOREFLAG_DEFAULT), 0)
        self.assertEqual(int(IDataStore.StoreFlags.EN_STOREFLAG_NO_LOCAL), 1 << 0)
        self.assertEqual(int(IDataStore.StoreFlags.EN_STOREFLAG_LOCAL_INCONSISTENT), 1 << 1)
        self.assertEqual(int(IDataStore.StoreFlags.EN_STOREFLAG_NO_CLUSTER), 1 << 2)
        self.assertEqual(int(IDataStore.StoreFlags.EN_STOREFLAG_CLUSTER_INCONSISTENT), 1 << 3)
        self.assertEqual(int(IDataStore.StoreFlags.EN_STOREFLAG_NO_REMOTE), 1 << 4)
        self.assertEqual(int(IDataStore.StoreFlags.EN_STOREFLAG_REMOTE_INCONSISTENT), 1 << 5)
        self.assertEqual(int(IDataStore.StoreFlags.EN_STOREFLAG_PERSISTENCE_PRIORITY_LOW), 1 << 6)
        self.assertEqual(int(IDataStore.StoreFlags.EN_STOREFLAG_PERSISTENCE_PRIORITY_MEDIUM), 2 << 6)
        self.assertEqual(int(IDataStore.StoreFlags.EN_STOREFLAG_PERSISTENCE_PRIORITY_HIGH), 3 << 6)

    async def test_request_handle(self):
        """validate binding of RequestHandle"""
        # validate the default value
        handle = RequestHandle()
        self.assertEqual(handle.value, 0)

        # only positive integer is accepted
        valid_values = [1, 1<<64 - 1, 0]
        invalid_values = [-1, -1.0, 1.0, 1<<64]

        # validate setter with valid values
        for val in valid_values:
            handle.value = val
            self.assertEqual(handle.value, val)

        # validate setter with invalid values
        for val in invalid_values:
            with self.assertRaises(TypeError):
                handle.value = val

        # only positive integer is accepted
        valid_indices = [1, 1<<32 - 1, 0]

        # validate initialization with valid values
        for val in valid_values:
            handle = RequestHandle(val)
            self.assertEqual(handle.value, val)

            handle = RequestHandle(value = val)
            self.assertEqual(handle.value, val)

        # validate initialization with invalid values
        for val in invalid_values:
            with self.assertRaises(TypeError):
                handle = RequestHandle(val)

            with self.assertRaises(TypeError):
                handle = RequestHandle(value = val)

    async def test_key_token(self):
        """validate binding of KeyToken"""
        # validate the default value
        token = KeyToken()
        self.assertEqual(token.value, 0)

        # only positive integer is accepted
        valid_values = [1, 1<<32 - 1, 0]
        invalid_values = [-1, -1.0, 1.0, 1<<32]

        # validate setter with valid values
        for val in valid_values:
            token.value = val
            self.assertEqual(token.value, val)

        # validate setter with invalid values
        for val in invalid_values:
            with self.assertRaises(TypeError):
                token.value = val

        # the value shouldn't change
        self.assertEqual(token.value, valid_values[-1])

        # validate initialization with valid values
        for val in valid_values:
            token = KeyToken(val)
            self.assertEqual(token.value, val)

            token = KeyToken(value = val)
            self.assertEqual(token.value, val)

        # validate initialization with invalid values
        for val in invalid_values:
            with self.assertRaises(TypeError):
                token = KeyToken(val)

            with self.assertRaises(TypeError):
                token = KeyToken(value = val)

        # validate the value of STATIC_STRING_HASH_MARKER
        self.assertEqual(KeyToken.STATIC_STRING_HASH_MARKER, 0x80000000)

        # can't set attribute
        with self.assertRaises(AttributeError):
            KeyToken.STATIC_STRING_HASH_MARKER = 0

        # validate __eq__
        for val in valid_values:
            self.assertEqual(KeyToken(val), KeyToken(val))
            self.assertNotEqual(KeyToken(val), KeyToken(2))

    async def test_key_token_ex(self):
        """validate binding of KeyTokenEx"""
        key_token_ex = KeyTokenEx(IRegistry.GlobalKeyToken.PATH)
        self.assertEqual(key_token_ex.value, IRegistry.GlobalKeyToken.PATH)

        key_token_ex = KeyTokenEx(IRegistry.PATH)
        self.assertEqual(key_token_ex.value, IRegistry.PATH)

        key_token_ex = KeyTokenEx("")
        key_token_ex = KeyTokenEx("test")

        key_token_ex = KeyTokenEx(KeyToken())
        self.assertEqual(key_token_ex.value, 0)

        valid_values = [0, 1, 1<<32 - 1]

        for val in valid_values:
            key_token_ex_a = KeyTokenEx(KeyToken(val))
            self.assertEqual(key_token_ex_a.value, val)

            key_token_ex_b = KeyTokenEx(KeyToken())
            key_token_ex_b.value = val
            self.assertEqual(key_token_ex_b.value, val)

            self.assertTrue(key_token_ex_a == key_token_ex_b)
            self.assertTrue(key_token_ex_b == key_token_ex_a)
            self.assertFalse(key_token_ex_a != key_token_ex_b)
            self.assertFalse(key_token_ex_b != key_token_ex_a)

            key_token = KeyToken(val)
            self.assertTrue(key_token_ex_a == key_token)
            self.assertTrue(key_token == key_token_ex_a)
            self.assertFalse(key_token_ex_a != key_token)
            self.assertFalse(key_token != key_token_ex_a)

            key_token_100 = KeyToken(100)
            self.assertFalse(key_token_ex_a == key_token_100)
            self.assertFalse(key_token_100 == key_token_ex_a)
            self.assertTrue(key_token_ex_a != key_token_100)
            self.assertTrue(key_token_100 != key_token_ex_a)

            key_token_ex_100 = KeyTokenEx(key_token_100)
            self.assertFalse(key_token_ex_a == key_token_ex_100)
            self.assertFalse(key_token_ex_100 == key_token_ex_a)
            self.assertTrue(key_token_ex_a != key_token_ex_100)
            self.assertTrue(key_token_ex_100 != key_token_ex_a)

        with self.assertRaises(TypeError):
            key_token_ex = KeyTokenEx()

        with self.assertRaises(TypeError):
            key_token_ex = KeyTokenEx(0)

        # This following line will end up calling C++ KeyTokenEx::KeyTokenEx(const char* str) with `str` being nullptr.
        # It will lead to crash but it is a problem of the C++ implementation rather than the Python binding code.
        #
        # key_token_ex = KeyTokenEx(None)

    async def test_dynamic_request(self):
        """validate binding of DynamicRequest"""
        # validate the default constructor
        dynamic_request = DynamicRequest()
        self.assertEqual(dynamic_request.size(), 0)

        request = dynamic_request.get_request()
        self.assertEqual(request.type.count, 0)
        self.assertEqual(request.type.keys.tolist(), [])
        self.assertEqual(request.type.types.tolist(), [])
        self.assertEqual(request.tokenValues, b'')

        # validate addition of different data types
        buf = [12, 13, 14]
        buf_token_type = UjitsoUtils.make_uint8_request_token_type()
        buf_token_type.size = len(buf)

        key_value_pairs = [
            (KeyTokenEx("void"),),
            (KeyTokenEx("key token"), KeyToken(1)),
            (KeyTokenEx("8-bit unsigned interger"), 2),
            (KeyTokenEx("16-bit signed integer"), 3),
            (KeyTokenEx("16-bit unsigned integer"), 4),
            (KeyTokenEx("32-bit signed integer"), 5),
            (KeyTokenEx("32-bit unsigned integer"), 6),
            (KeyTokenEx("64-bit signed integer"), 7),
            (KeyTokenEx("64-bit unsigned integer"), 8),
            (KeyTokenEx("double precision floating point"), 9.0),
            (KeyTokenEx("single precision floating point"), 10.0),
            (KeyTokenEx("string"), "11"),
            (KeyTokenEx("buffer"), buf_token_type, buf)]

        token_type_size_list = [0, 4, 1, 2, 2, 4, 4, 8, 8, 8, 4, 3, len(buf)]
        self.assertEqual(len(key_value_pairs), len(token_type_size_list))

        dynamic_request.reserve(len(key_value_pairs))

        counter = 0
        index = dynamic_request.add(*(key_value_pairs[counter]))
        self.assertEqual(index, counter)
        self.assertEqual(dynamic_request.size(), counter + 1)

        counter += 1
        index = dynamic_request.add_key_token(*(key_value_pairs[counter]))
        self.assertEqual(index, counter)
        self.assertEqual(dynamic_request.size(), counter + 1)

        counter += 1
        index = dynamic_request.add_uint8(*(key_value_pairs[counter]))
        self.assertEqual(index, counter)
        self.assertEqual(dynamic_request.size(), counter + 1)

        counter += 1
        index = dynamic_request.add_int16(*(key_value_pairs[counter]))
        self.assertEqual(index, counter)
        self.assertEqual(dynamic_request.size(), counter + 1)

        counter += 1
        index = dynamic_request.add_uint16(*(key_value_pairs[counter]))
        self.assertEqual(index, counter)
        self.assertEqual(dynamic_request.size(), counter + 1)

        counter += 1
        index = dynamic_request.add_int(*(key_value_pairs[counter]))
        self.assertEqual(index, counter)
        self.assertEqual(dynamic_request.size(), counter + 1)

        counter += 1
        index = dynamic_request.add_uint(*(key_value_pairs[counter]))
        self.assertEqual(index, counter)
        self.assertEqual(dynamic_request.size(), counter + 1)

        counter += 1
        index = dynamic_request.add_int64(*(key_value_pairs[counter]))
        self.assertEqual(index, counter)
        self.assertEqual(dynamic_request.size(), counter + 1)

        counter += 1
        index = dynamic_request.add_uint64(*(key_value_pairs[counter]))
        self.assertEqual(index, counter)
        self.assertEqual(dynamic_request.size(), counter + 1)

        counter += 1
        index = dynamic_request.add_double(*(key_value_pairs[counter]))
        self.assertEqual(index, counter)
        self.assertEqual(dynamic_request.size(), counter + 1)

        counter += 1
        index = dynamic_request.add_float(*(key_value_pairs[counter]))
        self.assertEqual(index, counter)
        self.assertEqual(dynamic_request.size(), counter + 1)

        counter += 1
        index = dynamic_request.add_string(*(key_value_pairs[counter]))
        self.assertEqual(index, counter)
        self.assertEqual(dynamic_request.size(), counter + 1)

        counter += 1
        index = dynamic_request.add_buffer(*(key_value_pairs[counter]))
        self.assertEqual(index, counter)
        self.assertEqual(dynamic_request.size(), counter + 1)

        for i in range(dynamic_request.size()):
            key_value_pair = key_value_pairs[i]
            token_type_size = token_type_size_list[i]

            key = key_value_pair[0]

            found, request_token_type = dynamic_request.find_key(key)
            self.assertTrue(found)
            self.assertEqual(request_token_type.size, token_type_size)

            self.assertEqual(dynamic_request.get_key(i), key)
            self.assertEqual(dynamic_request.get_type(i), request_token_type)

        # validate reading of existent requests through matching template.
        counter = 1
        found, value = dynamic_request.get_as_key_token(key_value_pairs[counter][0])
        self.assertTrue(found)
        self.assertEqual(value, key_value_pairs[counter][1])

        counter += 1
        found, value = dynamic_request.get_as_uint8(key_value_pairs[counter][0])
        self.assertTrue(found)
        self.assertEqual(value, key_value_pairs[counter][1])

        counter += 1
        found, value = dynamic_request.get_as_int16(key_value_pairs[counter][0])
        self.assertTrue(found)
        self.assertEqual(value, key_value_pairs[counter][1])

        counter += 1
        found, value = dynamic_request.get_as_uint16(key_value_pairs[counter][0])
        self.assertTrue(found)
        self.assertEqual(value, key_value_pairs[counter][1])

        counter += 1
        found, value = dynamic_request.get_as_int32(key_value_pairs[counter][0])
        self.assertTrue(found)
        self.assertEqual(value, key_value_pairs[counter][1])

        counter += 1
        found, value = dynamic_request.get_as_uint32(key_value_pairs[counter][0])
        self.assertTrue(found)
        self.assertEqual(value, key_value_pairs[counter][1])

        counter += 1
        found, value = dynamic_request.get_as_int64(key_value_pairs[counter][0])
        self.assertTrue(found)
        self.assertEqual(value, key_value_pairs[counter][1])

        counter += 1
        found, value = dynamic_request.get_as_uint64(key_value_pairs[counter][0])
        self.assertTrue(found)
        self.assertEqual(value, key_value_pairs[counter][1])

        counter += 1
        found, value = dynamic_request.get_as_double(key_value_pairs[counter][0])
        self.assertTrue(found)
        self.assertEqual(value, key_value_pairs[counter][1])

        counter += 1
        found, value = dynamic_request.get_as_float(key_value_pairs[counter][0])
        self.assertTrue(found)
        self.assertEqual(value, key_value_pairs[counter][1])

        counter += 1
        found, value = dynamic_request.get_as_string(key_value_pairs[counter][0])
        self.assertTrue(found)
        self.assertEqual(value, key_value_pairs[counter][1])

        # validate reading of an existent request through mismatching template.
        found, value = dynamic_request.get_as_uint8(key_value_pairs[counter][0])
        self.assertFalse(found)
        self.assertEqual(value, 0)

        found, value = dynamic_request.get_as_uint8(key_value_pairs[0][0])
        self.assertFalse(found)
        self.assertEqual(value, 0)

        # validate reading of an nonexistent request.
        nonexistent_key_token = KeyTokenEx("nonexistent")
        found, value = dynamic_request.get_as_key_token(nonexistent_key_token)
        self.assertFalse(found)
        self.assertEqual(value, KeyToken())

        found, value = dynamic_request.get_as_uint8(nonexistent_key_token)
        self.assertFalse(found)
        self.assertEqual(value, 0)

        found, value = dynamic_request.get_as_int16(nonexistent_key_token)
        self.assertFalse(found)
        self.assertEqual(value, 0)

        found, value = dynamic_request.get_as_uint16(nonexistent_key_token)
        self.assertFalse(found)
        self.assertEqual(value, 0)

        found, value = dynamic_request.get_as_int32(nonexistent_key_token)
        self.assertFalse(found)
        self.assertEqual(value, 0)

        found, value = dynamic_request.get_as_uint32(nonexistent_key_token)
        self.assertFalse(found)
        self.assertEqual(value, 0)

        found, value = dynamic_request.get_as_int64(nonexistent_key_token)
        self.assertFalse(found)
        self.assertEqual(value, 0)

        found, value = dynamic_request.get_as_uint64(nonexistent_key_token)
        self.assertFalse(found)
        self.assertEqual(value, 0)

        found, value = dynamic_request.get_as_double(nonexistent_key_token)
        self.assertFalse(found)
        self.assertEqual(value, 0.0)

        found, value = dynamic_request.get_as_float(nonexistent_key_token)
        self.assertFalse(found)
        self.assertEqual(value, 0.0)

        found, value = dynamic_request.get_as_string(nonexistent_key_token)
        self.assertFalse(found)
        self.assertEqual(value, None)

        # validate the constructor with arguments
        new_dynamic_request = DynamicRequest(Request())
        self.assertEqual(new_dynamic_request.size(), 0)

        request = new_dynamic_request.get_request()
        self.assertEqual(request.type.count, 0)
        self.assertEqual(request.type.keys.tolist(), [])
        self.assertEqual(request.type.types.tolist(), [])
        self.assertEqual(request.tokenValues, b'')

        # validate copying of nonexistent requests between DynamicRequest instances
        found = new_dynamic_request.copy(nonexistent_key_token, dynamic_request)
        self.assertFalse(found)
        self.assertEqual(new_dynamic_request.size(), 0)

        # validate copying of existent requests between DynamicRequest instances
        existent_key_token = KeyTokenEx("8-bit unsigned interger")
        found = new_dynamic_request.copy(existent_key_token, dynamic_request)
        self.assertTrue(found)
        self.assertEqual(new_dynamic_request.size(), 1)

        # validate key replacement of a nonexistent request
        new_key_token = KeyTokenEx("unsigned char")
        found = new_dynamic_request.replace_key(nonexistent_key_token, new_key_token)
        self.assertFalse(found)

        # validate key replacement of an existent request
        found = new_dynamic_request.replace_key(existent_key_token, new_key_token)
        self.assertTrue(found)

        found, value = new_dynamic_request.get_as_uint8(new_key_token)
        self.assertTrue(found)
        self.assertEqual(value, 2)

        # validate value replacement
        found = new_dynamic_request.replace_value_uint8(new_key_token, 100)
        self.assertTrue(found)

        # validate removal of a nonexistent request
        found = new_dynamic_request.remove_key(nonexistent_key_token)
        self.assertFalse(found)
        self.assertEqual(new_dynamic_request.size(), 1)

        # validate removal of an existent request
        found = new_dynamic_request.remove_key(new_key_token)
        self.assertTrue(found)
        self.assertEqual(new_dynamic_request.size(), 0)

    async def test_request_token_type(self):
        """validate binding of RequestTokenType"""
        # validate the default value
        request_token_type = RequestTokenType()
        self.assertEqual(request_token_type.size, 0)
        self.assertEqual(request_token_type.typeNameHash, 0)

        # only positive integer is accepted
        valid_values = [1, 1<<32 - 1, 0]
        invalid_values = [-1, -1.0, 1.0, 1<<32]

        # validate setter with valid values
        for val in valid_values:
            request_token_type.size = val
            self.assertEqual(request_token_type.size, val)

            request_token_type.typeNameHash = val
            self.assertEqual(request_token_type.typeNameHash, val)

        # validate setter with invalid values
        for val in invalid_values:
            with self.assertRaises(TypeError):
                request_token_type.size = val

            with self.assertRaises(TypeError):
                request_token_type.typeNameHash = val

        # the value shouldn't change
        self.assertEqual(request_token_type.size, valid_values[-1])
        self.assertEqual(request_token_type.typeNameHash, valid_values[-1])

        # validate initialization with valid values
        for val in valid_values:
            request_token_type = RequestTokenType(val, val)
            self.assertEqual(request_token_type.size, val)
            self.assertEqual(request_token_type.typeNameHash, val)

            request_token_type = RequestTokenType(size = val, type_name_hash= val)
            self.assertEqual(request_token_type.size, val)
            self.assertEqual(request_token_type.typeNameHash, val)

        # validate initialization with invalid values
        for val in invalid_values:
            with self.assertRaises(TypeError):
                request_token_type = KeyToken(val, val)

            with self.assertRaises(TypeError):
                request_token_type = KeyToken(value = val, type_name_hash = val)

    async def test_make_request_token_type(self):
        """validate bindings of template function makeRequestTokenType()"""
        request_token_type = UjitsoUtils.make_key_token_request_token_type()
        self.assertEqual(request_token_type.size, 4)
        self.assertNotEqual(request_token_type.typeNameHash, 0)

        request_token_type = UjitsoUtils.make_uint8_request_token_type()
        self.assertEqual(request_token_type.size, 1)
        self.assertNotEqual(request_token_type.typeNameHash, 0)

        request_token_type = UjitsoUtils.make_int16_request_token_type()
        self.assertEqual(request_token_type.size, 2)
        self.assertNotEqual(request_token_type.typeNameHash, 0)

        request_token_type = UjitsoUtils.make_uint16_request_token_type()
        self.assertEqual(request_token_type.size, 2)
        self.assertNotEqual(request_token_type.typeNameHash, 0)

        request_token_type = UjitsoUtils.make_int32_request_token_type()
        self.assertEqual(request_token_type.size, 4)
        self.assertNotEqual(request_token_type.typeNameHash, 0)

        request_token_type = UjitsoUtils.make_uint32_request_token_type()
        self.assertEqual(request_token_type.size, 4)
        self.assertNotEqual(request_token_type.typeNameHash, 0)

        request_token_type = UjitsoUtils.make_int64_request_token_type()
        self.assertEqual(request_token_type.size, 8)
        self.assertNotEqual(request_token_type.typeNameHash, 0)

        request_token_type = UjitsoUtils.make_uint64_request_token_type()
        self.assertEqual(request_token_type.size, 8)
        self.assertNotEqual(request_token_type.typeNameHash, 0)

        request_token_type = UjitsoUtils.make_double_request_token_type()
        self.assertEqual(request_token_type.size, 8)
        self.assertNotEqual(request_token_type.typeNameHash, 0)

        request_token_type = UjitsoUtils.make_float_request_token_type()
        self.assertEqual(request_token_type.size, 4)
        self.assertNotEqual(request_token_type.typeNameHash, 0)

        request_token_type = UjitsoUtils.make_void_request_token_type()
        self.assertEqual(request_token_type.size, 0)
        self.assertEqual(request_token_type.typeNameHash, 0)

    async def test_get_request_value(self):
        """validate bindings of template function getRequestValue()"""
        # validate whether these functions are available in UjitsoUtils.
        self.assertTrue(hasattr(UjitsoUtils, "get_request_value_key_token"))
        self.assertTrue(hasattr(UjitsoUtils, "get_request_value_uint8"))
        self.assertTrue(hasattr(UjitsoUtils, "get_request_value_int16"))
        self.assertTrue(hasattr(UjitsoUtils, "get_request_value_uint16"))
        self.assertTrue(hasattr(UjitsoUtils, "get_request_value_int"))
        self.assertTrue(hasattr(UjitsoUtils, "get_request_value_uint"))
        self.assertTrue(hasattr(UjitsoUtils, "get_request_value_int64"))
        self.assertTrue(hasattr(UjitsoUtils, "get_request_value_uint64"))
        self.assertTrue(hasattr(UjitsoUtils, "get_request_value_double"))
        self.assertTrue(hasattr(UjitsoUtils, "get_request_value_float"))
        self.assertTrue(hasattr(UjitsoUtils, "get_request_value_string"))

    async def test_request_type(self):
        """validate binding of RequestType"""
        # validate the default constructor
        request_type = RequestType()
        self.assertEqual(request_type.count, 0)
        self.assertEqual(request_type.keys.tolist(), [])
        self.assertEqual(request_type.types.tolist(), [])

        # can't set attribute
        with self.assertRaises(AttributeError):
            request_type.count = 1

        with self.assertRaises(AttributeError):
            request_type.keys = []

        with self.assertRaises(AttributeError):
            request_type.types = []

        valid_args_list = [
            ([], []),
            ([(0,), (1,), (2,)], [(1, 2), (11, 12), (21, 22)]),
        ]

        for keys, types in valid_args_list:
            count = len(keys)
            self.assertEqual(count, len(types))

            request_type = RequestType(keys, types)
            self.assertEqual(request_type.count, count)
            self.assertEqual(request_type.keys.tolist(), keys)
            self.assertEqual(request_type.types.tolist(), types)

        # The array size of keys and types doesn't match
        with self.assertRaises(ValueError):
            request_type = RequestType([(0,), (1,), (2,)], [])

    async def test_request_filter(self):
        """validate binding of RequestFilter"""
        # validate the default constructor
        request_filter = RequestFilter()
        self.assertEqual(request_filter.count, 0)
        self.assertEqual(request_filter.keys.tolist(), [])

        # can't set attribute
        with self.assertRaises(AttributeError):
            request_filter.count = 1

        with self.assertRaises(AttributeError):
            request_filter.keys = []

        valid_args_list = [
            [],
            [(0,), (1,), (2,)],
        ]

        for keys in valid_args_list:
            request_filter = RequestFilter(keys)
            self.assertEqual(request_filter.count, len(keys))
            self.assertEqual(request_filter.keys.tolist(), keys)

    async def test_request(self):
        """validate binding of Request"""
        # validate the default constructor
        request = Request()
        self.assertEqual(request.type.count, 0)
        self.assertEqual(request.type.keys.tolist(), [])
        self.assertEqual(request.type.types.tolist(), [])
        self.assertEqual(request.tokenValues, b'')

        # can't set attribute
        with self.assertRaises(AttributeError):
            request.type = RequestType()

        with self.assertRaises(AttributeError):
            request.tokenValues = b''

        request = Request(RequestType(), b'')
        self.assertEqual(request.type.count, 0)
        self.assertEqual(request.type.keys.tolist(), [])
        self.assertEqual(request.type.types.tolist(), [])
        self.assertEqual(request.tokenValues, b'')

        # validate non-default arguments
        keys = [(0,), (1,), (2,)]
        types = [(1, 2), (2, 12), (3, 22)]
        request_type = RequestType(keys, types)
        token_values = b'\x01\x02\x03\x04\x05\x06'

        request_size = sum([t[0] for t in types])
        self.assertEqual(request_size, len(token_values))

        request = Request(request_type, token_values)
        self.assertEqual(request.type.count, 3)
        self.assertEqual(request.type.keys.tolist(), keys)
        self.assertEqual(request.type.types.tolist(), types)
        self.assertEqual(request.tokenValues, token_values)

    async def test_agent(self):
        """validate binding of Agent"""
        with self.assertRaises(TypeError):
            agent = Agent()

    async def test_request_callback_data(self):
        """validate binding of RequestCallbackData"""
        # validate the default constructor
        request_callback_data = RequestCallbackData()
        self.assertEqual(request_callback_data.callbackContext0, None)
        self.assertEqual(request_callback_data.callbackContext1, None)

        # can't read and write the callback attribute
        with self.assertRaises(AttributeError):
            callback = request_callback_data.callback

        with self.assertRaises(AttributeError):
            request_callback_data.callback = None

        class _Context:
            def __init__(self, name):
                self.name = name

            def print_name(self):
                print(self.name)

        def _callback(callback_context0, callback_context1, request_handle, request_index, operation_result):
            callback_context0.print_name()
            callback_context1.print_name()

        context0 = _Context("context0")
        context1 = _Context("context1")
        request_callback_data = RequestCallbackData(_callback, context0, context1)

        self.assertEqual(request_callback_data.callbackContext0, context0)
        self.assertEqual(request_callback_data.callbackContext1, context1)

    async def test_external_storage(self):
        """validate binding of ExternalStorage"""
        values = list(range(4))
        external_storage = ExternalStorage(values)
        self.assertEqual(external_storage.values.tolist(), values)

        external_storage = ExternalStorage(tuple(values))
        self.assertEqual(external_storage.values.tolist(), values)

        values = list(range(100, 104))
        external_storage.values = values
        self.assertEqual(external_storage.values.tolist(), values)

        external_storage.values = tuple(values)
        self.assertEqual(external_storage.values.tolist(), values)

        with self.assertRaises(TypeError):
            external_storage.values = None

        with self.assertRaises(ValueError):
            external_storage.values = list(range(3))

        with self.assertRaises(ValueError):
            external_storage.values = list(range(5))

        with self.assertRaises(TypeError):
            external_storage = ExternalStorage()

        with self.assertRaises(TypeError):
            external_storage = ExternalStorage(*values)

        with self.assertRaises(ValueError):
            external_storage = ExternalStorage(list(range(3)))

        with self.assertRaises(ValueError):
            external_storage = ExternalStorage(list(range(5)))

    async def test_service_interface(self):
        """validate binding of IService"""
        service_interface = acquire_service_interface()
        self.assertIsNotNone(service_interface)
        release_service_interface(service_interface)

    async def test_local_data_store_interface(self):
        """ validate binding of ILocalDataStore"""
        local_data_store_interface = acquire_local_data_store_interface()
        self.assertIsNotNone(local_data_store_interface)

        data_store = local_data_store_interface.create("test", 1024)
        self.assertIsNotNone(data_store)

        local_data_store_interface.destroy(data_store)

        release_local_data_store_interface(local_data_store_interface)

    async def test_nucleus_data_store_interface(self):
        """ validate binding of INucleusDataStore"""
        nucleus_data_store_interface = acquire_nucleus_data_store_interface()
        self.assertIsNotNone(nucleus_data_store_interface)

        data_store = nucleus_data_store_interface.create("test", "test", False)
        self.assertIsNotNone(data_store)

        nucleus_data_store_interface.destroy(data_store)

        release_nucleus_data_store_interface(nucleus_data_store_interface)

    async def test_processor_information(self):
        """ validate binding of ProcessorInformation"""
        valid_args_list = [(None, 0, 0), ("", 0, 0), ("test1", 0, 0), ("test2", 1, 1)]
        for name, version, remote_execution_batch_hint in valid_args_list:
            processor_information = ProcessorInformation(name, version, remote_execution_batch_hint)
            self.assertEqual(processor_information.name, name)
            self.assertEqual(processor_information.version, version)
            self.assertEqual(processor_information.remoteExecutionBatchHint, remote_execution_batch_hint)

        with self.assertRaises(TypeError):
            processor_information = ProcessorInformation()

        invalid_args_list = [("test1", -1, 0), ("test2", 0, -1), ("test3", 0.5, 0), ("test4", 0, 0.5)]
        for name, version, remote_execution_batch_hint in invalid_args_list:
            with self.assertRaises(TypeError):
                processor_information = ProcessorInformation(name, version, remote_execution_batch_hint)

        processor_information = ProcessorInformation(None, 0, 0)
        for name, version, remote_execution_batch_hint in valid_args_list:
            processor_information.name = name
            processor_information.version = version
            processor_information.remoteExecutionBatchHint = remote_execution_batch_hint

            self.assertEqual(processor_information.name, name)
            self.assertEqual(processor_information.version, version)
            self.assertEqual(processor_information.remoteExecutionBatchHint, remote_execution_batch_hint)

        processor_information = ProcessorInformation(None, 0, 0)
        for name, version, remote_execution_batch_hint in invalid_args_list:
            with self.assertRaises(TypeError):
                processor_information.name = name
                processor_information.version = version
                processor_information.remoteExecutionBatchHint = remote_execution_batch_hint

    async def test_processor(self):
        """ validate binding of Processor"""
        processor = Processor(None, None, None, None)

        # It is fine to intialize with functions with mismatched signature, exception will be raised later when triggering these callbacks.
        processor = Processor(lambda : None, lambda : None, lambda : None, lambda : None)

        with self.assertRaises(TypeError):
            processor = Processor()

        with self.assertRaises(TypeError):
            processor = Processor(None, None, None)

        with self.assertRaises(TypeError):
            processor = Processor("", "", "", "")

        with self.assertRaises(TypeError):
            processor = Processor(0, 0, 0, 0)

    async def test_match_context(self):
        """ validate binding of MatchContext"""
        with self.assertRaises(TypeError):
            match_context = MatchContext()

    async def test_dependency_context(self):
        """ validate binding of DependencyContext"""
        with self.assertRaises(TypeError):
            dependency_context = DependencyContext()

    async def test_build_context(self):
        """ validate binding of BuildContext"""
        with self.assertRaises(TypeError):
            build_context = BuildContext()

    async def test_dependency_handle(self):
        """validate binding of DependencyHandle"""
        # validate the default value
        handle = DependencyHandle()
        self.assertEqual(handle.value, 0)

        # only positive integer is accepted
        valid_values = [1, 1<<64 - 1, 0]
        invalid_values = [-1, -1.0, 1.0, 1<<64]

        # validate setter with valid values
        for val in valid_values:
            handle.value = val
            self.assertEqual(handle.value, val)

        # validate setter with invalid values
        for val in invalid_values:
            with self.assertRaises(TypeError):
                handle.value = val

        # the value shouldn't change
        self.assertEqual(handle.value, valid_values[-1])

        # validate initialization with valid values
        for val in valid_values:
            handle = DependencyHandle(val)
            self.assertEqual(handle.value, val)

            handle = DependencyHandle(value = val)
            self.assertEqual(handle.value, val)

        # validate initialization with invalid values
        for val in invalid_values:
            with self.assertRaises(TypeError):
                handle = DependencyHandle(val)

            with self.assertRaises(TypeError):
                handle = DependencyHandle(value = val)

    async def test_build_handle(self):
        """validate binding of BuildHandle"""
        # validate the default value
        handle = BuildHandle()
        self.assertEqual(handle.value, 0)

        # only positive integer is accepted
        valid_values = [1, 1<<64 - 1, 0]
        invalid_values = [-1, -1.0, 1.0, 1<<64]

        # validate setter with valid values
        for val in valid_values:
            handle.value = val
            self.assertEqual(handle.value, val)

        # validate setter with invalid values
        for val in invalid_values:
            with self.assertRaises(TypeError):
                handle.value = val

        # the value shouldn't change
        self.assertEqual(handle.value, valid_values[-1])

        # validate initialization with valid values
        for val in valid_values:
            handle = BuildHandle(val)
            self.assertEqual(handle.value, val)

            handle = BuildHandle(value = val)
            self.assertEqual(handle.value, val)

        # validate initialization with invalid values
        for val in invalid_values:
            with self.assertRaises(TypeError):
                handle = BuildHandle(val)

            with self.assertRaises(TypeError):
                handle = BuildHandle(value = val)

    async def test_dependency_job(self):
        """ validate binding of DependencyJob"""
        # validate default values
        dependency_job = DependencyJob()
        request = dependency_job.request
        self.assertEqual(request.type.count, 0)
        self.assertEqual(request.type.keys.tolist(), [])
        self.assertEqual(request.type.types.tolist(), [])
        self.assertEqual(request.tokenValues, b'')

        keys = [(0,), (1,), (2,)]
        types = [(1, 2), (2, 12), (3, 22)]
        request_type = RequestType(keys, types)
        token_values = b'\x11\x12\x13\x14\x15\x16'

        request_size = sum([t[0] for t in types])
        self.assertEqual(request_size, len(token_values))

        # validate setter and getter
        dependency_job.request = Request(request_type, token_values)
        request = dependency_job.request
        self.assertEqual(request.type.count, len(keys))
        self.assertEqual(request.type.keys.tolist(), keys)
        self.assertEqual(request.type.types.tolist(), types)
        self.assertEqual(request.tokenValues, token_values)

        # validate resetting of requests
        dependency_job.request = Request()
        request = dependency_job.request
        self.assertEqual(request.type.count, 0)
        self.assertEqual(request.type.keys.tolist(), [])
        self.assertEqual(request.type.types.tolist(), [])
        self.assertEqual(request.tokenValues, b'')

        with self.assertRaises(TypeError):
            dependency_job.request = None

    async def test_build_job(self):
        """ validate binding of BuildJob"""
        # validate default values
        build_job = BuildJob()
        request = build_job.request
        self.assertEqual(request.type.count, 0)
        self.assertEqual(request.type.keys.tolist(), [])
        self.assertEqual(request.type.types.tolist(), [])
        self.assertEqual(request.tokenValues, b'')

        keys = [(0,), (1,), (2,)]
        types = [(1, 2), (2, 12), (3, 22)]
        request_type = RequestType(keys, types)
        token_values = b'\x11\x12\x13\x14\x15\x16'

        request_size = sum([t[0] for t in types])
        self.assertEqual(request_size, len(token_values))

        # validate setter and getter
        request = Request(request_type, token_values)
        build_job.request = request
        request = build_job.request
        self.assertEqual(request.type.count, len(keys))
        self.assertEqual(request.type.keys.tolist(), keys)
        self.assertEqual(request.type.types.tolist(), types)
        self.assertEqual(request.tokenValues, token_values)

        # validate resetting of requests
        build_job.request = Request()
        request = build_job.request
        self.assertEqual(request.type.count, 0)
        self.assertEqual(request.type.keys.tolist(), [])
        self.assertEqual(request.type.types.tolist(), [])
        self.assertEqual(request.tokenValues, b'')

        with self.assertRaises(TypeError):
            build_job.request = None

    async def test_data_grid(self):
        """ validate binding of DataGrid"""
        with self.assertRaises(TypeError):
            data_grid = DataGrid()

    async def test_data_grid_interface(self):
        """validate binding of IDataGrid"""
        data_grid_interface = acquire_data_grid_interface()
        self.assertIsNotNone(data_grid_interface)

        data_grid = data_grid_interface.create_data_grid()
        self.assertIsNotNone(data_grid)
        self.assertIsNotNone(data_grid.iface)

        # can't set attribute
        with self.assertRaises(AttributeError):
            data_grid.iface = None

        data_grid_interface.destroy_data_grid(data_grid)

        release_data_grid_interface(data_grid_interface)

    async def test_in_progress_factory_interface(self):
        """ validate binding of IInProgressFactory"""
        factory_interface = acquire_in_progress_factory_interface()
        self.assertIsNotNone(factory_interface)

        task_agent = factory_interface.create_agent()
        self.assertIsNotNone(task_agent)

        task_service = factory_interface.get_service(task_agent)
        self.assertIsNotNone(task_service)

        task_service.destroy()
        task_agent.destroy()

        release_in_progress_factory_interface(factory_interface)

    async def test_tcp_factory_interface(self):
        """ validate binding of ITCPFactory"""
        factory_interface = acquire_tcp_factory_interface()
        self.assertIsNotNone(factory_interface)

        port = 1113
        task_service = factory_interface.create_service(port)
        self.assertIsNotNone(task_service)

        address_and_port = ("127.0.0.1", port)
        addresses = [address_and_port]
        task_agent = factory_interface.create_agent(addresses)
        self.assertIsNotNone(task_agent)

        service_ip = factory_interface.get_service_ip(task_service)
        self.assertIsNotNone(service_ip)
        self.assertTrue(isinstance(service_ip, str))

        task_service.destroy()
        task_agent.destroy()

        release_tcp_factory_interface(factory_interface)

    async def test_http_factory_interface(self):
        """ validate binding of IHTTPFactory"""
        factory_interface = acquire_http_factory_interface()
        self.assertIsNotNone(factory_interface)

        task_agent = factory_interface.create_agent("test")
        self.assertIsNotNone(task_agent)

        task_service = factory_interface.create_service()
        self.assertIsNotNone(task_service)

        result = factory_interface.run_http_jobs(task_service, "desc", "store_path")
        self.assertIsNotNone(result)
        self.assertTrue(isinstance(result, str))

        task_service.destroy()
        task_agent.destroy()

        release_http_factory_interface(factory_interface)
