/* Some basic util functions.
 */

/*

    Copyright (C) 1991-2003 The National Gallery

    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 */

/*

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

 */

/*
#define DEBUG
 */

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

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

#include <sys/types.h>
#include <sys/stat.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif /*HAVE_UNISTD_H*/
#include <fcntl.h>

#ifdef OS_WIN32
#include <windows.h>
#endif /*OS_WIN32*/

#include <assert.h>

#include <vips/vips.h>
#include <vips/internal.h>

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

/* Temp buffer for snprintf() layer on old systems.
 */
#define MAX_BUF (32768)

/* Test two lists for eqality.
 */
gboolean
im_slist_equal( GSList *l1, GSList *l2 )
{
	while( l1 && l2 ) {
		if( l1->data != l2->data )
			return( FALSE );

		l1 = l1->next;
		l2 = l2->next;
	}

	if( l1 || l2 )
		return( FALSE );
	
	return( TRUE );
}

/* Map over an slist. _copy() the list in case the callback changes it.
 */
void *
im_slist_map2( GSList *list, VSListMap2Fn fn, void *a, void *b )
{
	GSList *copy;
	GSList *i;
	void *result;

	copy = g_slist_copy( list );
	result = NULL;
	for( i = copy; i && !(result = fn( i->data, a, b )); i = i->next ) 
		;
	g_slist_free( copy );

	return( result );
}

void *
im_slist_map4( GSList *list, 
	VSListMap4Fn fn, void *a, void *b, void *c, void *d )
{
	GSList *copy;
	GSList *i;
	void *result;

	copy = g_slist_copy( list );
	result = NULL;
	for( i = copy; 
		i && !(result = fn( i->data, a, b, c, d )); i = i->next ) 
		;
	g_slist_free( copy );

	return( result );
}

/* Map backwards. We _reverse() rather than recurse and unwind to save stack.
 */
void *
im_slist_map2_rev( GSList *list, VSListMap2Fn fn, void *a, void *b )
{
	GSList *copy;
	GSList *i;
	void *result;

	copy = g_slist_copy( list );
	copy = g_slist_reverse( copy );
	result = NULL;
	for( i = copy; i && !(result = fn( i->data, a, b )); i = i->next ) 
		;
	g_slist_free( copy );

	return( result );
}

void *
im_map_equal( void *a, void *b )
{
	if( a == b )
		return( a );

	return( NULL );
}

void *
im_slist_fold2( GSList *list, void *start, VSListFold2Fn fn, void *a, void *b )
{
        void *c;
        GSList *this, *next;

        for( c = start, this = list; this; this = next ) {
                next = this->next;

                if( !(c = fn( this->data, c, a, b )) )
			return( NULL );
        }

        return( c );
}

static void
im_slist_free_all_cb( void * thing, void * dummy )
{
	im_free( thing );
}

/* Free a g_slist of things which need im_free()ing.
 */
void
im_slist_free_all( GSList *list )
{
	g_slist_foreach( list, im_slist_free_all_cb, NULL );
	g_slist_free( list );
}

/* Remove all occurences of an item from a list.
 */
GSList *
im_slist_filter( GSList *list, VSListMap2Fn fn, void *a, void *b )
{
	GSList *tmp;
	GSList *prev;

	prev = NULL;
	tmp = list;

	while( tmp ) {
		if( fn( tmp->data, a, b ) ) {
			GSList *next = tmp->next;

			if( prev )
				prev->next = next;
			if( list == tmp )
				list = next;

			tmp->next = NULL;
			g_slist_free( tmp );
			tmp = next;
		}
		else {
			prev = tmp;
			tmp = tmp->next;
		}
	}

	return( list );
}

typedef struct {
	void *a;
	void *b;
	VSListMap2Fn fn;
	void *result;
} Pair;

static gboolean
im_hash_table_predicate( const char *key, void *value, Pair *pair )
{
	return( (pair->result == pair->fn( value, pair->a, pair->b )) );
}

/* Like slist map, but for a hash table.
 */
void *
im_hash_table_map( GHashTable *hash, VSListMap2Fn fn, void *a, void *b )
{
	Pair pair;

	pair.a = a;
	pair.b = b;
	pair.fn = fn;
	pair.result = NULL;

	g_hash_table_find( hash, (GHRFunc) im_hash_table_predicate, &pair ); 

	return( pair.result );
}

/* Map over all a type's children.
 */
void *
vips_type_map( GType base, VipsTypeMap2 fn, void *a, void *b )
{
	GType *child;
	guint n_children;
	unsigned int i;
	void *result;

	child = g_type_children( base, &n_children );
	result = NULL;
	for( i = 0; i < n_children && !result; i++ )
		result = fn( child[i], a, b );
	g_free( child );

	return( result );
}

/* Loop over all the concrete subtypes of a base type.
 */
