import flatty/binny, chroma, pixie/common, pixie/images

# See: https://en.wikipedia.org/wiki/BMP_file_format

const bmpSignature* = "BM"

proc decodeBmp*(data: string): Image =
  ## Decodes bitmap data into an Image.

  # BMP Header
  if data[0 .. 1] != "BM":
    raise newException(PixieError, "Invalid BMP data")

  let
    width = data.readInt32(18).int
    height = data.readInt32(22).int
    bits = data.readUint16(28).int
    compression = data.readUint32(30).int
  var
    offset = data.readUInt32(10).int

  if bits notin [32, 24]:
    raise newException(PixieError, "Unsupported BMP data format")

  if compression notin [0, 3]:
    raise newException(PixieError, "Unsupported BMP data format")

  let channels = if bits == 32: 4 else: 3
  if width * height * channels + offset > data.len:
    raise newException(PixieError, "Invalid BMP data size")

  result = newImage(width, height)

  for y in 0 ..< result.height:
    for x in 0 ..< result.width:
      var rgba: ColorRGBA
      if bits == 32:
        rgba.r = data.readUint8(offset + 0)
        rgba.g = data.readUint8(offset + 1)
        rgba.b = data.readUint8(offset + 2)
        rgba.a = data.readUint8(offset + 3)
        offset += 4
      elif bits == 24:
        rgba.r = data.readUint8(offset + 2)
        rgba.g = data.readUint8(offset + 1)
        rgba.b = data.readUint8(offset + 0)
        rgba.a = 255
        offset += 3
      result[x, result.height - y - 1] = rgba

proc decodeBmp*(data: seq[uint8]): Image {.inline.} =
  decodeBmp(cast[string](data))

proc encodeBmp*(image: Image): string =
  ## Encodes an image into the BMP file format.

  # BMP Header
  result.add("BM") # The header field used to identify the BMP
  result.addUint32(0) # The size of the BMP file in bytes.
  result.addUint16(0) # Reserved.
  result.addUint16(0) # Reserved.
  result.addUint32(122) # The offset to the pixel array.

  # DIB Header
  result.addUint32(108) # Size of this header
  result.addInt32(image.width.int32) # Signed integer.
  result.addInt32(image.height.int32) # Signed integer.
  result.addUint16(1) # Must be 1 (color planes).
  result.addUint16(32) # Bits per pixels, only support RGBA.
  result.addUint32(3) # BI_BITFIELDS, no pixel array compression used
  result.addUint32(32) # Size of the raw bitmap data (including padding)
  result.addUint32(2835) # Print resolution of the image
  result.addUint32(2835) # Print resolution of the image
  result.addUint32(0) # Number of colors in the palette
  result.addUint32(0) # 0 means all colors are important
  result.addUint32(uint32(0x000000FF)) # Red channel.
  result.addUint32(uint32(0x0000FF00)) # Green channel.
  result.addUint32(uint32(0x00FF0000)) # Blue channel.
  result.addUint32(uint32(0xFF000000)) # Alpha channel.
  result.add("Win ") # little-endian.
  for i in 0 ..< 48:
    result.addUint8(0) # Unused

  for y in 0 ..< image.height:
    for x in 0 ..< image.width:
      let rgba = image[x, image.height - y - 1]
      result.addUint8(rgba.r)
      result.addUint8(rgba.g)
      result.addUint8(rgba.b)
      result.addUint8(rgba.a)

  result.writeUInt32(2, result.len.uint32)
