/* Read a ppm file.
 * 
 * Stephen Chan ... original code
 *
 * 21/11/00 JC
 *	- hacked for VIPS
 *	- reads ppm/pgm/pbm
 *	- mmaps binary pgm/ppm
 *	- reads all ascii formats (slowly!)
 * 22/11/00 JC
 *	- oops, ascii read was broken
 *	- does 16/32 bit ascii now as well
 * 24/5/01
 *	- im_ppm2vips_header() added
 * 22/5/04
 *	- does 16/32 bit binary too
 *	- tiny fix for missing file close on read error
 * 19/8/05
 * 	- use im_raw2vips() for binary read
 * 9/9/05
 * 	- tiny cleanups
 */

/*

    This file is part of VIPS.
    
    VIPS is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser 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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 */

/*

    These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk

 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>

#include <ctype.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>

#include <vips/vips.h>

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif /*WITH_DMALLOC*/

/* The largest number/field/whatever we can read.
 */
#define IM_MAX_THING (80)

static void 
skip_line( FILE *fp )
{
        int ch;

        while( (ch = fgetc( fp )) != '\n' )
		;
}

static void 
skip_white_space( FILE *fp )
{
        int ch;

        while( isspace( ch = fgetc( fp ) ) )
		;
	ungetc( ch, fp );

	if( ch == '#' ) {
		skip_line( fp );
		skip_white_space( fp );
	}
}

static int
read_uint( FILE *fp )
{
	int i;
	char buf[IM_MAX_THING];
	int ch;

	skip_white_space( fp );

	/* Stop complaints about used-before-set on ch.
	 */
	ch = -1;

	for( i = 0; i < IM_MAX_THING - 1 && isdigit( ch = fgetc( fp ) ); i++ ) 
		buf[i] = ch;
	buf[i] = '\0';

	if( i == 0 ) {
		im_error( "im_ppm2vips", _( "bad unsigned int" ) );
		return( -1 );
	}

	ungetc( ch, fp );

	return( atoi( buf ) );
}

static int
read_header( FILE *fp, IMAGE *out, int *bits, int *ascii )
{
	int width, height, bands, fmt, type;
	int i;
	char buf[IM_MAX_THING];
	int max_value;

	/* ppm types.
	 */
	static char *magic_names[] = {
		"P1",	/* pbm ... 1 band 1 bit, ascii */
		"P2",	/* pgm ... 1 band many bit, ascii */
		"P3",	/* ppm ... 3 band many bit, ascii */
		"P4",	/* pbm ... 1 band 1 bit, binary */
		"P5",	/* pgm ... 1 band 8 bit, binary */
		"P6"	/* ppm ... 3 band 8 bit, binary */
	};

	/* Characteristics, indexed by ppm type.
	 */
	static int lookup_bits[] = {
		1, 8, 8, 1, 8, 8
	};
	static int lookup_bands[] = {
		1, 1, 3, 1, 1, 3
	};
	static int lookup_ascii[] = {
		1, 1, 1, 0, 0, 0
	};

	/* Read in the magic number.
	 */
	buf[0] = fgetc( fp );
	buf[1] = fgetc( fp );
	buf[2] = '\0';

	for( i = 0; i < IM_NUMBER( magic_names ); i++ )
		if( strcmp( magic_names[i], buf ) == 0 ) 
			break;
	if( i == IM_NUMBER( magic_names ) ) {
		im_error( "im_ppm2vips", _( "bad magic number" ) );
		return( -1 );
	}
	*bits = lookup_bits[i];
	bands = lookup_bands[i];
	*ascii = lookup_ascii[i];

	/* Read in size.
	 */
	if( (width = read_uint( fp )) < 0 ||
		(height = read_uint( fp )) < 0 )
		return( -1 );

	/* Read in max value for >1 bit images.
	 */
	if( *bits > 1 ) {
		if( (max_value = read_uint( fp )) < 0 )
			return( -1 );

		if( max_value > 255 )
			*bits = 16;
		if( max_value > 65535 )
			*bits = 32;
	}

	/* For binary images, there is always exactly 1 more whitespace
	 * character before the data starts.
	 */
	if( !*ascii && !isspace( fgetc( fp ) ) ) {
		im_error( "im_ppm2vips", 
			_( "not whitespace before start of binary data" ) );
		return( -1 );
	}

	/* Choose a VIPS bandfmt.
	 */
	switch( *bits ) {
	case 1:
	case 8:
		fmt = IM_BANDFMT_UCHAR;
		break;

	case 16:
		fmt = IM_BANDFMT_USHORT;
		break;

	case 32:
		fmt = IM_BANDFMT_UINT;
		break;

	default:
		assert( 0 );
	}

	if( bands == 1 ) {
		if( fmt == IM_BANDFMT_USHORT )
			type = IM_TYPE_GREY16;
		else
			type = IM_TYPE_B_W;
	}
	else {
		if( fmt == IM_BANDFMT_USHORT )
			type = IM_TYPE_RGB16;
		else if( fmt == IM_BANDFMT_UINT )
			type = IM_TYPE_RGB;
		else 
			type = IM_TYPE_sRGB;
	}

	im_initdesc( out, width, height, bands, (*bits == 1) ? 8 : *bits, fmt, 
		IM_CODING_NONE, 
		type,
		1.0, 1.0,
		0, 0 );

	return( 0 );
}

/* Read a ppm/pgm file using mmap().
 */
