# License of this file:
# Consider this file an extension of dds-ktx.h (2-clause BSD)

type KtxInfo* = object
    width*, height*, depth*: int32
    num_layers*, num_mipmaps*: int32
    is_cubemap*, has_alpha*, is_sRGB*: bool
    is_bc*, is_astc*: bool
    internal_format*: int32

type KtxPart* = object
    width*, height*: int32
    layer*, face*, mip_level*: int32
    slice*: int32
    data*: pointer
    len*: int
    row_len*: int

type KtxInfoParts* = object
    info*: KtxInfo
    parts*: seq[KtxPart]

{.compile:("impl.c","-I.").}

type ddsktx_format = enum
    DDSKTX_FORMAT_BC1,         ## DXT1
    DDSKTX_FORMAT_BC2,         ## DXT3
    DDSKTX_FORMAT_BC3,         ## DXT5
    DDSKTX_FORMAT_BC4,         ## ATI1
    DDSKTX_FORMAT_BC5,         ## ATI2
    DDSKTX_FORMAT_BC6H,        ## BC6H
    DDSKTX_FORMAT_BC7,         ## BC7
    DDSKTX_FORMAT_ETC1,        ## ETC1 RGB8
    DDSKTX_FORMAT_ETC2,        ## ETC2 RGB8
    DDSKTX_FORMAT_ETC2A,       ## ETC2 RGBA8
    DDSKTX_FORMAT_ETC2A1,      ## ETC2 RGB8A1
    DDSKTX_FORMAT_PTC12,       ## PVRTC1 RGB 2bpp
    DDSKTX_FORMAT_PTC14,       ## PVRTC1 RGB 4bpp
    DDSKTX_FORMAT_PTC12A,      ## PVRTC1 RGBA 2bpp
    DDSKTX_FORMAT_PTC14A,      ## PVRTC1 RGBA 4bpp
    DDSKTX_FORMAT_PTC22,       ## PVRTC2 RGBA 2bpp
    DDSKTX_FORMAT_PTC24,       ## PVRTC2 RGBA 4bpp
    DDSKTX_FORMAT_ATC,         ## ATC RGB 4BPP
    DDSKTX_FORMAT_ATCE,        ## ATCE RGBA 8 BPP explicit alpha
    DDSKTX_FORMAT_ATCI,        ## ATCI RGBA 8 BPP interpolated alpha
    DDSKTX_FORMAT_ASTC4x4,     ## ASTC 4x4 8.00 BPP
    DDSKTX_FORMAT_ASTC5x4,     ## ASTC 5x4 6.40 BPP
    DDSKTX_FORMAT_ASTC5x5,     ## ASTC 5x5 5.12 BPP
    DDSKTX_FORMAT_ASTC6x5,     ## ASTC 6x5 4.27 BPP
    DDSKTX_FORMAT_ASTC6x6,     ## ASTC 6x6 3.56 BPP
    DDSKTX_FORMAT_ASTC8x5,     ## ASTC 8x5 3.20 BPP
    DDSKTX_FORMAT_ASTC8x6,     ## ASTC 8x6 2.67 BPP
    DDSKTX_FORMAT_ASTC10x5,    ## ASTC 10x5 2.56 BPP
    DDSKTX_FORMAT_ASTC10x6,    ## ASTC 10x6 2.13 BPP
    DDSKTX_FORMAT_ASTC8x8,     ## ASTC 8x8 2.00 BPP
    DDSKTX_FORMAT_ASTC10x8,    ## ASTC 10x8 1.60 BPP
    DDSKTX_FORMAT_ASTC10x10,   ## ASTC 10x10 1.28 BPP
    DDSKTX_FORMAT_ASTC12x10,   ## ASTC 12x10 1.07 BPP
    DDSKTX_FORMAT_ASTC12x12,   ## ASTC 12x12 0.89 BPP
    DDSKTX_FORMAT_COMPRESSED,
    DDSKTX_FORMAT_A8,
    DDSKTX_FORMAT_R8,
    DDSKTX_FORMAT_RGBA8,
    DDSKTX_FORMAT_RGBA8S,
    DDSKTX_FORMAT_RG16,
    DDSKTX_FORMAT_RGB8,
    DDSKTX_FORMAT_R16,
    DDSKTX_FORMAT_R32F,
    DDSKTX_FORMAT_R16F,
    DDSKTX_FORMAT_RG16F,
    DDSKTX_FORMAT_RG16S,
    DDSKTX_FORMAT_RGBA16F,
    DDSKTX_FORMAT_RGBA16,
    DDSKTX_FORMAT_BGRA8,
    DDSKTX_FORMAT_RGB10A2,
    DDSKTX_FORMAT_RG11B10F,
    DDSKTX_FORMAT_RG8,
    DDSKTX_FORMAT_RG8S,
    DDSKTX_FORMAT_COUNT

