/*-
 * Copyright (c) 2001 Jordan DeLong
 * Copyright (c) 2004 Florian octo Forster
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include "wm.h"

/* flag if mitshm is available and usable */
static int image_mitshm		= 0;
static int errors = 0;

/* determine if we can use MITSHM */
void image_init()
{
	if (options.wantmitshm)
   {
		image_mitshm = XShmQueryExtension(display);
		if (image_mitshm)
			warnx("using MITSHM for images");
	} else
		image_mitshm = 0;
}

/* create an ximage, using MITSHM if possible */
static XImage *createximg(screen_t *screen, int width, int height,
                          XShmSegmentInfo **shminfo, int fallback)
{
	XImage *ximage;

	/* use mitshm if available */
	if (!image_mitshm)
		goto nomitshm;

	/* create the seginfo and the ximage */
	*shminfo = malloc(sizeof(XShmSegmentInfo));
	if (!*shminfo)
		return NULL;
	ximage = XShmCreateImage(display, DefaultVisual(display, screen->num),
                            DefaultDepth(display, screen->num), ZPixmap, NULL,
                            *shminfo, width, height);
	if (!ximage)
		goto free1;

	/* get a shared memory segment */
	(*shminfo)->shmid = shmget(IPC_PRIVATE,
                              ximage->bytes_per_line * ximage->height, IPC_CREAT | 0777);
	if ((*shminfo)->shmid == -1)
		goto free2;
	(*shminfo)->shmaddr = ximage->data = shmat((*shminfo)->shmid, 0, 0);
	(*shminfo)->readOnly = 0;

	/* try to attach the server */
	if (XShmAttach(display, *shminfo) == 0)
		goto free3;
	XSync(display, 0);

	/* cause the seg to remove when all procs detach */
	shmctl((*shminfo)->shmid, IPC_RMID, 0);

	/* if we get here we succeeded with shm */
	return ximage;

	/* falls through to try a non shm ximage */
free3:
	shmctl((*shminfo)->shmid, IPC_RMID, 0);
	shmdt((*shminfo)->shmaddr);
free2:
	XDestroyImage(ximage);
free1:
	free(*shminfo);
	*shminfo = NULL;
	warnx("mitshm failed; falling back to normal ximage");

nomitshm:
	/* only proceed if non-mitshm are ok */
	if (!fallback)
		return NULL;

	/* if mitshm isn't available, just use normal ximage */
	ximage = XCreateImage(display, DefaultVisual(display, screen->num),
                         DefaultDepth(display, screen->num), ZPixmap, 0,
                         NULL, width, height, 8, 0);

	/* allocate memory for the image data */
	ximage->data = malloc(ximage->bytes_per_line * height);
	if (!ximage->data)
   {
		XDestroyImage(ximage);
		return NULL;
	}

	return ximage;
}

/* create an image of a particular size */
image_t *image_create(screen_t *screen, int width, int height)
{
	image_t *image;

	/* get memory for the image_t */
	image = calloc(1, sizeof(image_t));
	if (!image)
		return NULL;
	image->screen = screen;

	/* create the ximage */
	image->ximage = createximg(screen, width, height, &image->shminfo, 1);
	if (!image->ximage)
		goto free;
	image->shmimg = image->shminfo != NULL;

	return image;

free:
	free(image);
	return NULL;
}

