/* main-command.c
 * Originally written by Daniel Foote.
 *
 * Command line program to match GPS data and Photo EXIF timestamps
 * together, to figure out where you were at the time.
 * Writes the output back into the GPS exif tags.
 */

/* Copyright 2005-2024 Daniel Foote, Dan Fandrich.
 *
 * This file is part of gpscorrelate.
 *
 * gpscorrelate 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.
 *
 * gpscorrelate 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 gpscorrelate; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <getopt.h>
#include <string.h>
#include <locale.h>
#include <math.h>

#include "i18n.h"
#include "gpsstructure.h"
#include "exif-gps.h"
#include "unixtime.h"
#include "gpx-read.h"
#include "latlong.h"
#include "correlate.h"

#define GPS_EXIT_WARNING 2

enum OutputFormat {
	TEXT_FORMAT = 0, // Plain text format
	CSV_FORMAT = 1,	 // CSV format
	GPX_FORMAT = 2	 // GPX format
};

/* Command line options structure. */
static const struct option program_options[] = {
	{ "gps", required_argument, 0, 'g' },
	{ "latlong", required_argument, 0, 'l' },
	{ "timeadd", required_argument, 0, 'z'},
	{ "no-interpolation", no_argument, 0, 'i'},
	{ "help", no_argument, 0, 'h'},
	{ "verbose", no_argument, 0, 'v'},
	{ "datum", required_argument, 0, 'd'},
	{ "no-write", no_argument, 0, 'n'},
	{ "replace", no_argument, 0, 'R'},
	{ "max-dist", required_argument, 0, 'm'},
	{ "show", no_argument, 0, 's'},
	{ "machine", no_argument, 0, 'o'},
	{ "show-gpx", no_argument, 0, 'x'},
	{ "remove", no_argument, 0, 'r'},
	{ "ignore-tracksegs", no_argument, 0, 't'},
	{ "no-mtime", no_argument, 0, 'M'},
	{ "version", no_argument, 0, 'V'},
	{ "fix-datestamps", no_argument, 0, 'f'},
	{ "degmins", no_argument, 0, 'p'},
	{ "photooffset", required_argument, 0, 'O'},
	{ "heading", no_argument, 0, '('},
	{ "direction", required_argument, 0, 'b'},
	{ "max-heading", required_argument, 0, 'B'},
	{ "no-photo-tz", no_argument, 0, ')'},
	{ 0, 0, 0, 0 }
};

/* Function to print the version - near the top for easy modification. */
static void PrintVersion(const char* ProgramName)
{
	printf(_("%s, ver. %s. %s\n"),
	       ProgramName, PACKAGE_VERSION,
	       _("Copyright (C) 2005-2024 Daniel Foote, Dan Fandrich"));
}

/* Function to print the usage info. */
static void PrintUsage(const char* ProgramName)
{
	printf(_("Usage: %s [options] file.jpg ...\n"), ProgramName);
	puts(  _("-g, --gps file.gpx       Specifies GPX file with GPS data"));
	puts(  _("-l, --latlong LAT,LONG[,E] Specifies latitude/longitude/elevation directly"));
	puts(  _("-z, --timeadd +/-HH[:MM] Time to add to GPS data to make it match photos"));
	puts(  _("-i, --no-interpolation   Disable interpolation between points; interpolation\n"
	         "                         is linear, points rounded if disabled"));
	puts(  _("-d, --datum DATUM        Specify measurement datum (defaults to WGS-84)"));
	puts(  _("-n, --no-write           Do not write the EXIF data. Useful with --verbose"));
	puts(  _("-R, --replace            Overwrite any GPS tags already in the image file"));
	puts(  _("-m, --max-dist SECS      Max time outside points that photo will be matched"));
	puts(  _("    --heading            Store the direction of movement in the image"));
	puts(  _("-B, --max-heading DEG    Max degrees between points that will be matched"));
	puts(  _("-b, --direction DEG      Angular offset of camera from forward direction"));
	puts(  _("-s, --show               Just show the GPS data from the given files"));
	puts(  _("-o, --machine            Like --show but with machine-readable CSV output"));
	puts(  _("-x, --show-gpx           Like --show but with GPX output"));
	puts(  _("-r, --remove             Strip GPS tags from the given files"));
	puts(  _("-t, --ignore-tracksegs   Interpolate between track segments, too"));
	puts(  _("-M, --no-mtime           Don't change mtime of modified files"));
	puts(  _("-f, --fix-datestamps     Fix broken GPS datestamps written with ver. < 1.5.2"));
	puts(  _("    --degmins            Write location as DD MM.MM (was default before v1.5.3)"));
	puts(  _("-O, --photooffset SECS   Offset added to photo time to make it match the GPS"));
	puts(  _("    --no-photo-tz        Ignore time zone tags within photos"));
	puts(  _("-h, --help               Display this help message"));
	puts(  _("-v, --verbose            Show more detailed output"));
	puts(  _("-V, --version            Display version information"));
}

