/**
	\file	util.c
	\brief	Contains all-purpose tables and functions.

	This file contains all general tables (or const variables) and functions definitions
	that are need as general tools.
*/

#include "util.h"
#include "iptc.h"

#include <stdlib.h>
#include <stdio.h>
#ifndef OS_WIN32
#ifdef __GNUC__ 
#if (__GNUC__ < 4)
#include <unistd.h>
#endif
#else
#include <unistd.h>
#endif
#endif
#include <string.h>
#include <errno.h>
#include <sys/stat.h>

extern struct prefs_struct prefs;	/**< preferences struct defined in main.c */


/** \var	op_labels
	\brief	Description table for enum OP values.

	This table implements all data for all enum OP values and their relevant
	label structures.
*/
struct op_label op_labels[]=
{
	{	OP_CHECK,			"check",		"just check embedded data structures"	},
	{	OP_DUMP,			"dump",			"show embedded data (headers only)"	},
	{	OP_DUMP_FULL,		"dump-full",	"show all embedded data (including values)"	},
	{	OP_DUMP_VALUE,		"dump-value",	"show requested value (headers only)"	},
	{	OP_FILTER,			"filter",		"filter embedded data (see -i, -e and --edit)"	},
	/** \TODO
	   - purge (iptc, exif, .., *),
	   - merge (picture file+iptc/exif/photoshop file)
	   - filter/extract (exif)
	*/
	/* reserved */
	{	OP_EOT, NULL, NULL	}
};

/** \var	log_levels
	\brief	Description table for enum log_level values.

	This table implements all data for all enum log_level values and their relevant
	description structures.
*/
struct log_level_descr log_levels[]=
{
	{	LOG_LEVEL_QUIET,	"quiet, no output at all"	},
	{	LOG_LEVEL_ERRORS,	"error messages only (default)"	},
	{	LOG_LEVEL_WARNINGS,	"warning and error messages"	},
	{	LOG_LEVEL_INFO,		"informative, warning and error messages"	},
	{	LOG_LEVEL_DEBUG,	"all messages including debug ones"	},
	/* reserved */
	{	LOG_LEVEL_EOT,		NULL	}
};

/** \var	exit_code_labels
	\brief	Description table for enum EXIT_CODE values.

	This table implements all data for all enum EXIT_CODE values and their relevant
	label structures.
*/
struct exit_code_label exit_code_labels[]=
{
	{	EXIT_CODE_NORMAL,				"normal exit"	},
	{	EXIT_CODE_USAGE_ERROR,			"usage error"	},
	{	EXIT_CODE_ASYNCHRONOUS_SIGNAL,	"asynchronous signal termination"	},
	{	EXIT_CODE_NORMAL_WITH_WARNINGS,	"normal exit, with warning(s)"	},
	{	EXIT_CODE_NORMAL_WITH_ERRORS,	"normal exit, with error(s)"	},
	{	EXIT_CODE_FATAL_ERROR,			"fatal error encountered"	},
	/* reserved */
	{	EXIT_EOT, NULL	}
};


/** \var	rewrite_cache
	\brief	Write cache buffer.

	Pointer to write cache buffer (to be allocated and freed).
*/
/** \var	rewrite_cache_offset
	\brief	Write cache current offset.

	The offset is increasing as bytes are stored in the cache. It starts at 0.
*/
/** \var	rewrite_cache_size
	\brief	Write cache size.

	Size of the write cache. Usually equal to the input file size. Does not
	increase dynamically.
*/
/** \var	output_file
	\brief	Output file handler.

	Pointer to the output file handler.
*/
/** \var	output_filename
	\brief	Output filename.
*/
/** \var	input_length
	\brief	Input file length.
*/
unsigned char* rewrite_cache;
unsigned long int rewrite_cache_offset;
unsigned long int rewrite_cache_size;
FILE* output_file;
char* output_filename;
size_t input_length;


/** \brief		Get a file size.
	\param[in]	file: pointer to an file handler (it must have been fopen()'ed).
	\return		The file length or 0 if an error occurred.
*/
size_t fsize(FILE* file)
{
	size_t tmp;

	fseek(file, 0, SEEK_END);
	tmp=(size_t)ftell(file);
	fseek(file, 0, SEEK_SET);
	return(tmp);
}

/** \brief		Check if a file exists (whatever its type is).
	\param[in]	filename: filename to check if it exists.
	\return		True if filename exists, otherwise returns false.
*/
bool fexist(const char* filename)
{
	struct stat st;

	if (stat(filename, &st)==0)
		return(true);
	return(false);
}

/** \brief		Rename source_filename to target_filename.
	\param[in]	source_filename: original filename to rename.
	\param[in]	target_filename: new filename.
	\return		0 if the file could be renamed, otherwise return an int (err).

	Rename a file. Tries first to remove the destination file if it already
	exists. This function doesn't interpret the return code, it's up to the
	caller to check if it's 0 (OK) or non-0 (NOK). But if the removal of any
	already existing target file fails, it will show an error.
*/
int frename(const char* source_filename, const char* target_filename)
{
	int err;

	if (fexist(target_filename))
	{
#ifdef OS_WIN32
		err=remove(target_filename);
		if (err!=0)
			error("frename: couldn't remove '%s' (error #%d)",
					target_filename, err);
#else
		err=unlink(target_filename);
		if (err!=0)
			error("frename: couldn't remove '%s' (error #%d)",
					target_filename, err);
#endif
	}

	err=rename(source_filename, target_filename);
	return(err);
}

