"""
Test lldb data formatter subsystem for std::span
"""

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

class LibcxxSpanDataFormatterTestCase(TestBase):

    def findVariable(self, name):
        var = self.frame().FindVariable(name)
        self.assertTrue(var.IsValid())
        return var

    def check_size(self, var_name, size):
        var = self.findVariable(var_name)
        self.assertEqual(var.GetNumChildren(), size)

    def check_numbers(self, var_name):
        """Helper to check that data formatter sees contents of std::span correctly"""

        expectedSize = 5
        self.check_size(var_name, expectedSize)

        self.expect_expr(
                var_name, result_type=f'std::span<int, {expectedSize}>',
                result_summary=f'size={expectedSize}',
                result_children=[
                    ValueCheck(name='[0]', value='1'),
                    ValueCheck(name='[1]', value='12'),
                    ValueCheck(name='[2]', value='123'),
                    ValueCheck(name='[3]', value='1234'),
                    ValueCheck(name='[4]', value='12345')
        ])

        # check access-by-index
        self.expect_var_path(f'{var_name}[0]', type='int', value='1')
        self.expect_var_path(f'{var_name}[1]', type='int', value='12')
        self.expect_var_path(f'{var_name}[2]', type='int', value='123')
        self.expect_var_path(f'{var_name}[3]', type='int', value='1234')
        self.expect_var_path(f'{var_name}[4]', type='int', value='12345')

    @add_test_categories(['libc++'])
    @skipIf(compiler='clang', compiler_version=['<', '11.0'])
    def test_with_run_command(self):
        """Test that std::span variables are formatted correctly when printed."""
        self.build()
        (self.target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
            self, 'break here', lldb.SBFileSpec('main.cpp', False))

        lldbutil.continue_to_breakpoint(process, bkpt)

        # std::span of std::array with extents known at compile-time
        self.check_numbers('numbers_span')

        # check access to synthetic children for static spans
        self.runCmd('type summary add --summary-string "item 0 is ${var[0]}" -x "std::span<" span')
        self.expect_expr('numbers_span',
                         result_type='std::span<int, 5>',
                         result_summary='item 0 is 1')

        self.runCmd('type summary add --summary-string "item 0 is ${svar[0]}" -x "std::span<" span')
        self.expect_expr('numbers_span',
                         result_type='std::span<int, 5>',
                         result_summary='item 0 is 1')

        self.runCmd('type summary delete span')

        # New span with strings
        lldbutil.continue_to_breakpoint(process, bkpt)

        expectedStringSpanChildren = [ ValueCheck(name='[0]', summary='"smart"'),
                                       ValueCheck(name='[1]', summary='"!!!"') ]

        self.expect_var_path('strings_span',
                             summary='size=2',
                             children=expectedStringSpanChildren)

        # check access to synthetic children for dynamic spans
        self.runCmd('type summary add --summary-string "item 0 is ${var[0]}" dynamic_string_span')
        self.expect_var_path('strings_span', summary='item 0 is "smart"')

        self.runCmd('type summary add --summary-string "item 0 is ${svar[0]}" dynamic_string_span')
        self.expect_var_path('strings_span', summary='item 0 is "smart"')

        self.runCmd('type summary delete dynamic_string_span')

        # test summaries based on synthetic children
        self.runCmd(
                'type summary add --summary-string "span has ${svar%#} items" -e dynamic_string_span')

        self.expect_var_path('strings_span', summary='span has 2 items')

        self.expect_var_path('strings_span',
                             summary='span has 2 items',
                             children=expectedStringSpanChildren)

        # check access-by-index
        self.expect_var_path('strings_span[0]', summary='"smart"');
        self.expect_var_path('strings_span[1]', summary='"!!!"');

        # Newly inserted value not visible to span
        lldbutil.continue_to_breakpoint(process, bkpt)

        self.expect_expr('strings_span',
                         result_summary='span has 2 items',
                         result_children=expectedStringSpanChildren)

        self.runCmd('type summary delete dynamic_string_span')

        lldbutil.continue_to_breakpoint(process, bkpt)

        # Empty spans
        self.expect_expr('static_zero_span',
                         result_type='std::span<int, 0>',
                         result_summary='size=0')
        self.check_size('static_zero_span', 0)

        self.expect_expr('dynamic_zero_span',
                         result_summary='size=0')
        self.check_size('dynamic_zero_span', 0)

        # Nested spans
        self.expect_expr('nested',
                         result_summary='size=2',
                         result_children=[
                             ValueCheck(name='[0]', summary='size=2',
                                        children=expectedStringSpanChildren),
                             ValueCheck(name='[1]', summary='size=2',
                                        children=expectedStringSpanChildren)
        ])
        self.check_size('nested', 2)

    @add_test_categories(['libc++'])
    @skipIf(compiler='clang', compiler_version=['<', '11.0'])
    def test_ref_and_ptr(self):
        """Test that std::span is correctly formatted when passed by ref and ptr"""
        self.build()
        (self.target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
            self, 'Stop here to check by ref', lldb.SBFileSpec('main.cpp', False))

        # The reference should display the same was as the value did
        self.check_numbers('ref')

        # The pointer should just show the right number of elements:

        ptrAddr = self.findVariable('ptr').GetValue()
        self.expect_expr('ptr', result_type='std::span<int, 5> *',
                         result_summary=f'{ptrAddr} size=5')
