/** \file
 * \brief iupim utilities
 *
 * See Copyright Notice in "iup.h"
 */

#include <im.h>
#include <im_lib.h>
#include <im_convert.h>
#include <im_counter.h>
#include <im_util.h>
#include <im_image.h>
#include <im_raw.h>
#include <im_palette.h>

#include "iup.h"
#include "iupim.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "iup_object.h"
#include "iup_assert.h"
#include "iup_str.h"
#include "iup_image.h"


static void iSaveErrorMsg(int error)
{
  char* lang = IupGetLanguage();
  char* msg;
  if (iupStrEqualNoCase(lang, "ENGLISH"))
  {
    switch (error)
    {
    case IM_ERR_NONE:
      msg = NULL;
      break;
    case IM_ERR_OPEN:
      msg = "Error Opening Image File.";
      break;
    case IM_ERR_MEM:
      msg = "Insufficient memory.";
      break;
    case IM_ERR_ACCESS:
      msg = "Error Accessing Image File.";
      break;
    case IM_ERR_DATA:
      msg = "Image type not Supported.";
      break;
    case IM_ERR_FORMAT:
      msg = "Invalid Image File Format.";
      break;
    case IM_ERR_COMPRESS:
      msg = "Invalid or unsupported compression.";
      break;
    default:
      msg = "Unknown Error.";
      break;
    }
  }
  else
  {
    switch (error)
    {
    case IM_ERR_NONE:
      msg = NULL;
      break;
    case IM_ERR_OPEN:
      msg = "Erro Abrindo Arquivo de Imagem.";
      break;
    case IM_ERR_MEM:
      msg = "Memória Insuficiente.";
      break;
    case IM_ERR_ACCESS:
      msg = "Erro Acessando Arquivo de Imagem.";
      break;
    case IM_ERR_DATA:
      msg = "Tipo de Imagem não Suportado.";
      break;
    case IM_ERR_FORMAT:
      msg = "Formato de Arquivo de Imagem Inválido.";
      break;
    case IM_ERR_COMPRESS:
      msg = "Compressão Inválida ou não Suportada.";
      break;
    default:
      msg = "Erro Desconhecido.";
      break;
    }
  }

  IupSetGlobal("IUPIM_LASTERROR", msg);
}

static Ihandle* iupLoadImageFile(imFile* ifile, int index)
{
  int i, error, width, height, color_mode, flags,
    data_type, has_alpha = 0;
  Ihandle* iup_image = NULL;
  void* image_data = NULL;

  error = imFileReadImageInfo(ifile, index, &width, &height, &color_mode, &data_type);
  if (error)
    goto load_finish;

  flags = IM_TOPDOWN;
  flags |= IM_PACKED;
  if (imColorModeHasAlpha(color_mode))
  {
    has_alpha = 1;
    flags |= IM_ALPHA;
  }

  color_mode = imColorModeToBitmap(color_mode);
  data_type = IM_BYTE;

  image_data = malloc(imImageDataSize(width, height, flags | color_mode, data_type));
  if (!image_data)
    goto load_finish;

  error = imFileReadImageData(ifile, image_data, 1, flags);
  if (error)
    goto load_finish;

  if (color_mode == IM_RGB)
  {
    if (has_alpha)
      iup_image = IupImageRGBA(width, height, (unsigned char*)image_data);
    else
      iup_image = IupImageRGB(width, height, (unsigned char*)image_data);
  }
  else
  {
    const unsigned char* transp_index;
    int palette_count;
    long palette[256];

    imFileGetPalette(ifile, palette, &palette_count);
    iup_image = IupImage(width, height, (unsigned char*)image_data);

    for (i = 0; i < palette_count; i++)
    {
      unsigned char r, g, b;
      imColorDecode(&r, &g, &b, palette[i]);
      IupSetRGBId(iup_image, "", i, r, g, b);
    }

    transp_index = imFileGetAttribute(ifile, "TransparencyIndex", NULL, NULL);
    if (transp_index)
      IupSetAttributeId(iup_image, "", (int)(*transp_index), "BGCOLOR");
  }

load_finish:
  if (image_data) free(image_data);
  iSaveErrorMsg(error);
  return iup_image;
}

Ihandle* IupLoadImage(const char* filename)
{
  int error;
  Ihandle* iup_image = NULL;
  imCounterCallback old_callback;
  imFile* ifile;

  iupASSERT(filename);
  if (!filename)
    return NULL;

  old_callback = imCounterSetCallback(NULL, NULL);

  ifile = imFileOpen(filename, &error);
  if (!error)
    iup_image = iupLoadImageFile(ifile, 0);
  else
    iSaveErrorMsg(error);

  imCounterSetCallback(NULL, old_callback);

  if (ifile) imFileClose(ifile);
  return iup_image;
}

