/* This file contains code for unified image loading from file  

 *  Copyright (c) 1998 Sasha Vasko <sashav@sprintmail.com>  
 * 
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#define LIBASIMAGE
/*#define DEBUG_LOADIMAGE */


#include "../configure.h"

#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <math.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xproto.h>
#include <X11/Xatom.h>
#include <X11/Intrinsic.h>
#include <X11/xpm.h>

#include "../include/aftersteplib.h"
#include "../include/afterstep.h"

/* our input-output data structures definitions */

#include "../include/ascolor.h"
#include "../include/loadimg.h"
#include "../include/XImage_utils.h"

#ifdef DEBUG_LOADIMAGE
#define LOG1(a)       fprintf( stderr, a );
#define LOG2(a,b)    fprintf( stderr, a, b );
#define LOG3(a,b,c)    fprintf( stderr, a, b, c );
#define LOG4(a,b,c,d)    fprintf( stderr, a, b, c, d );
#else
#define LOG1(a)
#define LOG2(a,b)
#define LOG3(a,b,c)
#define LOG4(a,b,c,d)
#endif

/* Here we'll detect image type */
int bReportErrorIfTypeUnknown = 1;
/* we might have a compressed XPM file */
int
CheckIfCompressed (const char *realfilename)
{
  int len = strlen (realfilename);

  if (len > 3 && strcasecmp (realfilename + len - 3, ".gz") == 0)
    return 1;
  if (len > 2 && strcasecmp (realfilename + len - 2, ".z") == 0)
    return 1;
  return 0;
}

#define FORMAT_TYPE_MAX_SIZE 128
#define FORMAT_TYPE_MIN_SIZE 10

/* return 0 if not detected      */
int
GetImageType (const char *realfilename)
{
  int fd;
  unsigned char head[FORMAT_TYPE_MAX_SIZE];
  int bytes_in = FORMAT_TYPE_MAX_SIZE;

  if ((fd = open (realfilename, O_RDONLY)) == -1)
    return -1;
  bytes_in = read (fd, head, bytes_in);
  close (fd);

  if (bytes_in > FORMAT_TYPE_MIN_SIZE)
    {
      if (bytes_in < FORMAT_TYPE_MAX_SIZE)
	head[bytes_in] = '\0';
      else
	head[bytes_in - 1] = '\0';

      if (head[0] == 0xff && head[1] == 0xd8 && head[2] == 0xff)
	return F_JPEG;
      else if (head[0] == 'G' && head[1] == 'I' && head[2] == 'F')
	return F_GIF;
      else if (head[0] == head[1] && (head[0] == 'I' || head[0] == 'M'))
	return F_TIFF;
      else if (head[0] == 0xa && head[1] <= 5 && head[2] == 1)
	return F_PCX;
      else if (head[0] == 'B' && head[1] == 'M')
	return F_BMP;
      else if (head[0] == 0 && head[1] == 0 &&
	       head[2] == 2 && head[3] == 0 &&
	       head[4] == 0 && head[5] == 0 &&
	       head[6] == 0 && head[7] == 0)
	return F_TARGA;
      else if (strncmp ((char *) head + 1, "PNG", (size_t) 3) == 0)
	return F_PNG;
      else if (strncmp ((char *) head, "#define", (size_t) 7) == 0)
	return F_XBM;
      else if (strstr ((char *) head, "XPM") != NULL)
	return F_XPM;
      else if (CheckIfCompressed (realfilename) != 0)
	return F_XPM;
      /* nothing yet for PCD */

    }
  return F_UNKNOWN;
}



int
CreateTarget (LImageParams * pParams)
{
  pParams->m_Target = XCreatePixmap (pParams->m_dpy, pParams->m_w,
				     pParams->m_width, pParams->m_height,
				     pParams->m_depth);
  XSync (pParams->m_dpy, False);
  LOG2 ("\n loadimage.c: Target Pixmap created: %lu .", pParams->m_Target);
  if (pParams->m_Target)
    if ((pParams->m_pImage = CreateXImageAndData (pParams->m_dpy,
						  pParams->m_visual,
						  pParams->m_depth,
						  ZPixmap, 0,
						  pParams->m_width,
						pParams->m_height)) == NULL)
      {
	XFreePixmap (pParams->m_dpy, pParams->m_Target);
	pParams->m_Target = 0;
      }
  LOG2 ("\n loadimage.c: Target XImage %s.", (pParams->m_pImage) ? "created" : "failed");
  return ((pParams->m_pImage == NULL) ? 0 : 1);
}

