/*
 * Cogl
 *
 * An object oriented GL/GLES Abstraction/Utility Layer
 *
 * Copyright (C) 2007,2008,2009 Intel Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 *
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "cogl-util.h"
#include "cogl-bitmap-private.h"
#include "cogl-context-private.h"
#include "cogl-private.h"
#include "cogl-error-private.h"

#include <string.h>

#ifdef USE_QUARTZ
#include <ApplicationServices/ApplicationServices.h>
#elif defined(USE_GDKPIXBUF)
#include <gdk-pixbuf/gdk-pixbuf.h>
#endif

#ifdef USE_QUARTZ

CoglBool
_cogl_bitmap_get_size_from_file (const char *filename,
                                 int        *width,
                                 int        *height)
{
  if (width)
    *width = 0;

  if (height)
    *height = 0;

  return TRUE;
}

/* the error does not contain the filename as the caller already has it */
CoglBitmap *
_cogl_bitmap_from_file (CoglContext *ctx,
                        const char *filename,
			CoglError **error)
{
  CFURLRef url;
  CGImageSourceRef image_source;
  CGImageRef image;
  int save_errno;
  CFStringRef type;
  size_t width, height, rowstride;
  uint8_t *out_data;
  CGColorSpaceRef color_space;
  CGContextRef bitmap_context;
  CoglBitmap *bmp;

  url = CFURLCreateFromFileSystemRepresentation (NULL,
                                                 (guchar *) filename,
                                                 strlen (filename),
                                                 false);
  image_source = CGImageSourceCreateWithURL (url, NULL);
  save_errno = errno;
  CFRelease (url);

  if (image_source == NULL)
    {
      /* doesn't exist, not readable, etc. */
      _cogl_set_error_literal (error,
                               COGL_BITMAP_ERROR,
                               COGL_BITMAP_ERROR_FAILED,
                               g_strerror (save_errno));
      return NULL;
    }

  /* Unknown images would be cleanly caught as zero width/height below, but try
   * to provide better error message
   */
  type = CGImageSourceGetType (image_source);
  if (type == NULL)
    {
      CFRelease (image_source);
      _cogl_set_error_literal (error,
                               COGL_BITMAP_ERROR,
                               COGL_BITMAP_ERROR_UNKNOWN_TYPE,
                               "Unknown image type");
      return NULL;
    }

  CFRelease (type);

  image = CGImageSourceCreateImageAtIndex (image_source, 0, NULL);
  CFRelease (image_source);

  width = CGImageGetWidth (image);
  height = CGImageGetHeight (image);
  if (width == 0 || height == 0)
    {
      /* incomplete or corrupt */
      CFRelease (image);
      _cogl_set_error_literal (error,
                               COGL_BITMAP_ERROR,
                               COGL_BITMAP_ERROR_CORRUPT_IMAGE,
                               "Image has zero width or height");
      return NULL;
    }

  /* allocate buffer big enough to hold pixel data */
  bmp = _cogl_bitmap_new_with_malloc_buffer (ctx,
                                             width, height,
                                             COGL_PIXEL_FORMAT_ARGB_8888,
                                             error);
  if (bmp == NULL)
    {
      CFRelease (image);
      return NULL;
    }
  rowstride = cogl_bitmap_get_rowstride (bmp);
  out_data = _cogl_bitmap_map (bmp,
                               COGL_BUFFER_ACCESS_WRITE,
                               COGL_BUFFER_MAP_HINT_DISCARD,
                               error);
  if (out_data == NULL)
    {
      cogl_object_unref (bmp);
      CFRelease (image);
      return NULL;
    }

  /* render to buffer */
  color_space = CGColorSpaceCreateWithName (kCGColorSpaceGenericRGB);
  bitmap_context = CGBitmapContextCreate (out_data,
                                          width, height, 8,
                                          rowstride, color_space,
                                          kCGImageAlphaPremultipliedFirst);
  CGColorSpaceRelease (color_space);

  CGContextSetBlendMode (bitmap_context, kCGBlendModeCopy);

  {
    const CGRect rect = {{0, 0}, {width, height}};

    CGContextDrawImage (bitmap_context, rect, image);
  }

  CGImageRelease (image);
  CGContextRelease (bitmap_context);

  _cogl_bitmap_unmap (bmp);

  /* store bitmap info */
  return bmp;
}

