/*
 * Copyright © 2006 Novell, Inc.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Author: David Reveman <davidr@novell.com>
 */

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

#include <beryl.h>

#define PNG_SIG_SIZE 8

static int displayPrivateIndex;

typedef struct _PngDisplay
{
	FileToImageProc fileToImage;
	ImageToFileProc imageToFile;
} PngDisplay;

#define GET_PNG_DISPLAY(d)				    \
    ((PngDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define PNG_DISPLAY(d)			 \
    PngDisplay *pd = GET_PNG_DISPLAY (d)


static void
premultiplyData(png_structp png, png_row_infop row_info, png_bytep data)
{
	unsigned int i;

	for (i = 0; i < row_info->rowbytes; i += 4)
	{
		unsigned char *base = &data[i];
		unsigned char blue = base[0];
		unsigned char green = base[1];
		unsigned char red = base[2];
		unsigned char alpha = base[3];
		int p;

		red = (unsigned)red *(unsigned)alpha / 255;
		green = (unsigned)green *(unsigned)alpha / 255;
		blue = (unsigned)blue *(unsigned)alpha / 255;

		p = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
		memcpy(base, &p, sizeof(int));
	}
}

static Bool
readPngData(png_struct * png,
			png_info * info, void **data, int *width, int *height)
{
	png_uint_32 png_width, png_height;
	int depth, color_type, interlace, i;
	unsigned int pixel_size;
	png_byte **row_pointers;
	char *d;

	png_read_info(png, info);

	png_get_IHDR(png, info,
				 &png_width, &png_height, &depth,
				 &color_type, &interlace, NULL, NULL);

	*width = (int)png_width;
	*height = (int)png_height;

	/* convert palette/gray image to rgb */
	if (color_type == PNG_COLOR_TYPE_PALETTE)
		png_set_palette_to_rgb(png);

	/* expand gray bit depth if needed */
	if (color_type == PNG_COLOR_TYPE_GRAY && depth < 8)
		png_set_gray_1_2_4_to_8(png);

	/* transform transparency to alpha */
	if (png_get_valid(png, info, PNG_INFO_tRNS))
		png_set_tRNS_to_alpha(png);

	if (depth == 16)
		png_set_strip_16(png);

	if (depth < 8)
		png_set_packing(png);

	/* convert grayscale to RGB */
	if (color_type == PNG_COLOR_TYPE_GRAY ||
		color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
		png_set_gray_to_rgb(png);

	if (interlace != PNG_INTERLACE_NONE)
		png_set_interlace_handling(png);

	png_set_bgr(png);
	png_set_filler(png, 0xff, PNG_FILLER_AFTER);

	png_set_read_user_transform_fn(png, premultiplyData);

	png_read_update_info(png, info);

	pixel_size = 4;
	d = (char *)malloc(png_width * png_height * pixel_size);
	if (!d)
		return FALSE;

	*data = d;

	row_pointers = (png_byte **) malloc(png_height * sizeof(char *));
	if (!row_pointers)
	{
		free(d);
		return FALSE;
	}

	for (i = 0; i < png_height; i++)
		row_pointers[i] = (png_byte *) (d + i * png_width * pixel_size);

	png_read_image(png, row_pointers);
	png_read_end(png, info);

	free(row_pointers);

	return TRUE;
}

static Bool
readPngFileToImage(FILE * file, int *width, int *height, void **data)
{
	unsigned char png_sig[PNG_SIG_SIZE];
	int sig_bytes;
	png_struct *png;
	png_info *info;
	Bool status;

	sig_bytes = fread(png_sig, 1, PNG_SIG_SIZE, file);
	if (png_check_sig(png_sig, sig_bytes) == 0)
		return FALSE;

	png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (!png)
		return FALSE;

	info = png_create_info_struct(png);
	if (!info)
	{
		png_destroy_read_struct(&png, NULL, NULL);

		return FALSE;
	}

	png_init_io(png, file);
	png_set_sig_bytes(png, sig_bytes);

	status = readPngData(png, info, data, width, height);

	png_destroy_read_struct(&png, &info, NULL);

	return status;
}

#if 0
static void
userReadData(png_structp png_ptr, png_bytep data, png_size_t length)
{
	const unsigned char **buffer = (const unsigned char **)
			png_get_io_ptr(png_ptr);

	memcpy(data, *buffer, length);
	*buffer += length;
}

static Bool
readPngBuffer(const unsigned char *buffer,
			  char **data, unsigned int *width, unsigned int *height)
{
	unsigned char png_sig[PNG_SIG_SIZE];
	png_struct *png;
	png_info *info;
	const unsigned char *b = buffer + PNG_SIG_SIZE;
	Bool status;

	memcpy(png_sig, buffer, PNG_SIG_SIZE);
	if (png_check_sig(png_sig, PNG_SIG_SIZE) == 0)
		return FALSE;

	png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (!png)
		return FALSE;

	info = png_create_info_struct(png);
	if (!info)
	{
		png_destroy_read_struct(&png, NULL, NULL);
		return FALSE;
	}

	png_set_read_fn(png, (void *)&b, userReadData);
	png_set_sig_bytes(png, PNG_SIG_SIZE);

	status = readPngData(png, info, data, width, height);

	png_destroy_read_struct(&png, &info, NULL);

	return status;
}
#endif

static Bool
writePng(unsigned char *buffer,
		 png_rw_ptr writeFunc,
		 void *closure, int width, int height, int stride)
{
	png_struct *png;
	png_info *info;
	png_byte **rows;
	png_color_16 white;
	int i;

	rows = malloc(height * sizeof(png_byte *));
	if (!rows)
		return FALSE;

	for (i = 0; i < height; i++)
		rows[height - i - 1] = buffer + i * stride;

	png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (!png)
	{
		free(rows);

		return FALSE;
	}

	info = png_create_info_struct(png);
	if (!info)
	{
		png_destroy_read_struct(&png, NULL, NULL);
		free(rows);

		return FALSE;
	}

	if (setjmp(png_jmpbuf(png)))
	{
		png_destroy_read_struct(&png, NULL, NULL);
		free(rows);

		return FALSE;
	}

	png_set_write_fn(png, closure, writeFunc, NULL);

	png_set_IHDR(png, info,
				 width, height, 8,
				 PNG_COLOR_TYPE_RGB_ALPHA,
				 PNG_INTERLACE_NONE,
				 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

	white.red = 0xff;
	white.blue = 0xff;
	white.green = 0xff;

	png_set_bKGD(png, info, &white);

	png_write_info(png, info);
	png_write_image(png, rows);
	png_write_end(png, info);

	png_destroy_write_struct(&png, &info);
	free(rows);

	return TRUE;
}

static void stdioWriteFunc(png_structp png, png_bytep data, png_size_t size)
{
	FILE *fp;

	fp = png_get_io_ptr(png);
	if (fwrite(data, 1, size, fp) != size)
		png_error(png, "Write Error");
}

static char *pngExtension(const char *name)
{

	if (strlen(name) > 4)
	{
		if (strcasecmp(name + (strlen(name) - 4), ".png") == 0)
			return "";
	}

	return ".png";
}

static Bool
pngImageToFile(CompDisplay * d,
			   const char *path,
			   const char *name,
			   const char *format,
			   int width, int height, int stride, void *data)
{
	Bool status = FALSE;
	char *extension = pngExtension(name);
	char *file;
	FILE *fp;
	int len;

	PNG_DISPLAY(d);

	len = (path ? strlen(path) : 0) + strlen(name) + strlen(extension) + 2;

	file = malloc(len);
	if (file)
	{
		if (path)
			sprintf(file, "%s/%s%s", path, name, extension);
		else
			sprintf(file, "%s%s", name, extension);
	}

	if (file && strcasecmp(format, "png") == 0)
	{
		fp = fopen(file, "wb");
		if (fp)
		{
			status = writePng(data, stdioWriteFunc, fp, width, height,
							  stride);
			fclose(fp);
		}

		if (status)
		{
			free(file);
			return TRUE;
		}
	}

	UNWRAP(pd, d, imageToFile);
	status = (*d->imageToFile) (d, path, name, format, width, height, stride,
								data);
	WRAP(pd, d, imageToFile, pngImageToFile);

	if (!status && file)
	{
		fp = fopen(file, "wb");
		if (fp)
		{
			status = writePng(data, stdioWriteFunc, fp, width, height,
							  stride);
			fclose(fp);
		}
	}

	if (file)
		free(file);

	return status;
}

static Bool
pngFileToImage(CompDisplay * d,
			   const char *path,
			   const char *name,
			   int *width, int *height, int *stride, void **data)
{
	Bool status = FALSE;
	char *extension = pngExtension(name);
	char *file;
	int len;

	PNG_DISPLAY(d);

	len = (path ? strlen(path) : 0) + strlen(name) + strlen(extension) + 2;

	file = malloc(len);
	if (file)
	{
		FILE *fp;

		if (path)
			sprintf(file, "%s/%s%s", path, name, extension);
		else
			sprintf(file, "%s%s", name, extension);

		fp = fopen(file, "r");
		if (fp)
		{
			status = readPngFileToImage(fp, width, height, data);
			fclose(fp);
		}

		free(file);

		if (status)
		{
			*stride = *width * 4;
			return TRUE;
		}
	}

	UNWRAP(pd, d, fileToImage);
	status = (*d->fileToImage) (d, path, name, width, height, stride, data);
	WRAP(pd, d, fileToImage, pngFileToImage);

	return status;
}

static Bool pngInitDisplay(CompPlugin * p, CompDisplay * d)
{
	PngDisplay *pd;
	CompScreen *s;

	pd = malloc(sizeof(PngDisplay));
	if (!pd)
		return FALSE;

	WRAP(pd, d, fileToImage, pngFileToImage);
	WRAP(pd, d, imageToFile, pngImageToFile);

	d->privates[displayPrivateIndex].ptr = pd;

	for (s = d->screens; s; s = s->next)
		updateDefaultIcon(s);

	return TRUE;
}

static void pngFiniDisplay(CompPlugin * p, CompDisplay * d)
{
	CompScreen *s;

	PNG_DISPLAY(d);

	UNWRAP(pd, d, fileToImage);
	UNWRAP(pd, d, imageToFile);

	for (s = d->screens; s; s = s->next)
		updateDefaultIcon(s);

	free(pd);
}

static Bool pngInit(CompPlugin * p)
{
	displayPrivateIndex = allocateDisplayPrivateIndex();
	if (displayPrivateIndex < 0)
		return FALSE;

	return TRUE;
}

static void pngFini(CompPlugin * p)
{
	if (displayPrivateIndex >= 0)
		freeDisplayPrivateIndex(displayPrivateIndex);
}

CompPluginFeature pngFeatures[] = {
	{"imageext:png"}
	,
	{"imagemime:image/png"}
	,
};

CompPluginVTable pngVTable = {
	"png",
	N_("Png"),
	N_("Png image loader"),
	pngInit,
	pngFini,
	pngInitDisplay,
	pngFiniDisplay,
	0,							/* InitScreen */
	0,							/* FiniScreen */
	0,							/* InitWindow */
	0,							/* FiniWindow */
	0,							/* GetDisplayOptions */
	0,							/* SetDisplayOption */
	0,							/* GetScreenOptions */
	0,							/* SetScreenOption */
	0,							/* Deps */
	0,							/* nDeps */
	pngFeatures,				/* Features */
	sizeof(pngFeatures) / sizeof(pngFeatures[0]),	/* nFeatures */
	BERYL_ABI_INFO,
	"beryl-plugins",
	"imageformat",
	0,
	0,
	True,
};

CompPluginVTable *getCompPluginInfo(void)
{
	return &pngVTable;
}


syntax highlighted by Code2HTML, v. 0.9.1