/* CSV escape a string by doubling quotation marks.
 * A pointer to a malloced string is returned which must be freed by the caller.
 */
static char *CsvEscape(const char *str)
{
	char *newstr = (char*)malloc(strlen(str)*2+1); // worst-case size
	char *cpy = newstr;
	if (!newstr)
		return newstr;
	do {
		*cpy = *str;
		if (*cpy++ == '"')
			*cpy++ = '"';
	} while (*str++);
	return newstr;
}

/* Make the string safe for inclusion in an XML comment.
 * Such strings are not allowed to contain the sequence -- so if it's found,
 * the second dash is replaced with a question mark. */
void XmlCommentSafe(char *SafeFile)
{
	while (1)
	{
		SafeFile = strstr(SafeFile, "--");
		if (SafeFile == NULL)
			break;
		SafeFile[1] = '?';
	}
}

/* Display the information from an existing file. */
static int ShowFileDetails(const char* File, enum OutputFormat Format,
	struct CorrelateOptions* Options)
{
	double Lat, Long, Elev;
	int IncludesGPS = 0;
	Lat = Long = 0;
	Elev = NAN; /* Elevation is optional, so this means it's missing */
	long OffsetTime = 0;
	char* Time = ReadExifData(File, &IncludesGPS, &Lat, &Long, &Elev, &OffsetTime);
	int rc = 1;
	char* OldLocale = NULL;
	static int Started = 0;

	if (Format != TEXT_FORMAT)
	{
		OldLocale = setlocale(LC_NUMERIC, "C");
	}
	if (Format == GPX_FORMAT && !Started)
	{
		// This header is closed off in ShowFileDone()
		printf(
			"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
			"<gpx version=\"1.1\" creator=\"gpscorrelate %s\"\n"
			"  xmlns=\"http://www.topografix.com/GPX/1/1\"\n"
			"  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
			"  xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\n"
			" <trk>\n"
			"  <trkseg>\n",
			PACKAGE_VERSION);
		if (Options->AutoTimeZone && Time)
		{
			SetAutoTimeZoneOptions(Time, Options);
			Options->AutoTimeZone = 0;
		}
		Started = 1;
	}

