"""
Test some SBStructuredData API.
"""


import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil

import json

class TestStructuredDataAPI(TestBase):
    NO_DEBUG_INFO_TESTCASE = True

    def test(self):
        self.structured_data_api_test()

    def structured_data_api_test(self):
        error = lldb.SBError()
        s = lldb.SBStream()

        dict_str = json.dumps(
            {
                "key_dict": {
                    "key_string": "STRING",
                    "key_uint": 0xFFFFFFFF00000000,
                    "key_sint": -42,
                    "key_float": 2.99,
                    "key_bool": True,
                    "key_array": ["23", "arr"],
                }
            }
        )
        s.Print(dict_str)
        example = lldb.SBStructuredData()

        # Check SetFromJSON API for dictionaries, integers, floating point
        # values, strings and arrays
        error = example.SetFromJSON(s)
        if not error.Success():
            self.fail("FAILED:   " + error.GetCString())

        # Tests for invalid data type
        self.invalid_struct_test(example)

        # Test that GetDescription works:
        s.Clear()
        error = example.GetDescription(s)
        self.assertSuccess(error, "GetDescription works")
        # Ensure str() doesn't raise an exception.
        self.assertTrue(str(example))
        if not "key_float" in s.GetData():
            self.fail("FAILED: could not find key_float in description output")

        dict_struct = example.GetValueForKey("key_dict")

        # Tests for dictionary data type
        self.dictionary_struct_test(example)

        # Tests for string data type
        self.string_struct_test(dict_struct)

        # Tests for integer data type
        self.uint_struct_test(dict_struct)

        # Tests for integer data type
        self.sint_struct_test(dict_struct)

        # Tests for floating point data type
        self.double_struct_test(dict_struct)

        # Tests for boolean data type
        self.bool_struct_test(dict_struct)

        # Tests for array data type
        self.array_struct_test(dict_struct)

        s.Clear()
        self.assertSuccess(example.GetAsJSON(s))
        py_obj = json.loads(s.GetData())
        self.assertTrue(py_obj)
        self.assertIn("key_dict", py_obj)

        py_dict = py_obj["key_dict"]
        self.assertEqual(py_dict["key_string"], "STRING")
        self.assertEqual(py_dict["key_uint"], 0xFFFFFFFF00000000)
        self.assertEqual(py_dict["key_sint"], -42)
        self.assertEqual(py_dict["key_float"], 2.99)
        self.assertEqual(py_dict["key_bool"], True)
        self.assertEqual(py_dict["key_array"], ["23", "arr"])

        class MyRandomClass:
            payload = "foo"

        py_dict["key_generic"] = MyRandomClass()

        stp = lldb.SBScriptObject(py_dict, lldb.eScriptLanguagePython)
        self.assertEqual(stp.ptr, py_dict)

        sd = lldb.SBStructuredData(stp, self.dbg)
        self.assertTrue(sd.IsValid())
        self.assertEqual(sd.GetSize(), len(py_dict))

        generic_sd = sd.GetValueForKey("key_generic")
        self.assertTrue(generic_sd.IsValid())
        self.assertEqual(generic_sd.GetType(), lldb.eStructuredDataTypeGeneric)

        my_random_class = generic_sd.GetGenericValue()
        self.assertTrue(my_random_class)
        self.assertEqual(my_random_class.payload, MyRandomClass.payload)

        example = lldb.SBStructuredData()
        self.assertSuccess(example.SetFromJSON("1"))
        self.assertEqual(example.GetType(), lldb.eStructuredDataTypeInteger)
        self.assertEqual(example.GetIntegerValue(), 1)
        self.assertEqual(int(example), 1)

        self.assertSuccess(example.SetFromJSON("4.19"))
        self.assertEqual(example.GetType(), lldb.eStructuredDataTypeFloat)
        self.assertEqual(example.GetFloatValue(), 4.19)
        self.assertEqual(float(example), 4.19)

        self.assertSuccess(example.SetFromJSON('"Bonjour, 123!"'))
        self.assertEqual(example.GetType(), lldb.eStructuredDataTypeString)
        self.assertEqual(example.GetStringValue(42), "Bonjour, 123!")
        self.assertEqual(str(example), "Bonjour, 123!")

        self.assertSuccess(example.SetFromJSON("true"))
        self.assertEqual(example.GetType(), lldb.eStructuredDataTypeBoolean)
        self.assertTrue(example.GetBooleanValue())
        self.assertTrue(example)

        self.assertSuccess(example.SetFromJSON("null"))
        self.assertEqual(example.GetType(), lldb.eStructuredDataTypeNull)

        example = lldb.SBStructuredData()
        example.SetUnsignedIntegerValue(1)
        self.assertEqual(example.GetType(), lldb.eStructuredDataTypeInteger)
        self.assertEqual(example.GetIntegerValue(), 1)

        example.SetSignedIntegerValue(-42)
        self.assertEqual(example.GetType(), lldb.eStructuredDataTypeSignedInteger)
        self.assertEqual(example.GetSignedIntegerValue(), -42)

        example.SetFloatValue(4.19)
        self.assertEqual(example.GetType(), lldb.eStructuredDataTypeFloat)
        self.assertEqual(example.GetFloatValue(), 4.19)

        example.SetStringValue("Bonjour, 123!")
        self.assertEqual(example.GetType(), lldb.eStructuredDataTypeString)
        self.assertEqual(example.GetStringValue(42), "Bonjour, 123!")

        value = lldb.SBStructuredData()
        example.SetValueForKey("Hello", value)
        self.assertEqual(example.GetSize(), 0)

        nested_obj = lldb.SBStructuredData()
        nested_obj.SetStringValue("World")
        example.SetValueForKey("Hello", nested_obj)
        self.assertEqual(example.GetType(), lldb.eStructuredDataTypeDictionary)
        nested_obj = None
        nested_obj = example.GetValueForKey("Hello")
        self.assertTrue(nested_obj.IsValid())
        self.assertEqual(nested_obj.GetType(), lldb.eStructuredDataTypeString)
        self.assertEqual(nested_obj.GetStringValue(42), "World")

        example.SetBooleanValue(True)
        self.assertEqual(example.GetType(), lldb.eStructuredDataTypeBoolean)
        self.assertTrue(example.GetBooleanValue())

        rnd_obj = MyRandomClass()
        stp = lldb.SBScriptObject(rnd_obj, lldb.eScriptLanguagePython)
        self.assertEqual(stp.ptr, rnd_obj)

        example.SetGenericValue(stp)
        self.assertEqual(example.GetType(), lldb.eStructuredDataTypeGeneric)

        my_random_class = example.GetGenericValue()
        self.assertTrue(my_random_class)
        self.assertEqual(my_random_class.payload, MyRandomClass.payload)

        example_arr = [1, 2.3, "4", {"5": False}]
        arr_str = json.dumps(example_arr)
        s.Clear()
        s.Print(arr_str)
        self.assertSuccess(example.SetFromJSON(s))

        s.Clear()
        self.assertSuccess(example.GetAsJSON(s))
        sb_data = json.loads(s.GetData())
        self.assertEqual(sb_data, example_arr)

    def invalid_struct_test(self, example):
        invalid_struct = example.GetValueForKey("invalid_key")
        if invalid_struct.IsValid():
            self.fail("An invalid object should have been returned")

        # Check Type API
        if invalid_struct.GetType() != lldb.eStructuredDataTypeInvalid:
            self.fail("Wrong type returned: " + str(invalid_struct.GetType()))

    def dictionary_struct_test(self, example):
        # Check API returning a valid SBStructuredData of 'dictionary' type
        dict_struct = example.GetValueForKey("key_dict")
        if not dict_struct.IsValid():
            self.fail("A valid object should have been returned")

        # Check Type API
        if dict_struct.GetType() != lldb.eStructuredDataTypeDictionary:
            self.fail("Wrong type returned: " + str(dict_struct.GetType()))

        # Check Size API for 'dictionary' type
        if dict_struct.GetSize() != 6:
            self.fail("Wrong no of elements returned: " + str(dict_struct.GetSize()))

    def string_struct_test(self, dict_struct):
        string_struct = dict_struct.GetValueForKey("key_string")
        if not string_struct.IsValid():
            self.fail("A valid object should have been returned")

        # Check Type API
        if string_struct.GetType() != lldb.eStructuredDataTypeString:
            self.fail("Wrong type returned: " + str(string_struct.GetType()))

        # Check API returning 'string' value
        output = string_struct.GetStringValue(25)
        if not "STRING" in output:
            self.fail("wrong output: " + output)

        # Calling wrong API on a SBStructuredData
        # (e.g. getting an integer from a string type structure)
        output = string_struct.GetIntegerValue()
        if output:
            self.fail(
                "Valid integer value " + str(output) + " returned for a string object"
            )

    def uint_struct_test(self, dict_struct):
        # Check a valid SBStructuredData containing an unsigned integer.
        # We intentionally make this larger than what an int64_t can hold but
        # still small enough to fit a uint64_t
        uint_struct = dict_struct.GetValueForKey("key_uint")
        if not uint_struct.IsValid():
            self.fail("A valid object should have been returned")

        # Check Type API
        if uint_struct.GetType() != lldb.eStructuredDataTypeInteger:
            self.fail("Wrong type returned: " + str(uint_struct.GetType()))

        # Check API returning unsigned integer value
        output = uint_struct.GetUnsignedIntegerValue()
        if output != 0xFFFFFFFF00000000:
            self.fail("wrong output: " + str(output))

        # Calling wrong API on a SBStructuredData
        # (e.g. getting a string value from an integer type structure)
        output = uint_struct.GetStringValue(25)
        if output:
            self.fail("Valid string " + output + " returned for an integer object")

    def sint_struct_test(self, dict_struct):
        # Check a valid SBStructuredData containing an signed integer.
        # We intentionally make this smaller than what an uint64_t can hold but
        # still small enough to fit a int64_t
        sint_struct = dict_struct.GetValueForKey("key_sint")
        if not sint_struct.IsValid():
            self.fail("A valid object should have been returned")

        # Check Type API
        if sint_struct.GetType() != lldb.eStructuredDataTypeSignedInteger:
            self.fail("Wrong type returned: " + str(sint_struct.GetType()))

        # Check API returning signed integer value
        output = sint_struct.GetSignedIntegerValue()
        if output != -42:
            self.fail("wrong output: " + str(output))

        # Calling wrong API on a SBStructuredData
        # (e.g. getting a string value from an integer type structure)
        output = sint_struct.GetStringValue(69)
        if output:
            self.fail("Valid string " + output + " returned for an integer object")

    def double_struct_test(self, dict_struct):
        floating_point_struct = dict_struct.GetValueForKey("key_float")
        if not floating_point_struct.IsValid():
            self.fail("A valid object should have been returned")

        # Check Type API
        if floating_point_struct.GetType() != lldb.eStructuredDataTypeFloat:
            self.fail("Wrong type returned: " + str(floating_point_struct.GetType()))

        # Check API returning 'double' value
        output = floating_point_struct.GetFloatValue()
        if output != 2.99:
            self.fail("wrong output: " + str(output))

    def bool_struct_test(self, dict_struct):
        bool_struct = dict_struct.GetValueForKey("key_bool")
        if not bool_struct.IsValid():
            self.fail("A valid object should have been returned")

        # Check Type API
        if bool_struct.GetType() != lldb.eStructuredDataTypeBoolean:
            self.fail("Wrong type returned: " + str(bool_struct.GetType()))

        # Check API returning 'bool' value
        output = bool_struct.GetBooleanValue()
        if not output:
            self.fail("wrong output: " + str(output))

    def array_struct_test(self, dict_struct):
        # Check API returning a valid SBStructuredData of 'array' type
        array_struct = dict_struct.GetValueForKey("key_array")
        if not array_struct.IsValid():
            self.fail("A valid object should have been returned")

        # Check Type API
        if array_struct.GetType() != lldb.eStructuredDataTypeArray:
            self.fail("Wrong type returned: " + str(array_struct.GetType()))

        # Check Size API for 'array' type
        if array_struct.GetSize() != 2:
            self.fail("Wrong no of elements returned: " + str(array_struct.GetSize()))

        # Check API returning a valid SBStructuredData for different 'array'
        # indices
        string_struct = array_struct.GetItemAtIndex(0)
        if not string_struct.IsValid():
            self.fail("A valid object should have been returned")
        if string_struct.GetType() != lldb.eStructuredDataTypeString:
            self.fail("Wrong type returned: " + str(string_struct.GetType()))
        output = string_struct.GetStringValue(5)
        if output != "23":
            self.fail("wrong output: " + str(output))

        string_struct = array_struct.GetItemAtIndex(1)
        if not string_struct.IsValid():
            self.fail("A valid object should have been returned")
        if string_struct.GetType() != lldb.eStructuredDataTypeString:
            self.fail("Wrong type returned: " + str(string_struct.GetType()))
        output = string_struct.GetStringValue(5)
        if output != "arr":
            self.fail("wrong output: " + str(output))

    def test_round_trip_scalars(self):
        for original in (0, 11, -1, 0.0, 4.5, -0.25):
            constructor = type(original)
            data = lldb.SBStructuredData()
            data.SetFromJSON(json.dumps(original))
            round_tripped = constructor(data)
            self.assertEqual(round_tripped, original)

    def test_dynamic(self):
        for original in (0, 11, -1, 0.0, 4.5, -0.25, "", "dirk", True, False):
            data = lldb.SBStructuredData()
            data.SetFromJSON(json.dumps(original))
            self.assertEqual(data.dynamic, original)

    def test_round_trip_int(self):
        for original in (0, 11, -1):
            data = lldb.SBStructuredData()
            data.SetFromJSON(json.dumps(original))
            self.assertEqual(int(data), int(original))

    def test_round_trip_float(self):
        for original in (0, 11, -1, 0.0, 4.5, -0.25):
            data = lldb.SBStructuredData()
            data.SetFromJSON(json.dumps(original))
            self.assertEqual(float(data), float(original))

    def test_iterate_array(self):
        array = [0, 1, 2]
        data = lldb.SBStructuredData()
        data.SetFromJSON(json.dumps(array))
        for value in data:
            self.assertEqual(value, array.pop(0))

    def test_iterate_dictionary(self):
        dictionary = {"0": 0, "1": 1, "2": 2}
        keys = set(dictionary.keys())
        data = lldb.SBStructuredData()
        data.SetFromJSON(json.dumps(dictionary))
        for key in data:
            self.assertIn(key, keys)
            keys.remove(key)

    def test_getitem_array(self):
        array = [1, 2, 3]
        data = lldb.SBStructuredData()
        data.SetFromJSON(json.dumps(array))
        for i in range(len(array)):
            self.assertEqual(data[i], array[i])

    def test_getitem_dictionary(self):
        dictionary = {"one": 1, "two": 2, "three": 3}
        data = lldb.SBStructuredData()
        data.SetFromJSON(json.dumps(dictionary))
        for key in dictionary:
            self.assertEqual(data[key], dictionary[key])