int
CreateMask (LImageParams * pParams)
{

  pParams->m_Mask = XCreatePixmap (pParams->m_dpy, pParams->m_w,
				   pParams->m_width, pParams->m_height,
				   MASK_DEPTH);
  if (pParams->m_Mask)
    if ((pParams->m_pMaskImage = XGetImage (pParams->m_dpy, pParams->m_Mask, 0, 0,
					pParams->m_width, pParams->m_height,
					    AllPlanes, ZPixmap)) == NULL)
      {
	XFreePixmap (pParams->m_dpy, pParams->m_Mask);
	pParams->m_Mask = 0;
      }
  LOG1 ("\nLoadImage: CreateMask done.");
  return ((pParams->m_pMaskImage == NULL) ? 0 : 1);
}

void
XImageToPixmap (LImageParams * pParams, XImage * pImage, Pixmap * pTarget)
{
  if (pImage == NULL)
    {
      if (*pTarget)
	{
	  LOG1 ("\nXImageToPixmap: XImageToPixmap pImage is NULL.");
	  XFreePixmap (pParams->m_dpy, *pTarget);
	  *pTarget = None;
	}
      return;
    }
  LOG1 ("\nXImageToPixmap: XImageToPixmap pImage is not NULL.");
  LOG4 ("\nXImageToPixmap: XImage depth=%u, format=%s, width=%d", pImage->depth, (pImage->format == ZPixmap) ? "ZPixmap" : ((pImage->format == XYBitmap) ? "XYBitmap" : "XYPixmap"), pImage->width)
    LOG3 (", height=%d, bpp=%u ", pImage->height, pImage->bits_per_pixel)
    if (*pTarget == None)
    if ((*pTarget = XCreatePixmap (pParams->m_dpy, pParams->m_w,
				   pParams->m_width, pParams->m_height,
				   pImage->depth)))
      {
	if (pParams->m_gc)
	  XFreeGC (pParams->m_dpy, pParams->m_gc);
	pParams->m_gc = XCreateGC (pParams->m_dpy, *pTarget, 0, NULL);
      }

  if (*pTarget)
    {
      GC mgc = XCreateGC (pParams->m_dpy, *pTarget, 0, NULL);
      XPutImage (pParams->m_dpy, *pTarget, mgc,
		 pImage, 0, 0, 0, 0, pParams->m_width, pParams->m_height);
      XFreeGC (pParams->m_dpy, mgc);
    }
}

void
CheckImageSize (LImageParams * pParams, unsigned int real_width, unsigned int real_height)
{
  if (pParams->m_width > 0 && pParams->m_x_net)
    {
      LOG3 ("\nCheckImageSize(): to_width = %d, from_width=%d", pParams->m_width, real_width)
	if (pParams->m_width == real_width)
	{
	  free (pParams->m_x_net);
	  pParams->m_x_net = NULL;
	}
      else
	Scale (pParams->m_x_net, pParams->m_width, 0, real_width - 1);

    }
  else if (pParams->m_max_x > 0 && pParams->m_max_x < real_width)
    pParams->m_width = pParams->m_max_x;
  else
    pParams->m_width = real_width;
  LOG2 ("\nCheckImageSize(): width = %d", pParams->m_width)
    if (pParams->m_height > 0 && pParams->m_y_net)
    {
      LOG3 ("\nCheckImageSize(): to_height = %d, from_height=%d", pParams->m_height, real_height)
	if (pParams->m_height == real_height)
	{
	  free (pParams->m_y_net);
	  pParams->m_y_net = NULL;
	}
      else
	Scale (pParams->m_y_net, pParams->m_height, 0, real_height - 1);
    }
  else if (pParams->m_max_y > 0 && pParams->m_max_y < real_height)
    pParams->m_height = pParams->m_max_y;
  else
    pParams->m_height = real_height;
  LOG2 ("\nCheckImageSize(): height = %d", pParams->m_height)
}

/* main image loading code here */

extern int LoadJPEGFile (LImageParams * pParams);
extern int LoadXPMFile (LImageParams * pParams);
extern void LoadPNGFile (LImageParams * pParams);

pixmap_ref_t *pixmap_ref_first = NULL;
int use_pixmap_ref = 1;

