#include <stdlib.h>
#include <stdio.h>
#if defined(OS_UNIX) || defined(OS_LINUX)
#include <unistd.h>
#endif
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>

#ifndef PRODUCT_ID
#error PRODUCT_ID must be defined
#endif
#ifndef PRODUCT_VERSION
#error PRODUCT_VERSION must be defined
#endif

#include "jfif.h"
#include "iptc.h"
#include "exif.h"
#include "util.h"
#include "adobe.h"


extern struct op_label op_labels[];
extern struct log_level_descr log_levels[];
extern struct exit_code_label exit_code_labels[];
extern unsigned char* rewrite_cache;
extern unsigned long int rewrite_cache_offset;
extern unsigned long int rewrite_cache_size;
extern FILE* output_file;
extern char* output_filename;
extern size_t input_length;
extern unsigned char* iptc_extract_buffer;
extern unsigned long int iptc_extract_buffer_size;


/* global vars */
struct prefs_struct prefs;
char* program_name;

char* default_iptc_extension="iptc";
/*char* default_iptc_extension="exv";*/


void version(void)
{
	printf(PRODUCT_ID " " PRODUCT_VERSION "\n");
	exit(EXIT_CODE_NORMAL);
}

void usage(enum EXIT_CODE ret)
{
	char spaces[19];
	int i;

	/* command-line usage */
	printf("usage:\n");
	printf("    %s [<option> ..] <op> [<expr>] <file> [<file> ..]\n", program_name);
	printf("    %s [<option> ..] <op> [<expr>] @<file>\n", program_name);
	printf("usage of form <op> <expr> .. only concerns op mode dump-value, see below.\n");

	printf("\n");
	printf("iuse @<file> to read a list of files from that file. the file must contain exactly\n");
	printf("one filename to process per line (don't escape anything, filenames are taken as-is).\n");

	/* various general or specific options */
	printf("\n");
	printf("general options:\n");
	printf("    -v --version          show version number then exit\n");
	printf("    -h --help             show this help then exit\n");
	printf("       --list             list all supported embedded data structures\n");
	printf("    -l --log-level <num>  define verbosity (see possible values below)\n");
	printf("    -c --check-compliance perform full checks to test the strict validity and\n");
	printf("                          compliance of structures to official formats\n");

	printf("\n");
	printf("filtering and extracting options:\n");
	printf("    -b --backup           create backup files of (re)written files if necessary\n");
	printf("    -d --target-dir <dir> target directory for all written files\n");
	printf("    -f --fix              fix metadata inconsistencies when possible\n");

	printf("\n");
	printf("extracting options:\n");
	printf("    -x --extract <type>   extract and save metadata to a standalone file (see\n");
	printf("                          below for a list of metadata that can be extracted)\n");
	printf("    -t --ext <extension>  define what filename extension to use when writing\n");
	printf("                          metadata that is extracted (default: .%s)\n",
			default_iptc_extension);
/*	printf("                          metadata that is extracted (default: .%s or .%s)\n",
			default_iptc_extension, default_exif_extension);*/
	printf("    -a --append-ext       append extension to original filename when saving\n");
	printf("                          the extracted metadata (default: replace original file\n");
	printf("                          extension)\n");

	printf("\n");
	printf("filtering options:\n");
	printf("    -i --include <expr>   a filter expression describing the datasets to keep\n");
	printf("    -i --include @<file>  or a file containing a list of datasets to keep,\n");
	printf("                          all other datasets will be filtered out.\n");
	printf("                          if not used, all datasets will be kept\n");
	printf("    -e --exclude <expr>   a filter expression describing the datasets to filter out\n");
	printf("    -e --exclude @<file>  or a file containing a list of datasets to filter out,\n");
	printf("                          all other datasets will be kept. if not used all\n");
	printf("                          datasets will be kept\n");
	printf("       --edit <expr>      a filter edit expression describing the datasets to add\n");
	printf("       --edit @<file>     or a file containing a list of datasets to add.\n");
	printf("                          datasets won't be inserted if that breaks compliance\n");
	printf("                          to the standards\n");
	printf("       --test             don't override original file(s), create *.rewrite file(s)\n");
	printf("                          instead\n");
/*	printf("    -o --overwrite        allow overwriting already existing field values in embedded\n");
	printf("                          data (with --edit only). if -o is not used, attempting to\n");
	printf("                          replace an existing value will fail with a warning\n"); */
	/* TODO: add a switch to force applying record size restrictions (pad or crop) */
	printf("-i and -e can't be mixed together. --edit can be used in conjonction to -i or -e,\n");
	printf("in that case, the edits will be processed after the include or exclude filter.\n");

	printf("\n");
	printf("dump options:\n");
	printf("    -w --wrap <num>       max column for wrapping (num must be in range\n");
	printf("                          [%d-%d]). default is to dump unwrapped\n",
			MIN_WRAP_DUMP, MAX_WRAP_DUMP);

	/* supported operations */
	printf("\n");
	printf("possible ops:\n");
	i=0;
	while (op_labels[i].op!=OP_EOT)
	{
		int j;

		for (j=0; j<(int)(18-strlen(op_labels[i].label)); j++)
			spaces[j]=' ';
		spaces[j]='\0';
		printf("    %s%s%s\n", op_labels[i].label, spaces, op_labels[i].description);
		i++;
	}

	/* dump-value expressions */
	printf("\n");
	printf("dump-value expressions:\n");
	printf("    IPTC.<record>\n");
	printf("same as filter include/exclude expression, see below:\n");

	/* filter expressions */
	printf("\n");
	printf("filter include/exclude expressions:\n");
	printf("    IPTC.<record>\n");
	printf("where <record> can be either:\n");
	printf("    all:               *\n");
	printf("    hexadecimal unary: 0xhhhh\n");
	printf("    hexadecimal range: 0xhhhh-0xhhhh\n");
	printf("    decimal unary:     nnn:nnn\n");
	printf("    decimal ranges:    nnn:nnn-nnn\n");
	printf("                       nnn:*\n");
	printf("examples:\n");
	printf("    IPTC.0x0219\n");
	printf("    IPTC.0x0300-0x0364\n");
	printf("    IPTC.3:0-100\n");
	
	/* filter edit expressions */
	printf("\n");
	printf("filter edit expressions:\n");
	printf("    IPTC.<record>=<type>:<value>\n");
	printf("where <record> can be either:\n");
	printf("    hexadecimal unary: 0xhhhh\n");
	printf("    decimal unary:     nnn:nnn\n");
	printf("where <type> can be either:\n");
	printf("    hex\n");
	printf("    text\n");
	printf("and <value> is:\n");
	printf("    hex:  [a-zA-Z0-9] pairs\n");
	printf("    text: any char (even widechars) on the line is taken as text, until\n");
	printf("          a newline is found\n");
	printf("examples:\n");
	printf("    IPTC.0x0200=hex:0002\n");
	printf("    IPTC.0x0219=text:this is a keyword\n");

	/* metadata extraction */
	printf("\n");
	printf("supported metadata for extraction:\n");
	printf("    iptc   IPTC datasets\n");
/*	printf("    exif   Exif datasets\n");*/

	/* verbosity (log) levels */
	printf("\n");
	printf("possible log levels:\n");
	i=0;
	while (log_levels[i].level!=LOG_LEVEL_EOT)
	{
		printf("   %2d   %s\n", i, log_levels[i].description);
		i++;
	}

	/* supported file formats */
	printf("\n");
	printf("supported input files:\n");
	printf("    JPEG files (commonly .jpeg, .jpg, .jpe, .jfif, .jif)\n");
	printf("    Adobe Photoshop files (commonly .psd, .pdd, .ffo)\n");
	printf("    FotoStation files (commonly .fdp, .ipt)\n");
	printf("    standalone IPTC metadata (commonly .iptc)\n");
/*	printf("    standalone Exif metadata (commonly .exv)\n");*/

	/* exit codes */
	printf("\n");
	printf("exit codes:\n");
	i=0;
	while (exit_code_labels[i].code!=EXIT_EOT)
	{
		printf("    %d %s\n", exit_code_labels[i].code, exit_code_labels[i].label);
		i++;
	}

	exit(ret);
}