Ihandle* IupLoadAnimation(const char* filename)
{
  int error;
  Ihandle* animation = NULL;
  imCounterCallback old_callback;
  imFile* ifile;

  iupASSERT(filename);
  if (!filename)
    return NULL;

  old_callback = imCounterSetCallback(NULL, NULL);

  ifile = imFileOpen(filename, &error);
  if (!error)
  {
    int image_count, i;
    imFileGetInfo(ifile, NULL, NULL, &image_count);

    for (i = 0; i < image_count; i++)
    {
      Ihandle* iup_image = iupLoadImageFile(ifile, i);
      if (!iup_image)
        break;

      if (!animation)
      {
        double fps;

        animation = IupUser();

        fps = imFileGetAttribReal(ifile, "FPS", 0);
        if (fps)
        {
          int frametime = iupRound(1000.0 / fps);
          IupSetInt(animation, "FRAMETIME", frametime);
        }
      }

      IupAppend(animation, iup_image);
    }
  }
  else
    iSaveErrorMsg(error);

  imCounterSetCallback(NULL, old_callback);

  if (ifile) imFileClose(ifile);
  return animation;
}

Ihandle* IupLoadAnimationFrames(const char** filename_list, int file_count)
{
  Ihandle* animation = NULL;
  int i;

  iupASSERT(filename_list);
  if (!filename_list)
    return NULL;

  for (i = 0; i < file_count; i++)
  {
    Ihandle* iup_image = IupLoadImage(filename_list[i]);
    if (!iup_image)
      break;

    if (!animation)
      animation = IupUser();

    IupAppend(animation, iup_image);
  }

  return animation;
}

#if 0
/* Study. Not public yet */
Ihandle* IupLoadImageRaw(const char* filename, 
                         int width, int height, int data_type, int color_mode,
                         int switch_type, int byte_order, int padding, int start_offset, int ascii)
{
  int error, has_alpha = 0, i, flags, color_space;
  Ihandle* iup_image = NULL;
  void* image_data = NULL;
  imCounterCallback old_callback;
  imFile* ifile;

  old_callback = imCounterSetCallback(NULL, NULL);

  ifile = imFileOpenRaw(filename, &error);
  if (error)
    goto load_raw_finish;

  /* the bitmap data configuration */
  flags = IM_TOPDOWN;
  flags |= IM_PACKED;
  if (imColorModeHasAlpha(color_mode))
  {
    has_alpha = 1;
    flags |= IM_ALPHA;
  }

  color_space = imColorModeToBitmap(color_mode);

  image_data = malloc(imImageDataSize(width, height, flags|color_space, IM_BYTE));
  if (!image_data)
    goto load_raw_finish;

  /* the original data configuration */
  imFileSetAttribute(ifile, "Width", IM_INT, 1, &width);
  imFileSetAttribute(ifile, "Height", IM_INT, 1, &height);
  imFileSetAttribute(ifile, "ColorMode", IM_INT, 1, &color_mode);
  imFileSetAttribute(ifile, "DataType", IM_INT, 1, &data_type);

  imFileSetAttribute(ifile, "SwitchType", IM_INT, 1, &switch_type);
  imFileSetAttribute(ifile, "ByteOrder", IM_INT, 1, &byte_order);
  imFileSetAttribute(ifile, "Padding", IM_INT, 1, &padding);
  imFileSetAttribute(ifile, "StartOffset", IM_INT, 1, &start_offset);

  if (ascii)
    imFileSetInfo(ifile, "ASCII");

  error = imFileReadImageInfo(ifile, 0, NULL, NULL, NULL, NULL);
  if (error)
    goto load_raw_finish;
  
  error = imFileReadImageData(ifile, image_data, 1, flags);
  if (error)
    goto load_raw_finish;

  if (color_mode == IM_RGB)
  {
    if (has_alpha)
      iup_image = IupImageRGBA(width, height, (unsigned char*)image_data);
    else
      iup_image = IupImageRGB(width, height, (unsigned char*)image_data);
  }
  else
  {
    int palette_count;
    long palette[256];

    imFileGetPalette(ifile, palette, &palette_count);
    iup_image = IupImage(width, height, (unsigned char*)image_data);

    for (i = 0; i < palette_count; i++)
    {
      unsigned char r, g, b;
      imColorDecode(&r, &g, &b, palette[i]);
      IupSetRGBId(iup_image, "", i, r, g, b); 
    }
  }

load_raw_finish:
  imCounterSetCallback(NULL, old_callback);
  if (ifile) imFileClose(ifile);
  if (image_data) free(image_data);
  iSaveErrorMsg(error);
  return iup_image;
}
#endif