void *
vips_type_map_concrete_all( GType base, VipsTypeMap fn, void *a )
{
	void *result;

	result = NULL;
	if( !G_TYPE_IS_ABSTRACT( base ) )
		result = fn( base, a );
	if( !result )
		result = vips_type_map( base, 
			(VipsTypeMap2) vips_type_map_concrete_all, fn, a );

	return( result );
}

/* Loop over all the subclasses of a base type.
 */
void *
vips_class_map_concrete_all( GType type, VipsClassMap fn, void *a )
{
	void *result;

	result = NULL;
	if( !G_TYPE_IS_ABSTRACT( type ) ) {
		GTypeClass *class;

		/* Does this class exist? Try to create if not.
		 */
		if( !(class = g_type_class_peek( type )) ) 
			/* We don't unref, so the class is never finalized. 
			 * This will make the peek work next time around and 
			 * save us from constantly building and destroying 
			 * classes.
			 */
			if( !(class = g_type_class_ref( type )) ) {
				im_error( "vips_class_map_concrete_all",
					"%s", _( "unable to build class" ) );
				return( NULL );
			}

		result = fn( VIPS_OBJECT_CLASS( class ), a );
	}
	if( !result )
		result = vips_type_map( type, 
			(VipsTypeMap2) vips_class_map_concrete_all, fn, a );

	return( result );
}

static void *
test_name( VipsObjectClass *class, const char *nickname )
{
	if( strcasecmp( class->nickname, nickname ) == 0 )
		return( class );

	/* Check the class name too, why not.
	 */
	if( strcasecmp( G_OBJECT_CLASS_NAME( class ), nickname ) == 0 )
		return( class );

	return( NULL );
}

/* Find a class ... search below base, return the first match on a nickname or
 * a name.
 */
VipsObjectClass *
vips_class_find( const char *basename, const char *nickname )
{
	VipsObjectClass *class;
	GType base;

	if( !(base = g_type_from_name( basename )) ) {
		im_error( "vips_class_find", 
			_( "base class \"%s\" not found" ), basename ); 
		return( NULL );
	}

	if( !(class = vips_class_map_concrete_all( base, 
		(VipsClassMap) test_name, (void *) nickname )) ) {
		im_error( "vips_class_find", 
			_( "class \"%s\" not found" ), nickname ); 
		return( NULL );
	}

	return( class );
}

GType
vips_type_find( const char *basename, const char *nickname )
{
	VipsObjectClass *class;

	if( !(class = vips_class_find( "VipsInterpolate", nickname )) )
		return( 0 );

	/* FIXME ... we've not supposed to use G_TYPE_FROM_CLASS(), I think. 
	 * I'm not * sure what the alternative is.
	 */
	return( G_TYPE_FROM_CLASS( class ) );
}

/* Like strncpy(), but always NULL-terminate, and don't pad with NULLs.
 */
char *
im_strncpy( char *dest, const char *src, int n )
{
        int i;

        assert( n > 0 );

        for( i = 0; i < n - 1; i++ )
                if( !(dest[i] = src[i]) )
                        break;
        dest[i] = '\0';

        return( dest );
}

/* Find the rightmost occurrence of needle in haystack.
 */
char *
im_strrstr( const char *haystack, const char *needle )
{
	int haystack_len = strlen( haystack );
	int needle_len = strlen( needle );
	int i;

	for( i = haystack_len - needle_len; i >= 0; i-- )
		if( strncmp( needle, haystack + i, needle_len ) == 0 )
			return( (char *) haystack + i );
	
	return( NULL );
}

/* strdup local to a descriptor.
 */
char *
im_strdup( IMAGE *im, const char *str )
{
	int l = strlen( str );
	char *buf;

	if( !(buf = (char *) im_malloc( im, l + 1 )) )
		return( NULL );
	strcpy( buf, str );

	return( buf );
}

/* Test for string b ends string a. 
 */
gboolean
im_ispostfix( const char *a, const char *b )
{	
	int m = strlen( a );
	int n = strlen( b );

	if( n > m )
		return( FALSE );

	return( strcmp( a + m - n, b ) == 0 );
}

/* Test for string a starts string b. 
 */
gboolean
im_isprefix( const char *a, const char *b )
{
	int n = strlen( a );
	int m = strlen( b );
	int i;

	if( m < n )
		return( FALSE );
	for( i = 0; i < n; i++ )
		if( a[i] != b[i] )
			return( FALSE );
	
	return( TRUE );
}

