//
// Copyright (c) 2017, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   15 Jun 2017  Matthew Giannini  Creation
//

**
** Graphical image.  Images are loaded from a file using `GraphicsEnv.image`.
**
@Js
mixin Image
{
  ** Render a new image with the given MIME type and size using the
  ** provided Graphics instance. Throws 'UnsupportedErr' if rendering
  ** is not supported in this env.
  static Image render(MimeType mime, Size size, |Graphics| f)
  {
    try
    {
      // render is currently only supported for the Java2D env, which
      // is not the default for VM. So we need to directly query impl
      // to check for platform support
      GraphicsEnv env := Type.find("graphicsJava::Java2DGraphicsEnv").make
      return env.renderImage(mime, size, f)
    }
    catch
    {
      throw UnsupportedErr("Image.render not supported in this env")
    }
  }

  ** Unique uri key for this image in the GraphicsEnv cache.
  abstract Uri uri()

  ** Is this image completely loaded into memory for use.  When a given
  ** uri is first accessed by `GraphicsEnv.image` it may be asynchronously
  ** loaded in the background and false is returned until load is complete.
  abstract Bool isLoaded()

  ** Image format based on file type:
  **   - 'image/png'
  **   - 'image/gif'
  **   - 'image/jpeg'
  **   - 'image/svg+xml'
  abstract MimeType mime()

  ** Get the natural size of this image.
  ** If the image has not been loaded yet, then return 0,0.
  abstract Size size()

  ** Get the size width
  virtual Float w() { size.w }

  ** Get the size height
  virtual Float h() { size.h }

  ** Write image content to the given output stream, where encoding
  ** is based on `mime` type.  Throws 'UnsupportedErr' if write is
  ** not supported in this env.
  virtual Void write(OutStream out)
  {
    throw UnsupportedErr("Image.write not supported in this env")
  }

  ** Image properties
  **  - 'colorSpace' (Str) - the image color space (e.g.RGB, RGBA, CMYK)
  **  - 'colorSpaceBits' (Int) - bits-per-channel of the color space
  @NoDoc @Operator abstract Obj? get(Str prop)

  ** Map file extension for GraphicsEnv.image
  @NoDoc static MimeType mimeForLoad(Uri uri, Buf? data)
  {
    // prefer data over URI if available
    if (data != null)
    {
      mime := mimeForData(data)
      if (mime != null) return mime
    }

    // try to infer from URI extension
    return mimeForUri(uri) ?: mimeUnknown
  }

  ** Map file extension to mime type or return null
  @NoDoc static MimeType? mimeForUri(Uri uri)
  {
    ext := uri.ext?.lower ?: ""
    if (ext == "svg") return mimeSvg
    if (ext == "png") return mimePng
    if (ext == "jpg" || ext == "jpeg") return mimeJpeg
    if (ext == "gif") return mimeGif
    return MimeType.forExt(ext)
  }

  ** Try to map image file to mime type
  @NoDoc static MimeType? mimeForData(Buf data)
  {
    if (data.size > 4)
    {
      d0 := data[0]
      d1 := data[1]
      d2 := data[2]
      d3 := data[3]
      if (d0 == '<' && d1 == 's' && d2 == 'v' && d3 == 'g') return mimeSvg
      if (d0==0x89 && d1==0x50 && d2==0x4E && d3==0x47) return mimePng
      if (d0==0x47 && d1==0x49 && d2==0x46) return mimeGif
      if (d0==0xFF && d1==0xD8) return mimeJpeg
    }
    return null
  }

  @NoDoc static const MimeType mimePng     := MimeType("image/png")
  @NoDoc static const MimeType mimeGif     := MimeType("image/gif")
  @NoDoc static const MimeType mimeJpeg    := MimeType("image/jpeg")
  @NoDoc static const MimeType mimeSvg     := MimeType("image/svg+xml")
  @NoDoc static const MimeType mimeUnknown := MimeType("image/unknown")
}

**************************************************************************
** PngImage
**************************************************************************

**
** Details for an PNG image.  This is just a temporary solution
** until we flush out formal APIs for color models, pixel access, etc.
**
@Js @NoDoc
mixin PngImage : Image
{
  ** Does the image have an alpha channel
  Bool hasAlpha() { colorType == 4 || colorType == 6 }

  ** Does the image have a palette index
  Bool hasPalette() { palette.size > 0 }

  ** Does the image have simple transparency alpha channel
  Bool hasTransparency() { transparency.size > 0 }

  ** Color type code
  Int colorType() { get("colorType") }

  ** Number of color components
  Int colors()
  {
    c := (colorType == 2 || colorType == 6) ? 3 : 1
    return hasAlpha ? c + 1 : c
  }

  ** Number of bits in a pixel
  Int pixelBits() { colors * ((Int)get("colorSpaceBits")) }

  ** The palette index. The Buf is immutable.
  Buf palette() { get("palette") }

  ** The simple transparency alpha channel. The Buf is immutable.
  Buf transparency() { get("transparency") }

  ** Raw image data. The Buf is immutable.
  Buf imgData() { get("imgData") }

  ** Get decompressed pixels. The Buf is immutable.
  abstract Buf pixels()
}