int IupSaveImage(Ihandle* ih, const char* filename, const char* format)
{
  int width, height, bpp;
  unsigned char* data;
  int error;
  imFile* ifile;

  iupASSERT(iupObjectCheck(ih));
  if (!iupObjectCheck(ih))
    return 0;

  iupASSERT(filename);
  if (!filename)
    return 0;

  ifile = imFileNew(filename, format, &error);
  if (!ifile)
  {
    iSaveErrorMsg(error);
    return 0;
  }

  data = (unsigned char*)IupGetAttribute(ih, "WID");
  if (!data)
  {
    iSaveErrorMsg(IM_ERR_DATA);
    return 0;
  }

  width = IupGetInt(ih, "WIDTH");
  height = IupGetInt(ih, "HEIGHT");
  bpp = IupGetInt(ih, "BPP");

  if (bpp == 24)
    error = imFileWriteImageInfo(ifile, width, height, IM_RGB|IM_TOPDOWN|IM_PACKED, IM_BYTE);
  else if (bpp == 32)
    error = imFileWriteImageInfo(ifile, width, height, IM_RGB|IM_TOPDOWN|IM_PACKED|IM_ALPHA, IM_BYTE);
  else /* bpp == 8 */
  {
    int i;
    long* palette = imPaletteGray();

    for (i = 0; i < 256; i++)
    {
      char* color = IupGetAttributeId(ih, "", i);
      if (!color)
          break;

      if (iupStrEqualNoCase(color, "BGCOLOR"))
      {
        unsigned char transp_index = (unsigned char)i;
        imFileSetAttribute(ifile, "TransparencyIndex", IM_BYTE, 1, &transp_index);
        palette[i] = imColorEncode(0, 0, 0);
      }
      else
      {
        unsigned char r, g, b;
        iupStrToRGB(color, &r, &g, &b);
        palette[i] = imColorEncode(r, g, b);
      }
    }

    imFileSetPalette(ifile, palette, 256);
    imPaletteRelease(palette);

    error = imFileWriteImageInfo(ifile, width, height, IM_MAP|IM_TOPDOWN, IM_BYTE);
  }

  if (error == IM_ERR_NONE)
    error = imFileWriteImageData(ifile, data);

  imFileClose(ifile); 

  iSaveErrorMsg(error);

  return error == IM_ERR_NONE? 1: 0;
}

/******************************************************************************/

static void iInitColors(iupColor* colors, int count, long* palette)
{
  int i;
  for (i=0; i< count; i++)
  {
    imColorDecode(&(colors[i].r), &(colors[i].g), &(colors[i].b), palette[i]);
    colors[i].a = 0;
  }
}

static void iInitPalette(long* palette, int count, iupColor* colors)
{
  int i;
  for (i=0; i< count; i++)
  {
    palette[i] = imColorEncode(colors[i].r, colors[i].g, colors[i].b);
  }
}

void* IupGetImageNativeHandle(const imImage* image)
{
  int bpp = image->depth*8;
  iupColor colors[256];
  iInitColors(colors, image->palette_count, image->palette);
  if (image->has_alpha && bpp == 24)
    bpp = 32;
  return iupdrvImageCreateImageRaw(image->width, image->height, bpp, colors, image->palette_count, image->data[0]);
}

imImage* IupGetNativeHandleImage(void* handle)
{
  int width, height, bpp;
  iupColor colors[256];
  int colors_count = 0;
  if (iupdrvImageGetRawInfo(handle, &width, &height, &bpp, colors, &colors_count))
  {
    imImage* image = imImageCreate(width, height, bpp>8? IM_RGB: IM_MAP, IM_BYTE);
    if (image)
    {
      if (bpp==32)
        imImageAddAlpha(image);

      if (bpp<=8 && colors_count)
      {
        long* palette = imPaletteNew(256);
        iInitPalette(palette, colors_count, colors);
        imImageSetPalette(image, palette, colors_count);
      }

      iupdrvImageGetRawData(handle, image->data[0]);
        
      return image;
    }

    iupdrvImageDestroy(handle, IUPIMAGE_IMAGE);
  }
  return NULL;
}

static void iFlipDataPacked(unsigned char* data, int width, int height, int depth)
{
  int line_size = depth*width;
  unsigned char* temp_line = (unsigned char*)malloc(line_size);
  int y, half_height = height / 2;

  for (y = 0; y < half_height; y++)
  {
    int yd = height - 1 - y;
    memcpy(temp_line, data + yd*line_size, line_size);
    memcpy(data + yd*line_size, data + y*line_size, line_size);
    memcpy(data + y*line_size, temp_line, line_size);
  }

  free(temp_line);
}

static int iCalcPalCount(unsigned char* data, int count)
{
  int i;
  int pal_count = 0;
  for (i = 0; i < count; i++)
  {
    if (data[i] > pal_count)
      pal_count = data[i];
  }
  pal_count++;
  return pal_count;
}