	if (Time)
	{
		if (IncludesGPS)
		{
			/* Display the data as CSV if we want
			 * it machine readable. */
			if (Format == CSV_FORMAT)
			{
				char *EscapedFile = CsvEscape(File);
				if (!EscapedFile) {
					fprintf(stderr, _("Out of memory.\n"));
					exit(EXIT_FAILURE);
				}
				printf("\"%s\",\"%s\",%f,%f,",
					EscapedFile, Time, Lat, Long);

				if (!isnan(Elev))
					printf("%.3f", Elev);
				printf("\n");
				free(EscapedFile);

			} else if (Format == GPX_FORMAT)
			{
				/* Now convert the time into Unixtime. */
				struct timespec PhotoTime = ConvertTimeToUnixTime(Time, EXIF_DATE_FORMAT,
						OffsetTime, NULL, Options);
				static time_t LastGpxTime;
				if (CompareTimes(PhotoTime, LastGpxTime) < 0)
					fprintf(stderr, _("Warning: image files are not ordered by time.\n"));
				LastGpxTime = PhotoTime.tv_sec;

				char GpxTime[24];
				struct tm* Tm = gmtime(&PhotoTime.tv_sec);
				strftime(GpxTime, sizeof(GpxTime), "%Y-%m-%dT%H:%M:%SZ", Tm);

				char *SafeFile = strdup(File);
				if (!SafeFile) {
					fprintf(stderr, _("Out of memory.\n"));
					exit(EXIT_FAILURE);
				}
				XmlCommentSafe(SafeFile);

				char MaybeElev[32] = "";
				if (!isnan(Elev))
					snprintf(MaybeElev, sizeof(MaybeElev), "    <ele>%.3f</ele>\n", Elev);

				printf("   <trkpt lat=\"%f\" lon=\"%f\">\n"
					   "%s"
					   "    <time>%s</time>\n"
					   "    <!-- %s -->\n"
					   "   </trkpt>\n", Lat, Long, MaybeElev, GpxTime, SafeFile);
				free(SafeFile);

			} else {
				printf(_("%s: %s, Lat %f, Long %f, Elevation "),
					File, Time, Lat, Long);
				if (!isnan(Elev))
					printf("%.3f", Elev);
				else
					printf(_("(unknown)"));

				printf(".\n");
			}
		} else {
			/* Don't display anything if we want machine
			 * readable data and there is no data. */
			if (Format == TEXT_FORMAT)
			{
				printf(_("%s: %s, No GPS Data.\n"),
					File, Time);
			}
		}
	} else {
		/* Say that there was no data & return error, except if we want
		 * machine readable output */
		if (Format == TEXT_FORMAT)
		{
			printf(_("%s: No EXIF data.\n"), File);
			rc = 0;
		}
	}

	free(Time);

	if (Format != TEXT_FORMAT) {
		setlocale(LC_NUMERIC, OldLocale);
	}

	return rc;
}

/* Complete the information in an output file. */
static void ShowFileDone(enum OutputFormat Format)
{
	if (Format == GPX_FORMAT) {
		printf("  </trkseg>\n"
			   " </trk>\n"
			   "</gpx>\n");
	}
}

/* Remove all GPS exif tags from a file. Not really that useful, but... */
static int RemoveGPSTags(const char* File, int NoChangeMtime, int NoWriteExif)
{
	if (RemoveGPSExif(File, NoChangeMtime, NoWriteExif))
	{
		printf(_("%s: Removed GPS tags.\n"), File);
		return 1;
	} else {
		printf(_("%s: Tag removal failure.\n"), File);
		return 0;
	}
}

/* Fix GPSDatestamp tags, if they were incorrect, as found with versions
 * earlier than 1.5.2. */