/* Like strtok(). Give a string and a list of break characters. Then:
 * - skip initial break characters
 * - EOS? return NULL
 * - skip a series of non-break characters
 * - write a '\0' over the next break character and return a pointer to the
 *   char after that
 *
 * The idea is that this can be used in loops as the iterator. Example:
 *
 * char *p = " 1 2 3   "; // mutable 
 * char *q;
 * int i;
 * int v[...];
 *
 * for( i = 0; (q = im_break_token( p, " " )); i++, p = q )
 * 	v[i] = atoi( p );
 *
 * will set
 * 	v[0] = 1;
 * 	v[1] = 2;
 * 	v[2] = 3;
 *
 * or with just one pointer, provided your atoi() is OK with trailing chars
 * and you know there is at least one item there
 *
 * char *p = " 1 2 3   "; // mutable
 * int i;
 * int v[...];
 *
 * for( i = 0; p; p = im_break_token( p, " " ) )
 *   v[i] = atoi( p );
 */
char *
im_break_token( char *str, const char *brk )
{
        char *p;

        /* Is the string empty? If yes, return NULL immediately.
         */
        if( !str || !*str )
                return( NULL );

        /* Skip initial break characters.
         */
        p = str + strspn( str, brk );

	/* No item?
	 */
        if( !*p ) 
		return( NULL );

        /* We have a token ... search for the first break character after the 
	 * token.
         */
        p += strcspn( p, brk );

        /* Is there string left?
         */
        if( *p ) {
                /* Write in an end-of-string mark and return the start of the
                 * next token.
                 */
                *p++ = '\0';
                p += strspn( p, brk );
        }

        return( p );
}

/* Wrapper over (v)snprintf() ... missing on old systems.
 */
int
im_vsnprintf( char *str, size_t size, const char *format, va_list ap )
{
#ifdef HAVE_VSNPRINTF
	return( vsnprintf( str, size, format, ap ) );
#else /*HAVE_VSNPRINTF*/
	/* Bleurg!
	 */
	int n;
	static char buf[MAX_BUF];

	if( size > MAX_BUF )
		error_exit( "panic: buffer overflow "
			"(request to write %d bytes to buffer of %d bytes)",
			size, MAX_BUF );
	n = vsprintf( buf, format, ap );
	if( n > MAX_BUF )
		error_exit( "panic: buffer overflow "
			"(%d bytes written to buffer of %d bytes)",
			n, MAX_BUF );

	im_strncpy( str, buf, size );

	return( n );
#endif /*HAVE_VSNPRINTF*/
}

int
im_snprintf( char *str, size_t size, const char *format, ... )
{
	va_list ap;
	int n;

	va_start( ap, format );
	n = im_vsnprintf( str, size, format, ap );
	va_end( ap );

	return( n );
}

/* Split filename into name / mode components. name and mode should both be
 * FILENAME_MAX chars.
 *
 * We look for the ':' splitting the name and mode by searching for the
 * rightmost occurence of the regexp ".[A-Za-z0-9]+:". Example: consider the
 * horror that is
 *
 * 	c:\silly:dir:name\fr:ed.tif:jpeg:95,,,,c:\icc\srgb.icc
 *
 */
void
im_filename_split( const char *path, char *name, char *mode )
{
        char *p;

        im_strncpy( name, path, FILENAME_MAX );

	/* Search back towards start stopping at each ':' char.
	 */
	for( p = name + strlen( name ) - 1; p > name; p -= 1 )
		if( *p == ':' ) {
			char *q;

			for( q = p - 1; isalnum( *q ) && q > name; q -= 1 )
				;

			if( *q == '.' )
				break;
		}

	if( *p == ':' ) {
                im_strncpy( mode, p + 1, FILENAME_MAX );
                *p = '\0';
        }
        else
                strcpy( mode, "" );
}

/* Skip any leading path stuff. Horrible: if this is a filename which came
 * from win32 and we're a *nix machine, it'll have '\\' not '/' as the
 * separator :-(
 *
 * Try to fudge this ... if the file doesn't contain any of our native
 * separators, look for the opposite one as well. If there are none of those
 * either, just return the filename.
 */
const char *
im_skip_dir( const char *path )
{
	char name[FILENAME_MAX];
	char mode[FILENAME_MAX];
        const char *p;
        const char *q;

	const char native_dir_sep = G_DIR_SEPARATOR;
	const char non_native_dir_sep = native_dir_sep == '/' ? '\\' : '/';

	/* Remove any trailing save modifiers: we don't want '/' or '\' in the
	 * modifier confusing us.
	 */
	im_filename_split( path, name, mode );

	/* The '\0' char at the end of the string.
	 */
	p = name + strlen( name );

	/* Search back for the first native dir sep, or failing that, the first
	 * non-native dir sep.
	 */
	for( q = p; q > name && q[-1] != native_dir_sep; q-- )
		;
	if( q == name )
		for( q = p; q > name && q[-1] != non_native_dir_sep; q-- )
			;

        return( path + (q - name) );
}

/* Extract suffix from filename, ignoring any mode string. Suffix should be
 * FILENAME_MAX chars. Include the "." character, if any.
 */
void
im_filename_suffix( const char *path, char *suffix )
{
	char name[FILENAME_MAX];
	char mode[FILENAME_MAX];
        char *p;

	im_filename_split( path, name, mode );
        if( (p = strrchr( name, '.' )) ) 
                strcpy( suffix, p );
        else
                strcpy( suffix, "" );
}