Ihandle* IupImageFromImImage(const imImage* image)
{
  Ihandle* iup_image = NULL;
  void* image_data;

  if (!imImageIsBitmap(image))
    return NULL;

  if (image->color_space == IM_RGB)
  {
    int color_mode = image->color_space;
    int depth = image->depth;

    if (image->has_alpha)
    {
      color_mode |= IM_ALPHA;
      depth++;
    }

    image_data = malloc(imImageDataSize(image->width, image->height, color_mode, IM_BYTE));
    if (!image_data)
      return NULL;

    /* imImage is always unpacked, IUP is always packed */
    imConvertPacking(image->data[0], image_data, image->width, image->height, depth, depth, IM_BYTE, 0);  /* to packed */

    /* imImage is always bottom top, IUP is always top bottom */
    iFlipDataPacked(image_data, image->width, image->height, depth);

    if (image->has_alpha)
      iup_image = IupImageRGBA(image->width, image->height, (unsigned char*)image_data);
    else
      iup_image = IupImageRGB(image->width, image->height, (unsigned char*)image_data);

    free(image_data);
  }
  else
  {
    int i;
    const unsigned char* transp_index;
    int pal_count;

    image_data = image->data[0];

    iup_image = IupImage(image->width, image->height, (unsigned char*)image_data);

    image_data = (unsigned char*)IupGetAttribute(iup_image, "WID");

    /* imImage is always bottom top, IUP is always top bottom */
    iFlipDataPacked(image_data, image->width, image->height, 1);

    pal_count = iCalcPalCount(image_data, image->count);
    for (i = 0; i < pal_count; i++)
    {
      unsigned char r, g, b;
      imColorDecode(&r, &g, &b, image->palette[i]);
      IupSetRGBId(iup_image, "", i, r, g, b);
    }

    transp_index = imImageGetAttribute(image, "TransparencyIndex", NULL, NULL);
    if (transp_index)
      IupSetAttributeId(iup_image, "", (int)(*transp_index), "BGCOLOR");
  }

  return iup_image;
}

imImage* IupImageToImImage(Ihandle* iup_image)
{
  int width, height, bpp;
  imImage* image = NULL;

  unsigned char* image_data = (unsigned char*)IupGetAttribute(iup_image, "WID");
  if (!image_data)
    return NULL;

  width = IupGetInt(iup_image, "WIDTH");
  height = IupGetInt(iup_image, "HEIGHT");
  bpp = IupGetInt(iup_image, "BPP");

  if (bpp == 24 || bpp == 32)
  {
    int color_mode = IM_RGB;
    int depth = 3;

    image = imImageCreate(width, height, color_mode, IM_BYTE);
    if (!image)
      return NULL;

    if (bpp == 32)
    {
      imImageAddAlpha(image);
      depth++;
    }

    /* imImage is always bottom top, IUP is always top bottom */
    iFlipDataPacked(image_data, image->width, image->height, depth);

    /* imImage is always unpacked, IUP is always packed */
    imConvertPacking(image_data, image->data[0], width, height, depth, depth, IM_BYTE, 1);  /* to un-packed */

    /* Undo Flip */
    iFlipDataPacked(image_data, image->width, image->height, depth);
  }
  else
  {
    long* palette = imPaletteNew(256);
    int i;

    image = imImageCreate(width, height, IM_MAP, IM_BYTE);
    if (!image)
      return NULL;

    memcpy(image->data[0], image_data, image->size);

    /* imImage is always bottom top, IUP is always top bottom */
    iFlipDataPacked(image->data[0], image->width, image->height, 1);

    for (i = 0; i < 256; i++)
    {
      char* color = IupGetAttributeId(iup_image, "", i);
      if (!color)
          break;

      if (iupStrEqualNoCase(color, "BGCOLOR"))
      {
        unsigned char transp_index = (unsigned char)i;
        imImageSetAttribute(image, "TransparencyIndex", IM_BYTE, 1, &transp_index);
        palette[i] = imColorEncode(0, 0, 0);
      }
      else
      {
        unsigned char r, g, b;
        iupStrToRGB(color, &r, &g, &b);
        palette[i] = imColorEncode(r, g, b);
      }
    }

    if (i > 0)
      imImageSetPalette(image, palette, i);
  }

  return image;
}

void IupImOpen(void)
{
  if (IupGetGlobal("_IUP_IM_OPEN"))
    return;

  IupSetGlobal("_IUP_IM_OPEN", "1");

  IupSetGlobal("IM_NAME", IM_NAME);
  IupSetGlobal("IM_VERSION", imVersion());
  IupSetGlobal("IM_VERSIONDATE", imVersionDate());

  return;
}