/** \brief		Check if a file is a regular file.
	\param[in]	filename: file to check if it's a regular file.
	\return		True if filename is a regular file, otherwise returns false.
*/
bool fisfile(const char* filename)
{
	struct stat st;

	if (stat(filename, &st)<0)
		return(false);
	if (S_ISREG(st.st_mode))
		return(true);
	return(false);
}

/** \brief		Check if a file is to a link.
	\param[in]	filename: file to check if it's a link.
	\return		True if filename is a link, otherwise returns false.
*/
bool fislink(const char* filename)
{
	struct stat st;

	if (stat(filename, &st)<0)
		return(false);
	if (S_ISLNK(st.st_mode))
		return(true);
	return(false);
}

/** \brief 		Check if a file is a directory.
	\param[in]	filename: file to check if it's a directory.
	\return		True if filename is a directory, otherwise returns false.
*/
bool fisdir(const char* filename)
{
	struct stat st;

	if (stat(filename, &st)<0)
		return(false);
	if (S_ISDIR(st.st_mode))
		return(true);
	return(false);
}

/**	\brief		Get a file's extension.
	\param[in]	path: file path/name to look for an extension into.
	\return		A pointer to the 1st char of the file extension (if none, pointer to \\0).

	Return a newly allocated string with the file extension if any
 	(or "" if none). it's up to the caller to free it using returned pointer.
*/
char* fextension(const char* path)
{
	char* extension;
	const char* filename;
	int dot;
	int sep;

	/* keep the filename only */
	sep=strrpos(path, DIR_SEPARATOR_CHAR);
	if (sep==0)
		filename=path;
	else
		filename=path+sep;

	dot=strrpos(filename, '.');
	if (dot==0)
		extension=(char*)strdup("");
	else
		extension=(char*)strdup(filename+dot);
	return(extension);
}

/**	\brief		Get a file's base name (no path, no extension).
	\param[in]	path: file path/name to look for a base name into.
	\return		A pointer to the 1st char of the file base name (if none, pointer to \\0).

	Return a newly allocated string with the file base name (the filename
	itself, with no path nor extension) if any (or "" if none). it's up to
	the caller to free it using returned pointer.
*/
char* fbasename(const char* path)
{
	char* basename;
	const char* filename;
	int dot;
	int sep;

	/* keep the filename only */
	sep=strrpos(path, DIR_SEPARATOR_CHAR);
	if (sep==0)
		filename=path;
	else
		filename=path+sep;

	dot=strrpos(filename, '.');
	if (dot==0)
		basename=(char*)strdup(filename);
	else
		basename=(char*)strndup(filename, dot-1);
	return(basename);
}

/**	\brief		Get a file's base name (no path).
	\param[in]	path: file path/name to look for a filename into.
	\return		A pointer to the 1st char of the file name (if none, pointer to \\0).

	Return a newly allocated string with the file name (with no path) if any
	(or "" if none). it's up to the caller to free it using returned pointer.
*/
char* ffilename(const char* path)
{
	char* filename;
	int sep;

	sep=strrpos(path, DIR_SEPARATOR_CHAR);
	if (sep==0)
		filename=(char*)strdup(path);
	else
		filename=(char*)strdup(path+sep);
	return(filename);
}

/**	\brief		Get a file's base name (no filename, only the path remains).
	\param[in]	path: file path/name to look for a dir name into.
	\return		A pointer to the 1st char of the dir name (if none, pointer to \\0).

	Return a newly allocated string with the dirname (everything but the
	filename) if any (or "" if none). it's up to the caller to free the
	returned pointer.
*/
char* fdirname(const char* path)
{
	char* dirname;
	int sep;

	sep=strrpos(path, DIR_SEPARATOR_CHAR);
	if (sep==0)
		dirname=(char*)strdup("");
	else
		dirname=(char*)strndup(path, sep-1);
	return(dirname);
}

/**	\brief		Get a cleaned-up pathname string (ensure that the path string is canonical).
	\param[in]	path: pathname to clean
	\return		A pointer to the 1st char of the dir name (if none, pointer to \\0).

	Return a newly allocated string with the clean path (no duplicate or
	trailing /). It's up to the caller to free the returned pointer.
*/
char* fcleanpath(const char* path)
{
	const char* ptr;
	size_t len;

	/* skip trailing slashes */
	len=strlen(path);
	ptr=path+len-1;
	while ((ptr>=path) && (*ptr==DIR_SEPARATOR_CHAR))
	{
		ptr--;
		len--;
	}
	return((char*)strndup(path, len));
}

/**	\brief		Create a backup (.bak) of a file. Any already existing backup file will be removed.
	\param[in]	filename File name to backup.
	\return		Nothing.

	Create a backup file by renaming the filename to a .bak one. Any already
	existing .bak file gets removed. Errors (non-fatal for file I/O) are
	managed here.
*/
void backup_file(const char* filename)
{
	char* backup_filename;
	int err;

	/* build the backup filename */
	err=0;
	backup_filename=malloc((strlen(filename)+1+4)*sizeof(char));
	if (!backup_filename)
		fatal_error("backup_file: couldn't allocate space for backup filename");
	strcpy(backup_filename, filename);
	strcat(backup_filename, ".bak");
	debug("backing up '%s' to '%s'", filename, backup_filename);

	/* remove any already existing backup file */
	if (fisfile(backup_filename) || fislink(backup_filename))
	{
		err=remove(backup_filename);
		if (err!=0)
			error("backup_file: couldn't remove previous backup file '%s' (error #%d)",
					backup_filename, err);
	}

	/* rename the original file to back it up */
	if (fisfile(filename) || fislink(backup_filename))
	{
		err=frename(filename, backup_filename);
		if (err!=0)
			error("backup_file: couldn't create backup '%s' from '%s' (error #%d)",
				backup_filename, filename, err);
	}
	if (err==0)
		info("'%s' file created", backup_filename);

	/* free resources */
	free(backup_filename);
	backup_filename=NULL;
}