static int FixDatestamp(const char* File, int AdjustmentHours, int AdjustmentMinutes, int NoWriteExif)
{
	/* Read the timestamp data. */
	char DateStamp[12];
	char TimeStamp[12];
	char CombinedTime[24];
	int IncludesGPS = 0;
	char* OriginalDateStamp = NULL;
	int rc = 1;

	OriginalDateStamp = ReadGPSTimestamp(File, DateStamp, TimeStamp, &IncludesGPS);

	if (OriginalDateStamp == NULL)
	{
		printf(_("%s: No EXIF data.\n"), File);
		rc = 0;
	} else if (IncludesGPS == 0) {
		printf(_("%s: No GPS data.\n"), File);
		rc = 0;
	} else {
		/* Check the timestamp. */
		struct timespec PhotoTime = ConvertToUnixTime(OriginalDateStamp, EXIF_DATE_FORMAT,
			AdjustmentHours * 3600 + AdjustmentMinutes * 60);

		snprintf(CombinedTime, sizeof(CombinedTime), "%s %s", DateStamp, TimeStamp);

		struct timespec GPSTime = ConvertToUnixTime(CombinedTime, EXIF_DATE_FORMAT, 0);

		if (CompareTimes(PhotoTime, GPSTime.tv_sec)) {
			/* Timestamp is wrong. Fix it.
			 * Should be photo time - this also corrects
			 * GPSTimestamp, which was wrong too. */
			if (!NoWriteExif)
			{
				rc = WriteFixedDatestamp(File, PhotoTime);
			}
			char PhotoTimeFormat[100];
			char GPSTimeFormat[100];

			strftime(PhotoTimeFormat, sizeof(PhotoTimeFormat),
				 "%a %b %d %H:%M:%S %Y UTC", gmtime(&PhotoTime.tv_sec));
			strftime(GPSTimeFormat, sizeof(GPSTimeFormat),
				 "%a %b %d %H:%M:%S %Y UTC", gmtime(&GPSTime.tv_sec));
			printf(_("%s: Wrong timestamp:\n   Photo:     %s\n"
				 "   GPS:       %s\n   Corrected: %s\n"),
					File, PhotoTimeFormat, GPSTimeFormat, PhotoTimeFormat);
		} else {
			/* Inside the range. Do nothing! */
			printf(_("%s: Timestamp is OK: Photo %s (localtime), GPS %s (UTC).\n"),
			       File, OriginalDateStamp, CombinedTime);
		}
	}

	free(OriginalDateStamp);
	return rc;
}