static int
read_mmap( FILE *fp, const char *filename, IMAGE *out )
{
	const int header_offset = ftell( fp );
	IMAGE *t[2];
	im_arch_type arch = im_amiMSBfirst() ?
		IM_ARCH_NATIVE : IM_ARCH_BYTE_SWAPPED;

	if( im_open_local_array( out, t, 2, "im_ppm2vips", "p" ) ||
		im_raw2vips( filename, t[0], 
			out->Xsize, out->Ysize, 
			IM_IMAGE_SIZEOF_PEL( out ), header_offset ) ||
		im_copy_morph( t[0], t[1],
			out->Bands, out->BandFmt, out->Coding ) ||
		im_copy_from( t[1], out, arch ) )
		return( -1 );

	return( 0 );
}

/* Read an ascii ppm/pgm file.
 */
static int
read_ascii( FILE *fp, IMAGE *out )
{
	int x, y;
	PEL *buf;

	if( im_outcheck( out ) || im_setupout( out ) ||
		!(buf = IM_ARRAY( out, IM_IMAGE_SIZEOF_LINE( out ), PEL )) )
		return( -1 );

	for( y = 0; y < out->Ysize; y++ ) {
		for( x = 0; x < out->Xsize * out->Bands; x++ ) {
			int val;

			if( (val = read_uint( fp )) < 0 )
				return( -1 );
			
			switch( out->BandFmt ) {
			case IM_BANDFMT_UCHAR:
				buf[x] = IM_CLIP( 0, val, 255 );
				break;

			case IM_BANDFMT_USHORT:
				((unsigned short *) buf)[x] = 
					IM_CLIP( 0, val, 65535 );
				break;

			case IM_BANDFMT_UINT:
				((unsigned int *) buf)[x] = val;
				break;

			default:
				assert( 0 );
			}
		}

		if( im_writeline( y, out, buf ) )
			return( -1 );
	}

	return( 0 );
}

/* Read an ascii 1 bit file.
 */
static int
read_1bit_ascii( FILE *fp, IMAGE *out )
{
	int x, y;
	PEL *buf;

	if( im_outcheck( out ) || im_setupout( out ) ||
		!(buf = IM_ARRAY( out, IM_IMAGE_SIZEOF_LINE( out ), PEL )) )
		return( -1 );

	for( y = 0; y < out->Ysize; y++ ) {
		for( x = 0; x < out->Xsize * out->Bands; x++ ) {
			int val;

			if( (val = read_uint( fp )) < 0 )
				return( -1 );

			if( val == 1 )
				buf[x] = 0;
			else
				buf[x] = 255;
		}

		if( im_writeline( y, out, buf ) )
			return( -1 );
	}

	return( 0 );
}

/* Read a 1 bit binary file.
 */
static int
read_1bit_binary( FILE *fp, IMAGE *out )
{
	int x, y, i;
	int bits;
	PEL *buf;

	if( im_outcheck( out ) || im_setupout( out ) ||
		!(buf = IM_ARRAY( out, IM_IMAGE_SIZEOF_LINE( out ), PEL )) )
		return( -1 );

	bits = fgetc( fp );
	for( i = 0, y = 0; y < out->Ysize; y++ ) {
		for( x = 0; x < out->Xsize * out->Bands; x++, i++ ) {
			buf[x] = (bits & 128) ? 255 : 0;
			bits <<= 1;
			if( (i & 7) == 7 )
				bits = fgetc( fp );
		}

		if( im_writeline( y, out, buf ) )
			return( -1 );
	}

	return( 0 );
}

static int 
parse_ppm( FILE *fp, const char *filename, IMAGE *out )
{
	int bits;
	int ascii;

	if( read_header( fp, out, &bits, &ascii ) )
		return( -1 );

	/* What sort of read are we doing?
	 */
	if( !ascii && bits >= 8 )
		return( read_mmap( fp, filename, out ) );
	else if( !ascii && bits == 1 )
		return( read_1bit_binary( fp, out ) );
	else if( ascii && bits == 1 )
		return( read_1bit_ascii( fp, out ) );
	else 
		return( read_ascii( fp, out ) );
}

int
im_ppm2vips_header( const char *filename, IMAGE *out )
{
        FILE *fp;
	int bits;
	int ascii;

#ifdef BINARY_OPEN
	if( !(fp = fopen( filename, "rb" )) ) {
#else /*BINARY_OPEN*/
	if( !(fp = fopen( filename, "r" )) ) {
#endif /*BINARY_OPEN*/
                im_error( "im_ppm2vips_header",
			_( "unable to open \"%s\"" ), filename );
                return( -1 );
        }

	if( read_header( fp, out, &bits, &ascii ) ) {
		fclose( fp );
		return( -1 );
	}

	fclose( fp );

	return( 0 );
}

int
im_ppm2vips( const char *filename, IMAGE *out )
{
        FILE *fp;

#ifdef BINARY_OPEN
	if( !(fp = fopen( filename, "rb" )) ) {
#else /*BINARY_OPEN*/
	if( !(fp = fopen( filename, "r" )) ) {
#endif /*BINARY_OPEN*/
                im_error( "im_ppm2vips", 
			_( "unable to open \"%s\"" ), filename );
                return( -1 );
        }

	if( parse_ppm( fp, filename, out ) ) {
		fclose( fp );
		return( -1 );
	}

	fclose( fp );

	return( 0 );
}