/*
	lmp.c

	lump file tool

	Copyright (C) 1996-1997 Id Software, Inc.
	Copyright (C) 2002 Bill Currie <bill@taniwha.org>
	Copyright (C) 2002,2011 Jeff Teunissen <deek@quakeforge.net>

	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:

		Free Software Foundation, Inc.
		59 Temple Place - Suite 330
		Boston, MA  02111-1307, USA
*/
/*
	read 4 bytes -> width
	read 4 bytes -> height
	width * height bytes
	no attached palette
*/

#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif

#include <string.h>

#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <getopt.h>

#include <QF/cmd.h>
#include <QF/cvar.h>
#include <QF/image.h>
#include <QF/pcx.h>
#include <QF/qtypes.h>
#include <QF/qendian.h>
#include <QF/quakeio.h>
#include <QF/sys.h>
#include <QF/va.h>
#include <QF/zone.h>

#include "lmp.h"

#define MEMSIZE (8 * 1024 * 1024)

const char	*this_program;

options_t	options;
Pixel		*palette;

static const struct option long_options[] = {
	{"export", no_argument, 0, 'e'},
	{"import", no_argument, 0, 'i'},
	{"help", no_argument, 0, 'h'},
	{"version", no_argument, 0, 'V'},
	{"geometry", required_argument, 0, 'g'},
	{"geometry", required_argument, 0, 'g'},
	{"palette", required_argument, 0, 'p'},
	{"raw", no_argument, 0, 'r'},
	{"quiet", no_argument, 0, 'q'},
	{"verbose", no_argument, 0, 'v'},
	{NULL, 0, NULL, 0},
};


static char *
replaceExtension (const char *oldstr, const char *extension)
{
	char *tmp = strdup (oldstr);
	char *blank = strrchr (tmp, '.');
	char *newstr;

	*blank = 0;
	newstr = nva ("%s.%s", tmp, extension);
	free (tmp);
	return newstr;
}


static void
usage (int status)
{
	Sys_Printf ("%s - QuakeForge Lump tool\n", this_program);
	Sys_Printf ("Usage: %s <command> [options] [FILE...]\n", this_program);

	Sys_Printf ("Commands:\n"
			"    -e, --export              Export lump to PCX\n"
			"    -i, --import              Import lump from PCX\n"
			"    -h, --help                Display this help and exit\n"
			"    -V, --version             Output version information and exit\n\n");

	Sys_Printf ("Options:\n"
			"    -g, --geometry WIDTHxHEIGHT Ignore geometry for image data\n"
			"                              (Warning: does not check validity of input)\n"
			"    -p, --palette FILE        Use palette FILE for conversion\n"
			"    -r, --raw                 File(s) are raw (no lump header)\n"
			"                              (must use --geometry to be useful)\n"
			"    -q, --quiet               Inhibit usual output\n"
			"    -v, --verbose             Display more output than usual\n");
	exit (status);
}


static qboolean
exportFile (const char *inpath)
{
	char 		*outpath = replaceExtension (inpath, "pcx");
	QFile		*infile = Qopen (inpath, "rb");
	QFile		*outfile = Qopen (outpath, "wb");
	int			fsize = Qfilesize (infile);
	pcx_t		*pcx;
	int			pcx_size;
	int32_t		width;
	int32_t		height;
	void		*data;
	qboolean	ret = false;

	if (options.verbosity > 1)
		Sys_Printf ("file size: %d\n", fsize);

	if (options.raw) {
		width = options.width;
		height = options.height;
		if (width <= 0 || height <= 0) {
			Sys_Printf ("%s: cowardly refusing to write a raw image with no geometry\n",
						this_program);
			goto die;
		}
	} else {
		Qread (infile, &width, sizeof (width));
		Qread (infile, &height, sizeof (height));
		fsize -= 8;
		if (options.width > 0 && options.height > 0) {
			width = options.width;
			height = options.height;
		} else {
			width = LittleLong (width);
			height = LittleLong (height);
		}
	}

	if (options.verbosity > 1)
		Sys_Printf ("dimensions: %dx%d\n", width, height);

	if (options.verbosity > 1)
		Sys_Printf ("data size: %d\n", fsize);

	if ((width * height) > fsize) {
		Sys_Printf ("%s: %s: not enough image data for a %dx%d image.\n",
					this_program, inpath, width, height);
		goto die;
	}

	data = malloc (fsize);
	Qread (infile, data, fsize);

	pcx = EncodePCX (data, width, height, width, (byte *) palette, false, &pcx_size);
	free (data);

	if (options.verbosity > 1)
		Sys_Printf ("PCX data size: %d\n", pcx_size);

	if (Qwrite (outfile, pcx, pcx_size) != pcx_size) {
		Sys_Printf ("%s: Error writing to %s\n", this_program, outpath);
		goto die;
	}
	ret = true;

die:
	if (outpath)
		free (outpath);

	Qclose (infile);
	Qclose (outfile);

	return ret;
}