int main(int argc, char** argv)
{
	/* Initialize locale & gettext */
	setlocale (LC_ALL, "");
	(void) bindtextdomain(TEXTDOMAIN, PACKAGE_LOCALE_DIR);
	(void) textdomain(TEXTDOMAIN);

	/* If you didn't pass any arguments... */
	if (argc == 1)
	{
		PrintVersion(argv[0]);
		PrintUsage(argv[0]);
		exit(EXIT_SUCCESS);
	}

	/* Parse our command line options. */
	/* But first, some variables to store stuff in. */
	int c;

	struct GPSTrack* Track = NULL;/* Array of lists of GPS waypoints. The
					 final entry of all 0 signals the end. */
	int NumTracks = 0;	     /* Number of track structures at Track,
					not including the terminating entry. */
	int HaveTimeAdjustment = 0;  /* Whether -z option was given. */
	int TimeZoneHours = 0;       /* Integer version of the timezone. */
	int TimeZoneMins = 0;
	char* Datum = NULL;          /* Datum of input GPS data. */
	int Interpolate = 1;         /* Do we interpolate? By default, yes. */
	int NoWriteExif = 0;         /* Do we not write to file? By default, no. */
	int OverwriteExisting = 0;   /* Do we overwrite existing tags? By default, no */
	int ShowDetails = 0;         /* Do we show lots of details? By default, no. */
	int FeatherTime = 0;         /* The "feather" time, in seconds. 0 = disabled. */
	int ShowOnlyDetails = 0;
	enum OutputFormat ShowFormat = TEXT_FORMAT;
	int RemoveTags = 0;
	int DoBetweenTrackSegs = 0;
	int NoChangeMtime = 0;
	int NoPhotoTz = 0;
	int FixDatestamps = 0;
	int DegMinSecs = 1;
	double PhotoOffset = 0;
	int WriteHeading = 0;
	int HeadingOffset = -1;      /* Negative means disabled */
	int MaxHeadingDelta = -1;    /* Negative means disabled */
	struct GPSPoint *LatLong = NULL;

	/* Create the empty terminating array entry */
	Track = (struct GPSTrack*) calloc(1, sizeof(*Track));
	if (!Track)
	{
		fprintf(stderr, _("Out of memory.\n"));
		exit(EXIT_FAILURE);
	}

	while (1)
	{
		/* Call getopt to do all the hard work
		 * for us... */
		c = getopt_long(argc, argv, "b:B:g:z:il:hvd:m:nsortxRMVfO:",
				program_options, 0);

		if (c == -1) break;

		/* Determine what getopt saw. */
		switch (c)
		{
			case 'g':
				/* This parameter specifies the GPS data.
				 * It or 'l' must be present at least once. */
				if (optarg)
				{
					int HaveTrack;

					/* Read the XML file into memory and extract the "points". */
					printf(_("Reading GPS Data..."));
					fflush(stdout);
					HaveTrack = ReadGPX(optarg, &Track[NumTracks]);
					printf("\n");
					if (!HaveTrack)
					{
						exit(EXIT_FAILURE);
					}

					/* Make room for a new end-of-array entry */
					++NumTracks;
					Track = (struct GPSTrack*) realloc(Track, sizeof(*Track)*(NumTracks+1));
					if (!Track)
					{
						fprintf(stderr, _("Out of memory.\n"));
						exit(EXIT_FAILURE);
					}
					memset(&Track[NumTracks], 0, sizeof(*Track));
				}
				break;
			case 'l':
				/* This parameter specifies a direct latitude/longitude
				   coordinate to use for all images.
				   It or 'g' must be present at least once. */
				LatLong = NewGPSPoint();
				if (!LatLong)
				{
					fprintf(stderr, _("Out of memory.\n"));
					exit(EXIT_FAILURE);
				}
				if (!ParseLatLong(optarg, LatLong))
				{
					fprintf(stderr, _("Error parsing location.\n"));
					exit(EXIT_FAILURE);
				}
				break;

			case 'z':
				/* This parameter specifies the time to add to the
				 * GPS data to make it match the timezone for
				 * the photos. */
				/* We only store it here, convert it to numbers later. */
				if (optarg)
				{
					/* Break up the adjustment and convert to numbers. */
					if (strstr(optarg, ":"))
					{
						/* Found colon. Split into two. */
						sscanf(optarg, "%d:%d", &TimeZoneHours, &TimeZoneMins);
						/* Can't look at sign of TimeZoneHours because it might be -0 */
						if (*optarg == '-')
						    TimeZoneMins *= -1;
					} else {
						/* No colon. Just parse. */
						TimeZoneHours = atoi(optarg);
					}
					HaveTimeAdjustment = 1;
				}
				break;
			case 'O':
				if (optarg)
				{
					PhotoOffset = atof(optarg);
				}
				break;
			case 'i':
				/* This option disables interpolation. */
				Interpolate = 0;
				break;
			case 'v':
				/* This option asks us to show more info. */
				PrintVersion(argv[0]);
				ShowDetails = 1;
				break;
			case 'd':
				/* This option specifies a Datum, if other than WGS-84. */
				if (optarg)
				{
					Datum = strdup(optarg);
				}
				break;
			case 'n':
				/* This option specifies not to write to file. */
				NoWriteExif = 1;
				break;
			case 'R':
				/* This option specifies to overwrite existing GPS tags. */
				OverwriteExisting = 1;
				break;
			case 'm':
				/* This option gives us the allowable "feather" time. */
				if (optarg)
				{
					FeatherTime = atoi(optarg);
				}
				break;
			case '(':
				/* This option specifies to write the GPSTrack tag, if possible. */
				WriteHeading = 1;
				break;
			case ')':
				/* This option causes OffsetTime tags within photos to be ignored. */
				NoPhotoTz = 1;
				break;
			case 'b':
				/* This option gives us the offset of the camera from forward. */
				if (optarg)
				{
					HeadingOffset = atoi(optarg);
					if (HeadingOffset <= -360 || HeadingOffset >= 360)
					{
						fprintf(stderr, _("The --direction value must be between -359..359\n"));
						exit(EXIT_FAILURE);
					}
					if (HeadingOffset < 0)
					{
						// Ensure the offset is positive
						HeadingOffset += 360;
					}
				}
				break;
			case 'B':
				/* This option gives us the maximum change of heading to accept. */
				if (optarg)
				{
					MaxHeadingDelta = atoi(optarg);
					if (MaxHeadingDelta < 0 || MaxHeadingDelta >= 180)
					{
						fprintf(stderr, _("The --max-heading value must be between 0..179\n"));
						exit(EXIT_FAILURE);
					}
				}
				break;
			case 'h':
				/* Display the help/usage information. And then quit. */
				PrintUsage(argv[0]);
				exit(EXIT_SUCCESS);
				break;
			case 'V':
				/* Display version information, and then quit. */
				PrintVersion(argv[0]);
				printf(_("This is free software; see the source for copying conditions.  There is NO\n"
					 "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"));
				exit(EXIT_SUCCESS);
				break;
			case 'f':
				/* Fix Datestamps. */
				FixDatestamps = 1;
				break;
			case 's':
				/* Show the data in the photos. Mark this. */
				ShowOnlyDetails = 1;
				ShowFormat = TEXT_FORMAT;
				break;
			case 'o':
				/* Show the data in the photos, machine readable (CSV format). */
				ShowOnlyDetails = 1;
				ShowFormat = CSV_FORMAT;
				break;
			case 'x':
				/* Show the data in the photos, GPX format. */
				ShowOnlyDetails = 1;
				ShowFormat = GPX_FORMAT;
				break;
			case 'r':
				/* Remove GPS tags from the file. Mark this. */
				RemoveTags = 1;
				break;
			case 't':
				/* Interpolate between track segments. */
				DoBetweenTrackSegs = 1;
				break;
			case 'M':
				NoChangeMtime = 1;
				break;
			case 'p':
				/* Write in old DegMins format. */
				DegMinSecs = 0;
				break;
			case '?':
				/* Unrecognised option. Or, missing argument. */
				/* The user has already been informed, so just exit. */
				exit(EXIT_FAILURE);
				break;
			default:
				/* Unrecognised code that getopt returned anyway.
				 * Oops... */
				break;
		} /* End switch(c) */
	} /* End While(1) */

    /* Create a fake track if a position was manually given */
    if (LatLong) {
        if (!MakeTrackFromLatLong(LatLong, HeadingOffset, &Track[NumTracks]))
        {
            fprintf(stderr, _("Out of memory.\n"));
            exit(EXIT_FAILURE);
        }
        free(LatLong);
        LatLong = NULL;

        /* Make room for a new end-of-array entry */
        ++NumTracks;
        Track = (struct GPSTrack*) realloc(Track, sizeof(*Track)*(NumTracks+1));
        if (!Track)
        {
            fprintf(stderr, _("Out of memory.\n"));
            exit(EXIT_FAILURE);
        }
        memset(&Track[NumTracks], 0, sizeof(*Track));
    }

	/* Check to see if the user passed some files to work with. Not much
	 * good if they didn't. */
	if (optind < argc)
	{
		/* You passed some files. Handy! */
	} else {
		/* Hmm. It seems there were no other files... that doesn't work. */
		fprintf(stderr, _("At least one image file name must be given.\n"));
		exit(EXIT_FAILURE);
	}

	/* Set up any other command line options... */
	if (!Datum)
	{
		Datum = strdup("WGS-84");
	}

	/* Set up our options structure for the correlation function. */
	struct CorrelateOptions Options;
	Options.NoWriteExif   = NoWriteExif;
	Options.OverwriteExisting = OverwriteExisting;
	Options.NoInterpolate = !Interpolate;
	Options.AutoTimeZone  = !HaveTimeAdjustment;
	Options.TimeZoneFromPhoto = !NoPhotoTz && !HaveTimeAdjustment;
	Options.TimeZoneHours = TimeZoneHours;
	Options.TimeZoneMins  = TimeZoneMins;
	Options.FeatherTime   = FeatherTime;
	Options.Datum         = Datum;
	Options.DoBetweenTrkSeg = DoBetweenTrackSegs;
	Options.NoChangeMtime = NoChangeMtime;
	Options.DegMinSecs    = DegMinSecs;
	Options.PhotoOffset   = PhotoOffset;
	Options.Track         = Track;
	Options.WriteHeading  = WriteHeading;
	Options.HeadingOffset = HeadingOffset;
	Options.MaxHeadingDelta = MaxHeadingDelta;

	/* If we only wanted to display info on the passed photos, do so now. */
	if (ShowOnlyDetails)
	{
		int result = 1;
		while (optind < argc)
		{
			result = ShowFileDetails(argv[optind++], ShowFormat, &Options) && result;
		}
		ShowFileDone(ShowFormat);
		exit(result ? EXIT_SUCCESS : EXIT_FAILURE);
	}

	/* If we wanted to delete tags, do this now. */
	if (RemoveTags)
	{
		int result = 1;
		while (optind < argc)
		{
			result = RemoveGPSTags(argv[optind++], NoChangeMtime, NoWriteExif) && result;
		}
		exit(result ? EXIT_SUCCESS : EXIT_FAILURE);
	}

	/* If we wanted to fix datestamps, do this now.
	 * This is to fix incorrect GPSDateStamp values written by versions
	 * earlier than 1.5.2. */
	if (FixDatestamps)
	{
		if (!HaveTimeAdjustment)
		{
			fprintf(stderr, _("A time offset must be given with the -z option to fix photos.\n"));
			exit(EXIT_FAILURE);
		}

		int result = 1;
		while (optind < argc)
		{
			result = FixDatestamp(argv[optind++], TimeZoneHours, TimeZoneMins, NoWriteExif) && result;
		}
		exit(result ? EXIT_SUCCESS : EXIT_FAILURE);
	}

	if (!NumTracks)
	{
		/* GPS Data was not read correctly... */
		/* Tell the user we are bailing.
		 * Not really required, seeing as ReadGPX should
		 * inform the user anyway... but, doesn't hurt! */
		fprintf(stderr, _("Cannot continue since no GPS data is available.\n"));
		exit(EXIT_FAILURE);
	}

	/* Print a legend for the matching process.
	 * If we're not being verbose. Otherwise, this would be pointless. */
	if (!ShowDetails)
	{
		printf(_("Legend: . = Ok, / = Interpolated, < = Rounded, - = No match, ^ = Too far\n"
			 "        w = Write Fail, ? = No EXIF date, ! = GPS already present\n"));
	}

	/* Make it all look nice and pretty... so the user knows what's going on. */
	printf(_("\nCorrelate: "));
	if (ShowDetails) printf("\n");

	/* A few variables that we'll require later. */
	struct GPSPoint* Result;
	char* File;
	/* Including stats on what happened. */
	int MatchExact = 0;
	int MatchInter = 0;
	int MatchRound = 0;
	int NotMatched = 0;
	int WriteFail  = 0;
	int TooFar     = 0;
	int NoDate     = 0;
	int GPSPresent = 0;
	long UsedOffset = NO_OFFSET_TIME;
	int OffsetCount = 0;

	/* Now it is time to correlate the photos. Feed one in at a time, and
	 * see what happens.*/
	/* We already checked to make sure that files were passed on the
	 * command line, so just go for it... */
	/* printf("Remaining non-option arguments: %d.\n", argc - optind); */
	while (optind < argc)
	{
		File = argv[optind++];
		long ThisOffset;
		/* Pass the file along to Correlate and see what happens. */
		Result = CorrelatePhoto(File, &ThisOffset, &Options);
		if (UsedOffset == NO_OFFSET_TIME)
			UsedOffset = ThisOffset;
		else if (ThisOffset != NO_OFFSET_TIME && ThisOffset != UsedOffset)
			/* Multiple offsets were encountered */
			++OffsetCount;

		/* Was result non-NULL? */
		if (Result)
		{
			/* Result not null. But what did happen? */
			if (Options.Result == CORR_OK)
			{
				MatchExact++;
				if (ShowDetails)
				{
					printf(_("%s: Exact match: "), File);
				} else {
					printf(".");
				}
			} else if (Options.Result == CORR_INTERPOLATED)
			{
				MatchInter++;
				if (ShowDetails)
				{
					printf(_("%s: Interpolated: "), File);
				} else {
					printf("/");
				}
			} else if (Options.Result == CORR_ROUND)
			{
				MatchRound++;
				if (ShowDetails)
				{
					printf(_("%s: Rounded: "), File);
				} else {
					printf("<");
				}
			} else if (Options.Result == CORR_EXIFWRITEFAIL)
			{
				WriteFail++;
				if (ShowDetails)
				{
					printf(_("%s: EXIF write failure: "), File);
				} else {
					printf("w");
				}
			}
			if (ShowDetails)
			{
				/* Print out the "point". */
				printf(_("Lat %f, Long %f, Elev "),
					Result->Lat, Result->Long);
				if (Result->ElevDecimals >=0)
					printf("%.3f.\n", Result->Elev);
				else
					printf(_("(unknown).\n"));
			}
			free(Result);
			/* Ok, that's all from this part... */
		} else {
			/* We got nothing back. One of a few errors. */
			if (Options.Result == CORR_NOMATCH)
			{
				NotMatched++;
				if (ShowDetails)
				{
					printf(_("%s: No match.\n"), File);
				} else {
					printf("-");
				}
			} else if (Options.Result == CORR_TOOFAR)
			{
				TooFar++;
				if (ShowDetails)
				{
					printf(_("%s: Too far from nearest point.\n"), File);
				} else {
					printf("^");
				}
			} else if (Options.Result == CORR_NOEXIFINPUT)
			{
				NoDate++;
				if (ShowDetails)
				{
					printf(_("%s: No EXIF date tag present.\n"), File);
				} else {
					printf("?");
				}
			} else if (Options.Result == CORR_GPSDATAEXISTS)
			{
				GPSPresent++;
				if (ShowDetails)
				{
					printf(_("%s: GPS Data already present.\n"), File);
				} else {
					printf("!");
				}
			}
			/* Handled all those errors, now... */
		} /* End if Result. */

		/* Display the character code immediately */
		fflush(stdout);

		/* And, once we've got here, we've finished with that file.
		 * We can now do the next one. Now wasn't that too easy? */

	} /* End while parse command line files. */

	/* Right, so now we're done. That really wasn't that hard. Right? */

	/* Add a new line if we were doing the not-show-details thing. */
	if (!ShowDetails)
	{
		printf("\n");
	}

	/* Print details of what happened. */
	printf(_("\nCompleted correlation process.\n"));
	if (ShowDetails && UsedOffset != NO_OFFSET_TIME) {
		/* This has to be shown at the end in case auto time zone
		 * was used, since it isn't known before the first file
		 * is processed. */
		if (OffsetCount)
			printf(_("Used time zone offset (multiple)\n"));
		else
			printf(_("Used time zone offset %s%ld:%02ld\n"), UsedOffset < 0 ? "-" : "",
				   labs(UsedOffset / 3600), labs(UsedOffset % 3600) / 60);
	}
	printf(_("Matched: %5d (%d Exact, %d Interpolated, %d Rounded).\n"),
			MatchExact + MatchInter + MatchRound,
			MatchExact, MatchInter, MatchRound);
	printf(_("Failed:  %5d (%d Not matched, %d Write failure, %d Too Far,\n"),
			NotMatched + WriteFail + TooFar + NoDate + GPSPresent,
			NotMatched, WriteFail, TooFar);
	printf(_("                %d No Date, %d GPS Already Present.)\n"),
			NoDate, GPSPresent);


	/* Clean up! */
	while (NumTracks > 0)
	{
		--NumTracks;
		FreeTrack(&Track[NumTracks]);
	}
	free(Track);
	free(Datum);

	if (WriteFail)
		/* A write failure is considered serious */
		return EXIT_FAILURE;

	/* Other failures aren't necessarily bad, depending on the input,
	 * so provide a different return code to distinguish them.
	 */
	return(NotMatched + TooFar + NoDate + GPSPresent ? GPS_EXIT_WARNING : EXIT_SUCCESS);
}