/**	\brief
	\param		
	\return

	TODO
*/
unsigned char getbyte(unsigned char* ptr, unsigned long int offset)
{
	return(ptr[offset]);
}

/**	\brief
	\param		
	\return

	TODO
*/
unsigned short int getword(unsigned char* ptr, unsigned long int offset)
{
	return((ptr[offset]<<8)|ptr[offset+1]);
}

/**	\brief
	\param		
	\return

	TODO
*/
unsigned short int getrawword(unsigned char* ptr, unsigned long int offset)
{
	return((ptr[offset+1]<<8)|ptr[offset]);
}

/**	\brief
	\param		
	\return

	TODO
*/
unsigned long int getlong(unsigned char* ptr, unsigned long int offset)
{
	return((((ptr[offset]<<8)|ptr[offset+1])<<16)|((ptr[offset+2]<<8)|ptr[offset+3]));
}

/**	\brief
	\param		
	\return

	TODO
*/
unsigned long int getrawlong(unsigned char* ptr, unsigned long int offset)
{
	return((((ptr[offset+3]<<8)|ptr[offset+2])<<16)|((ptr[offset+1]<<8)|ptr[offset]));
}

/**	\brief
	\param		
	\return

	TODO
*/
unsigned char* getstring(unsigned char* ptr, unsigned long int offset, unsigned char* buf,
		const unsigned long int len)
{
	register unsigned int i;

	for (i=0; i<len; i++)
		buf[i]=ptr[offset+i];
	buf[i]='\0';
	return(buf);
}

/**	\brief
	\param		
	\return

	TODO
*/
unsigned char* getraw(unsigned char* ptr, unsigned long int offset, unsigned char* buf,
		const unsigned long int len)
{
	memcpy(buf, ptr+offset, len*sizeof(unsigned char));
	return(buf);
}

/**	\brief
	\param		
	\return

	TODO
*/
void setbyte(unsigned char* ptr, unsigned long int offset, unsigned char byte)
{
	ptr[offset]=byte;
}

/**	\brief
	\param		
	\return

	TODO
*/
void setword(unsigned char* ptr, unsigned long int offset, unsigned short int word)
{
	ptr[offset]=(word&0xff00)>>8;
	ptr[offset+1]=word&0x00ff;
}

/**	\brief
	\param		
	\return

	TODO
*/
void setlong(unsigned char* ptr, unsigned long int offset, unsigned long int dword)
{
	ptr[offset]=(dword&0xff000000UL)>>24;
	ptr[offset+1]=(dword&0x00ff0000UL)>>16;
	ptr[offset+2]=(dword&0x0000ff00UL)>>8;
	ptr[offset+3]=dword&0x000000ffUL;
}

/**	\brief
	\param		
	\return

	TODO
*/
void message_fatal_error(char* format, va_list args)
{
	if (prefs.log_level>=LOG_LEVEL_ERRORS)
	{
		context_print_text();
		prefs.context_errors++;
		prefs.errors++;
		fprintf(stderr, "error: ");
		vfprintf(stderr, format, args);
		fprintf(stderr, "\nfatal error encountered, bailing out\n");
		context_print_info();
	}
	_exit(EXIT_CODE_FATAL_ERROR);
}

/**	\brief
	\param		
	\return

	TODO
*/
void message_error(char* format, va_list args)
{
	if (prefs.log_level>=LOG_LEVEL_ERRORS)
	{
		context_print_text();
		prefs.context_errors++;
		prefs.errors++;
		fprintf(stderr, "error: ");
		vfprintf(stderr, format, args);
		fprintf(stderr, "\n");
	}
}

/**	\brief
	\param		
	\return

	TODO
*/
void message_warning(char* format, va_list args)
{
	if (prefs.log_level>=LOG_LEVEL_WARNINGS)
	{
		context_print_text();
		prefs.context_warnings++;
		prefs.warnings++;
		fprintf(stderr, "warning: ");
		vfprintf(stderr, format, args);
		fprintf(stderr, "\n");
	}
}

/**	\brief
	\param		
	\return

	TODO
*/
void message_info(char* format, va_list args)
{
	if (prefs.log_level>=LOG_LEVEL_INFO)
	{
		fprintf(stdout, "info: ");
		vfprintf(stdout, format, args);
		fprintf(stdout, "\n");
		fflush(stdout);
	}
}

/**	\brief
	\param		
	\return

	TODO
*/
void message_debug(char* format, va_list args)
{
	if (prefs.log_level>=LOG_LEVEL_DEBUG)
	{
		fprintf(stdout, "debug: ");
		vfprintf(stdout, format, args);
		fprintf(stdout, "\n");
		fflush(stdout);
	}
}

/**	\brief
	\param		
	\return

	TODO
*/
void fatal_error(char* format, ...)
{
	va_list args;

	va_start(args, format);
	message_fatal_error(format, args);
	va_end(args);
}

/**	\brief
	\param		
	\return

	TODO
*/
void error(char* format, ...)
{
	va_list args;

	va_start(args, format);
	message_error(format, args);
	va_end(args);
}