/* create an image_t out of a pixmap for a particular screen */
image_t *image_frompixmap(pixmap_t *pixmap, screen_t *screen)
{
	image_t *image;
	int have_image = 0;

	PDEBUG ("image_frompixmap entered");

	/* get memory for the image_t */
	image = calloc(1, sizeof(image_t));
	if (!image)
   {
		return (NULL);
   }

	image->screen = screen;

	/* for shm we need to create the shm image first */
	if (image_mitshm)
	{
		image->ximage = createximg(screen, pixmap->width,
                                 pixmap->height, &image->shminfo, 0);

		if (image->ximage)
		{
			if (XShmGetImage(display, pixmap->pixmaps[screen->num],
                          image->ximage, 0, 0, AllPlanes))
			{
				image->shmimg = 1;
				have_image = 1;
				PDEBUG ("XShmGetImage succeeded");
			}
		}
	}

	if (!have_image)
	{
		if (image_mitshm)
			PDEBUG ("XShmGetImage failed, using normal XGetImage");
		errors = 0;
		image->shmimg = 0;
		image->ximage = XGetImage(display, pixmap->pixmaps[screen->num],
                                0, 0, pixmap->width, pixmap->height, AllPlanes, ZPixmap);

		if (errors)
      {
         /* let's just struggle through */
         //	return (NULL);
      }
	}


	return (image);
}


int image_error_handler (Display* dpy, XErrorEvent* e)
{
	char buffer[100];

	XGetErrorText (dpy, e->error_code, buffer, 100);
	errors++;

//   printf( "ERROR %s\n", buffer );

	return (errors); /* ignored anyway */
}

/* 2004-02-16 Florian octo Forster */
image_t *image_fromscreenshot (client_t *c)
{
	image_t *image;
	int have_screenshot = 0;
	int width, height;

	XSetErrorHandler (image_error_handler);

	PDEBUG ("image_fromscreenshot entered");

	/* get memory for the image_t */
	image = calloc (1, sizeof (image_t));
	if (!image)
		return NULL;

	image->screen = c->screen;
	width = c->width;
	height = c->height;

	if ((c->x + width) > image->screen->width)
		width = image->screen->width - c->x;

	if ((c->y + height) > image->screen->height)
		height = image->screen->height - c->y;

	if (image_mitshm) /* WTF doesn't this work?!? */
	{
		PDEBUG ("Trying to take screenshot using XShmGetImage");

		image->ximage = createximg (c->screen, width, height,
                                  &image->shminfo, 0);

		/*
		 * I have no idea why, but this call always fails with a
		 * ``BadMatch'' error on my system (Xfree86 4.2.?). If
		 * anyone knows why, please let me know. -octo
		 */
		if (image->ximage)
		{
			if (XShmGetImage (display, c->window, image->ximage,
                           width, height, AllPlanes) )
			{
				have_screenshot = 1;
				image->shmimg = 1;
				PDEBUG ("Non-zero status code. Seems like everything's fine..");
			}
			else
			{
				PDEBUG ("Zero exit code. There appears to be a problem!");
			}
		}
	}

	if (!have_screenshot)
	{
		PDEBUG ("Trying to take screenshot using normal XGetImage");

		if (errors != 0)
			errors = 0;

		image->ximage = XGetImage (display, c->window, 0, 0,
                                 width, height, AllPlanes, ZPixmap);

		if (errors != 0)
		{
			PDEBUG ("Error handler has been called..");
			return (NULL);
		}

		image->shmimg = 0;
	}

	return (image);
}

/* destroy an image_t */
void image_destroy(image_t *image)
{
	if (image->shmimg) {
		XShmDetach(display, image->shminfo);
		shmdt(image->shminfo->shmaddr);
		free(image->shminfo);
	}
	XDestroyImage(image->ximage);
	free(image);
}

/* wrap image putting for abstraction of shm images */
void image_put(image_t *image, Drawable d, GC gc, int src_x, int src_y,
               int dest_x, int dest_y, int width, int height)
{
	PDEBUG ("image_put entered");

	if( image->shmimg )
   {
		XShmPutImage( display, d, gc, image->ximage, src_x, src_y,
                    dest_x, dest_y, width, height, 0 );
   }
	else
   {
		XPutImage( display, d, gc, image->ximage, src_x, src_y,
                 dest_x, dest_y, width, height );
   }

	XFlush(display);
}

