/* Input/Output support for PGM files */
#include <string.h>

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <limits.h>

#ifdef __cplusplus
extern "C"
#endif

/*
 * Internal.
 * Reads one unsigned integer from file stream.
 * Skips whitespace and comments (from `#' till eol)
 *   Args:
 *	*f	input stream
 *   Returns:
 *   	Unsigned integer read from stream
 *   	-1 	if end of file
 *   	-2	if wrong input char
 *   	-3	if overflow
 */
static int
_read_uint(FILE *f)
{
	int c;
	int val;
	int ignore_line = 0;

	/* skip wspace and comments */
	do {
	again:
		c = fgetc(f);
		switch (c) {
		case EOF:
				return -1;
		case '#':
				ignore_line = 1;
				goto again;
		case '\n':
		case '\r':
				ignore_line = 0;
				goto again;
		}

		if (ignore_line)
		{
			goto again;
		}
	} while (isspace(c));

	if (c < '0' ||
	    c > '9'
	   )
	{
		ungetc(c, f);
		return -2;
	}

	val = c - '0';
	while ((c=fgetc(f)) != EOF)
	{
		if (c >= '0' &&
		    c <= '9'
		   )
		{
			c -= '0';
			/* detect overflow */
			if (val > ((INT_MAX - c)/10)) /* (val*10 + c) > INT_MAX */
			{
				return -3;
			}
			val = val * 10 + (c);
		}
		else
		{
			ungetc(c, f);
			break;
		}
	}
	return val;
}

/*
 * Internal.
 * Reads Plain text encoded Image pixels (for Plain PGM format ("P2"))
 *   Args:
 *	*f	input stream
 *	*buf	where to but bytes
 *	count	how many numbers to read
 *	maxval	maximum value of one pixel
 *   Returns:
 *   	0	on success
 *   	-1	if end of file
 *   	-2	if wrong input char
 *   	-3	if pixel value exceeds maxval
 */
static int
_read_plain_pixels(FILE *f, unsigned char *buf, int count, int maxval)
{
	int i;
	int val;

	for (i=0; i < count; i++)
	{
		val = _read_uint(f);
		if (val < 0)
		{
			return val;
		}
		if (val > maxval)
		{
			return -3;
		}
		buf[i] = val;
	}

	return 0;
}

/*
 * Loads inamge from portable gray map (PGM) file.
 * Supports: binary PGM ("P5")
 * 	     plain PGM ("P2")
 *   Args:
 *	*fineName	input file name
 *	**image		pointer to loaded image bytes
 *	*width		where image width should be put
 *	*height		where image height should be put
 *   Returns:
 *   	0	on success
 *   	-1 	if some error
 */
int
load_pgm_file(const char *fileName, unsigned char **image, int *width, int *height)
{
	char magic[2];
	int max_val;
	FILE *f = NULL;
	static unsigned char *buf = NULL;
	int i;
	size_t ret;


	if (buf)
	{
		free(buf);
		buf = NULL;
	}

	if (!fileName)	/* null file name */
	{
		goto failed;
	}

	f = fopen(fileName, "rb");
	if (!f)		/* unable to open file */
	{
		goto failed;
	}

	/* read magic "P5": binary PGM
	 * 	      "P2": plain PGM */
	ret = fscanf(f, "%2c", magic);
	if (ret != 1 ||		/* unable to read magic */
	    magic[0] != 'P' ||	/* wrong magic */
	    (magic[1] != '5' && magic[1] != '2') )
	{
		goto failed;
	}

	*width = _read_uint(f);
	*height = _read_uint(f);
	max_val = _read_uint(f);
	if (*width <= 0 ||	/* unable to read header */
	    *height <= 0 ||
	    max_val <= 0)
	{
		goto failed;
	}

	if (max_val >= 256)	/* only 1 byte per pixel images supported */
	{
		goto failed;
	}

	i = *width * *height;
	buf = (unsigned char *) malloc(i);
	if (buf == NULL)	/* low mem */
	{
		goto failed;
	}

	if (magic[1] == '2')	/* read plain PGM pixels */
	{
		ret = _read_plain_pixels(f, buf, *width * *height, max_val);
		if (ret != 0)
		{
			goto failed;
		}
	}
	else
	{
		/* "eat" singel whitespace after max_val */
		fgetc(f);

		ret = fread(buf, *width, *height, f);
		if (ret != (*height))		/* read failed */
		{
			goto failed;
		}
	}

	fclose(f);
	*image = buf;
	return 0;

failed:
	if (buf)
	{
		free(buf);
		buf = NULL;
	}
	*image = NULL;

	if (f)
	{
		fclose(f);
	}
	return -1;
}

/*
 * Writes image as binary portable gray map (PGM Bin) file.
 *   Args:
 *	*fineName	output file name
 *	**image		pointer to image bytes
 *	width		image width
 *	height		image height
 *   Returns:
 *   	Unsigned integer read from stream
 *   	-1 	if some error
 */
int
write_pgmb_file(const char *fileName, const unsigned char *image, int width, int height)
{
	FILE *f = NULL;


	if (!fileName)	/* null file name */
	{
		return -1;
	}

	f = fopen(fileName, "wb");
	if (!f)		/* unable to open file */
	{
		return -1;
	}

	/* write header */
	fprintf(f, "P5\n"
		   "%i %i\n"
		   "%i\n", width, height, 255);

	/* write pixel bytes */
	fwrite(image, width, height, f);

	fclose(f);
	return 0;
}

#ifdef __cplusplus
}
#endif