/**	\brief
	\param		
	\return

	TODO
*/
void warning(char* format, ...)
{
	va_list args;

	va_start(args, format);
	message_warning(format, args);
	va_end(args);
}

/**	\brief
	\param		
	\return

	TODO
*/
void info(char* format, ...)
{
	va_list args;

	va_start(args, format);
	message_info(format, args);
	va_end(args);
}

/**	\brief
	\param		
	\return

	TODO
*/
void debug(char* format, ...)
{
	va_list args;

	va_start(args, format);
	message_debug(format, args);
	va_end(args);
}

/**	\brief
	\param		
	\return

	TODO
*/
void message(enum SEVERITY severity, char* format, ...)
{
	va_list args;

	va_start(args, format);
	switch (severity)
	{
		case SEV_FATAL_ERROR:
			message_fatal_error(format, args);
			break;

		case SEV_ERROR:
			message_error(format, args);
			break;

		case SEV_WARNING:
			message_warning(format, args);
			break;

		case SEV_INFO:
			message_info(format, args);
			break;

		case SEV_DEBUG:
			message_debug(format, args);
			break;
	}
	va_end(args);
}

/**	\brief
	\param		
	\return

	TODO
*/
void context_set_text(char* text)
{
	prefs.context_text=text;
}

/**	\brief
	\param		
	\return

	TODO
*/
void context_print_text(void)
{
	/* show context information when first warning/error is encounter */
	if (prefs.context_text)
		if ((prefs.context_warnings+prefs.context_errors)==0)
			fprintf(stderr, "in file '%s':\n", prefs.context_text);
}

/**	\brief
	\param
	\return

	TODO
*/
void context_print_info(void)
{
	if ((prefs.context_warnings+prefs.context_errors)>0)
		fprintf(stderr, "%lu warning(s), %lu error(s)\n",
				prefs.context_warnings, prefs.context_errors);
	if (prefs.fix && (prefs.context_fixes>0))
		fprintf(stderr, "%lu fix(es)\n",
				prefs.fixes);
}

/**	\brief
	\param		
	\return

	TODO
*/
void context_reset(void)
{
	prefs.context_text=NULL;
	prefs.context_warnings=0;
	prefs.context_errors=0;
	prefs.context_fixes=0;
}

/**	\brief
	\param		
	\return

	TODO
*/
struct op_label* op_match_label(char* label)
{
	register int i;

	i=0;
	while (op_labels[i].op!=OP_EOT)
	{
		if (strcmp(op_labels[i].label, label)==0)
			return(&op_labels[i]);
		i++;
	}
	return(NULL);
}

/**	\brief
	\param		
	\return

	TODO
*/
void init_rewrite(void)
{
	char* filename;
	size_t len;
	int fd;
	FILE* file;

	if (!prefs.rewrite)
		fatal_error("init_rewrite: rewrite flag not set");
	if (output_file)
		fatal_error("init_rewrite: bad output file handler");
	if (!output_filename)
		fatal_error("init_rewrite: bad output filename");

	/* get a temporary filename */
	len=strlen(output_filename);
	filename=malloc((len+1+6+1)*sizeof(char));
	if (!filename)
		fatal_error("init_rewrite: couldn't allocate space for init_rewrite");
	strcpy(filename, output_filename);
	strcat(filename, ".XXXXXX");
	fd=mkstemp(filename);
	if (fd<0)
		fatal_error("init_rewrite: couldn't create a temporary file from '%s'", output_filename);

	/* open file for writing */
	file=fdopen(fd, "w+b");
	if (!file)
	{
		close(fd);
		fatal_error("init_rewrite: couldn't open temporary file '%s' for writing", output_filename);
	}

	/* write back values to caller */
	output_file=file;
	free(output_filename); /* free previous allocation (from caller) */
	output_filename=(char*)strdup(filename);
	free(filename);

	/* cached rewriting */
	if (prefs.rewrite_cached)
	{
		rewrite_cache=malloc(input_length*sizeof(char));
		if (!rewrite_cache)
			fatal_error("init_rewrite: couldn't allocate space for rewrite cache (%lu byte(s))",
					rewrite_cache_size);
		rewrite_cache_offset=0;
		rewrite_cache_size=input_length;
	}
}

/**	\brief
	\param		
	\return

	TODO
*/
size_t dump_rewrite(unsigned char* buffer, unsigned long int length)
/* returns the number of bytes written */
{
	size_t len;

	if (!prefs.rewrite)
		fatal_error("init_rewrite: rewrite flag not set");

	debug("dump_rewrite: writing %lu byte(s)", length);
	len=0;
	if (length>0)
	{
		if (prefs.rewrite_cached)
		{
			if (rewrite_cache_offset+length>rewrite_cache_size)
			{
				/* reallocate more space for cached writing */
				rewrite_cache_size+=length+1024;
				rewrite_cache=realloc(rewrite_cache, rewrite_cache_size);
				if (!rewrite_cache)
					fatal_error("dump_rewrite: couldn't rellocate more space for rewrite_cache (%lu byte(s))",
							rewrite_cache_size);
			}
			/* write bytes to cache */
			memcpy(rewrite_cache+rewrite_cache_offset, buffer, length);
			rewrite_cache_offset+=length;
			len=length;
		}
		else
		{
			/* dump bytes to file */
			len=fwrite(buffer, 1, length, output_file);
			if (len!=length)
				error("dump_rewrite: error %d while attempting to write %lu byte(s) (%lu written)",
						errno, length, len);
		}
	}
	return(len);
}