/* Does a filename have one of a set of suffixes. Ignore case.
 */
int
im_filename_suffix_match( const char *path, const char *suffixes[] )
{
	char suffix[FILENAME_MAX];
	const char **p;

	im_filename_suffix( path, suffix );
	for( p = suffixes; *p; p++ )
		if( g_ascii_strcasecmp( suffix, *p ) == 0 )
			return( 1 );

	return( 0 );
}

/* p points to the start of a buffer ... move it on through the buffer (ready
 * for the next call), and return the current option (or NULL for option
 * missing). ',' characters inside options can be escaped with a '\'.
 */
char *
im_getnextoption( char **in )
{
        char *p = *in;
        char *q = p;

        if( !p || !*p )
                return( NULL );

	/* Find the next ',' not prefixed with a '\'.
	 */
	while( (p = strchr( p, ',' )) && p[-1] == '\\' )
		p += 1;

        if( p ) {
                /* Another option follows this one .. set up to pick that out
                 * next time.
                 */
                *p = '\0';
                *in = p + 1;
        }
        else {
                /* This is the last one.
                 */
                *in = NULL;
        }

        if( strlen( q ) > 0 )
                return( q );
        else
                return( NULL );
}

/* Get a suboption string, or NULL.
 */
char *
im_getsuboption( const char *buf )
{
        char *p, *q, *r;

        if( !(p = strchr( buf, ':' )) ) 
		/* No suboption.
		 */
		return( NULL );

	/* Step over the ':'.
	 */
	p += 1;

	/* Need to unescape any \, pairs. Shift stuff down one if we find one.
	 */
	for( q = p; *q; q++ ) 
		if( q[0] == '\\' && q[1] == ',' )
			for( r = q; *r; r++ )
				r[0] = r[1];

        return( p );
}

/* Make something local to an image descriptor ... pass in a constructor
 * and a destructor, plus three args.
 */
void *
im_local( IMAGE *im, 
	im_construct_fn cons, im_callback_fn dest, void *a, void *b, void *c )
{
	void *obj;

	if( !im ) {
		im_error( "im_local", "%s", _( "NULL image descriptor" ) );
		return( NULL );
	}

        if( !(obj = cons( a, b, c )) )
                return( NULL );
        if( im_add_close_callback( im, (im_callback_fn) dest, obj, a ) ) {
                dest( obj, a );
                return( NULL );
        }
 
        return( obj );
}

/* Make an array of things local to a descriptor ... eg. make 6 local temp
 * images.
 */
int
im_local_array( IMAGE *im, void **out, int n,
	im_construct_fn cons, im_callback_fn dest, void *a, void *b, void *c )
{
	int i;

	for( i = 0; i < n; i++ )
		if( !(out[i] = im_local( im, cons, dest, a, b, c )) )
			return( -1 );

	return( 0 );
}

/* Get file length ... 64-bitally. -1 for error.
 */