type ddsktx_texture_info = object
    data_offset: cint
    size_bytes: cint
    format: ddsktx_format
    flags: cuint
    width: cint
    height: cint
    depth: cint
    num_layers: cint
    num_mips: cint
    bpp: cint
    metadata_offset: cint
    metadata_size: cint

type ddsktx_sub_data = object
    buff: pointer
    width: cint
    height: cint
    size_bytes: cint
    row_pitch_bytes: cint


type ddsktx_error = object
    msg: array[256, char]

{.push importc, cdecl.}

proc ddsktx_parse(tc: var ddsktx_texture_info, file_data: pointer, size: cint, err: var ddsktx_error): bool
proc ddsktx_get_sub(tex: var ddsktx_texture_info, buff: var ddsktx_sub_data, file_data: pointer, size, array_idx, slice_face_idx, mip_idx: cint): void
# proc ddsktx_format_str(format: ddsktx_format): cstring
# proc ddsktx_format_compressed(format: ddsktx_format): bool

{.pop.}

const GL_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0'i32
const GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1'i32
const GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2'i32
const GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3'i32
const GL_COMPRESSED_SRGB_S3TC_DXT1_EXT = 0x8C4C'i32
const GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT = 0x8C4D'i32
const GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT = 0x8C4E'i32
const GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT = 0x8C4F'i32
const GL_COMPRESSED_RGBA_BPTC_UNORM_EXT = 0x8E8C'i32
const GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT = 0x8E8D'i32
# const GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT = 0x8E8E'i32
# const GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT = 0x8E8F'i32
const GL_COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0'i32
const GL_COMPRESSED_RGBA_ASTC_5x4_KHR = 0x93B1'i32
const GL_COMPRESSED_RGBA_ASTC_5x5_KHR = 0x93B2'i32
const GL_COMPRESSED_RGBA_ASTC_6x5_KHR = 0x93B3'i32
const GL_COMPRESSED_RGBA_ASTC_6x6_KHR = 0x93B4'i32
const GL_COMPRESSED_RGBA_ASTC_8x5_KHR = 0x93B5'i32
const GL_COMPRESSED_RGBA_ASTC_8x6_KHR = 0x93B6'i32
const GL_COMPRESSED_RGBA_ASTC_8x8_KHR = 0x93B7'i32
const GL_COMPRESSED_RGBA_ASTC_10x5_KHR = 0x93B8'i32
const GL_COMPRESSED_RGBA_ASTC_10x6_KHR = 0x93B9'i32
const GL_COMPRESSED_RGBA_ASTC_10x8_KHR = 0x93BA'i32
const GL_COMPRESSED_RGBA_ASTC_10x10_KHR = 0x93BB'i32
const GL_COMPRESSED_RGBA_ASTC_12x10_KHR = 0x93BC'i32
const GL_COMPRESSED_RGBA_ASTC_12x12_KHR = 0x93BD'i32
const GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR = 0x93D0'i32
const GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR = 0x93D1'i32
const GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR = 0x93D2'i32
const GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR = 0x93D3'i32
const GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR = 0x93D4'i32
const GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR = 0x93D5'i32
const GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR = 0x93D6'i32
const GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR = 0x93D7'i32
const GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR = 0x93D8'i32
const GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR = 0x93D9'i32
const GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR = 0x93DA'i32
const GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR = 0x93DB'i32
const GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR = 0x93DC'i32
const GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR = 0x93DD'i32