int
set_use_pixmap_ref (int on)
{
  int old = use_pixmap_ref;
  use_pixmap_ref = on;
  return old;
}

pixmap_ref_t *
pixmap_ref_new (const char *filename, Pixmap pixmap, Pixmap mask)
{
  pixmap_ref_t *ref = safemalloc (sizeof (pixmap_ref_t));
  ref->next = pixmap_ref_first;
  pixmap_ref_first = ref;
  ref->refcount = 1;
  ref->name = (filename == NULL) ? NULL : mystrdup (filename);
  ref->pixmap = pixmap;
  ref->mask = mask;
  return ref;
}

void
pixmap_ref_delete (pixmap_ref_t * ref)
{
  if (ref == NULL)
    return;

  if (pixmap_ref_first == ref)
    pixmap_ref_first = (*ref).next;
  else if (pixmap_ref_first != NULL)
    {
      pixmap_ref_t *ptr;
      for (ptr = pixmap_ref_first; (*ptr).next != NULL; ptr = (*ptr).next)
	if ((*ptr).next == ref)
	  break;
      if ((*ptr).next == ref)
	(*ptr).next = (*ref).next;
    }

  if (ref->name != NULL)
    free (ref->name);
  if (ref->pixmap != None)
    XFreePixmap (dpy, ref->pixmap);
  if (ref->mask != None)
    XFreePixmap (dpy, ref->mask);

  free (ref);
}

pixmap_ref_t *
pixmap_ref_find_by_name (const char *filename)
{
  pixmap_ref_t *ref;
  for (ref = pixmap_ref_first; ref != NULL; ref = ref->next)
    if (!strcmp (ref->name, filename))
      break;
  return ref;
}

pixmap_ref_t *
pixmap_ref_find_by_pixmap (Pixmap pixmap)
{
  pixmap_ref_t *ref;
  for (ref = pixmap_ref_first; ref != NULL; ref = ref->next)
    if (ref->pixmap == pixmap)
      break;
  return ref;
}

pixmap_ref_t *
pixmap_ref_find_by_mask (Pixmap mask)
{
  pixmap_ref_t *ref;
  for (ref = pixmap_ref_first; ref != NULL; ref = ref->next)
    if (ref->mask == mask)
      break;
  return ref;
}

int
pixmap_ref_increment (pixmap_ref_t * ref)
{
  return ++ref->refcount;
}

int
pixmap_ref_decrement (pixmap_ref_t * ref)
{
  int c = --ref->refcount;
/* don't delete the reference, the pixmap might be immediately reloaded; 
   ** let the app tell us when to purge the pixmap via pixmap_ref_purge() */
/*
   if (c <= 0)
   pixmap_ref_delete(ref);
 */
  return c;
}

int
pixmap_ref_purge (void)
{
  int done = 0, count;
  for (count = -1; !done; count++)
    {
      pixmap_ref_t *ref;
      for (ref = pixmap_ref_first; ref != NULL; ref = ref->next)
	if (ref->refcount <= 0)
	  {
	    pixmap_ref_delete (ref);
	    break;
	  }
      done = (ref == NULL);
    }
  return count;
}

/* UnloadImage()
 * if pixmap is found in reference list, decrements reference count; else 
 * XFreePixmap()'s the pixmap
 * returns number of remaining references to pixmap */