#elif defined(USE_GDKPIXBUF)

CoglBool
_cogl_bitmap_get_size_from_file (const char *filename,
                                 int        *width,
                                 int        *height)
{
  _COGL_RETURN_VAL_IF_FAIL (filename != NULL, FALSE);

  if (gdk_pixbuf_get_file_info (filename, width, height) != NULL)
    return TRUE;

  return FALSE;
}

CoglBitmap *
_cogl_bitmap_from_file (CoglContext *ctx,
                        const char *filename,
			CoglError **error)
{
  static CoglUserDataKey pixbuf_key;
  GdkPixbuf *pixbuf;
  CoglBool has_alpha;
  GdkColorspace color_space;
  CoglPixelFormat pixel_format;
  int width;
  int height;
  int rowstride;
  int bits_per_sample;
  int n_channels;
  CoglBitmap *bmp;
  GError *glib_error = NULL;

  /* Load from file using GdkPixbuf */
  pixbuf = gdk_pixbuf_new_from_file (filename, &glib_error);
  if (pixbuf == NULL)
    {
      _cogl_propagate_gerror (error, glib_error);
      return FALSE;
    }

  /* Get pixbuf properties */
  has_alpha       = gdk_pixbuf_get_has_alpha (pixbuf);
  color_space     = gdk_pixbuf_get_colorspace (pixbuf);
  width           = gdk_pixbuf_get_width (pixbuf);
  height          = gdk_pixbuf_get_height (pixbuf);
  rowstride       = gdk_pixbuf_get_rowstride (pixbuf);
  bits_per_sample = gdk_pixbuf_get_bits_per_sample (pixbuf);
  n_channels      = gdk_pixbuf_get_n_channels (pixbuf);

  /* According to current docs this should be true and so
   * the translation to cogl pixel format below valid */
  g_assert (bits_per_sample == 8);

  if (has_alpha)
    g_assert (n_channels == 4);
  else
    g_assert (n_channels == 3);

  /* Translate to cogl pixel format */
  switch (color_space)
    {
    case GDK_COLORSPACE_RGB:
      /* The only format supported by GdkPixbuf so far */
      pixel_format = has_alpha ?
	COGL_PIXEL_FORMAT_RGBA_8888 :
	COGL_PIXEL_FORMAT_RGB_888;
      break;

    default:
      /* Ouch, spec changed! */
      g_object_unref (pixbuf);
      return FALSE;
    }

  /* We just use the data directly from the pixbuf so that we don't
     have to copy to a seperate buffer. Note that Cogl is expected not
     to read past the end of bpp*width on the last row even if the
     rowstride is much larger so we don't need to worry about
     GdkPixbuf's semantics that it may under-allocate the buffer. */
  bmp = cogl_bitmap_new_for_data (ctx,
                                  width,
                                  height,
                                  pixel_format,
                                  rowstride,
                                  gdk_pixbuf_get_pixels (pixbuf));

  cogl_object_set_user_data (COGL_OBJECT (bmp),
                             &pixbuf_key,
                             pixbuf,
                             g_object_unref);

  return bmp;
}

#else

#include "stb_image.c"

CoglBool
_cogl_bitmap_get_size_from_file (const char *filename,
                                 int        *width,
                                 int        *height)
{
  if (width)
    *width = 0;

  if (height)
    *height = 0;

  return TRUE;
}

/* stb_image.c supports an STBI_grey_alpha format which we don't have
 * a corresponding CoglPixelFormat for so as a special case we
 * convert this to rgba8888.
 *
 * If we have a use case where this is an important format to consider
 * then it could be worth adding a corresponding CoglPixelFormat
 * instead.
 */
static uint8_t *
convert_ra_88_to_rgba_8888 (uint8_t *pixels,
                            int width,
                            int height)
{
  int x, y;
  uint8_t *buf;
  size_t in_stride = width * 2;
  size_t out_stride = width * 4;

  buf = malloc (width * height * 4);
  if (buf)
    return NULL;

  for (y = 0; y < height; y++)
    for (x = 0; x < width; x++)
      {
        uint8_t *src = pixels + in_stride * y + 2 * x;
        uint8_t *dst = buf + out_stride * y + 4 * x;

        dst[0] = src[0];
        dst[1] = src[0];
        dst[2] = src[0];
        dst[3] = src[1];
      }

  return buf;
}