import std/bitops, std/options
export options

type TexFlags = enum
    FlagCubemap
    FlagSrgb
    FlagAlpha
    FlagDds
    FlagKtx
    FlagVolume

proc GetDdsKtxInfo*(p: pointer, len: int, is_sRGB = false): Option[KtxInfo] =
    var tc: ddsktx_texture_info
    var err: ddsktx_error
    if ddsktx_parse(tc, p, len.cint, err):
        let is_cubemap = cast[int8](tc.flags).testBit FlagCubemap.int8
        let is_sRGB = is_sRGB or cast[int8](tc.flags).testBit FlagSrgb.int8
        let has_alpha = cast[int8](tc.flags).testBit FlagAlpha.int8
        let is_bc = tc.format >= DDSKTX_FORMAT_BC1 and
                    tc.format <= DDSKTX_FORMAT_BC7
        let is_astc = tc.format >= DDSKTX_FORMAT_ASTC4x4 and
                      tc.format <= DDSKTX_FORMAT_ASTC12x12
        let internal_format = if is_sRGB:
            case tc.format:
            of DDSKTX_FORMAT_BC1:
                if has_alpha: GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT
                else: GL_COMPRESSED_SRGB_S3TC_DXT1_EXT
            of DDSKTX_FORMAT_BC3: GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT
            of DDSKTX_FORMAT_BC5: GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT
            of DDSKTX_FORMAT_BC7: GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT
            of DDSKTX_FORMAT_ASTC4x4: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR
            of DDSKTX_FORMAT_ASTC5x4: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR
            of DDSKTX_FORMAT_ASTC5x5: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR
            of DDSKTX_FORMAT_ASTC6x5: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR
            of DDSKTX_FORMAT_ASTC6x6: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR
            of DDSKTX_FORMAT_ASTC8x5: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR
            of DDSKTX_FORMAT_ASTC8x6: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR
            of DDSKTX_FORMAT_ASTC10x5: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR
            of DDSKTX_FORMAT_ASTC10x6: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR
            of DDSKTX_FORMAT_ASTC8x8: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR
            of DDSKTX_FORMAT_ASTC10x8: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR
            of DDSKTX_FORMAT_ASTC10x10: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR
            of DDSKTX_FORMAT_ASTC12x10: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR
            of DDSKTX_FORMAT_ASTC12x12: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR
            else: 0
        else:
            case tc.format:
            of DDSKTX_FORMAT_BC1:
                if has_alpha: GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
                else: GL_COMPRESSED_RGB_S3TC_DXT1_EXT
            of DDSKTX_FORMAT_BC3: GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
            of DDSKTX_FORMAT_BC5: GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
            of DDSKTX_FORMAT_BC7: GL_COMPRESSED_RGBA_BPTC_UNORM_EXT
            of DDSKTX_FORMAT_ASTC4x4: GL_COMPRESSED_RGBA_ASTC_4x4_KHR
            of DDSKTX_FORMAT_ASTC5x4: GL_COMPRESSED_RGBA_ASTC_5x4_KHR
            of DDSKTX_FORMAT_ASTC5x5: GL_COMPRESSED_RGBA_ASTC_5x5_KHR
            of DDSKTX_FORMAT_ASTC6x5: GL_COMPRESSED_RGBA_ASTC_6x5_KHR
            of DDSKTX_FORMAT_ASTC6x6: GL_COMPRESSED_RGBA_ASTC_6x6_KHR
            of DDSKTX_FORMAT_ASTC8x5: GL_COMPRESSED_RGBA_ASTC_8x5_KHR
            of DDSKTX_FORMAT_ASTC8x6: GL_COMPRESSED_RGBA_ASTC_8x6_KHR
            of DDSKTX_FORMAT_ASTC10x5: GL_COMPRESSED_RGBA_ASTC_10x5_KHR
            of DDSKTX_FORMAT_ASTC10x6: GL_COMPRESSED_RGBA_ASTC_10x6_KHR
            of DDSKTX_FORMAT_ASTC8x8: GL_COMPRESSED_RGBA_ASTC_8x8_KHR
            of DDSKTX_FORMAT_ASTC10x8: GL_COMPRESSED_RGBA_ASTC_10x8_KHR
            of DDSKTX_FORMAT_ASTC10x10: GL_COMPRESSED_RGBA_ASTC_10x10_KHR
            of DDSKTX_FORMAT_ASTC12x10: GL_COMPRESSED_RGBA_ASTC_12x10_KHR
            of DDSKTX_FORMAT_ASTC12x12: GL_COMPRESSED_RGBA_ASTC_12x12_KHR
            else: 0
        assert internal_format != 0, "Unsupported format"
        return some(KtxInfo(
            width: tc.width, height: tc.height, depth: tc.depth,
            num_layers: tc.num_layers, num_mipmaps: tc.num_mips,
            is_cubemap: is_cubemap, has_alpha: has_alpha, is_sRGB: is_sRGB,
            is_bc: is_bc, is_astc: is_astc,
            internal_format: internalformat,
        ))