/**	\brief
	\param		
	\return

	TODO
*/
void post_rewrite(unsigned char* buffer, struct parser_result* result)
{
	unsigned long int len;

	if (!prefs.rewrite)
		fatal_error("post_rewrite: rewrite flag not set");
	if (!result)
		fatal_error("post_rewrite: bad pointer to struct result");

	/* write remaining bytes */
	if (input_length>result->parsed_bytes)
	{
		len=input_length-result->parsed_bytes;
		/*
		if (result->parsed_bytes!=result->written_bytes)
			len-=(result->parsed_bytes-result->written_bytes);
		*/
	}
	else
		len=0;
	if (prefs.rewrite_cached)
	{
		if (len>0)
		{
			debug("post_rewrite: writing remaining %lu byte(s) to cache", len);
			memcpy(rewrite_cache+rewrite_cache_offset, buffer+result->parsed_bytes, len);
			rewrite_cache_offset+=len;
		}
		/* flush cache to disk */
		len=fwrite(rewrite_cache, 1, rewrite_cache_offset, output_file);
		if (len!=rewrite_cache_offset)
			error("post_rewrite: error %d while attempting to flush %lu cached bytes (%lu written)",
					errno, rewrite_cache_offset, len);
		if (rewrite_cache_offset>input_length)
			info("rewritten file is %lu byte(s) bigger than the original",
					rewrite_cache_offset-input_length);
		else
			if (rewrite_cache_offset<input_length)
				info("rewritten file is %lu byte(s) smaller than the original",
						input_length-rewrite_cache_offset);
			else
				info("rewritten file and original file have the same size");
	}
	else
		if (len>0)
		{
			debug("post_rewrite: writing remaining %lu byte(s)", len);
			dump_rewrite(buffer+result->parsed_bytes, len);
		}

	/* deallocate resource */
	free(rewrite_cache);
	rewrite_cache=NULL;
}

/**	\brief
	\param		
	\return

	TODO
*/
bool get_unsigned_value(const char* param, unsigned long int* var)
{
	unsigned int value;
	char* chk;

	value=(unsigned int) strtoul(param, &chk, 10);
	if (!*chk)
	{
		*var=value;
		return(true);
	}
	return(false);
}

/**	\brief
	\param		
	\return

	TODO
*/
bool get_signed_value(const char* param, long int* var)
{
	int value;
	char* chk;

	value=(int)strtol(param, &chk, 10);
	if (!*chk)
	{
		*var=value;
		return(true);
	}
	return(false);
}

/**	\brief
	\param		
	\return

	TODO
*/
bool get_hexa_value(const char* param, unsigned long int* var)
{
	unsigned int value;
	char* chk;

	value=strtoul(param, &chk, 16);
	if (!*chk)
	{
		*var=value;
		return(true);
	}
	return(false);
}

/**	\brief
	\param		
	\return

	TODO
*/
size_t strpos(const char* string, const char c)
{
	char* ptr;

	ptr=(char*)string;
	while (*ptr)
		if (*ptr==c)
			return((size_t)(ptr-string+1));
		else
			ptr++;
	return(0);
}

/**	\brief
	\param		
	\return

	TODO
*/
size_t strrpos(const char* string, const char c)
{
	char* ptr;

	ptr=(char*)string+strlen(string)-1;
	while (ptr>=string)
		if (*ptr==c)
			return((size_t)(ptr-string+1));
		else
			ptr--;
	return(0);
}

/**	\brief
	\param		
	\return

	TODO
*/
void filenames_list_load(char*** filenames_list_ptr, int* filenames_count_ptr, FILE* list_file)
{
	char** filenames_list;
	char* buffer;
	ssize_t len;
	unsigned long int filenames_count;
	size_t size;
	int copy_limit;

	/* allocate a minimum amount of copied-lines */
	copy_limit=256;
	filenames_list=(char**)malloc(copy_limit*sizeof(char*));
	if (!filenames_list)
		fatal_error("filenames_list_load: couldn't allocate more space for filenames list (%lu byte(s))",
					copy_limit*sizeof(char*));

	filenames_count=0;
	buffer=NULL;
	size=32*1024;	/* should match any MAX_PATH */
	while (((len=getline(&buffer, &size, list_file))!=-1) && buffer)
	{
		char* ptr;
		char* p;

		ptr=buffer;
		/* skip leading non printable chars if any */
		while (*ptr && (*ptr<32))
			ptr++;

		/* skip trailing non printable chars if any */
		p=buffer+len-1;
		while ((p>=ptr) && (*p<32))
			*p--='\0';
		debug("filenames_list_load: line %lu, %lu byte(s)", filenames_count, len);

		if (*buffer)
		{
			/* dynamically realloc copied-lines buffer */
			if ((int)filenames_count>=copy_limit)
			{
				char** ptr;

				copy_limit+=256;
				ptr=(char**)realloc(filenames_list, copy_limit*sizeof(char*));
				if (!ptr)
				{
					free(filenames_list);
					fatal_error("filenames_list_load: couldn't rellocate more space for filenames list (%lu byte(s))",
							copy_limit*sizeof(char*));
				}
				filenames_list=ptr;
			}
			/* duplicate the filename */
			filenames_list[filenames_count]=strndup(buffer, strlen(buffer)+1);
			if (!filenames_list[filenames_count])
				fatal_error("filenames_list_load: couldn't allocate space for filenames list contents (%lu byte(s))",
						(strlen(buffer)+1)*sizeof(char));
			filenames_count++;
		}
	}
	if (buffer)
		free(buffer);
	*filenames_list_ptr=filenames_list;
	*filenames_count_ptr=filenames_count;
}