void signal_handler(int sig)
{
	fprintf(stderr, "signal %d received\n", sig);
	fflush(stderr);
	_exit(EXIT_CODE_ASYNCHRONOUS_SIGNAL);
}

int main(int argc, char** argv)
{
	char** filenames_list;
	unsigned char tmp[7]; /* max length of file signature sequence + 1 */
	struct parser_result result;
	FILE* input_file;
	unsigned char* buffer;
	char* ptr;
	char* input_filename;
	char* include_expression;
	char* exclude_expression;
	char* edit_expression;
	char* extract_extension;
	char* target_dirname;
	char* iptc_filename;
	char* op;
	char* arg;
	char* dump_value;
	size_t ret;
	enum EXIT_CODE exit_code;
	int filenames_count;
	int i;

	/* signal re-hooking */
#ifdef SIGHUP
	signal(SIGHUP, signal_handler);	/* some unix window manager's logout */
#endif
#ifdef SIGQUIT
	signal(SIGQUIT, signal_handler);
#endif
#ifdef SIGUSR1
	signal(SIGUSR1, signal_handler);
#endif
#ifdef SIGINT
	signal(SIGINT, signal_handler);
#endif
#ifdef SIGTERM
	signal(SIGTERM, signal_handler);
#endif

	/* variables init */
	prefs.log_level=LOG_LEVEL_WARNINGS;
	prefs.technical=false;
	prefs.warnings=0;
	prefs.errors=0;
	prefs.fixes=0;
	prefs.wrap_dump=DEFAULT_WRAP_DUMP;
	prefs.rewrite=false;
	prefs.rewrite_create_backup=false;
	prefs.filter_mode=FILTER_NONE;
	prefs.test_rewrite=false;
#ifdef ALWAYS_CACHE_REWRITE
	prefs.rewrite_cached=true;
#else
	prefs.rewrite_cached=false;
#endif
	prefs.extract_iptc=false;
	prefs.extract_extension_append=false;
	prefs.fix=false;
/*	prefs.edit_overwrite=false; */
	context_reset();
	input_filename=NULL;
	output_filename=NULL;
	iptc_filename=NULL;
	include_expression=NULL;
	exclude_expression=NULL;
	edit_expression=NULL;
	extract_extension=NULL;
	target_dirname=NULL;
	input_file=NULL;
	output_file=NULL;
	rewrite_cache=NULL;
	rewrite_cache_offset=0;
	rewrite_cache_size=0;
	iptc_extract_buffer=NULL;
	iptc_extract_buffer_size=0;
	buffer=NULL;
	filenames_list=NULL;
	filenames_count=0;
	dump_value=NULL;

#ifdef OS_WIN32
	program_name=*argv;
#else
	/* strip pathname from argv[0] */
	ptr=*argv;
	ptr+=strlen(ptr)-1;
	while ((ptr>*argv))
		if (*ptr==DIR_SEPARATOR_CHAR)
		{
			/* make ptr to point to the first char of the filename itself */
			ptr++;
			break;
		}
		else
			--ptr;
	program_name=ptr;
#endif

	/* parse command line options */
	argc--;
	for (i=1; i<=argc; i++)
	{
		arg=argv[i];
		if ((strcmp(arg, "--help")==0) || (strcmp(arg, "-h")==0))
			usage(0);
		else
		if ((strcmp(arg, "--version")==0) || (strcmp(arg, "-v")==0))
			version();
		else
		if (strcmp(arg, "--list")==0)
		{
			iptc_dump_list();
			exit(EXIT_CODE_NORMAL);
		}
		else
		if ((strcmp(arg, "--log-level")==0) || (strcmp(arg, "-l")==0))
		{
			if (i<argc)
			{
				unsigned long int value;

				i++;
				if (get_unsigned_value(argv[i], &value))
				{
					if (((int)value<MIN_LOG_LEVEL) || ((int)value>MAX_LOG_LEVEL))
					{
						error("invalid log level value %d, not in range [%d-%d]",
								value, MIN_LOG_LEVEL, MAX_LOG_LEVEL);
						usage(EXIT_CODE_USAGE_ERROR);
					}
					prefs.log_level=(int)value;
				}
				else
				{
					error("invalid value for option '%s': %s", arg, argv[i]);
					usage(EXIT_CODE_USAGE_ERROR);
				}
			}
			else
			{
				error("missing value to argument '%s'", arg);
				usage(EXIT_CODE_USAGE_ERROR);
			}
		}
		else
		if ((strcmp(arg, "--check-compliance")==0) || (strcmp(arg, "-c")==0))
			prefs.technical=true;
		else
		if ((strcmp(arg, "--extract")==0) || (strcmp(arg, "-x")==0))
		{
			if (i<argc)
			{
				i++;
				if (strcasecmp(argv[i], "iptc")==0)
					prefs.extract_iptc=true;
				else
				{
					error("unsupported metadata type to argument '%s'", arg);
					usage(EXIT_CODE_USAGE_ERROR);
				}
			}
			else
			{
				error("missing metadata type to argument '%s'", arg);
				usage(EXIT_CODE_USAGE_ERROR);
			}
		}
		else
		if ((strcmp(arg, "--backup")==0) || (strcmp(arg, "-b")==0))
			prefs.rewrite_create_backup=true;
		else
		if ((strcmp(arg, "--target-dir")==0) || (strcmp(arg, "-d")==0))
		{
			if (i<argc)
			{
				i++;
				target_dirname=fcleanpath(argv[i]);
			}
			else
			{
				error("missing dirname to argument '%s'", arg);
				usage(EXIT_CODE_USAGE_ERROR);
			}
		}
		else
		if ((strcmp(arg, "--fix")==0) || (strcmp(arg, "-f")==0))
			prefs.fix=true;
		else
		if ((strcmp(arg, "--ext")==0) || (strcmp(arg, "-t")==0))
		{
			if (i<argc)
			{
				i++;
				extract_extension=argv[i];
			}
			else
			{
				error("missing extension to argument '%s'", arg);
				usage(EXIT_CODE_USAGE_ERROR);
			}
		}
		else
		if ((strcmp(arg, "--append-ext")==0) || (strcmp(arg, "-a")==0))
			prefs.extract_extension_append=true;
		else
		if ((strcmp(arg, "--include")==0) || (strcmp(arg, "-i")==0))
		{
			if (i<argc)
			{
				i++;
				include_expression=argv[i];
			}
			else
			{
				error("missing filename or expression to argument '%s'", arg);
				usage(EXIT_CODE_USAGE_ERROR);
			}
		}
		else
		if ((strcmp(arg, "--exclude")==0) || (strcmp(arg, "-e")==0))
		{
			if (i<argc)
			{
				i++;
				exclude_expression=argv[i];
			}
			else
			{
				error("missing filename or expression to argument '%s'", arg);
				usage(EXIT_CODE_USAGE_ERROR);
			}
		}
		else
		if (strcmp(arg, "--edit")==0)
		{
			if (i<argc)
			{
				i++;
				edit_expression=argv[i];
			}
			else
			{
				error("missing filename or expression to argument '%s'", arg);
				usage(EXIT_CODE_USAGE_ERROR);
			}
		}
		else
		if (strcmp(arg, "--test")==0)
			prefs.test_rewrite=true;
		else
		if ((strcmp(arg, "--wrap")==0) || (strcmp(arg, "-w")==0))
		{
			if (i<argc)
			{
				unsigned long int value;

				i++;
				if (get_unsigned_value(argv[i], &value))
				{
					if (((int)value<MIN_WRAP_DUMP) || ((int)value>MAX_WRAP_DUMP))
					{
						error("invalid wrap dump value %d, not in range [%d-%d]",
								value, MIN_WRAP_DUMP, MAX_WRAP_DUMP);
						usage(EXIT_CODE_USAGE_ERROR);
					}
					prefs.wrap_dump=(unsigned short int)value;
				}
				else
				{
					error("invalid value for option '%s': %s", arg, argv[i]);
					usage(EXIT_CODE_USAGE_ERROR);
				}
			}
			else
			{
				error("missing filename to argument '%s'", arg);
				usage(EXIT_CODE_USAGE_ERROR);
			}
		}
/*		else
		if ((strcmp(arg, "--overwrite")==0) || (strcmp(arg, "-o")==0))
			prefs.edit_overwrite=true; */
		else
		if ((strcmp(arg, "--")==0))
			break;
		else
		if (*arg=='-')
		{
			error("unrecognized option '%s'", arg);
			usage(EXIT_CODE_USAGE_ERROR);
		}
		else
			break;
	}

	/* get mode */
	filenames_list=argv;
	filenames_count=argc;
	if ((argc-i+1)>=2)	/* 2 parameters or more */
	{
		struct op_label* label;

		op=argv[i++];
		label=op_match_label(op);
		if (!label)
		{
			error("unknown op mode '%s'", op);
			usage(EXIT_CODE_USAGE_ERROR);
		}
		prefs.op=label->op;
		info("operating mode: %s", op);

		if (prefs.op==OP_FILTER)
			prefs.rewrite=true;

		/* extra parameter (value) for dump-value mode */
		if (prefs.op==OP_DUMP_VALUE)
		{
			if ((argc-i+1)>=2)
			{
				dump_value=argv[i++];
				/* setup filters (even if we don't rewrite/filter */
				filter_load_line(dump_value);
				prefs.filter_mode|=FILTER_INCLUDE;
			}
			else
			{
				error("missing parameter(s) for dump-value mode");
				usage(EXIT_CODE_USAGE_ERROR);
			}
		}

		/* detect @<file> pattern */
		if (*argv[i]=='@')
		{
			FILE* list_file;
			char* filename;

			/* any further file in command-line will be ignored */
			if ((argc-i+1)>1)
				warning("ignoring extra parameters in command-line after @<file>");

			/* open filenames list file */
			filename=argv[i];
			filename++;
			list_file=fopen(filename, "r");
			if (!list_file)
				fatal_error("could not open file '%s'", filename);

			/* load the filenames list from file */
			filenames_list=NULL;
			filenames_count=0;
			filenames_list_load(&filenames_list, &filenames_count, list_file);
			filenames_count--;	/* if 0 filenames -> count will be set to -1 */
			i=0;

			/* free local tmp resource */
			fclose(list_file);
		}
	}
	else
	{
		error("missing parameter(s)");
		usage(EXIT_CODE_USAGE_ERROR);
	}

	/* latest verifications around the passed options coherence */
	if (prefs.fix && (!prefs.extract_iptc && !prefs.rewrite))
	{
		error("nothing to fix if not extracting or filtering");
		usage(EXIT_CODE_USAGE_ERROR);
	}

	if (prefs.rewrite_create_backup && (!prefs.extract_iptc && !prefs.rewrite))
	{
		error("nothing to backup if not extracting or filtering");
		usage(EXIT_CODE_USAGE_ERROR);
	}
	if (prefs.test_rewrite && !prefs.rewrite)
	{
		error("test rewrite not possible if not filtering");
		usage(EXIT_CODE_USAGE_ERROR);
	}
	if (extract_extension && !prefs.extract_iptc)
	{
		error("nothing to do with extension '%s' when not extracting",
				extract_extension);
		usage(EXIT_CODE_USAGE_ERROR);
	}
	if (prefs.extract_extension_append && !prefs.extract_iptc)
	{
		error("no extension to append when not extracting");
		usage(EXIT_CODE_USAGE_ERROR);
	}
	if (include_expression && exclude_expression)
	{
		if (prefs.op!=OP_FILTER)
			error("don't know what to do with extraneous filename or expression for operating mode %s",
					op_labels[prefs.op].label);
		else
			error("cannot mix include and exclude filenames or expressions at the same time");
		usage(EXIT_CODE_USAGE_ERROR);
	}

	/* some adjustments according to passed options */
	if (prefs.extract_iptc && !extract_extension)
		/* default file extension to assume when extracting IPTC chunk */
		extract_extension=default_iptc_extension;
	if ((prefs.extract_iptc || prefs.rewrite) && target_dirname)
	{
		/* check if target dir does exist */
		if (!fexist(target_dirname))
		{
			int err;

			/* create the missing target dir */
			err=mkdir(target_dirname, 0x1ff);
			if (err<0)
				fatal_error("couldn't create target directory '%s' (error #%d)",
						target_dirname, errno);
		}
		else
			if (!fisdir(target_dirname))
				fatal_error("'%s' doesn't seem to be a directory", target_dirname);
	}
			
	/* load include/exclude/edit files for filtering */
	if (include_expression)
	{
		prefs.filter_mode|=FILTER_INCLUDE;
		if (*include_expression=='@')
		{
			FILE* include_file;
			char* include_filename;

			include_filename=include_expression+1;
			include_file=fopen(include_filename, "r");
			if (!include_file)
				fatal_error("could not open file '%s'", include_filename);
			debug("loading include file");
			filter_load_file(include_file);
			fclose(include_file);
		}
		else
		{
			debug("evaluating include filter expression");
			filter_load_line(include_expression);
		}
#if 0
		iptc_filter_table_dump();
#endif
		/* always cache rewriting */
		prefs.rewrite_cached=true;
	}
	else
		if (exclude_expression)
		{
			prefs.filter_mode|=FILTER_EXCLUDE;
			if (*exclude_expression=='@')
			{
				FILE* exclude_file;
				char* exclude_filename;

				exclude_filename=exclude_expression+1;
				exclude_file=fopen(exclude_filename, "r");
				if (!exclude_file)
					fatal_error("could not open file '%s'", exclude_filename);
				debug("loading exclude file");
				filter_load_file(exclude_file);
				fclose(exclude_file);
			}
			else
			{
				debug("evaluating exclude filter expression");
				filter_load_line(exclude_expression);
			}
#if 0
			iptc_filter_table_dump();
#endif
			/* force cached rewriting when some filters are set */
			if (iptc_filters()>0)
				prefs.rewrite_cached=true;
		}
	if (edit_expression)
	{
		prefs.filter_mode|=FILTER_EDIT;
		if (*edit_expression=='@')
		{
			FILE* edit_file;
			char* edit_filename;

			edit_filename=edit_expression+1;
			edit_file=fopen(edit_filename, "r");
			if (!edit_file)
				fatal_error("could not open file '%s'", edit_filename);
			debug("loading edit file");
			edit_load_file(edit_file);
			fclose(edit_file);
		}
		else
		{
			debug("evaluating edit filter expression");
			edit_load_line(edit_expression);
		}
#if 1
		iptc_edit_list_dump();
#endif
		/* force cached rewriting when some edits are set */
		if (iptc_edits()>0)
			prefs.rewrite_cached=true;
	}
	if (prefs.op==OP_FILTER)
	{
		if (prefs.filter_mode==FILTER_NONE)
			error("unexpected filter mode: none (not set)");
		else
			info("filter mode:%s%s%s",
					(prefs.filter_mode&FILTER_EXCLUDE)?" exclude":"",
					(prefs.filter_mode&FILTER_INCLUDE)?" include":"",
					(prefs.filter_mode&FILTER_EDIT)?" edit":"");
	}

	while (i<=filenames_count)
	{
		bool proceed;

		proceed=true;

		/* reset rewrite flag according to the current mode (it might has
		   been discarded for previous file */
		if (prefs.op==OP_FILTER)
			prefs.rewrite=true;

		if (prefs.extract_iptc)
		{
			iptc_extract_buffer=NULL;
			iptc_extract_buffer_size=0;
		}

		/* open the input file */
		input_filename=filenames_list[i];
		context_reset();
		context_set_text(input_filename);
		input_file=fopen(input_filename, "rb");
		if (!input_file)
		{
			error("could not open file '%s'", input_filename);
			i++;
			context_print_info();
			continue;
		}

		/* output filename will be based on the input filename */
		if (prefs.rewrite)
		{
			if (target_dirname)
			{
				char* pathname;
				char* filename;
				size_t sz;

				/* output file's path */
				sz=0;
				if (target_dirname)
					pathname=strdup(target_dirname);
				else
					pathname=fdirname(input_filename);
				if (pathname && *pathname)
					sz+=strlen(pathname)+1;

				/* output filename (get rid of any extension) */
				filename=ffilename(input_filename);
				sz+=strlen(filename);

				sz++;
				output_filename=malloc(sz*sizeof(char));
				if (!output_filename)
					fatal_error("couldn't allocate space for output filename");
				*output_filename='\0';
				if (pathname && *pathname)
				{
					strcat(output_filename, pathname);
					strcat(output_filename, DIR_SEPARATOR_STRING);
				}
				strcat(output_filename, filename);

				/* free resources */
				free(filename);
				filename=NULL;
				free(pathname);
				pathname=NULL;
			}
			else
				output_filename=strdup(input_filename);
		}
		else
			output_filename=NULL;

		/* output filename for extracted IPTC data */
		if (prefs.extract_iptc)
		{
			char* pathname;
			char* filename;
			size_t sz;

			/* output file's path */
			sz=0;
			if (target_dirname)
				pathname=strdup(target_dirname);
			else
				pathname=fdirname(input_filename);
			if (pathname && *pathname)
				sz+=strlen(pathname)+1;

			/* output filename (get rid of any extension) */
			if (prefs.extract_extension_append)
				filename=ffilename(input_filename);
			else
				filename=fbasename(input_filename);
			sz+=strlen(filename);

			/* filename to store the IPTC chunk to */
			sz+=1+strlen(extract_extension)+1;
			iptc_filename=malloc(sz*sizeof(char));
			if (!iptc_filename)
				fatal_error("couldn't allocate space for IPTC chunk extraction filename");
			*iptc_filename='\0';
			if (pathname && *pathname)
			{
				strcat(iptc_filename, pathname);
				strcat(iptc_filename, DIR_SEPARATOR_STRING);
			}
			strcat(iptc_filename, filename);
			strcat(iptc_filename, ".");
			strcat(iptc_filename, extract_extension);

			free(filename);
			filename=NULL;
			free(pathname);
			pathname=NULL;
		}

		/* check for concurrential write filenames */
		if (prefs.extract_iptc && prefs.rewrite)
		{
#ifdef OS_WIN32
			if (strcasecmp(output_filename, iptc_filename)==0)
#else
			if (strcmp(output_filename, iptc_filename)==0)
#endif
			{
				error("output filenames clash: filtering and extraction target files are identical (%s)\nthis would lead to unpredictable results",
						output_filename);
				proceed=false;
			}
		}

		if (proceed)
		{
			/* read the whole file contents in a newly allocated buffer (TODO: optim this to use
			   less memory */
			input_length=fsize(input_file);
			buffer=malloc(input_length*sizeof(unsigned char));
			if (!buffer)
				fatal_error("could not allocate %lu bytes", input_length*sizeof(unsigned char));
			ret=fread((void*)buffer, 1, input_length, input_file);
			if ((ret!=input_length) && (ferror(input_file)<0))
				error("read error #%d in file '%s', offset %lu: ret=%lu (expected=%lu)",
						errno, input_filename, ret, input_length);

			/* show input and output filenames */
			info("input file: '%s'", input_filename);
			if (prefs.rewrite)
				info("output file: '%s'%s", output_filename,
						fexist(output_filename)?" (already exists)":"");
			if (prefs.extract_iptc)
				info("IPTC extract output file: '%s'%s", iptc_filename,
						fexist(iptc_filename)?" (already exists)":"");

			result.parsed_bytes=0;
			result.written_bytes=0;
			result.ret=true;

			if (input_length==0)
				warning("empty file, skipped");
			else
			/* detect and parse contents */
			if (strcmp((char*)getstring(buffer, 0, tmp, 5), GIF87_MARKER_STRING)==0)
			{
				debug("GIF87 marker found");
				info("GIF87 contents expected (unsupported)");
				prefs.rewrite=false;
			}
			else
			if (strcmp((char*)getstring(buffer, 0, tmp, 5), GIF89_MARKER_STRING)==0)
			{
				debug("GIF89 marker found");
				info("GIF89 contents expected (unsupported)");
				prefs.rewrite=false;
			}
			else
			if (strcmp((char*)getstring(buffer, 0, tmp, 6), EXIF_MARKER_STRING)==0)
			{
				debug("Exif marker found");
				info("Exif contents expected");
				exif_parse(buffer, input_length, &result);
				if (!result.ret)
					debug("Exif parser has reported error(s)");
			}
			else
			if (getlong(buffer, 0)==ADOBE_8BPS_MARKER)
			{
				debug("8BPS marker found");
				info("Adobe Photoshop contents expected");
				if (prefs.rewrite)
					init_rewrite();
				adobe_8bps_parse(buffer, input_length, &result);
				if (!result.ret)
					debug("8BPS parser has reported error(s)");
			}
			else
			if (getlong(buffer, 0)==ADOBE_8BIM_MARKER)
			{
				debug("8BIM marker found");
				info("Adobe Photoshop contents expected");
				/* it seems not possible make a distinction between the generic
				   8BIM format and the Photoshop 4.0's one (it's only possible if the 8BIM chunk is
				   embedded in a JFIF APP0 chunk)
				*/
				if (prefs.rewrite)
					init_rewrite();
				adobe_8bim_parse(buffer, input_length, 0, &result);
				if (!result.ret)
					debug("8BIM parser has reported error(s)");
			}
			else
			if (getlong(buffer, 0)==TIFF_LITTLE_ENDIAN_MARKER_STRING)
			{
				debug("TIFF (little endian) marker found");
				info("TIFF (little endian) contents expected (unsupported)");
				prefs.rewrite=false;
			}
			else
			if (getlong(buffer, 0)==TIFF_BIG_ENDIAN_MARKER_STRING)
			{
				debug("TIFF (big endian) marker found");
				info("TIFF (big endian) contents expected (unsupported)");
				prefs.rewrite=false;
			}
			else
			if (getlong(buffer, 0)==PNG_MARKER_STRING)
			{
				debug("PNG marker found");
				info("PNG contents expected (unsupported)");
				prefs.rewrite=false;
			}
			else
			if (strcmp((char*)getstring(buffer, 0, tmp, 4), DJVU_MARKER_STRING)==0)
			{
				debug("DJVU marker found");
				info("DJVU contents expected (unsupported)");
				prefs.rewrite=false;
			}
			else
			if (getword(buffer, 0)==((JFIF_TAG<<8)|JFIF_SOI))
			{
				debug("SOI tag found");
				info("JPEG/JFIF contents expected");
				if (prefs.rewrite)
					init_rewrite();
				jfif_parse(buffer, input_length, &result);
				if (!result.ret)
					debug("JFIF parser has reported error(s)");
			}
			else
			if (strcmp((char*)getstring(buffer, 0, tmp, 2), BMP_MARKER_STRING)==0)
			{
				debug("Windows Bitmap marker found");
				info("Windows Bitmap contents expected (unsupported)");
				prefs.rewrite=false;
			}
			else
			if (getbyte(buffer, 0)==IPTC_MARKER)
			{
				debug("IPTC marker found");
				info("IPTC contents expected");
				if (prefs.rewrite)
					init_rewrite();
				iptc_parse(buffer, input_length, true, &result);
				if (!result.ret)
					debug("IPTC parser has reported error(s)");
			}
			else
			if (getbyte(buffer, 0)==ADOBE_FFO_MARKER)
			{
				debug("FF0 marker found");
				info("Adobe FF0 contents expected");
				if (prefs.rewrite)
					init_rewrite();
				adobe_ffo_parse(buffer, input_length, true, &result);
				if (!result.ret)
					debug("FF0 parser has reported error(s)");
			}
			else
			{
				error("not a supported file (contents not recognized)");
				prefs.rewrite=false;
			}

			if (result.parsed_bytes>input_length)
				error("%lu byte(s) have been parsed whereas the file length is %lu",
				result.parsed_bytes, input_length);
			if (result.parsed_bytes<input_length)
				debug("%lu remaining byte(s)", input_length-result.parsed_bytes);

			/* close input file */
			fclose(input_file);
			input_file=NULL;

			if (prefs.rewrite)
			{
				int err;
				size_t len;

				post_rewrite(buffer, &result);

				if (!result.ret)
				{
					warning("at least one parser error has been encountered,\nwritten file might be corrupted (unreliable data)");
/*					unlink(output_filename); */
				}
				len=fsize(output_file);
				if (output_file)
				{
					fclose(output_file);
					output_file=NULL;
				}

				/* rename the temp file to override the original filename */
				if (prefs.test_rewrite)
				{
					char* rewrite_filename;
					size_t len;

					len=strlen(output_filename)-7; /* get rid of the trailing random chars from mkstemp */
					rewrite_filename=malloc((len+1+7+1)*sizeof(char));
					if (!rewrite_filename)
						fatal_error("couldn't allocate space for rewrite filename");
					strncpy(rewrite_filename, output_filename, len);
					strncpy(rewrite_filename+len, ".rewrite", 1+7);
					rewrite_filename[len+1+7]='\0';

					/* create a backup of the target file by renaming it */
					if (prefs.rewrite_create_backup)
						backup_file(rewrite_filename);

					err=frename(output_filename, rewrite_filename);
					if (err!=0)
						error("couldn't rename '%s' to '%s' (error #%d)",
								output_filename, rewrite_filename, err);

					free(rewrite_filename);
					rewrite_filename=NULL;
				}
				else
				{
					char* target_filename;

					/* get rid of the trailing random chars from mkstemp */
					target_filename=strndup(output_filename, strlen(output_filename)-7);

					/* create a backup of the target file (it can be the input filename itself if no
					   target dir has been set) by renaming it */
					if (prefs.rewrite_create_backup)
						backup_file(target_filename);

					err=frename(output_filename, target_filename);
					if (err!=0)
						error("couldn't rename '%s' to '%s' (error #%d)",
								output_filename, target_filename, err);
					else
						info("written %d byte(s) to file '%s'", len, target_filename);

					free(target_filename);
					target_filename=NULL;
				}
			}

			if (prefs.extract_iptc)
			{
				if (iptc_extract_buffer)
				{
					FILE* iptc_file;
					unsigned long int len;

					if (prefs.rewrite_create_backup)
						backup_file(iptc_filename);

					/* store the extracted IPTC chunk to file */
					iptc_file=fopen(iptc_filename, "w+b");
					if (!iptc_file)
						fatal_error("couldn't create file '%s' for IPTC chunk extraction",
								iptc_filename);
					len=fwrite(iptc_extract_buffer, 1, iptc_extract_buffer_size, iptc_file);
					if (len!=iptc_extract_buffer_size)
						error("error %d while attempting to save %lu byte(s) from the IPTC chunk (%lu written)",
								errno, iptc_extract_buffer_size, len);
					debug("IPTC chunk extracted and saved: %lu byte(s) to file '%s'",
							len, iptc_filename);
					info("written %d byte(s) to file '%s'", len, iptc_filename);

					/* free resources */
					fclose(iptc_file);
					iptc_file=NULL;
					free(iptc_extract_buffer);
					iptc_extract_buffer=NULL;
					iptc_extract_buffer_size=0;
				}
				else
					error("unexpected uninitialized IPTC extract buffer");

			}

			/* free resources */
			if (buffer)
			{
				free(buffer);
				buffer=NULL;
			}
		}

		/* free resources */
		if (iptc_filename)
		{
			free(iptc_filename);
			iptc_filename=NULL;
		}
		if (output_filename)
		{
			free(output_filename);
			output_filename=NULL;
		}

		i++;
		context_print_info();
	}

	/* free resources */
	if (prefs.op==OP_DUMP_VALUE)
		iptc_filter_table_reset();
	else
		if (prefs.rewrite)
		{
			iptc_filter_table_reset();
			iptc_edit_list_reset();
		}
	if (target_dirname)
	{
		free(target_dirname);
		target_dirname=NULL;
	}

	if ((filenames_list!=argv) && filenames_list)
	{
		for (i=0; i<=filenames_count; i++)
		{
			free(filenames_list[i]);
			filenames_list[i]=NULL;
		}
		free(filenames_list);
		filenames_list=NULL;
	}

	info("total: %lu warning(s), %lu error(s)", prefs.warnings, prefs.errors);
	if (prefs.fix)
		info("total: %lu fix(es)", prefs.fixes);
	exit_code=EXIT_CODE_NORMAL;
	if (prefs.errors)
		exit_code=EXIT_CODE_NORMAL_WITH_ERRORS;
	else
		if (prefs.warnings)
			exit_code=EXIT_CODE_NORMAL_WITH_WARNINGS;
	exit(exit_code);
}