static qboolean
importFile (const char *inpath)
{
	char 		*outpath = replaceExtension (inpath, "lmp");
	QFile		*infile = Qopen (inpath, "rb");
	QFile		*outfile = Qopen (outpath, "wb");
	int			fsize = Qfilesize (infile);
	tex_t		*lmp;
	qboolean	ret = false;

	if (options.verbosity > 1)
		Sys_Printf ("PCX file size: %d\n", fsize);

	lmp = LoadPCX (infile, false, NULL);

	if (!lmp) {
		Sys_Printf ("%s: Failed to load %s as texture.\n",
					this_program, inpath);
		goto die;
	}

	if (lmp->format != tex_palette) {
		Sys_Printf ("%s: %s is not a paletted image.\n", this_program, inpath);
		goto die;
	} else {	// got a paletted image
		int32_t		width = lmp->width;
		int32_t		height = lmp->height;
		int			lmp_size = width * height;

		if (options.verbosity > 1)
			Sys_Printf ("geometry: %dx%d\ndata size: %d\n",
						width, height, lmp_size);

		if (!options.raw) {	// write lump header
			Qwrite (outfile, &width, sizeof (width));
			Qwrite (outfile, &height, sizeof (height));
		}

		if (Qwrite (outfile, lmp->data, lmp_size) != lmp_size) {
			Sys_Printf ("%s: Error writing to %s\n", this_program, outpath);
			goto die;
		}
		ret = true;
	}

die:
	if (outpath)
		free (outpath);

	Qclose (infile);
	Qclose (outfile);

	return ret;
}


static int
decode_args (int argc, char **argv)
{
	int		c;

	options.mode = mo_none;
	options.verbosity = 0;
	options.raw = false;
	options.width = options.height = -1;

	while ((c = getopt_long (argc, argv, "e"	// export pcx
										 "i"	// import pcx
										 "h"	// show help
										 "V"	// show version
										 "g:"	// geometry
										 "p:"	// palette
										 "r"	// raw
										 "q"	// quiet
										 "v"	// verbose
							 , long_options, (int *) 0)) != EOF) {
		switch (c) {
			case 'h':	// help
				usage (0);
				break;
			case 'V':	// version
				printf ("lmp version %s\n", PACKAGE_VERSION);
				exit (0);
				break;
			case 'e':	// export
				options.mode = mo_export;
				break;
			case 'i':	// import
				options.mode = mo_import;
				break;
			case 'g':
				{	// set geometry
					char *tmp = strdup (optarg);
					char *blank = strrchr (tmp, 'x');
					if (blank && *blank) {
						char *first, *second;

						first = tmp;
						second = blank + 1;

						options.width = atoi (first);
						options.height = atoi (second);
					}
				}
				break;
			case 'p':	// palette
				options.palette = strdup (optarg);
				break;
			case 'r':	// raw
				options.raw = 1;
				break;
			case 'q':	// lower verbosity
				options.verbosity--;
				break;
			case 'v':					// increase verbosity
				options.verbosity++;
				break;
			default:
				usage (1);
		}
	}

	return optind;
}

int
main (int argc, char **argv)
{
	QFile	*infile;
	size_t	ret;

	this_program = argv[0];

	Sys_Init ();
	Memory_Init (malloc (MEMSIZE), MEMSIZE);

	decode_args (argc, argv);

	if (options.palette) {
		if ((infile = Qopen (options.palette, "rb"))) {
			// read in the palette
			palette = malloc (sizeof (quake_palette));
			if (!(ret = Qread (infile, palette, sizeof (quake_palette)))) {
				Sys_Printf ("%s: failed to read palette from file \"%s\"\n",
							this_program, options.palette);
				Qclose (infile);
				exit (1);
			}
		} else {
			Sys_Printf ("%s: Couldn't open palette file \"%s\"\n",
						this_program, options.palette);
		}
		Qclose (infile);
	} else {
		palette = quake_palette;
	}

	switch (options.mode) {
		case mo_export:
			while (optind < argc) {
				if (options.verbosity > 0)
					Sys_Printf ("Exporting %s to PCX...\n", argv[optind]);
				exportFile (argv[optind]);
				optind++;
			}
			break;

		case mo_import:
			while (optind < argc) {
				if (options.verbosity > 0)
					Sys_Printf ("Converting %s to lump...\n", argv[optind]);
				importFile (argv[optind]);
				optind++;
			}
			break;

		default:
			Sys_Printf ("%s: No command given.\n", this_program);
			usage (1);
	}

#if 0
	for (i = 1; i < argc; i++) {
		int32_t	width = 0;
		int32_t	height = 0;
		QFile	*outfile;
		char	*in = argv[i];
		char	*out;
		unsigned char	*line;
		int		j;

		Sys_Printf ("%s\n", in);
		asprintf (&out, "%s.ppm", in);

		infile = Qopen (in, "rb");
		outfile = Qopen (out, "wb");

		// We use ASCII mode because we can put comments into it.
		Qprintf (outfile, "P3\n# %s\n", out);
		if (strcasestr (in, "conchars")) {	// don't read w/h from conchars
			width = height = 128;
		} else {
			Qread (infile, &width, sizeof (width));
			Qread (infile, &height, sizeof (height));
		}
		Qprintf (outfile, "%d\n", width);
		Qprintf (outfile, "%d\n", height);
		Qprintf (outfile, "255\n");

		for (j = 0; j < height; j++) {
			int k;

			line = malloc (width);	// doing a line at a time
			ret = Qread (infile, line, width);
			for (k = 0; k < width; k++) {
				Sys_Printf ("%u %d %d\n", (unsigned) ret, k, line[k]);
				if (k > 239)
					Qprintf (outfile, "# next pixel fullbright\n");

				Qprintf (outfile, "%3d %3d %3d\n",
							palette[line[k]].color.red,
							palette[line[k]].color.green,
							palette[line[k]].color.blue);
			}
			Qprintf (outfile, "# next line\n");
			free (line);
		}
		Qclose (infile);
		Qclose (outfile);
		free (out);
	}
#endif

	return 0;
}