/**	\brief
	\param		
	\return

	TODO
*/
bool filter_parse_line(char* buffer, size_t len, unsigned long int line)
{
	char *ptr;
	char* p;
	int errors;

	errors=0;

	ptr=buffer;
	/* skip leading non printable chars and whitespaces if any */
	while (*ptr && (*ptr<=32))
		ptr++;

	/* skip trailing non printable chars and whitespaces if any */
	p=buffer+len-1;
	while ((p>=ptr) && (*p<=32))
		*p--='\0';

	/* ignore empty lines as well as comments */
	if (!*ptr || (*ptr=='#'))
		debug("filter_parse_file: ignore blank line or comment at line %lu", line);
	else
	{
		/* IPTC filter masks decoding */
		if (strncmp(ptr, "IPTC.", 5)==0)
		{
			/* possible IPTC filter masks:
				IPTC.*					all
				IPTC.0xhhhh				record+dataset (4-digit hexadecimal)
				IPTC.0xhhhh-0xhhhh		record+dataset range (4-digit hexadecimals)
				IPTC.nnn:nnn			record+dataset (1-3 digits per decimal value, each value [0-255])
				IPTC.nnn:*				all datasets from record nnn (1-3 digits per decimal value, each value [0-255])
				IPTC.nnn:nnn-nnn		record and dataset range (1-3 digits per decimal value, each value [0-255])
			   for instance:
				IPTC.*
				IPTC.0x0219
				IPTC.0x0200-0x07ff
				IPTC.2:*
				IPTC.2:25
				IPTC.2:0-255
			*/
			debug("filter_parse_line: found IPTC filter item");
			ptr+=5;
			if (strcmp(ptr, "*")==0)
				iptc_filter_add_range(0x0000, 0xffff);
			else
			if (strncmp(ptr, "0x", 2)==0)
			{
				/* hexadecimal */
				unsigned long int code;
				unsigned long int code2;

				p=strstr(ptr, "-0x");
				if (p)
				{
					/* range value */
					debug("filter_parse_line: hexadecimal range at line %lu", line);
					*p='\0';
					/* skip the range separator */
					p++;
					if ((strlen(ptr)==6) && get_hexa_value(ptr, &code)
						&& (strlen(p)==6) && get_hexa_value(p, &code2))
						iptc_filter_add_range(code, code2);
					else
					{
						errors++;
						error("filter_parse_line: bad hexadecimal range (0xhhhh-0xhhhh) at line %lu",
								line);
					}
				}
				else
				{
					/* unary value */
					debug("filter_parse_line: hexadecimal unary value at line %lu", line);
					if ((strlen(ptr)==6) && get_hexa_value(ptr, &code))
						iptc_filter_add_value(code);
					else
					{
						errors++;
						error("filter_parse_line: bad hexadecimal unary value (0xhhhh) at line %lu",
								line);
					}
				}
			}
			else
			{
				/* decimal duplet */
				unsigned long int record;
				unsigned long int dataset;
				unsigned long int dataset2;
				int pos;

				pos=strpos(ptr, ':');
				if (pos)
				{
					debug("filter_parse_line: decimal value at line %lu", line);
					ptr[pos-1]='\0';
					p=ptr+pos;

					/* record value */
					if (get_unsigned_value(ptr, &record) && (record<=0xff))
					{
						if (strcmp(p, "*")==0)
							iptc_filter_add_range((unsigned short int)(record<<8),
									(unsigned short int)(record<<8)|0xff);
						else
						{
							/* range value */
							ptr=p;
							pos=strpos(ptr, '-');
							if (pos)
							{
								ptr[pos-1]='\0';
								p=ptr+pos;

								if (get_unsigned_value(ptr, &dataset) && (dataset<=0xff)
									&& get_unsigned_value(p, &dataset2) && (dataset2<=0xff))
									iptc_filter_add_range((unsigned short int)(record<<8)|dataset,
											(unsigned short int)(record<<8)|dataset2);
								else
								{
									errors++;
									error("filter_parse_line: bad range value in decimal duplet (nnn-nnn) at line %lu",
											line);
								}
							}
							else
							{
								/* unary value */
								if (get_unsigned_value(p, &dataset) && (dataset<0xff))
									iptc_filter_add_value((unsigned short int)((record<<8)|dataset));
								else
								{
									errors++;
									error("filter_parse_line: bad dataset value in decimal duplet (nnn) at line %lu",
											line);
								}
							}
						}
					}
					else
					{
						errors++;
						error("filter_parse_line: bad record value in decimal duplet (nnn) at line %lu",
								line);
					}
				}
				else
				{
					errors++;
					error("filter_parse_line: bad decimal duplet value (nnn:nnn) at line %lu: %s",
							line, ptr);
				}
			}
		}
/*		else
		if (strncmp(buffer, "Exif.", 5))
		{
		}
		else
		if (strncmp(buffer, "Adobe.", 6))
		{
		}
*/		else
		{
			errors++;
			error("filter_parse_line: invalid filter file contents at line %lu (%lu byte(s))",
					line, len);
			debug("filter_parse_line: line is: %s", buffer);
		}
	}
	return((errors==0));
}

/**	\brief
	\param		
	\return

	TODO
*/
void filter_load_line(char* buffer)
{
	ssize_t len;

	if (!buffer)
		error("filter_load_line: bad pointer to buffer");

	if (!*buffer)
		error("filter_load_line: empty buffer");

	iptc_filter_table_init();
	len=strlen(buffer);
	if (!filter_parse_line(buffer, len, 0))
		fatal_error("filter_load_line: bad filter line");
}

