# based on https://github.com/nesrak1/AssetsTools.NET/blob/dev/AssetsTools.NET.Texture/Swizzle/SwitchSwizzle.cs
from typing import Dict, List, Optional, Tuple, Union

from ..enums import BuildTarget, TextureFormat

GOB_X_TEXEL_COUNT = 4
GOB_Y_TEXEL_COUNT = 8
TEXEL_BYTE_SIZE = 16
TEXELS_IN_GOB = GOB_X_TEXEL_COUNT * GOB_Y_TEXEL_COUNT
GOB_MAP = [(((v >> 3) & 0b10) | ((v >> 1) & 0b1), ((v >> 1) & 0b110) | (v & 0b1)) for v in range(TEXELS_IN_GOB)]


def ceil_divide(a: int, b: int) -> int:
    return (a + b - 1) // b


def deswizzle(
    data: Union[bytes, bytearray, memoryview],
    width: int,
    height: int,
    block_width: int,
    block_height: int,
    texels_per_block: int,
) -> bytearray:
    block_count_x = ceil_divide(width, block_width)
    block_count_y = ceil_divide(height, block_height)
    gob_count_x = block_count_x // GOB_X_TEXEL_COUNT
    gob_count_y = block_count_y // GOB_Y_TEXEL_COUNT
    new_data = bytearray(len(data))
    data_view = memoryview(data)

    for i in range(gob_count_y // texels_per_block):
        for j in range(gob_count_x):
            base_gob_dst_x = j * 4
            for k in range(texels_per_block):
                base_gob_dst_y = (i * texels_per_block + k) * GOB_Y_TEXEL_COUNT
                for gob_x, gob_y in GOB_MAP:
                    dst_offset = ((base_gob_dst_y + gob_y) * block_count_x + (base_gob_dst_x + gob_x)) * TEXEL_BYTE_SIZE
                    new_data[dst_offset : dst_offset + TEXEL_BYTE_SIZE] = data_view[:TEXEL_BYTE_SIZE]
                    data_view = data_view[TEXEL_BYTE_SIZE:]
    return new_data


def swizzle(
    data: Union[bytes, bytearray, memoryview],
    width: int,
    height: int,
    block_width: int,
    block_height: int,
    texels_per_block: int,
) -> bytearray:
    block_count_x = ceil_divide(width, block_width)
    block_count_y = ceil_divide(height, block_height)
    gob_count_x = block_count_x // GOB_X_TEXEL_COUNT
    gob_count_y = block_count_y // GOB_Y_TEXEL_COUNT
    new_data = bytearray(len(data))
    data_view = memoryview(new_data)

    for i in range(gob_count_y // texels_per_block):
        for j in range(gob_count_x):
            base_gob_dst_x = j * 4
            for k in range(texels_per_block):
                base_gob_dst_y = (i * texels_per_block + k) * GOB_Y_TEXEL_COUNT
                for gob_x, gob_y in GOB_MAP:
                    src_offset = ((base_gob_dst_y + gob_y) * block_count_x + (base_gob_dst_x + gob_x)) * TEXEL_BYTE_SIZE
                    data_view[:TEXEL_BYTE_SIZE] = data[src_offset : src_offset + TEXEL_BYTE_SIZE]
                    data_view = data_view[TEXEL_BYTE_SIZE:]

    return new_data


# this should be the amount of pixels that can fit 16 bytes
TEXTURE_FORMAT_BLOCK_SIZE_MAP: Dict[TextureFormat, Tuple[int, int]] = {
    TextureFormat.Alpha8: (16, 1),  # 1 byte per pixel
    TextureFormat.ARGB4444: (8, 1),  # 2 bytes per pixel
    TextureFormat.RGBA32: (4, 1),  # 4 bytes per pixel
    TextureFormat.ARGB32: (4, 1),  # 4 bytes per pixel
    TextureFormat.ARGBFloat: (1, 1),  # 16 bytes per pixel (?)
    TextureFormat.RGB565: (8, 1),  # 2 bytes per pixel
    TextureFormat.R16: (8, 1),  # 2 bytes per pixel
    TextureFormat.DXT1: (8, 4),  # 8 bytes per 4x4=16 pixels
    TextureFormat.DXT5: (4, 4),  # 16 bytes per 4x4=16 pixels
    TextureFormat.RGBA4444: (8, 1),  # 2 bytes per pixel
    TextureFormat.BGRA32: (4, 1),  # 4 bytes per pixel
    TextureFormat.BC6H: (4, 4),  # 16 bytes per 4x4=16 pixels
    TextureFormat.BC7: (4, 4),  # 16 bytes per 4x4=16 pixels
    TextureFormat.BC4: (8, 4),  # 8 bytes per 4x4=16 pixels
    TextureFormat.BC5: (4, 4),  # 16 bytes per 4x4=16 pixels
    TextureFormat.ASTC_RGB_4x4: (4, 4),  # 16 bytes per 4x4=16 pixels
    TextureFormat.ASTC_RGB_5x5: (5, 5),  # 16 bytes per 5x5=25 pixels
    TextureFormat.ASTC_RGB_6x6: (6, 6),  # 16 bytes per 6x6=36 pixels
    TextureFormat.ASTC_RGB_8x8: (8, 8),  # 16 bytes per 8x8=64 pixels
    TextureFormat.ASTC_RGB_10x10: (10, 10),  # 16 bytes per 10x10=100 pixels
    TextureFormat.ASTC_RGB_12x12: (12, 12),  # 16 bytes per 12x12=144 pixels
    TextureFormat.ASTC_RGBA_4x4: (4, 4),  # 16 bytes per 4x4=16 pixels
    TextureFormat.ASTC_RGBA_5x5: (5, 5),  # 16 bytes per 5x5=25 pixels
    TextureFormat.ASTC_RGBA_6x6: (6, 6),  # 16 bytes per 6x6=36 pixels
    TextureFormat.ASTC_RGBA_8x8: (8, 8),  # 16 bytes per 8x8=64 pixels
    TextureFormat.ASTC_RGBA_10x10: (10, 10),  # 16 bytes per 10x10=100 pixels
    TextureFormat.ASTC_RGBA_12x12: (12, 12),  # 16 bytes per 12x12=144 pixels
    TextureFormat.RG16: (8, 1),  # 2 bytes per pixel
    TextureFormat.R8: (16, 1),  # 1 byte per pixel
}


def get_padded_texture_size(width: int, height: int, block_width: int, block_height: int, texels_per_block: int):
    width = ceil_divide(width, block_width * GOB_X_TEXEL_COUNT) * block_width * GOB_X_TEXEL_COUNT
    height = (
        ceil_divide(height, block_height * GOB_Y_TEXEL_COUNT * texels_per_block)
        * block_height
        * GOB_Y_TEXEL_COUNT
        * texels_per_block
    )
    return width, height


def get_switch_gobs_per_block(platform_blob: List[int]) -> int:
    return 1 << int.from_bytes(platform_blob[8:12], "little")


def is_switch_swizzled(platform: Union[BuildTarget, int], platform_blob: Optional[List[int]]) -> bool:
    if platform != BuildTarget.Switch:
        return False
    if not platform_blob or len(platform_blob) < 12:
        return False
    gobs_per_block = get_switch_gobs_per_block(platform_blob)
    return gobs_per_block > 1