/* make a copy of an image */
image_t *image_clone(image_t *src) {
	image_t *dest;

	/* get memory for the new image */
	dest = calloc(1, sizeof(image_t));
	if (!dest)
		return NULL;
	dest->screen = src->screen;

	/* copy the image */
	if (src->shmimg) {
		dest->ximage = createximg(src->screen,
			src->ximage->width, src->ximage->height,
			&dest->shminfo, 0);
		if (!dest->ximage)
			goto nomitshm;
		dest->shmimg = 1;
		memcpy(dest->ximage->data, src->ximage->data,
			dest->ximage->bytes_per_line * dest->ximage->height);
		return dest;
	}

nomitshm:
	dest->ximage = XSubImage(src->ximage, 0, 0,
		src->ximage->width, src->ximage->height);

	return dest;
}

/* scale an image to the size specified */
image_t *image_scale(image_t *src, int width, int height)
{
	image_t *dest;
	Pixel pixel;
	double dx, dy;
	double srcx, srcy;
	int x, y;

	PDEBUG ("image_scale entered");

	if (!src || !src->ximage)
		return (NULL);

	PDEBUG ("Scaling image: %ux%u -> %ux%u",
			src->ximage->width, src->ximage->height,
			width, height);

	/* can't size to nothing */
	assert(width > 0 && height > 0);

	/*
	 * if the requested size is the current size,
	 * just return a copy of the image.
	 */
	if (width == src->ximage->width
			&& height == src->ximage->height)
		return image_clone(src);

	/* create the destination image */
	dest = image_create(src->screen, width, height);
	if (!dest)
		return NULL;

	/* find the ratio of the old dims to new */
	dx = (double) src->ximage->width / (double) width;
	dy = (double) src->ximage->height / (double) height;

	/*
	 * perform the scaling; this is probably much slower than
	 * it could be...
	 */
	for (y = 0, srcy = 0; y < height; y++, srcy += dy)
		for (x = 0, srcx = 0; x < width; x++, srcx += dx) {
			pixel = XGetPixel(src->ximage,
				(int) srcx, (int) srcy);
			XPutPixel(dest->ximage, x, y, pixel);
		}

	return (dest);
}

/*
 * scale an image to the size specified
 * This function uses ``area averaging'' which is much slower than the
 * ``nearest neighbor'' method used in the ``image_scale'' function.
 * However, the results are _much_ better, too..
 */