/**	\brief
	\param		
	\return

	TODO
*/
void filter_load_file(FILE* list_file)
{
	ssize_t len;
	unsigned long int line;
	unsigned long int errors;
	char* buffer;
	size_t size;

	if (!list_file)
		error("filter_load_file: bad input file handler");

	iptc_filter_table_init();
	line=0;
	errors=0;
	buffer=NULL;
	size=100;
	while (((len=getline(&buffer, &size, list_file))!=-1) && buffer)
	{
		buffer[len]='\0';
		debug("filter_load_file: line %lu, %lu byte(s)", line, len);

		if (!filter_parse_line(buffer, len, line))
			errors++;
		line++;
	}
	if (buffer)
		free(buffer);
	debug("filter_load_file: %lu line(s), %lu error(s)", line, errors);
	if (errors)
		fatal_error("filter_load_file: bad filter file");
}

/**	\brief
	\param		
	\return

	TODO
*/
bool edit_parse_line(const char* buffer, size_t len, unsigned long int line)
/* strdup/strlen/etc. functions can't be used there, as it's possible to get '\0' char in the
   value part of the edit expression (if it contains widechars). all length calculation
   accross the whole line of the value part must be based upon 'len'. */
{
	const char* const_ptr;
	char* buffer_copy;
	char *ptr;
	char* p;
	size_t buffer_length;
	int errors;

	errors=0;

	/* skip trailing cr/lf chars */
	buffer_length=len;
	const_ptr=buffer+buffer_length-1;
	while ((const_ptr>=buffer) && ((*const_ptr=='\n') || (*const_ptr=='\r')))
	{
		buffer_length--;
		const_ptr++;
	}
	buffer_length++; /* count the trailing '\0' */

	/* work on a copy of the buffer (we'll need to write '\0' chars to work on sub-expressions) */

	buffer_copy=malloc(buffer_length);
	if (!buffer_copy)
		fatal_error("edit_parse_line: couldn't allocate buffer_copy for value");
	memcpy(buffer_copy, buffer, buffer_length);
	buffer_copy[buffer_length-1]='\0';
	ptr=buffer_copy;

	/* skip leading non printable chars and whitespaces if any */
	while (*ptr && (*ptr<=32))
		ptr++;

	/* ignore empty lines as well as comments */
	if (!*ptr || (*ptr=='#'))
		debug("edit_parse_file: ignore blank line or comment at line %lu", line);
	else
	{
		/* IPTC edit (add/replace) expression decoding */
		if (strncmp(ptr, "IPTC.", 5)==0)
		{
			/* possible IPTC edit masks:
				IPTC.0xhhhh=<type>:<value>		record+dataset (4-digit hexadecimal)
				IPTC.nnn:nnn=<type>:<value>		record+dataset (1-3 digits per decimal value, each value [0-255])
			   where <type> can be hex or text
			   where <value> is either a text: ASCII 7-bit string, trailing spaces matter
			   or a set of hexadecimal digit pairs (matching regex ([0-9a-fA-F]{2})+)
			   for instance:
				IPTC.0x0219=text:keyword1
				IPTC.0x0219=hex:
				IPTC.2:25=text:keyword1
				IPTC.2:25
			*/
			int pos;

			debug("edit_parse_line: found IPTC edit item");
			ptr+=5;

			pos=strpos(ptr, '=');
			if (pos)
			{
				/* decode type and value */
				ptr[pos-1]='\0';
				p=ptr+pos;
				pos=strpos(p, ':');
				if (pos)
				{
					enum FILTER_EDIT_VALUE_TYPE type=FEV_NONE;
					char* typename;
					char* value;
					char* stored_value=NULL;
					size_t stored_value_length=0;
					bool stored_value_in_use=false;

					/* get type */
					p[pos-1]='\0';
					typename=p;
					if (strcmp(typename, "hex")==0)
						type=FEV_HEX;
					else
						if (strcmp(typename, "text")==0)
							type=FEV_TEXT;
						else
						{
							errors++;
							error("edit_parse_line: bad value type '%s' at line %lu, expected 'hex' or 'text'",
									p, line);
						}
					if (errors)
						goto edit_parse_line_abort;

					/* get value */
					value=p+pos;
					stored_value_length=buffer_length-(value-buffer_copy)-1; /* don't count the trailing '\0' */

					/* check the value according to its type and store/encore it to binary */
					if (stored_value_length==0)
					{
						errors++;
						error("edit_parse_line: empty %s value at line %lu",
								typename, line);
					}
					if (type==FEV_HEX)
					{
						char* hex;
						char one_byte[3];
						unsigned long int byte_val;
						char* bin;

						if (stored_value_length%2)
						{
							errors++;
							error("edit_parse_line: invalid odd length (%lu byte(s)) for hex value '%s' at line %lu",
									stored_value_length, value, line);
						}

						hex=value;
						while (*hex &&
								(	(('a'<=*hex) && (*hex<='f'))
								||	(('A'<=*hex) && (*hex<='F'))
								||	(('0'<=*hex) && (*hex<='9'))))
							hex++;
						if (*hex!='\0')
						{
							errors++;
							error("edit_parse_line: invalid hex value '%s' at line %lu",
									value, line);
						}
						stored_value_length/=2;
						stored_value=malloc(stored_value_length);
						if (!stored_value)
							fatal_error("edit_parse_line: couldn't allocate storage buffer for hex value");

						/* encode hex string value to binary */
						one_byte[2]=0;
						hex=value;
						bin=stored_value;
						while (*hex)
						{
							*one_byte=*hex;
							one_byte[1]=hex[1];
							if (get_hexa_value(one_byte, &byte_val))
								*bin=(unsigned char)byte_val;
							hex+=2;
							bin++;
						}
					}
					else 
						if (type==FEV_TEXT)
						{
							stored_value=malloc(stored_value_length);
							if (!stored_value)
								fatal_error("edit_parse_line: couldn't allocate storage buffer for text value");
							memcpy(stored_value, value, stored_value_length);
						}
					if (errors)
						goto edit_parse_line_abort;

					/* decode the record+dataset part */
					if (strncmp(ptr, "0x", 2)==0)
					{
						/* hexadecimal */
						unsigned long int code;

						/* unary value */
						debug("edit_parse_line: hexadecimal unary value at line %lu", line);
						if ((strlen(ptr)==6) && get_hexa_value(ptr, &code))
							iptc_edit_add_edit(code, stored_value_length,
									(unsigned char*)stored_value,
									&stored_value_in_use);
						else
						{
							errors++;
							error("edit_parse_line: bad hexadecimal unary value (0xhhhh) at line %lu: %s",
									line, ptr);
						}
					}
					else
					{
						/* decimal duplet */
						unsigned long int record;
						unsigned long int dataset;
						int pos;

						pos=strpos(ptr, ':');
						if (pos)
						{
							debug("edit_parse_line: decimal value at line %lu", line);
							ptr[pos-1]='\0';
							p=ptr+pos;

							/* record value */
							if (get_unsigned_value(ptr, &record) && (record<=0xff))
							{
								/* unary value */
								if (get_unsigned_value(p, &dataset) && (dataset<0xff))
									iptc_edit_add_edit((unsigned short int)((record<<8)|dataset),
											stored_value_length, (unsigned char*)stored_value,
											&stored_value_in_use);
								else
								{
									errors++;
									error("edit_parse_line: bad dataset value in decimal duplet (nnn) at line %lu: %s",
											line, p);
								}
							}
							else
							{
								errors++;
								error("edit_parse_line: bad record value in decimal duplet (nnn) at line %lu: %s",
										line, ptr);
							}
						}
						else
						{
							errors++;
							error("edit_parse_line: bad decimal duplet value (nnn:nnn) at line %lu: %s",
									line, ptr);
						}
					}
edit_parse_line_abort:
					if (!stored_value_in_use)
					{
						free(stored_value);
						stored_value=NULL;
					}
				}
				else
				{
					errors++;
					error("edit_parse_line: bad value sub-expression in edit expression at line %lu: %s",
							line, ptr);
				}
			}
			else
			{
				errors++;
				error("edit_parse_line: invalid edit expression at line %lu: %s",
						line, ptr);
			}
		}
/*		else
		if (strncmp(buffer, "Exif.", 5))
		{
		}
		else
		if (strncmp(buffer, "Adobe.", 6))
		{
		}
*/		else
		{
			errors++;
			error("edit_parse_line: invalid edit file contents at line %lu (%lu byte(s))",
					line, buffer_length);
			debug("edit_parse_line: line is: %s", buffer);
		}
	}
	free(buffer_copy);
	return((errors==0));
}