#if defined(LOG_LOADIMG_CALLS) && defined(DEBUG_ALLOCS)
int
l_UnloadImage (const char *file, int line, Pixmap pixmap)
{
  log_call (file, line, "UnloadImage", NULL);
#else
int
UnloadImage (Pixmap pixmap)
{
#endif
  if (use_pixmap_ref)
    {
      pixmap_ref_t *ref = pixmap_ref_find_by_pixmap (pixmap);
      if (ref != NULL)
	return pixmap_ref_decrement (ref);
    }
  XFreePixmap (dpy, pixmap);

  return 0;
}

/* UnloadMask()
 * if mask is found in the reference list and there is no corresponding 
 * pixmap, decrements reference count; else if mask is found, does 
 * nothing; else XFreePixmap()'s the mask
 * returns number of remaining references to mask */
#if defined(LOG_LOADIMG_CALLS) && defined(DEBUG_ALLOCS)
int
l_UnloadMask (const char *file, int line, Pixmap mask)
{
  log_call (file, line, "UnloadImage", NULL);
#else
int
UnloadMask (Pixmap mask)
{
#endif
  if (mask == None)
    return 0;

  if (use_pixmap_ref)
    {
      pixmap_ref_t *ref = pixmap_ref_find_by_mask (mask);
      if (ref == NULL)
	XFreePixmap (dpy, mask);
      else if (ref->pixmap == None)
	return pixmap_ref_decrement (ref);
      return (ref != NULL) ? ref->refcount : 0;
    }
  XFreePixmap (dpy, mask);
  return 0;
}

/* this one will load image from file */
Pixmap
LoadImageEx (LImageParams * pParams)
{

  pixmap_ref_t *ref = NULL;
  if (use_pixmap_ref)
    {
      ref = pixmap_ref_find_by_name (pParams->m_realfilename);
      if (ref != NULL)
	{
	  pixmap_ref_increment (ref);
	  pParams->m_Target = ref->pixmap;
	  pParams->m_Mask = ref->mask;
	  return ref->pixmap;
	}
    }

  pParams->m_Target = None;
  pParams->m_Mask = None;
  pParams->m_pImage = NULL;
  pParams->m_pMaskImage = NULL;
  pParams->m_gc = None;
  pParams->m_img_colormap = NULL;
  {				/* adjusting gamma correction */
    char *gamma_str;
    if ((gamma_str = getenv ("SCREEN_GAMMA")) != NULL)
      pParams->m_gamma = atof (gamma_str);
    else
      pParams->m_gamma = SCREEN_GAMMA;
    if (pParams->m_gamma == 0.0)
      pParams->m_gamma = SCREEN_GAMMA;
    if (pParams->m_gamma != 1.0)
      {
	register int i;
	double gamma_i = 1.0 / pParams->m_gamma;
	pParams->m_gamma_table = safemalloc (256);
	for (i = 0; i < 256; i++)
	  pParams->m_gamma_table[i] = ADJUST_GAMMA8_INV ((CARD8) i, gamma_i);
      }
    else
      pParams->m_gamma_table = NULL;
  }

  {				/* queriing window attributes */
    XWindowAttributes win_attr;
    XGetWindowAttributes (pParams->m_dpy, pParams->m_w, &win_attr);
    LOG3 ("\nLoadImageWithMask: window size %dx%d", win_attr.width, win_attr.height);
    if (pParams->m_max_x < 0)
      pParams->m_max_x = win_attr.width;
    if (pParams->m_max_y < 0)
      pParams->m_max_y = win_attr.height;
    pParams->m_colormap = win_attr.colormap;
    pParams->m_visual = win_attr.visual;
    pParams->m_depth = win_attr.depth;
    if (pParams->m_width <= 0)
      pParams->m_x_net = NULL;
    if (pParams->m_height <= 0)
      pParams->m_y_net = NULL;
  }

  LOG1 ("\nLoadImage: Checking file format...");
  switch (GetImageType (pParams->m_realfilename))
    {
    case -1:
      fprintf (stderr, "\n LoadImage: cannot read file[%s].", pParams->m_realfilename);
      break;
    case F_XPM:
#ifdef XPM
      LOG1 ("\nLoadImage: XPM format");
      LoadXPMFile (pParams);
#else
      if (bReportErrorIfTypeUnknown)
	{
	  fprintf (stderr, "\n LoadImage: cannot load XPM file [%s]", pParams->m_realfilename);
	  fprintf (stderr, "\n LoadImage: you need to install libXPM v 4.0 or higher, in order to read images of this format.");
	}
#endif /* XPM */
      break;

    case F_JPEG:
#ifdef JPEG
      LoadJPEGFile (pParams);
      LOG1 ("\nLoadImage: JPEG format");
#else
      if (bReportErrorIfTypeUnknown)
	{
	  fprintf (stderr, "\n LoadImage: cannot load JPEG file [%s]", pParams->m_realfilename);
	  fprintf (stderr, "\n LoadImage: you need to install libJPEG v 6.0 or higher, in order to read images of this format.");
	}
#endif /* JPEG */
      break;

    case F_PNG:
#ifdef PNG
      LoadPNGFile (pParams);
      LOG1 ("\nLoadImage: PNG format");
#else
      if (bReportErrorIfTypeUnknown)
	{
	  fprintf (stderr, "\n LoadImage: cannot load PNG file [%s]", pParams->m_realfilename);
	  fprintf (stderr, "\n LoadImage: you need to install libPNG, in order to read images of this format.");
	}
#endif /* PNG */
      break;

    default:
      if (bReportErrorIfTypeUnknown)
	fprintf (stderr, "\n LoadImage: Unknown or unsupported image format.\n");
      break;
    }

  /* here deallocating resources if needed */
  if (pParams->m_pImage)
    {
      LOG1 ("\nLoadImage: Destroying m_pImage..");
      XDestroyImage (pParams->m_pImage);
      pParams->m_pImage = NULL;
      LOG1 (" Done.");
    }

  if (pParams->m_pMaskImage)
    {
      LOG1 ("\nLoadImage: Destroying m_pMaskImage..");
      XDestroyImage (pParams->m_pMaskImage);
      pParams->m_pMaskImage = NULL;
      LOG1 (" Done.");
    }

  if (pParams->m_gc != 0)
    {
      LOG1 ("\nLoadImage: Freeing GC...");
      XFreeGC (pParams->m_dpy, pParams->m_gc);
      pParams->m_gc = 0;
      LOG1 (" Done.");
    }

  if (pParams->m_gamma_table)
    {
      free (pParams->m_gamma_table);
      pParams->m_gamma_table = NULL;
    }

  if (pParams->m_img_colormap)
    {
      free (pParams->m_img_colormap);
      pParams->m_img_colormap = NULL;
    }

  if (use_pixmap_ref && pParams->m_Target)
    {
      int using_tmp_head = set_use_tmp_heap (0);
      pixmap_ref_new (pParams->m_realfilename, pParams->m_Target, pParams->m_Mask);
      set_use_tmp_heap (using_tmp_head);
    }
  return pParams->m_Target;
}

void
SetMask (LImageParams * pParams, Pixmap * pMask)
{
  if (pMask)
    {
      if (pParams->m_Target != None)
	*pMask = pParams->m_Mask;
      else
	*pMask = None;
    }
  else if (pParams->m_Target != None && pParams->m_Mask)
    {
      XFreePixmap (pParams->m_dpy, pParams->m_Mask);
      pParams->m_Mask = None;
    }

}

Pixmap
LoadImageWithMask (Display * dpy, Window w, unsigned long max_colors, const char *realfilename, Pixmap * pMask)
{
  LImageParams Params;

  if (dpy == NULL || w == 0 || realfilename == NULL)
    return 0;
  if (strlen (realfilename) <= 0 || max_colors == 0)
    return 0;

  Params.m_dpy = dpy;
  Params.m_w = w;
  Params.m_max_colors = max_colors;
  Params.m_realfilename = realfilename;

  Params.m_width = 0;
  Params.m_height = 0;

  Params.m_max_x = 0;
  Params.m_max_y = 0;

  LOG2 ("\nLoadImageWithMask: filename: [%s].", realfilename);
  LoadImageEx (&Params);
  LOG1 ("\nLoadImageWithMask: image loaded, saving mask...");
  SetMask (&Params, pMask);
  LOG1 (" Done.");

  return Params.m_Target;
}

Pixmap
LoadImageWithMaskAndScale (Display * dpy, Window w, unsigned long max_colors, const char *realfilename, unsigned int to_width, unsigned int to_height, Pixmap * pMask)
{
  LImageParams Params;

  if (dpy == NULL || w == 0 || realfilename == NULL)
    return 0;
  if (strlen (realfilename) <= 0 || max_colors == 0)
    return 0;

  Params.m_dpy = dpy;
  Params.m_w = w;
  Params.m_max_colors = max_colors;
  Params.m_realfilename = realfilename;
  Params.m_width = to_width;

  Params.m_max_x = -1;
  Params.m_max_y = -1;

  if (to_width > 0)
    Params.m_x_net = (int *) safemalloc (to_width * sizeof (int));
  Params.m_height = to_height;
  if (to_height > 0)
    Params.m_y_net = (int *) safemalloc (to_height * sizeof (int));

  LOG2 ("\nLoadImageWithMask: filename: [%s].", realfilename);
  LoadImageEx (&Params);
  LOG1 ("\nLoadImageWithMask: image loaded, saving mask...");
  SetMask (&Params, pMask);

  if (Params.m_x_net)
    free (Params.m_x_net);
  if (Params.m_y_net)
    free (Params.m_y_net);

  LOG1 (" Done.");

  return Params.m_Target;
}

/*Pixmap LoadImage( Display *dpy, Window w, unsigned long max_colors, char* realfilename ){} */
