import std/strformat

{.compile("oiio.cpp","-std=c++14").}
{.passL: "-lOpenImageIO -lOpenImageIO_Util".}

type OiioImageFormat* {.size: 4.} = enum
    FORMAT_UNKNOWN = 0
    FORMAT_BYTE    # 8-bit unsigned
    FORMAT_SHORT   # 16-bit unsigned
    FORMAT_HALF    # 16-bit float
    FORMAT_FLOAT   # 32-bit float

template size*(format: OiioImageFormat): int =
    case format:
    of FORMAT_BYTE: 1
    of FORMAT_SHORT: 2
    of FORMAT_HALF: 2
    of FORMAT_FLOAT: 4
    else: 0

proc decode(input: pointer, input_len: int,
        output: pointer, output_len: int,
        desired_channels: int32, format: OiioImageFormat, is_BGR: var byte,
        file_name: cstring): int32 {.importc:"oiio_image_decode", cdecl.}

proc get_attributes(input: pointer, input_len: int,
        width: var int32, height: var int32,
        channels: var int32, format: var OiioImageFormat,
        file_name: cstring): int32 {.importc:"oiio_image_get_attributes", cdecl.}

template has_bytes(p: pointer, bytes: string): bool =
    cmpMem(p, bytes.cstring, bytes.len) == 0

proc file_name_from_magic(input: openArray[byte]): string =
    if input.len < 10:
        return "image"
    var p = input[0].addr
    # OpenImageIO usually has no trouble detecting the most common formats,
    # however it seems we need to give it a file name for some, like EXR.
    if p.has_bytes "\x89PNG": return "image.png"
    if p.has_bytes "\xFF\xD8\xFF": return "image.jpg"
    if p.has_bytes "v/1\x01": return "image.exr"
    if p.has_bytes "#?RADIANCE": return "image.hdr"
    return "image"

proc oiioImageGetAttributes*(input: openArray[byte]): (int, int, int, OiioImageFormat) =
    # returns: width, height, channels, format
    doAssert input.len != 0, "input is empty"
    let file_name = file_name_from_magic(input)
    var w,h,c: int32
    var f: OiioImageFormat
    var res = get_attributes(input[0].addr, input.len, w,h,c,f, file_name.cstring)
    doAssert res != 0, "error getting image attibutes"
    doAssert f != FORMAT_UNKNOWN, "unknown image format"
    return (w.int, h.int, c.int, f)

proc oiioImageDecode*[T](input: openArray[byte], min_channels = 0): seq[T] =
    doAssert input.len != 0, "input is empty"
    let file_name = file_name_from_magic(input)
    var w,h,c: int32
    var f: OiioImageFormat
    var res = get_attributes(input[0].addr, input.len, w,h,c,f, file_name.cstring)
    doAssert res != 0, "error getting image attibutes"
    doAssert w != 0 and h != 0 and c != 0, "invalid image"
    let channels = max(min_channels.int32, c)
    # let size_in = w * h * c
    let size_out = w * h * channels
    result.setLen size_out * f.size
    var is_BGR: byte
    res = decode(input[0].addr, input.len,
                result[0].addr, size_out * f.size,
                channels, f, is_BGR, file_name.cstring)
    doAssert res != 0, "error decoding image"