/**	\brief
	\param		
	\return

	TODO
*/
void edit_load_line(const char* buffer)
{
	ssize_t len;

	if (!buffer)
		error("edit_load_line: bad pointer to buffer");

	if (!*buffer)
		error("edit_load_line: empty buffer");

	iptc_edit_list_init();
	/* FIXME: strlen should not be used there (buffer might contain widechars -> possibly '\0' chars) */
	len=strlen(buffer);
	if (!edit_parse_line(buffer, len, 0))
		fatal_error("edit_load_line: bad edit line");
}

/**	\brief
	\param		
	\return

	TODO
*/
void edit_load_file(FILE* list_file)
{
	ssize_t len;
	unsigned long int line;
	unsigned long int errors;
	char* buffer;
	size_t size;

	if (!list_file)
		error("edit_load_file: bad input file handler");

	iptc_edit_list_init();
	line=0;
	errors=0;
	buffer=NULL;
	size=100;
	while (((len=getline(&buffer, &size, list_file))!=-1) && buffer)
	{
		buffer[len]='\0';
		debug("edit_load_file: line %lu, %lu byte(s)", line, len);

		if (!edit_parse_line(buffer, len, line))
			errors++;
		line++;
	}
	if (buffer)
		free(buffer);
	debug("edit_load_file: %lu line(s), %lu error(s)", line, errors);
	if (errors)
		fatal_error("edit_load_file: bad edit file");
}

/**	\brief
	\param		
	\return

	TODO
*/
void dump_hexa(unsigned char* value, unsigned long int length)
{	
	unsigned long int i;

	printf("[%lu byte(s)]:", length);
	if (prefs.wrap_dump>0)
		printf("\n");
	else
		printf(" ");
	for (i=1; i<=length; i++)
	{
		printf("%02x ", value[i-1]);
		if (prefs.wrap_dump>0)
			/* append a newline char each 32 hexabyte printed */
			if ((i%prefs.wrap_dump)==0)
				printf("\n");
	}
	if (prefs.wrap_dump>0)
	{
		if (length!=prefs.wrap_dump) /* newline if not just appended */
			printf("\n");
	}
	else
		printf("\n");
}