static CoglBitmap *
_cogl_bitmap_new_from_stb_pixels (CoglContext *ctx,
                                  uint8_t *pixels,
                                  int stb_pixel_format,
                                  int width,
                                  int height,
                                  CoglError **error)
{
  static CoglUserDataKey bitmap_data_key;
  CoglBitmap *bmp;
  CoglPixelFormat cogl_format;
  size_t stride;

  if (pixels == NULL)
    {
      _cogl_set_error_literal (error,
                               COGL_BITMAP_ERROR,
                               COGL_BITMAP_ERROR_FAILED,
                               "Failed to load image with stb image library");
      return NULL;
    }

  switch (stb_pixel_format)
    {
    case STBI_grey:
      cogl_format = COGL_PIXEL_FORMAT_A_8;
      break;
    case STBI_grey_alpha:
      {
        uint8_t *tmp = pixels;

        pixels = convert_ra_88_to_rgba_8888 (pixels, width, height);
        free (tmp);

        if (!pixels)
          {
            _cogl_set_error_literal (error,
                                     COGL_BITMAP_ERROR,
                                     COGL_BITMAP_ERROR_FAILED,
                                     "Failed to alloc memory to convert "
                                     "gray_alpha to rgba8888");
            return NULL;
          }

        cogl_format = COGL_PIXEL_FORMAT_RGBA_8888;
        break;
      }
    case STBI_rgb:
      cogl_format = COGL_PIXEL_FORMAT_RGB_888;
      break;
    case STBI_rgb_alpha:
      cogl_format = COGL_PIXEL_FORMAT_RGBA_8888;
      break;

    default:
      g_warn_if_reached ();
      return NULL;
    }

  stride = width * _cogl_pixel_format_get_bytes_per_pixel (cogl_format);

  /* Store bitmap info */
  bmp = cogl_bitmap_new_for_data (ctx,
                                  width, height,
                                  cogl_format,
                                  stride,
                                  pixels);

  /* Register a destroy function so the pixel data will be freed
     automatically when the bitmap object is destroyed */
  cogl_object_set_user_data (COGL_OBJECT (bmp), &bitmap_data_key, pixels, free);

  return bmp;
}

CoglBitmap *
_cogl_bitmap_from_file (CoglContext *ctx,
                        const char *filename,
			CoglError **error)
{
  int stb_pixel_format;
  int width;
  int height;
  uint8_t *pixels;

  pixels = stbi_load (filename,
                      &width, &height, &stb_pixel_format,
                      STBI_default);

  return _cogl_bitmap_new_from_stb_pixels (ctx, pixels, stb_pixel_format,
                                           width, height,
                                           error);
}

#ifdef COGL_HAS_ANDROID_SUPPORT
CoglBitmap *
_cogl_android_bitmap_new_from_asset (CoglContext *ctx,
                                     AAssetManager *manager,
                                     const char *filename,
                                     CoglError **error)
{
  AAsset *asset;
  const void *data;
  off_t len;
  int stb_pixel_format;
  int width;
  int height;
  uint8_t *pixels;
  CoglBitmap *bmp;

  asset = AAssetManager_open (manager, filename, AASSET_MODE_BUFFER);
  if (!asset)
    {
      _cogl_set_error_literal (error,
                               COGL_BITMAP_ERROR,
                               COGL_BITMAP_ERROR_FAILED,
                               "Failed to open asset");
      return NULL;
    }

  data = AAsset_getBuffer (asset);
  if (!data)
    {
      _cogl_set_error_literal (error,
                               COGL_BITMAP_ERROR,
                               COGL_BITMAP_ERROR_FAILED,
                               "Failed to ::getBuffer from asset");
      return NULL;
    }

  len = AAsset_getLength (asset);

  pixels = stbi_load_from_memory (data, len,
                                  &width, &height,
                                  &stb_pixel_format, STBI_default);

  bmp = _cogl_bitmap_new_from_stb_pixels (ctx, pixels, stb_pixel_format,
                                          width, height,
                                          error);

  AAsset_close (asset);

  return bmp;
}
#endif

#endif