proc ParseDdsKtx*(p: pointer, len: int): seq[KtxPart] =
    var tc: ddsktx_texture_info
    var err: ddsktx_error
    if ddsktx_parse(tc, p, len.cint, err):
        let num_faces = if cast[int8](tc.flags).testBit FlagCubemap.int8: 6'i32 else: 1'i32
        var sd: ddsktx_sub_data
        for layer in 0 ..< tc.num_layers:
            for face in 0 ..< num_faces:
                for mip in 0 ..< tc.num_mips:
                    for slice in 0 ..< tc.depth:
                        let slice_face = slice + face*tc.depth
                        ddsktx_get_sub(tc, sd, p, len.cint, layer, slice_face, mip)
                        result.add KtxPart(
                            width: sd.width, height: sd.height,
                            layer: layer, face: face, mip_level: mip,
                            data: sd.buff,
                            len: sd.size_bytes,
                            row_len: sd.row_pitch_bytes,
                        )
    else:
        let err_msg = $cast[cstring](err.msg.addr)
        raise ValueError.newException err_msg

func get_ASTC_internal_format*(blk_size: (SomeInteger,SomeInteger), is_sRGB: bool): int32 =
    result = if is_sRGB:
        case (blk_size[0].int*100 or blk_size[1].int):
            of 04_04: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR
            of 05_04: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR
            of 05_05: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR
            of 06_05: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR
            of 06_06: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR
            of 08_05: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR
            of 08_06: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR
            of 10_05: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR
            of 10_06: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR
            of 08_08: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR
            of 10_08: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR
            of 10_10: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR
            of 12_10: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR
            of 12_12: GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR
            else: 0'i32
    else:
        case (blk_size[0].int*100 or blk_size[1].int):
            of 04_04: GL_COMPRESSED_RGBA_ASTC_4x4_KHR
            of 05_04: GL_COMPRESSED_RGBA_ASTC_5x4_KHR
            of 05_05: GL_COMPRESSED_RGBA_ASTC_5x5_KHR
            of 06_05: GL_COMPRESSED_RGBA_ASTC_6x5_KHR
            of 06_06: GL_COMPRESSED_RGBA_ASTC_6x6_KHR
            of 08_05: GL_COMPRESSED_RGBA_ASTC_8x5_KHR
            of 08_06: GL_COMPRESSED_RGBA_ASTC_8x6_KHR
            of 10_05: GL_COMPRESSED_RGBA_ASTC_10x5_KHR
            of 10_06: GL_COMPRESSED_RGBA_ASTC_10x6_KHR
            of 08_08: GL_COMPRESSED_RGBA_ASTC_8x8_KHR
            of 10_08: GL_COMPRESSED_RGBA_ASTC_10x8_KHR
            of 10_10: GL_COMPRESSED_RGBA_ASTC_10x10_KHR
            of 12_10: GL_COMPRESSED_RGBA_ASTC_12x10_KHR
            of 12_12: GL_COMPRESSED_RGBA_ASTC_12x12_KHR
            else: 0'i32
    assert result != 0, "Invalid ASTC block size " & $blk_size