gint64
im_file_length( int fd )
{
#ifdef OS_WIN32
	struct _stati64 st;

	if( _fstati64( fd, &st ) == -1 ) {
#else /*!OS_WIN32*/
	struct stat st;

	if( fstat( fd, &st ) == -1 ) {
#endif /*OS_WIN32*/
		im_error_system( errno, "im_file_length", 
			"%s", _( "unable to get file stats" ) );
		return( -1 );
	}

	return( st.st_size );
}

/* Wrap write() up
 */
int
im__write( int fd, const void *buf, size_t count )
{
	do {
		size_t nwritten = write( fd, buf, count );

		if( nwritten == (size_t) -1 ) {
                        im_error_system( errno, "im__write", 
				"%s", _( "write failed" ) );
                        return( -1 ); 
		}

		buf = (void *) ((char *) buf + nwritten);
		count -= nwritten;
	} while( count > 0 );

	return( 0 );
}

/* Load up a file as a string.
 */
char *
im__file_read( FILE *fp, const char *filename, unsigned int *length_out )
{
        long len;
	size_t read;
        char *str;

        /* Find length.
         */
        fseek( fp, 0L, 2 );
        len = ftell( fp );
	if( len > 20 * 1024 * 1024 ) {
		/* Seems crazy!
		 */
                im_error( "im__file_read", _( "\"%s\" too long" ), filename );
                return( NULL );
        }

	if( len == -1 ) {
		int size;

		/* Can't get length: read in chunks and realloc() to end of
		 * file.
		 */
		str = NULL;
		len = 0;
		size = 0;
		do {
			size += 1024;
			if( !(str = realloc( str, size )) ) {
				im_error( "im__file_read", 
					"%s", _( "out of memory" ) );
				return( NULL );
			}

			/* -1 to allow space for an extra NULL we add later.
			 */
			read = fread( str + len, sizeof( char ), 
				(size - len - 1) / sizeof( char ),
				fp );
			len += read;
		} while( !feof( fp ) );

#ifdef DEBUG
		printf( "read %ld bytes from unseekable stream\n", len );
#endif /*DEBUG*/
	}
	else {
		/* Allocate memory and fill.    
		 */
		if( !(str = im_malloc( NULL, len + 1 )) )
			return( NULL );
		rewind( fp );
		read = fread( str, sizeof( char ), (size_t) len, fp );
		if( read != (size_t) len ) {
			im_free( str );
			im_error( "im__file_read", 
				_( "error reading from file \"%s\"" ), 
				filename );
			return( NULL );
		}
	}

	str[len] = '\0';

	if( length_out )
		*length_out = len;

        return( str );
}

/* Does a filename contain a directory separator?
 */
static gboolean 
filename_hasdir( const char *filename )
{
	char *dirname;
	gboolean hasdir;

	dirname = g_path_get_dirname( filename );
	hasdir = (strcmp( dirname, "." ) != 0);
	g_free( dirname );

	return( hasdir );
}

/* Open a file. We take an optional fallback dir as well and will try opening
 * there if opening directly fails.
 *
 * This is used for things like finding ICC profiles. We try to open the file
 * directly first, and if that fails and the filename does not contain a
 * directory separator, we try looking in the fallback dir.
 */
FILE *
im__file_open_read( const char *filename, const char *fallback_dir )
{
	FILE *fp;

#ifdef BINARY_OPEN
        fp = fopen( filename, "rb" );
#else /*!BINARY_OPEN*/
        fp = fopen( filename, "r" );
#endif /*BINARY_OPEN*/
	if( fp )
		return( fp );

	if( fallback_dir && !filename_hasdir( filename ) ) {
		char *path;

		path = g_build_filename( fallback_dir, filename, NULL );
#ifdef BINARY_OPEN
	        fp = fopen( path, "rb" );
#else /*!BINARY_OPEN*/
        	fp = fopen( path, "r" );
#endif /*BINARY_OPEN*/
		g_free( path );

		if( fp )
			return( fp );
	}

	im_error( "im__file_open_read", 
		_( "unable to open file \"%s\" for reading" ), filename );

	return( NULL );
}

FILE *
im__file_open_write( const char *filename )
{
	FILE *fp;

#ifdef BINARY_OPEN
        if( !(fp = fopen( filename, "wb" )) ) {
#else /*BINARY_OPEN*/
        if( !(fp = fopen( filename, "w" )) ) {
#endif /*BINARY_OPEN*/
		im_error( "im__file_open_write", 
			_( "unable to open file \"%s\" for writing" ), 
			filename );
		return( NULL );
	}

	return( fp );
}

/* Load from a filename as a string. Used for things like reading in ICC
 * profiles.
 */
char *
im__file_read_name( const char *filename, const char *fallback_dir, 
	unsigned int *length_out )
{
	FILE *fp;
	char *buffer;

        if( !(fp = im__file_open_read( filename, fallback_dir )) ) 
		return( NULL );
	if( !(buffer = im__file_read( fp, filename, length_out )) ) {
		fclose( fp );
		return( NULL );
	}
	fclose( fp );

	return( buffer );
}

/* Alloc/free a GValue.
 */
static GValue *
im__gvalue_new( GType type )
{
	GValue *value;

	value = g_new0( GValue, 1 );
	g_value_init( value, type );

	return( value );
}

static GValue *
im__gvalue_copy( GValue *value )
{
	GValue *value_copy;

	value_copy = im__gvalue_new( G_VALUE_TYPE( value ) );
	g_value_copy( value, value_copy );

	return( value_copy );
}

static void
im__gvalue_free( GValue *value )
{
	g_value_unset( value );
	g_free( value );
}

GValue *
im__gvalue_ref_string_new( const char *text )
{
	GValue *value;

	value = im__gvalue_new( IM_TYPE_REF_STRING );
	im_ref_string_set( value, text );

	return( value );
}

/* Free a GSList of GValue.
 */
void
im__gslist_gvalue_free( GSList *list )
{
	g_slist_foreach( list, (GFunc) im__gvalue_free, NULL );
	g_slist_free( list );
}

/* Copy a GSList of GValue.
 */
GSList *
im__gslist_gvalue_copy( const GSList *list )
{
	GSList *copy;
	const GSList *p;

	copy = NULL;

	for( p = list; p; p = p->next ) 
		copy = g_slist_prepend( copy, 
			im__gvalue_copy( (GValue *) p->data ) );

	copy = g_slist_reverse( copy );

	return( copy );
}

/* Merge two GSList of GValue ... append to a all elements in b which are not 
 * in a. Return the new value of a. Works for any vips refcounted type 
 * (string, blob, etc.).
 */
GSList *
im__gslist_gvalue_merge( GSList *a, const GSList *b )
{
	const GSList *i, *j;
	GSList *tail;

	tail = NULL;

	for( i = b; i; i = i->next ) {
		GValue *value = (GValue *) i->data;

		assert( G_VALUE_TYPE( value ) == IM_TYPE_REF_STRING );

		for( j = a; j; j = j->next ) {
			GValue *value2 = (GValue *) j->data;

			assert( G_VALUE_TYPE( value2 ) == IM_TYPE_REF_STRING );

			/* Just do a pointer compare ... good enough 99.9% of 
			 * the time.
			 */
			if( im_ref_string_get( value ) ==
				im_ref_string_get( value2 ) )
				break;
		}

		if( !j )
			tail = g_slist_prepend( tail, 
				im__gvalue_copy( value ) );
	}

	a = g_slist_concat( a, g_slist_reverse( tail ) );

	return( a );
}

/* Make a char* from GSList of GValue. Each GValue should be a ref_string.
 * free the result. Empty list -> "", not NULL. Join strings with '\n'.
 */
char *
im__gslist_gvalue_get( const GSList *list )
{
	const GSList *p;
	size_t length;
	char *all;
	char *q;

	/* Need to estimate length first.
	 */
	length = 0;
	for( p = list; p; p = p->next ) {
		GValue *value = (GValue *) p->data;

		assert( G_VALUE_TYPE( value ) == IM_TYPE_REF_STRING );

		/* +1 for the newline we will add for each item.
		 */
		length += im_ref_string_get_length( value ) + 1;
	}

	if( length == 0 )
		return( NULL );

	/* More than 10MB of history? Madness!
	 */
	assert( length < 10 * 1024 * 1024 );

	/* +1 for '\0'.
	 */
	if( !(all = im_malloc( NULL, length + 1 )) )
		return( NULL );

	q = all;
	for( p = list; p; p = p->next ) {
		GValue *value = (GValue *) p->data;

		strcpy( q, im_ref_string_get( value ) );
		q += im_ref_string_get_length( value );
		strcpy( q, "\n" );
		q += 1;
	}

	g_assert( (size_t) (q - all) == length );

	return( all );
}

/* Need our own seek(), since lseek() on win32 can't do long files.
 */
int
im__seek( int fd, gint64 pos )
{
#ifdef OS_WIN32
{
	HANDLE hFile = (HANDLE) _get_osfhandle( fd );
	LARGE_INTEGER p;

	p.QuadPart = pos;
	if( !SetFilePointerEx( hFile, p, NULL, FILE_BEGIN ) ) {
                im_error_system( GetLastError(), "im__seek", 
			"%s", _( "unable to seek" ) );
		return( -1 );
	}
}
#else /*!OS_WIN32*/
	if( lseek( fd, pos, SEEK_SET ) == (off_t) -1 ) {
		im_error( "im__seek", "%s", _( "unable to seek" ) );
		return( -1 );
	}
#endif /*OS_WIN32*/

	return( 0 );
}

/* Need our own ftruncate(), since ftruncate() on win32 can't do long files.

	DANGER ... this moves the file pointer to the end of file on win32,
	but not on *nix; don't make any assumptions about the file pointer
	position after calling this

 */
int
im__ftruncate( int fd, gint64 pos )
{
#ifdef OS_WIN32
{
	HANDLE hFile = (HANDLE) _get_osfhandle( fd );
	LARGE_INTEGER p;

	p.QuadPart = pos;
	if( im__seek( fd, pos ) )
		return( -1 );
	if( !SetEndOfFile( hFile ) ) {
                im_error_system( GetLastError(), "im__ftruncate", 
			"%s", _( "unable to truncate" ) );
		return( -1 );
	}
}
#else /*!OS_WIN32*/
	if( ftruncate( fd, pos ) ) {
		im_error_system( errno, "im__ftruncate", 
			"%s", _( "unable to truncate" ) );
		return( -1 );
	}
#endif /*OS_WIN32*/

	return( 0 );
}

/* Like fwrite(), but returns non-zero on error and sets error message.
 */
int
im__file_write( void *data, size_t size, size_t nmemb, FILE *stream )
{
	size_t n;

	if( !data ) 
		return( 0 );

	if( (n = fwrite( data, size, nmemb, stream )) != nmemb ) {
		im_error( "im__file_write", 
			_( "writing error (%zd out of %zd blocks written) "
			"... disc full?" ), n, nmemb );
		return( -1 );
	}

	return( 0 );
}

/* Read a few bytes from the start of a file. For sniffing file types.
 */
int
im__get_bytes( const char *filename, unsigned char buf[], int len )
{
	int fd;

	/* File may not even exist (for tmp images for example!)
	 * so no hasty messages. And the file might be truncated, so no error
	 * on read either.
	 */
#ifdef BINARY_OPEN
	if( (fd = open( filename, O_RDONLY | O_BINARY )) == -1 )
#else /*BINARY_OPEN*/
	if( (fd = open( filename, O_RDONLY )) == -1 )
#endif /*BINARY_OPEN*/
		return( 0 );
	if( read( fd, buf, len ) != len ) {
		close( fd );
		return( 0 );
	}
	close( fd );

	return( 1 );
}

/* Break a command-line argument into tokens separated by whitespace. Strings
 * can't be adjacent, so "hello world" (without quotes) is a single string.
 * Strings are written (with \" escaped) into string, which must be size 
 * characters large.
 */
const char *
vips__token_get( const char *p, VipsToken *token, char *string, int size )
{
	const char *q;
	int ch;
	int n;

	/* Parse this token with p.
	 */
	if( !p )
		return( NULL );

	/* Skip initial whitespace.
	 */
        p += strspn( p, " \t\n\r" );
	if( !p[0] )
		return( NULL );

	switch( (ch = p[0]) ) {
	case '{':
	case '[':
	case '(':
		*token = VIPS_TOKEN_LEFT;
		p += 1;
		break;

	case ')':
	case ']':
	case '}':
		*token = VIPS_TOKEN_RIGHT;
		p += 1;
		break;

	case '=':
		*token = VIPS_TOKEN_EQUALS;
		p += 1;
		break;

	case ',':
		*token = VIPS_TOKEN_COMMA;
		p += 1;
		break;

	case '"':
	case '\'':
		/* Parse a quoted string. Copy up to ", interpret any \",
		 * error if no closing ".
		 */
		*token = VIPS_TOKEN_STRING;

		do {
			/* Number of characters until the next quote
			 * character or end of string.
			 */
			if( (q = strchr( p + 1, ch )) )
				n = q - p + 1;
			else
				n = strlen( p + 1 );

			g_assert( size > n + 1 );
			memcpy( string, p + 1, n );
			string[n] = '\0';

			/* p[n + 1] might not be " if there's no closing ".
			 */
			if( p[n + 1] == ch && p[n] == '\\' ) 
				/* An escaped ": overwrite the '\' with '"'
				 */
				string[n - 1] = ch;

			string += n;
			size -= n;
			p += n + 1;
		} while( p[0] && p[-1] == '\\' );

		p += 1;

		break;

	default:
		/* It's an unquoted string: read up to the next non-string
		 * character. We don't allow two strings next to each other,
		 * so the next break must be bracket, equals, comma.
		 */
		*token = VIPS_TOKEN_STRING;
		n = strcspn( p, "[{()}]=," );
		g_assert( size > n + 1 );
		memcpy( string, p, n );
		string[n] = '\0';
		p += n;

		/* We remove leading whitespace, so we trim trailing
		 * whitespace from unquoted strings too.
		 */
		while( isspace( string[n - 1] ) ) {
			string[n - 1] = '\0';
			n -= 1;
		}

		break;
	}

	return( p );
}

/* We expect a token.
 */
const char *
vips__token_must( const char *p, VipsToken *token, 
	char *string, int size )
{
	if( !(p = vips__token_get( p, token, string, size )) ) {
		im_error( "get_token", "%s", _( "unexpected end of string" ) );
		return( NULL );
	}

	return( p );
}

/* Turn a VipsToken into a string.
 */
static const char *
vips__token_string( VipsToken token )
{
	switch( token ) {
 	case VIPS_TOKEN_LEFT:	return( _( "opening brace" ) );
	case VIPS_TOKEN_RIGHT:	return( _( "closing brace" ) );
	case VIPS_TOKEN_STRING:	return( _( "string" ) );
	case VIPS_TOKEN_EQUALS:	return( "=" );
	case VIPS_TOKEN_COMMA:	return( "," );

	default:
		g_assert( 0 );
	}
}

/* We expect a certain token.
 */
const char *
vips__token_need( const char *p, VipsToken need_token, 
	char *string, int size )
{
	VipsToken token;

	if( !(p = vips__token_must( p, &token, string, size )) ) 
		return( NULL );
	if( token != need_token ) {
		im_error( "get_token", _( "expected %s, saw %s" ), 
			vips__token_string( need_token ), 
			vips__token_string( token ) );
		return( NULL );
	}

	return( p );
}

/* Test for file exists.
 */
int
im_existsf( const char *name, ... )
{
        va_list ap;
        char buf1[PATH_MAX];

        va_start( ap, name );
        (void) im_vsnprintf( buf1, PATH_MAX - 1, name, ap );
        va_end( ap );

        /* Try that.
         */
        if( !access( buf1, R_OK ) )
                return( 1 );

        return( 0 );
}

#ifdef OS_WIN32
#define popen(b,m) _popen(b,m)
#define pclose(f) _pclose(f)
#endif /*OS_WIN32*/

/* Do popen(), with printf-style args.
 */
FILE *
im_popenf( const char *fmt, const char *mode, ... )
{
        va_list args;
	char buf[4096];
	FILE *fp;

        va_start( args, mode );
        (void) im_vsnprintf( buf, 4096, fmt, args );
        va_end( args );

#ifdef DEBUG
	printf( "im_popenf: running: %s\n", buf );
#endif /*DEBUG*/

        if( !(fp = popen( buf, mode )) ) {
		im_error( "popenf", "%s", strerror( errno ) );
		return( NULL );
	}

	return( fp );
}

/* True if an int is a power of two ... 1, 2, 4, 8, 16, 32, etc. Do with just
 * integer arithmetic for portability. A previous Nicos version using doubles
 * and log/log failed on x86 with rounding problems. Return 0 for not
 * power of two, otherwise return the position of the set bit (numbering with
 * bit 1 as the lsb).
 */
int
im_ispoweroftwo( int p )
{
	int i, n;

	/* Count set bits. Could use a LUT, I guess.
	 */
	for( i = 0, n = 0; p; i++, p >>= 1 )
		if( p & 1 )
			n++;

	/* Should be just one set bit.
	 */
	if( n == 1 )
		/* Return position of bit.
		 */
		return( i );
	else
		return( 0 );
}

int
im_isvips( const char *filename )
{
	unsigned char buf[4];

	if( im__get_bytes( filename, buf, 4 ) ) {
		if( buf[0] == 0x08 && buf[1] == 0xf2 &&
			buf[2] == 0xa6 && buf[3] == 0xb6 )
			/* SPARC-order VIPS image.
			 */
			return( 1 );
		else if( buf[3] == 0x08 && buf[2] == 0xf2 &&
			buf[1] == 0xa6 && buf[0] == 0xb6 )
			/* INTEL-order VIPS image.
			 */
			return( 1 );
	}

	return( 0 );
}

/* Test this processor for endianness. True for SPARC order.
 */
int
im_amiMSBfirst( void )
{
        int test;
        unsigned char *p = (unsigned char *) &test;

        test = 0;
        p[0] = 255;

        if( test == 255 )
                return( 0 );
        else
                return( 1 );
}

/* Make a temporary file name. The format parameter is something like "%s.jpg" 
 * and will be expanded to something like "/tmp/vips-12-34587.jpg".
 *
 * You need to free the result. A real file will also be created, though we
 * delete it for you.
 */
char *
im__temp_name( const char *format )
{
	static int serial = 1;

	const char *tmpd;
	char file[FILENAME_MAX];
	char file2[FILENAME_MAX];

	char *name;
	int fd;

	if( !(tmpd = g_getenv( "TMPDIR" )) )
		tmpd = "/tmp";

	im_snprintf( file, FILENAME_MAX, "vips-%d-XXXXXX", serial++ );
	im_snprintf( file2, FILENAME_MAX, format, file );
	name = g_build_filename( tmpd, file2, NULL );

	if( (fd = g_mkstemp( name )) == -1 ) {
		im_error( "tempfile", 
			_( "unable to make temporary file %s" ), name );
		g_free( name );
		return( NULL );
	}
	close( fd );
	g_unlink( name );

	return( name );
}

/* Make a disc IMAGE which will be automatically unlinked on im_close().
 * Format is something like "%s.v" for a vips file.
 */
IMAGE *
im__open_temp( const char *format )
{
	char *name;
	IMAGE *disc;

	if( !(name = im__temp_name( format )) )
		return( NULL );

	if( !(disc = im_open( name, "w" )) ) {
		g_free( name );
		return( NULL );
	}
	g_free( name );

	/* Needs to be postclose so we can rewind after write without
	 * deleting the file.
	 */
	if( im_add_postclose_callback( disc, 
		(im_callback_fn) unlink, disc->filename, NULL ) ) {
		im_close( disc );
		g_unlink( name );
	}

	return( disc );
}

/* This gets indexed by (eg.) IM_IMAGE_SIZEOF_ELEMENT() to calculate object
 * size.
 */
const size_t im__sizeof_bandfmt[] = {
	IM_BBITS_BYTE >> 3,
	IM_BBITS_BYTE >> 3,
	IM_BBITS_SHORT >> 3,
	IM_BBITS_SHORT >> 3,
	IM_BBITS_INT >> 3,
	IM_BBITS_INT >> 3,
	IM_BBITS_FLOAT >> 3,
	IM_BBITS_COMPLEX >> 3,
	IM_BBITS_DOUBLE >> 3,
	IM_BBITS_DPCOMPLEX >> 3
};

/* Return number of pel bits for band format, or -1 on error.
 */
int 
im_bits_of_fmt( VipsBandFmt fmt )
{
	return( fmt < 0 || fmt > IM_BANDFMT_DPCOMPLEX ?
		im_error( "im_bits_of_fmt", 
			_( "unsupported band format: %d" ), fmt ),
		-1 :
		im__sizeof_bandfmt[fmt] << 3 );
}