/* 2004-02-16 Florian octo Forster */
image_t *image_scale_hq( client_t *client, image_t *src,
                         unsigned int width, unsigned int height,
                         int output_must_be_smaller_than_src )
{
	image_t *dest;
	double offx, offy;
	int srcx, srcy;
	int dstx, dsty;

	XColor color;
	Colormap cmap;
	Visual *vis;
	unsigned int temp_pixels;
	unsigned long int temp_red, temp_blue, temp_green;

	unsigned short int truecolor = 0;
	unsigned long int red_mask, green_mask, blue_mask;
	unsigned long int red_shift, green_shift, blue_shift;

	PDEBUG ("image_scale_hq entered");

	if (!src || !src->ximage)
   {
		return (NULL);
   }

   if( output_must_be_smaller_than_src )
   {
      if ((src->ximage->width < width) || (src->ximage->height < height))
      {
         return (NULL);
      }
   }

	/* can't size to nothing */
	assert ((width > 0) && (height > 0));

	/*
	 * if the requested size is the current size,
	 * just return a copy of the image.
	 */
	if (width == src->ximage->width
			&& height == src->ximage->height)
		return image_clone(src);

	PDEBUG ("Scaling image: %ux%u -> %ux%u",
			src->ximage->width, src->ximage->height,
			width, height);

	/* create the destination image */
	dest = image_create(src->screen, width, height);
	if (!dest)
		return NULL;

	/* find the ratio of the old dims to new */
	offx = ((double) src->ximage->width  / (double) width);
	offy = ((double) src->ximage->height / (double) height);

	PDEBUG("offx = %.2f; offy = %.2f; offx * width = %.2f; offy * height = %.2f;",
          offx, offy, offx * width, offy * height);

	vis = DefaultVisual (display, client->screen->num);
	cmap = DefaultColormap (display, client->screen->num);

	/*
	 * This is quite some magic (read: hack), but it's A LOT faster.
	 * This code finds the ``leftmost'' one in the color_masks.
	 * Later, we have to apply (bitwise-AND) the mask and shift this
	 * many position to the right to get a useable color.
	 *
	 * To compose the pixel-value we reverse the process. Enough talk,
	 * the rest follows the ``was hard to write - should be hard to
	 * read'' principle. Enjoy :) -octo
	 */
	if (vis->class == TrueColor || vis->class == DirectColor)
	{
		truecolor = 1;
		red_mask   = vis->red_mask;
		green_mask = vis->green_mask;
		blue_mask  = vis->blue_mask;

		red_shift = green_shift = blue_shift = 0L;
		while ((0x01 & (red_mask   >> red_shift  )) == 0)   red_shift++;
		while ((0x01 & (green_mask >> green_shift)) == 0) green_shift++;
		while ((0x01 & (blue_mask  >> blue_shift )) == 0)  blue_shift++;
	}

	/*
	 * perform the scaling; this is probably much slower than
	 * it could be...
	 */
	for (dsty = 0; dsty < height; dsty++)
	{
		for (dstx = 0; dstx < width; dstx++)
		{
			temp_pixels = 0;
			temp_red = temp_green = temp_blue = 0L;

			for (srcy = dsty * offy;
					(srcy < (dsty + 1) * offy)
					&& (srcy < src->ximage->height); srcy++)
			{
				for (srcx = dstx * offx;
						(srcx < (dstx + 1) * offx)
						&& (srcx < src->ximage->width); srcx++)
				{
					color.pixel = XGetPixel (src->ximage, srcx, srcy);

					if (truecolor)
					{
						/*
						 * Magic. See above for explanations..
						 */
						temp_red   += ((color.pixel & red_mask  ) >> red_shift  );
						temp_green += ((color.pixel & green_mask) >> green_shift);
						temp_blue  += ((color.pixel & blue_mask ) >> blue_shift );
					}
					else
					{
						XQueryColor (display, cmap, &color);

						temp_red   += color.red;
						temp_green += color.green;
						temp_blue  += color.blue;
					}
					temp_pixels++;
				} /* for srcx */
			} /* for srcy */

			/* we should not get there */
			if (temp_pixels == 0)
				temp_pixels = 1;

			temp_red   = ((double) temp_red   / (double) temp_pixels) + .5;
			temp_green = ((double) temp_green / (double) temp_pixels) + .5;
			temp_blue  = ((double) temp_blue  / (double) temp_pixels) + .5;

			/*
			 * TODO Make this optional
			 * Border:
			 * All pixels at the outmost positions are XOR'ed
			 * (i.e. inverted).
			 */
			if ((dstx == 0) || ((dstx + 1) == width)
					|| (dsty == 0) || ((dsty + 1) == height))
			{
				temp_red   ^= 0xffff;
				temp_green ^= 0xffff;
				temp_blue  ^= 0xffff;
			}

			color.red   = temp_red;
			color.green = temp_green;
			color.blue  = temp_blue;

			if (truecolor)
			{
				/*
				 * Reverse magic. Sometimes getting the
				 * rabbit into the hat is hard, too ;)
				 */
				color.pixel = ((temp_red << red_shift) & red_mask)
					| ((temp_green << green_shift) & green_mask)
					| ((temp_blue << blue_shift) & blue_mask);
			}
			else
			{
				XAllocColor (display, cmap, &color);
			}

			XPutPixel (dest->ximage, dstx, dsty, color.pixel);
		} /* for dstx */
	} /* for dsty */

	return (dest);
}
