import renderdoc as rd
import rdtest

# Not a real test, re-used by API-specific tests
class Discard_Zoo(rdtest.TestCase):
    internal = True

    def check_val(self, picked, val, fmt):
        if type(val) != list:
            val = [val, val, val, val]

        if fmt.compType == rd.CompType.UInt or fmt.compType == rd.CompType.SInt or fmt.type == rd.ResourceFormatType.S8:
            val = [int(a) for a in val]
            return rdtest.value_compare(picked.intValue[0:fmt.compCount], val[0:fmt.compCount])
        else:
            comp_val = picked.floatValue[0:fmt.compCount]
            if fmt.compType == rd.CompType.Depth or fmt.type in [rd.ResourceFormatType.D16S8, rd.ResourceFormatType.D24S8, rd.ResourceFormatType.D32S8]:
                comp_val = [min(1.0, a) for a in comp_val]

            return rdtest.value_compare(comp_val, val[0:fmt.compCount])

    def check_texture(self, id, discarded: bool):
        tex: rd.TextureDescription = self.get_texture(id)
        res: rd.ResourceDescription = self.get_resource(id)

        fmt: rd.ResourceFormat = tex.format

        props: rd.APIProperties = self.controller.GetAPIProperties()
        gl = (props.pipelineType == rd.GraphicsAPI.OpenGL)

        name = '{} - {}x{} {} mip {} slice {}x MSAA {} format texture'.format(res.name, tex.width, tex.height,
                                                                              tex.mips, tex.arraysize,
                                                                              tex.msSamp, tex.format.Name())

        minval = 0.0
        maxval = 1000.0

        if fmt.type == rd.ResourceFormatType.R9G9B9E5:
            maxval = 998.0
        elif fmt.type == rd.ResourceFormatType.BC6:
            maxval = 996.0
        elif fmt.type == rd.ResourceFormatType.R10G10B10A2 and fmt.compType == rd.CompType.UInt:
            maxval = [127.0, 127.0, 127.0, 3.0]
        elif fmt.type in [rd.ResourceFormatType.D16S8, rd.ResourceFormatType.D24S8, rd.ResourceFormatType.D32S8]:
            maxval = 1.0
            fmt.compCount = 2

            if "DepthOnly" in res.name:
                minval = [minval, float(0x40)/float(255)]
                maxval = [maxval, float(0x40)/float(255)]
            elif "StencilOnly" in res.name:
                minval = [0.4, minval]
                maxval = [0.4, maxval]
        elif fmt.type == rd.ResourceFormatType.S8:
            minval = [0.0]
            maxval = [255.0]
            fmt.compCount = 1
        elif fmt.compType == rd.CompType.UNorm or fmt.compType == rd.CompType.Depth:
            maxval = 1.0
        elif fmt.compType == rd.CompType.UInt or fmt.compType == rd.CompType.SInt:
            maxval = 127.0

        # ignore alpha in BC1 and BC6
        if fmt.type == rd.ResourceFormatType.BC1 or fmt.type == rd.ResourceFormatType.BC6:
            fmt.compCount = 3

        for mip in range(tex.mips):
            for slice in range(tex.arraysize):
                for samp in range(tex.msSamp):
                    sub = rd.Subresource(mip, slice, samp)

                    sub_discarded = discarded

                    if "Mip" in res.name:
                        idx = res.name.index('Mip')+3
                        try:
                            idx2 = res.name.index(' ', idx)
                        except ValueError:
                            idx2 = len(res.name)
                        if mip not in [int(m) for m in res.name[idx:idx2].split(',')]:
                            sub_discarded = False

                    if "Slice" in res.name:
                        idx = res.name.index('Slice') + 5
                        try:
                            idx2 = res.name.index(' ', idx)
                        except ValueError:
                            idx2 = len(res.name)
                        if slice not in [int(s) for s in res.name[idx:idx2].split(',')]:
                            sub_discarded = False

                    if "NoDiscard" in res.name:
                        sub_discarded = False

                    w = max(1, tex.width >> mip)
                    h = max(1, tex.height >> mip)

                    if not sub_discarded or "DiscardRect" in res.name:
                        # if not discarded, or we only discarded a rect, check a few locations in the corners and ensure
                        # that we don't see our pattern colours.
                        for (x, y) in [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (1, 1), (2, 2), (3, 3),
                                       (4, 4), (w - 1, h - 1), (w - 2, h - 2), (w - 1, 0), (w - 1, 1), (w - 1, 2),
                                       (0, h - 1), (1, h - 1), (2, h - 1), (3, h - 2)]:
                            # underflow can happen with 1D textures or tiny mips
                            if x < 0:
                                x = 0
                            if y < 0:
                                y = 0

                            if gl and h > 1:
                                y = h - 1 - y

                            picked: rd.PixelValue = self.controller.PickPixel(id, x, y, sub, rd.CompType.Typeless)

                            if self.check_val(picked, minval, fmt) or self.check_val(picked, maxval, fmt):
                                raise rdtest.TestFailureException(
                                    '{} has unexpected value at {},{}: {}'.format(name, x, y,
                                                                                  picked.floatValue))

                    if sub_discarded:
                        seen = [False, False]
                        # Check that pixels inside the rect only show our pattern colours (we don't check the actual
                        # pattern)
                        for (x, y) in [(50, 50), (51, 50), (52, 50), (53, 50), (54, 50), (55, 50), (51, 52), (120, 123),
                                       (124, 124), (119, 122), (119, 124), (122, 124), (70, 70), (80, 85), (73, 124),
                                       (123, 60)]:
                            # overflow can happen with 1D textures or tiny mips
                            if x >= w:
                                x = w-1
                            if y >= h:
                                y = h-1

                            if gl and h > 1:
                                y = h - 1 - y

                            picked: rd.PixelValue = self.controller.PickPixel(id, x, y, sub, rd.CompType.Typeless)

                            is_min = self.check_val(picked, minval, fmt)
                            is_max = self.check_val(picked, maxval, fmt)

                            if not is_min and not is_max:
                                raise rdtest.TestFailureException(
                                    '{} has unexpected value at {},{}: {}'.format(name, x, y,
                                                                                  picked.floatValue))

                            if is_min:
                                seen[0] = True
                            if is_max:
                                seen[1] = True

                        # Do an additional checks outside the rect
                        if "DiscardRect" not in res.name:
                            for (x, y) in [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (1, 1), (2, 2), (3, 3),
                                           (4, 4), (w - 1, h - 1), (w - 2, h - 2), (w - 1, 0), (w - 1, 1), (w - 1, 2),
                                           (0, h - 1), (1, h - 1), (2, h - 1), (3, h - 2)]:
                                # underflow can happen with 1D textures or tiny mips
                                if x < 0:
                                    x = 0
                                if y < 0:
                                    y = 0

                                if gl and h > 1:
                                    y = h - 1 - y

                                picked: rd.PixelValue = self.controller.PickPixel(id, x, y, sub, rd.CompType.Typeless)

                                is_min = self.check_val(picked, minval, fmt)
                                is_max = self.check_val(picked, maxval, fmt)

                                if not is_min and not is_max:
                                    raise rdtest.TestFailureException(
                                        '{} has unexpected value at {},{}: {}'.format(name, x, y,
                                                                                      picked.floatValue))

                                if is_min:
                                    seen[0] = True
                                if is_max:
                                    seen[1] = True

                        # We also expect to have seen both colours. That means if we only saw black for example then we
                        # fail
                        if not seen[0] or not seen[1]:
                            raise rdtest.TestFailureException('{} doesn\'t contain expected pattern'.format(name))

        rdtest.log.success('{} is OK {} discarding'.format(name, "after" if discarded else "before"))

    def check_textures(self):
        action = self.find_action("TestStart")

        self.check(action is not None)

        self.controller.SetFrameEvent(action.eventId, True)

        for tex in self.controller.GetTextures():
            tex: rd.TextureDescription
            res: rd.ResourceDescription = self.get_resource(tex.resourceId)

            if "Discard" in res.name:
                self.check_texture(tex.resourceId, False)

        action = self.find_action("TestEnd")

        self.check(action is not None)

        self.controller.SetFrameEvent(action.eventId, True)

        for tex in self.controller.GetTextures():
            tex: rd.TextureDescription
            res: rd.ResourceDescription = self.get_resource(tex.resourceId)

            if "Discard" in res.name:
                self.check_texture(tex.resourceId, True)