/***************************************************************************************
* Filename: sortlog.c
* Author:   Richard Everitt G4ZFE
*           g4zfe@g4zfe.com
* Version:  $Header: /home/rhe/tools/RCS/sortlog.c,v 1.27 2006/03/05 11:50:25 rhe Exp $
* Purpose:  Program to sort a contest log into alphabetical order
*           and then split into txt files for use by the Log
*           Search Java applet
* Input:    Printer output file (.prt), ascii log (.res or .log),
*           ADIF (Amateur Radio Data Interchange Format) or Cabrillo
* Changes:
*           15/JUN/97: Added ADIF support
*           12/DEC/97: Added nodate, notime flags
*           31/DEC/97: SORTing fixed.
*           04/MAR/98: ADIF support improved
*           03/SEP/98: Extract ADIF "Comment" field
*           11/OCT/98: Number of QSOs changed from int to long
*                      Added support for SAT QSOs
*			13/FEB/99: Added <FREQ> support for ADIF
*					   Cache date for ADIF logs that do not have 
*					   date in each record.
*           02/SEP/00: Update ADIF support
*                      Just <EOH> needed for ADIF detection   
*			11/MAR/01: ADIF fixes from DF4XX (many thanks!!)     
*           22/SEP/01: ADIF fixes from BA4RF (many thanks!!)
*                      Added support for cabrillo format
*           26/OCT/01: ADIF <FREQ> support fixed
*           13/NOV/01: Added -lower switch to create lower case filenames
*                      Fixed sorting of output files (dependant on callsign position)
*                      Added more Cabrillo log formats
*                      ADIF fix for EA6VQ
*                      Fixed overflow error with long (> 80 chars) ADIF lines
*			11/JAN/02: Fixed error when fields parsed as callsign start with non-alnum
*           23/JAN/02: ADIF: if both BAND and FREQ data available then use BAND
*           26/JAN/02: ADIF: specify ADIF tags exactly to prevent conflict with text
*                      in comments field
*                      ADIF: read COMMENTS field correctly
*           16/JUN/02: Added code fix from AD1C for callsigns such as LX/DF0AR
*                      Added PSK31 as a known mode. PSK31 is truncated to PSK for 
*                      display
*                      Fixed problem with very long ADIF lines (>255 chars)
*		    16/NOV/02: Added MFSK16, HELL and TOR as known modes. 
*		       MFSK16 is truncated to MFSK for display
*           19/DEC/02: Fixed problems decoding frequency in XMLog .txt files
*           22/JAN/03: Fixed problem with very very long ADIF lines (>512 chars)
*           06/JUL/03: Added support for PSK63/WSJT/FSK441/JT6M/JT44. All modes are
*				       truncated to 2 characters. Truncated RTTY to RY.
*                      Added support for the new 5 MHz/60m band
*			20/MAR/04: Default to using nodate and notime
*			22/MAY/04: ADIF: if both BAND and FREQ data available then use BAND
*			19/SEP/04: Added BPSK63 as a known mode
*           01/JAN/05: Added Kids Roundup to list of known Cabrillo contests
*           12/APR/05: BUGFIX: Frequency 3.6 was been truncated to 3. in the output file
*			05/MAR/06: Rationalisation of ADIF modes to CW/PHO/IMG/DAT as per LoTW.
****************************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define NUMB_COMMON_PREFIXES    13
#define TRUE                    1
#define FALSE                   0

/* Cabrillo contest types				*/
#define CBR_DEFAULT				0
#define	CBR_IOTA				1
#define CBR_ARRLVHF				2
#define CBR_CQWWRTTY			3
#define CBR_SWEEPSTAKES			4
#define	CBR_IARU				5
#define	CBR_APSPRINT			6
#define	CBR_ARRL10				7
#define	CBR_ARRL160				8
#define	CBR_ARRLDX				9
#define	CBR_CQWPX				10
#define	CBR_RSGB2128			11
#define CBR_KIDS				12

#define	SPACE_CHAR				32

#define	MAX_FREQ_LENGTH			6		/* truncate ADIF frequencies to 5 characters	*/

/* Columns to write out;                */
int column = 0;
int date_pos = -1;
int time_pos = -1;
int band_pos = -1;
int mode_pos = -1;
int call_pos = -1;

/* Streams to the most common callsigns. The files containing the most  */
/* common callsigns are left open to attempt to improve performance     */
/* This is needed as I was unable to get my DOS compiler to allow more  */
/* than 20 files to be opened at once! Tell me how to do this!          */
FILE *temp_files[NUMB_COMMON_PREFIXES];

int ADIF_format = FALSE;
int Cabrillo_format = FALSE;

int no_date_flag = TRUE;
int no_time_flag = TRUE;
int comment_flag = FALSE;

int lower_case_filenames_flag = FALSE;

int callsign_column = 0;

int cabrillo_contest = CBR_DEFAULT;		/* Which Cabrillo format the log is in	*/

void close_temp_file ()
{
	int i;

	for (i=0; i < NUMB_COMMON_PREFIXES; i++)
		fclose (temp_files[i]);
}

void  convert_mode (const char *input_mode, char *output_mode)
{
		/* Set mode - CW/PHO/IMG/DAT								*/
		/* CW mode													*/
		if (! strcmp (input_mode,"CW") || ! strcmp (input_mode,"A1A"))
		{
			strcpy (output_mode,"CW");
			return;
		}	

		/* PHONE modes												*/
		if (! strcmp (input_mode,"PHONE") || ! strcmp (input_mode,"AM") || !strcmp (input_mode,"FM")  || !strcmp (input_mode,"SSB")
			|| !strcmp (input_mode,"LSB")  || !strcmp (input_mode,"USB") || !strcmp (input_mode,"J3E")  || !strcmp (input_mode,"F3E")
			|| !strcmp (input_mode,"A3E"))
		{
			strcpy (output_mode,"PHO");			
			return;
		}

		/* IMAGE modes												*/
		if (! strcmp (input_mode,"ATV") || ! strcmp (input_mode,"FAX") || !strcmp (input_mode,"SSTV") || !strcmp (input_mode,"IMAGE"))
		{
			strcpy (output_mode,"IMG");
			return;
		}

		/* DATA modes												*/
		if (! strcmp (input_mode,"AMTOR") || ! strcmp (input_mode,"CLOVER") || !strcmp (input_mode,"FSK31") || !strcmp (input_mode,"FSK441")
			|| ! strcmp (input_mode,"GTOR") || ! strcmp (input_mode,"HELL") || !strcmp (input_mode,"HFSK") || !strcmp (input_mode,"JT65")
			|| ! strcmp (input_mode,"MFSK16") || ! strcmp (input_mode,"MFSK8") || !strcmp (input_mode,"MINIRTTY") || !strcmp (input_mode,"MT63")
			|| ! strcmp (input_mode,"OLIVIA") || ! strcmp (input_mode,"PACKET") || !strcmp (input_mode,"PACTOR") || !strcmp (input_mode,"PSK125")
			|| ! strcmp (input_mode,"PSK31") || ! strcmp (input_mode,"PSK63") || !strcmp (input_mode,"Q15") || !strcmp (input_mode,"RTTY")
			|| ! strcmp (input_mode,"THROB") || ! strcmp (input_mode,"TOR") || !strcmp (input_mode,"RY") || !strcmp (input_mode,"DATA"))
		{
			strcpy (output_mode,"DAT");
			return;
		}

		/*	Unknown mode											*/
		strcpy (output_mode,"DAT");
}

void open_temp_file (char first_char, FILE *out)
{
	if (lower_case_filenames_flag == TRUE)
		first_char = tolower(first_char);

	switch (first_char)
	{
		case 'K':
		case 'k':
			out = temp_files[0];
			break;
		case 'O':
		case 'o':
			out = temp_files[1];
			break;
		case 'W':
		case 'w':
			out = temp_files[2];
			break;
		case 'D':
		case 'd':
			out = temp_files[3];
			break;
		case 'U':
		case 'u':
			out = temp_files[4];
			break;
		case 'S':
		case 's':
			out = temp_files[5];
			break;
		case 'N':
		case 'n':
			out = temp_files[6];
			break;
		case 'R':
		case 'r':
			out = temp_files[7];
			break;
		case 'H':
		case 'h':
			out = temp_files[8];
			break;
		case 'Y':
		case 'y':
			out = temp_files[9];
			break;
		case 'I':
		case 'i':
			out = temp_files[10];
			break;
		case 'L':
		case 'l':
			out = temp_files[11];
			break;
		case 'G':
		case 'g':
			out = temp_files[12];
			break;
		default:
			out = NULL;
			break;
	}
}

void write_temp_file (char first_char, char *line)
{
	FILE *out = NULL;
	char tmp_fname [6];

	/* Write a line to the temporary file. If the callsign is a     */
	/* "common" callsign then the file does not need to be opened.  */
	/* Otherwise open the file and write the line and then close.   */

	/* Ignore invalid characters									*/
	if (!isalnum(first_char))
		return;

	open_temp_file (first_char, out);

	if (out == NULL)
	{
		if (lower_case_filenames_flag == TRUE)
				sprintf (tmp_fname,"%c.tmp",tolower(first_char));
		else
				sprintf (tmp_fname,"%c.TMP",first_char);

		out = fopen (tmp_fname, "a");
		fprintf (out, line);
		fclose (out);
	}
	else
		fprintf (out, line);
}



int check_band (char *p)
{
	int z;

	/* band must be between 1 and 7 characters long */
	if (strlen(p) < 1 || strlen(p) > 7)
		return (FALSE);

	z = strlen(p);
	/* band must have '. , :' if more than 4 characters	*/
	if (strlen(p) > 4)
	{
		if (strchr (p,'.') == NULL &&
		    strchr (p,',') == NULL &&
		    strchr (p,':') == NULL )

		return (FALSE);
	}

	if ( strstr (p,"1.") != NULL
	     || strstr (p,"3.") != NULL
	     || strstr (p,"5.") != NULL
	     || strstr (p,"7")  != NULL
	     || strstr (p,"14") != NULL
	     || strstr (p,"18") != NULL
	     || strstr (p,"21") != NULL
	     || strstr (p,"24") != NULL
	     || strstr (p,"28") != NULL
	     || strstr (p,"160") != NULL
	     || strstr (p,"75") != NULL
	     || strstr (p,"80") != NULL
	     || strstr (p,"60") != NULL
	     || strstr (p,"40") != NULL
	     || strstr (p,"30") != NULL
	     || strstr (p,"20") != NULL
	     || strstr (p,"17") != NULL
	     || strstr (p,"12") != NULL
	     || strstr (p,"15") != NULL
	     || strstr (p,"50") != NULL
	     || strstr (p,"70") != NULL
	     || strstr (p,"144") != NULL   
	     || strstr (p,"430") != NULL   
	     || strstr (p,"SAT") != NULL   
	     || strstr (p,"10") != NULL)
		return (TRUE);
	else
		return (FALSE);

}       

int check_mode (char *p)
{
	/* Mode must be between 2 and 5 chars long      */
	if (strlen(p) < 2 || strlen(p) >5)
		return (FALSE);

	if (! strcmp (p,"SSB")
	    || !strcmp (p,"J3E")
	    || !strcmp (p,"PHO")	    
		|| !strcmp (p,"SSB")
	    || !strcmp (p,"IMAGE")
		|| !strcmp (p,"DATA")
	    || !strcmp (p,"RTTY")
		|| !strcmp (p,"SSTV")
	    || !strcmp (p,"F1B")
	    || !strcmp (p,"FSK")
	    || !strcmp (p,"AM")
		|| !strcmp (p,"CW")
	    || !strcmp (p,"A1A")
		|| !strcmp (p,"FM")
		|| !strcmp (p,"AMTOR")		
		|| !strcmp (p,"PACKET")
		|| !strcmp (p,"TOR")		
		|| !strcmp (p,"ATV")		
		|| !strcmp (p,"FAX")
		|| !strcmp (p,"MFSK8")
		|| !strcmp (p,"MFSK16")
		|| !strcmp (p,"CLOVER")
		|| !strcmp (p,"OLIVIA")
		|| !strcmp (p,"HFSK")
		|| !strcmp (p,"MFSK")
		|| !strcmp (p,"HELL")
		|| !strcmp (p,"THROB")
		|| !strcmp (p,"PSK")		
		|| !strcmp (p,"PSK63")
		|| !strcmp (p,"BPSK63")
		|| !strcmp (p,"WSJT")
		|| !strcmp (p,"JT6M")
		|| !strcmp (p,"JT6M")
		|| !strcmp (p,"JT65")		
		|| !strcmp (p,"MT63")		
		|| !strcmp (p,"JT44")
		|| !strcmp (p,"Q15")
		|| !strcmp (p,"FSK31")
		|| !strcmp (p,"FSK441")
		|| !strcmp (p,"PSK125")
		|| !strcmp (p,"PSK63")
	    || !strcmp (p,"PSK31"))
		return (TRUE);
	else
		return (FALSE);
}

int check_call (char *p)
{
	int i;
	int numeric_count = 0;
	int alpha_count = 0;

	/* Callsign must be at least 3 characters long          */
	if (strlen(p) < 3)
		return (FALSE);

	/* Callsign is only alpha-numerics and "/" chars        */
	for (i=0; i < (int) strlen(p); i++)
	{
		if (isalnum (p[i]) || strchr(p,'/'))
		{                               
			/* valid character for callsign */
		}
		else
			return (FALSE);
	}

	/* Check that the callsign contains at least one        */
	/* numeric and alpha                                    */
	for (i=0; i < (int) strlen(p); i++)
	{               
		if (isdigit (p[i]))
			numeric_count++;

		if (isalpha (p[i]))
			alpha_count++;
	}

	if (numeric_count == 0 || alpha_count == 0)
		return (FALSE);

	numeric_count=0; alpha_count=0;

	/* Ignore text (i.e > 4 characters) as callsigns		*/
	for (i=0; i < (int) strlen(p); i++)
	{               
		if (isdigit (p[i]))
			break;

		if (isalpha (p[i]))
			alpha_count++;

		/* Break on slash character to allow for callsigns	*/
		/* such as LX/DF0AR									*/
		if (p[i] == '/')
			break;
	}

	if (alpha_count > 3)
		return (FALSE);

	return (TRUE);
}

void parse_line (char *p)
{
	int i;

	/* Is this the date?                    */
	if (date_pos == -1 && strlen(p) >= 6 )
	{
		if (strlen(p) == 6 || (strchr (p,'-') || strchr (p,'/')))
		{
			date_pos = column;
			fprintf(stderr,"Using column %d \(%s\) as date\n", date_pos, p);
			return;
		}
	}

	/* Is this the time?                    */
	if (strlen(p) >= 4 && time_pos == -1)
	{
		i = 0;
		while (i < (int) strlen(p) && isdigit (p[i]))
			i++;

		if (i == (int) strlen(p))
		{
			time_pos = column;
			fprintf (stderr,"Using column %d \(%s\) as time\n", time_pos, p);
			return;
		}
	}

	/* Is this the band?                    */
	if (band_pos == -1)
	{
		if (check_band (p))
		{
			band_pos = column;
			fprintf (stderr,"Using column %d \(%s\) as band\n", band_pos, p);
			return;
		}
	}

	/* Is this the mode?                    */
	if (mode_pos == -1)
	{
		if (check_mode (p))
		{
			mode_pos = column;
			fprintf (stderr,"Using column %d \(%s\) as mode\n", mode_pos, p);
			return;
		}
	}

	/* Is this the callsign?                */
	if (call_pos == -1)
	{
		if (check_call (p))
		{
			call_pos = column;
			fprintf (stderr,"Using column %d \(%s\) as callsign\n", call_pos, p);
			return;
		}
	}
}

int check_valid_line (char ipline[])
{
	int i;
	char *p;
	int call_ok = FALSE;
	char local_ipline[128];

	if (strlen(ipline) > 1)
	{
		ipline[strlen(ipline)-1] = '\0';
	}
	else
	{
		/* Empty line             */
		return (FALSE);
	}

	/* Convert to upper case          */
	for (i=0; i <= (int) strlen(ipline); i++)
		local_ipline[i] = toupper (ipline[i]);

	/* Check if ADIF format            */
	if (strstr (local_ipline, "ADIF") != NULL || 
		strstr (local_ipline,"<EOH>") != NULL ||
		strstr (local_ipline,"EA6VQ") != NULL)
	{
		ADIF_format = TRUE;
		return (TRUE);	
	}

	/* Check if cabrillo format            */
	if (strstr (local_ipline, "START-OF-LOG:") != NULL )
	{
		Cabrillo_format = TRUE;
		return (TRUE);	
	}

	/* Ignore lines with Line Feeds in */
	if (strchr(local_ipline,12))                  /* LF   */
		return (FALSE);

	/* Ignore lines with "PAGE" in     */
	if (strstr (local_ipline, "PAGE") != NULL)
		return (FALSE);

	/* Ignore lines with "CALL" in     */
	if (strstr (local_ipline, "CALL") != NULL)
		return (FALSE);

	/* Ignore lines with "WINLOG" in     */
	if (strstr (local_ipline, "WINLOG") != NULL)
		return (FALSE);

	/* Ignore lines with "LOGNAME" in     */
	if (strstr (local_ipline, "LOGNAME") != NULL)
		return (FALSE);

	/* Check that the line contains a callsign      */
	p = strtok (local_ipline, " ");
	call_ok = check_call (p);

	if (call_ok)
		return (TRUE);

	while ( (p = strtok (NULL, " ")) != NULL)
	{
		call_ok = check_call (p);

		if (call_ok)
			return (TRUE);
	}

	return (FALSE);
}

void process_ADIF_format (FILE* in, char *iline)
{
	long qso_count = 0;
	char ipline [1024];
	char line[1024];
	char opline [1024];
	int start = FALSE;
	int end = FALSE;
	int comment_found = FALSE;
	int band_or_freq_found = FALSE;	/* Process band or freq whichever is first, not both!	*/
	char call[512];
	char date[512];
	char previous_date[512];		/* XMLOG doesn't write date to every record	*/
	char time[512];
	char band[512];
	char mode[512];
	char comment[512];
	int length;
	int i;
	int values;
	char dummy;
	char *p;

	if (strlen(iline) > 1)
	{
		line[strlen(iline)-1] = '\0';
	}

	/* Convert to upper case          */
	for (i=0; i <= (int) strlen(iline); i++)
		ipline[i] = toupper (iline[i]);

	/* Check if ADIF format            */
	if (strstr (ipline,"<EOH>") != NULL)
	{
		/* End of Header found - no need to skip	*/
	}
	else
	{
	/* Skip ADIF header info                        */
	while (start ==  FALSE)
	{
		if (fgets (ipline, sizeof(ipline), in) == NULL)
			return;

		/* Convert to upper case        */
		for (i=0; i <= (int) strlen(ipline); i++)
			line[i] = toupper (ipline[i]);

		if (strstr (line, "<EOH>") != NULL)
			start = TRUE;
	}
	}

	fprintf (stderr,"\nADIF format detected\n");
	fprintf (stderr, "\nEach dot is 100 QSOs. Writing logs\n");

	/* Read in the rest of the input file           */
	while (fgets (ipline, sizeof(ipline), in))
	{
		/* Skip blank lines                     */
		while (strlen (ipline) <= 1)
		    if (fgets (ipline, sizeof(ipline), in) == NULL)
			{
				fprintf (stderr, "\n\n%d QSOs processed\n", qso_count);
				return;
			}

		/* Remove LF characeters				*/
		ipline [sizeof(ipline) - 1] = '\0';

		/* Clear out the previous values        */
		memset (date, SPACE_CHAR, sizeof(date));
		memset (time, SPACE_CHAR, sizeof(time));
		memset (band, SPACE_CHAR, sizeof(band));
		memset (mode, SPACE_CHAR, sizeof(mode));
		memset (call, SPACE_CHAR, sizeof(call));
		memset (comment, SPACE_CHAR, sizeof(comment));

		/* ADIF record may be on > 1 line       */
		end = FALSE;
		comment_found = FALSE;
		band_or_freq_found = FALSE;

		while (end == FALSE)
		{
			/* Skip blank lines             */
			while (strlen (ipline) <= 1)
			    if (fgets (ipline, sizeof(ipline), in) == NULL)
				{
					return;
				}

			/* Convert to upper case        */
			for (i=0; i <= (int) strlen(ipline); i++)
				line[i] = toupper (ipline[i]);

			/* Read following fields:       */
			/* Call, QSO_Date, Time_On, Mode*/
			/* Band. Allow for both         */
			/* <qso_date:8> and             */
			/* <qso_date:8:d> formats       */

			/* Parse QSO details			*/
			p = strstr (line, "\<CALL");
			if (p != NULL)
			{
				values = sscanf (p, "\<CALL:%d>%s ", &length,&call);

				if (values != 2)
					sscanf (p, "\<CALL:%d:%c>%s ", &length,&dummy,&call);

				call[length] = '\0';
			}

			if (comment_flag == TRUE)
			/* Read the Comment field       */
			{
				if ((p = strstr (line, "\<COMMENT")) != NULL)
				{
					comment_found = TRUE;

					comment[0] = '*';
					comment[1] = '*';
					comment[2] = ' ';
/*					values = sscanf (p, "\<COMMENT:%d>%s", &length,&comment[3]);*/
					values = sscanf (p, "\<COMMENT:%d>", &length);
					strncpy (&comment[3], p + 12, length);

					if (values != 1)
					{
						sscanf (p, "\<COMMENT:%d:%c>", &length,&dummy);
						strncpy (&comment[3], p + 15, length);
					}

					comment [length+3] = '\0';

				}
			}

			if (comment_found == FALSE)
				comment[0] = '\0';

			if ((p = strstr (line, "\<BAND")) != NULL && !band_or_freq_found)
			{
				values = sscanf (p, "\<BAND:%d>%s ", &length,&band);

				if (values != 2)
					sscanf (p, "\<BAND:%d:%c>%s ", &length,&dummy,&band);
				
				/* Strip the "M" off e.g 40M becomes 40 */
				if ((p = strchr (band, 'M')) != NULL)
					*p = '\0';

				/* Strip the "CM" off e.g 70CM becomes 70 */
				if ((p = strchr (band, 'C')) != NULL)
					*p = '\0';

				band_or_freq_found = TRUE;
			}

			if ((p = strstr (line, "\<FREQ")) != NULL && !band_or_freq_found)
			{
				values = sscanf (p, "\<FREQ:%d>%s ", &length,&band);

				if (values != 2)
					sscanf (p, "\<FREQ:%d:%c>%s ", &length,&dummy,&band);

				/* Trim frequency											*/
				if (length > MAX_FREQ_LENGTH)
					band [MAX_FREQ_LENGTH] = '\0';
				else
					band[length] = '\0';

				band_or_freq_found = TRUE;
				
			}


			/* Ignore PROP_MODE and other _MODE records							*/
			if ((p = strstr (line, "\<MODE")) != NULL)
			{
				values = sscanf (p, "\<MODE:%d>%s ", &length,&mode);

				if (values != 2)
					sscanf (p, "\<MODE:%d:%c>%s ", &length,&dummy,&mode);

				mode[length] = '\0';	

				/* Convert the mode to one of the following as per LoTW - CW/PHO/IMG/DAT				*/
				convert_mode (&mode, &mode);

			}

			if ((p = strstr (line, "\<QSO_DATE")) != NULL)
			{
				values = sscanf (p, "\<QSO_DATE:%d>%s ", &length, &date);

				if (values != 2)
					sscanf (p, "\<QSO_DATE:%d:%c>%s ", &length, &dummy, &date);

				date[length] = '\0';	

				/* Keep track of the date in case the next record doesn't have	*/
				/* the date (as in XMLOG)										*/
				strcpy (previous_date, date);
			}

			if ((p = strstr (line, "\<TIME_ON")) != NULL)
			{
				values = sscanf (p, "\<TIME_ON:%d>%s ",&length, &time);

				if (values != 2)
					sscanf (p, "\<TIME_ON:%d:%c>%s ",&length, &dummy, &time);

				time[length] = '\0';

				/* Ignore seconds part of time  */
				if (strlen(time) > 4)
					time[4] = '\0';
			}

			/* Continue reading until End   */
			/* Of Record                    */
			if (strstr (line, "<EOR>") != NULL)
				end = TRUE;
			else
			{
				end = FALSE;	
				fgets (ipline, sizeof(ipline), in);
			}
		}

		qso_count++;
		
		/* Display some progress!               */
		if (qso_count % 100 == 0)
			putchar ('.');

		if (qso_count % 8000 == 0)
			putchar ('\n');

		if (!strcmp (date,""))
		{
			strcpy (date, previous_date);
		}

		if (comment_flag == FALSE)
		{
			if (no_date_flag == TRUE && no_time_flag == TRUE)
			{
				sprintf (opline,"% 8s% 4s %s\n",
					band,
					mode,
					call);

				callsign_column = 14;
			}

			if (no_date_flag == TRUE && no_time_flag == FALSE)
			{
				sprintf (opline,"% 4s % 8s% 4s %s\n",
					time,
					band,
					mode,
					call);

				callsign_column = 19;
			}

			if (no_date_flag == FALSE && no_time_flag == TRUE)
			{
				sprintf (opline,"% 9s % 8s% 4s %s\n",
					date,
					band,
					mode,
					call);

				callsign_column = 24;
			}

			if (no_date_flag == FALSE && no_time_flag == FALSE)
			{
				sprintf (opline,"% 9s % 4s % 8s% 4s %s\n",
					date,
					time,
					band,
					mode,
					call);

				callsign_column = 29;
			}
		}
		else
		{
			if (no_date_flag == TRUE && no_time_flag == TRUE)
			{
				sprintf (opline,"% 8s% 4s %s %s\n",
					band,
					mode,
					call,
					comment);

				callsign_column = 14;
			}

			if (no_date_flag == TRUE && no_time_flag == FALSE)
			{
				sprintf (opline,"% 4s % 8s% 4s %s %s\n",
					time,
					band,
					mode,
					call,
					comment);

				callsign_column = 19;
			}

			if (no_date_flag == FALSE && no_time_flag == TRUE)
			{
				sprintf (opline,"% 9s % 8s% 4s %s %s\n",
					date,
					band,
					mode,
					call,
					comment);

				callsign_column = 24;
			}

			if (no_date_flag == FALSE && no_time_flag == FALSE)
			{
				sprintf (opline,"% 9s % 4s % 8s% 4s %s %s\n",
					date,
					time,
					band,
					mode,
					call,
					comment);

				callsign_column = 29;
			}
		}

		/* Save QSO details to temp file        */
		write_temp_file (call[0], opline);
	}

	fprintf (stderr, "\n\n%d QSOs processed\n", qso_count);
}

void read_Cabrillo_line (char *line, int qso_count)
{
	char call[80];
	char date[80];
	char time[80];
	char band[80];
	char mode[80];
	char comment[80];
	char opline [80];
	char *p;
	int values;

	/* each Cabrillo row starts with QSO:	*/
	p = strstr (line, "QSO:");
	if (p == NULL) 
		return;

	/* Clear out the previous values        */
	strcpy (date, "                    ");
	strcpy (time, "                    ");
	strcpy (band, "                    ");
	strcpy (mode, "                    ");
	strcpy (call, "                    ");
	strcpy (comment, "                    ");

	/* Cabrillo (anonyingly) has a number of different formats dependent on the	*/
	/* contest. Thus need to check which format and how many columns in each of	*/
	/* rows																		*/

	switch (cabrillo_contest)
	{
		case CBR_ARRLVHF:
		/* IOTA has 8 columns				*/
		values = sscanf (p, "QSO: %s %s %s %s %s %s %s %s",
		&band, &mode, &date, &time, &comment, &comment, &call, &comment);
		break;

		case CBR_IARU:
		case CBR_APSPRINT:
		case CBR_ARRL10:
		case CBR_ARRL160:
		case CBR_ARRLDX:
		case CBR_CQWPX:
		/* IOTA has 10 columns				*/
		values = sscanf (p, "QSO: %s %s %s %s %s %s %s %s %s %s",
		&band, &mode, &date, &time, &comment, &comment, &comment, &call, 
		&comment, &comment);
		break;

		case CBR_IOTA:
		case CBR_CQWWRTTY:
		/* IOTA has 12 columns				*/
		values = sscanf (p, "QSO: %s %s %s %s %s %s %s %s %s %s %s %s",
		&band, &mode, &date, &time, &comment, &comment, &comment, &comment, &call, 
		&comment, &comment, &comment);
		break;

		case CBR_RSGB2128:
		/* RSGB 21/28 MHz has 12 columns	*/
		values = sscanf (p, "QSO: %s %s %s %s %s %s %s %s %s %s %s %s %s",
		&band, &mode, &date, &time, &comment, &comment, &comment, &call, 
		&comment, &comment, &comment, &comment);
		break;

		case CBR_KIDS:
		/* Kids Roundup has 12 columns	*/
		values = sscanf (p, "QSO: %s %s %s %s %s %s %s %s %s %s %s %s",
		&band, &mode, &date, &time, &comment, &comment, &comment, &comment, &call, 
		&comment, &comment, &comment);
		break;

		case CBR_SWEEPSTAKES:
		/* Sweepstakes has 14 columns				*/
		values = sscanf (p, "QSO: %s %s %s %s %s %s %s %s %s %s %s %s %s %s",
		&band, &mode, &date, &time, &comment, &comment, &comment, &comment, &comment, &call, 
		&comment, &comment, &comment, &comment);
		break;

		default:
		/* Default has 8 columns			*/
/*		values = sscanf (p, "QSO: %s %s %s %s %s %s %s %s",
		&band, &mode, &date, &time, &comment, &call, &comment, &comment);*/
		/* These contests have 10 columns			*/
		values = sscanf (p, "QSO: %s %s %s %s %s %s %s %s %s %s",
		&band, &mode, &date, &time, &comment, &comment, &comment, &call, &comment, 
		&comment);
	}

	if (values == EOF)
	{
		fprintf (stderr,"sortlog: Internal Error - please e-mail log to G4ZFE!!\n");
		exit (1);
	}

	/* Convert the mode to one of the following as per LoTW - CW/PHO/IMG/DAT				*/
	convert_mode (&mode, &mode);


	/* Display some progress!               */
	if (qso_count % 100 == 0)
		putchar ('.');

	if (qso_count % 8000 == 0)
		putchar ('\n');

	/* Write to temporary file. Don't forget to strip off date and time	*/
	/* info if this has been requested									*/
	/* format "band" as 8 characters as it is the frequency				*/
	if (no_date_flag == TRUE && no_time_flag == TRUE)
	{
		sprintf (opline,"% 8s% 4s %s\n",
			band,
			mode,
			call);

		callsign_column = 14;
	}

	if (no_date_flag == TRUE && no_time_flag == FALSE)
	{
		sprintf (opline,"% 4s % 8s% 4s %s\n",
			time,
			band,
			mode,
			call);

		callsign_column = 19;
	}

	if (no_date_flag == FALSE && no_time_flag == TRUE)
	{
		sprintf (opline,"% 9s % 8s% 4s %s\n",
			date,
			band,
			mode,
			call);

		callsign_column = 24;
	}

	if (no_date_flag == FALSE && no_time_flag == FALSE)
	{
		sprintf (opline,"% 9s % 4s % 8s% 4s %s\n",
			date,
			time,
			band,
			mode,
			call);

		callsign_column = 29;
	}

	/* Save QSO details to temp file        */
	write_temp_file (call[0], opline);
}

void process_Cabrillo_format (FILE* in, char *iline)
{
	char ipline [255];
	char line[255];
	int start = FALSE;
	char contest[80];
	int i;
	int values;
	long qso_count = 0;

	if (strlen(iline) > 1)
	{
		line[strlen(iline)-1] = '\0';
	}

	/* Convert to upper case          */
	for (i=0; i <= (int) strlen(iline); i++)
		ipline[i] = toupper (iline[i]);

	/* Check if Cabrillo format            */
	if (strstr (ipline,"SOAPBOX:") != NULL)
	{
		/* End of Header found - no need to skip	*/
	}
	else
	{
		/* Skip Cabrillo header info                        */
		while (start ==  FALSE)
		{
			if (fgets (ipline, sizeof(ipline), in) == NULL)
				return;

			/* Convert to upper case        */
			for (i=0; i <= (int) strlen(ipline); i++)
				line[i] = toupper (ipline[i]);

			if (strstr (line, "CONTEST:") != NULL)
			{
				/* Check for IOTA contest as this has additional columns	*/
				values = sscanf (line, "CONTEST: %s ",&contest);
				if (strstr (contest, "RSGB-IOTA") != NULL)	
					cabrillo_contest = CBR_IOTA;
				else if (strstr (contest, "ARRL-VHF-SEP") != NULL)	
					cabrillo_contest = CBR_ARRLVHF;
				else if (strstr (contest, "CQ-WW-RTTY") != NULL)	
					cabrillo_contest = CBR_CQWWRTTY;
				else if (strstr (contest, "ARRL-SS-CW") != NULL)	
					cabrillo_contest = CBR_SWEEPSTAKES;
				else if (strstr (contest, "IARU") != NULL)	
					cabrillo_contest = CBR_IARU;
				else if (strstr (contest, "AP-SPRINT") != NULL)	
					cabrillo_contest = CBR_APSPRINT;
				else if (strstr (contest, "ARRL-10") != NULL)	
					cabrillo_contest = CBR_ARRL10;
				else if (strstr (contest, "ARRL-160") != NULL)	
					cabrillo_contest = CBR_ARRL160;
				else if (strstr (contest, "ARRL-DX") != NULL)	
					cabrillo_contest = CBR_ARRLDX;
				else if (strstr (contest, "CQ-WPX") != NULL)	
					cabrillo_contest = CBR_CQWPX;
				else if (strstr (contest, "RSGB 10") != NULL)	
					cabrillo_contest = CBR_RSGB2128;
				else if (strstr (contest, "RSGB 21") != NULL)	
					cabrillo_contest = CBR_RSGB2128;
				else if (strstr (contest, "KIDS") != NULL)	
					cabrillo_contest = CBR_KIDS;
				else
					cabrillo_contest = CBR_DEFAULT;
			}

			/* Continue reading the header until we get to the QSO data		*/
			if (strstr (line, "QSO:") != NULL)
				start = TRUE;
		}
	}

	fprintf (stderr,"\nCabrillo format detected\n");
	fprintf (stderr, "\nEach dot is 100 QSOs. Writing logs\n");

	qso_count++;
	read_Cabrillo_line (line, qso_count);

	ipline [0] = '\0';

	/* Read in the rest of the input file           */
	while (fgets (ipline, sizeof(ipline), in))
	{
		/* Skip blank lines                     */
		while (strlen (ipline) <= 1)
		    if (fgets (ipline, sizeof(ipline), in) == NULL)
			return;

		ipline [strlen(ipline)] = '\0';

		/* Convert to upper case        */
		for (i=0; i <= (int) strlen(ipline); i++)
			line[i] = toupper (ipline[i]);

		/* Check if the end of the log has been reached	*/
		if (strstr (line, "END-OF-LOG:") != NULL)
		{
			fprintf (stderr, "\n\n%d QSOs processed\n", qso_count);
			return;
		}

		qso_count++;
		read_Cabrillo_line (line, qso_count);

	}
}

void sort_logs ()
{
	char sort_cmd [80];
	char ch;


	/* Sort the temporary files                     */
	for (ch='0'; ch<='Z'; ch++)
	{
		if (!isalnum(ch))
			continue;

		/* Display some progress                */
		if (lower_case_filenames_flag == TRUE)
			putchar (tolower(ch));
		else
			putchar (ch);


		/* Determine which column the callsign  */
		/* is in so we can sort on the callsign */
		if (lower_case_filenames_flag == TRUE)
			sprintf (sort_cmd," sort /+%d < %c.tmp > %c.txt",callsign_column, tolower(ch),tolower(ch));
		else
			sprintf (sort_cmd," sort /+%d < %c.TMP > %c.TXT",callsign_column, ch,ch);
		

		/* Sort the file by callsign column    */
		system (sort_cmd);

		/* Delete the temporary file            */
		if (lower_case_filenames_flag == TRUE)
			sprintf (sort_cmd, "%c.tmp",tolower(ch));
		else
			sprintf (sort_cmd, "%c.TMP",ch);

		remove (sort_cmd);
	}

	putchar ('\n');
}

int main (int argc, char **argv)
{
	FILE *in, *out;
	int i=0;
	int j=0;
	char ipline[512];
	char line[512];
	char opline [80];
	char *p;
	char data[20][30];
	char filename [80];

	long qso_numb = 0;
	char txt_file [6];

	char ch;

	/* Check that a filename argument has been passed      */
	if (argc < 2)
	{
		fprintf (stderr, "usage: sortlog [-nodate | -date | -notime | -time] [-comment -lower] filename\n\n");
		fprintf (stderr, "       -nodate:  produce output files with no date information (default)\n");
		fprintf (stderr, "       -date:    produce output files with date information\n");
		fprintf (stderr, "       -notime:  produce output files with no time information (default)\n");
		fprintf (stderr, "       -time:    produce output files with time information\n");
		fprintf (stderr, "       -comment: leave ADIF commemt lines in the output files\n");
		fprintf (stderr, "       -lower:   create output files with lower case filenames\n");
		fprintf (stderr, "For Internet logs it is recommended to use the default -nodate and -notime parameters\n");
		exit (1);
	}

	if (argv [1][0] == '-' && (argv [1][1] == 'v' || argv [1][1] == 'V'))
	{
		fprintf (stderr,"G4ZFE sortlog: Version 1.27 - 05/MAR/2006\n");
		exit (0);
	}

	/* Check for command line switches              */
	if (argc >= 2)
	{
		for (i=1; i < argc; i++)
		{
			if (argv[i][0] == '-')
			{
				for (j=1; j <= (int) strlen(argv[1]); j++)
					argv[i][j] = toupper (argv[i][j]);

				if (!strcmp (argv[i], "-DATE"))
					no_date_flag = FALSE;

				if (!strcmp (argv[i], "-TIME"))
					no_time_flag = FALSE;

				if (!strcmp (argv[i], "-COMMENT"))
					comment_flag = TRUE;

				if (!strcmp (argv[i], "-LOWER"))
					lower_case_filenames_flag = TRUE;

				if (!strcmp (argv[i], "-HELP") || !strcmpi (argv[i], "-h") || !strcmpi (argv[i], "-?"))
				{
					fprintf (stderr, "usage: sortlog [-nodate | -date | -notime | -time] [-comment -lower] filename\n\n");
					fprintf (stderr, "       -nodate:  produce output files with no date information (default)\n");
					fprintf (stderr, "       -date:    produce output files with date information\n");
					fprintf (stderr, "       -notime:  produce output files with no time information (default)\n");
					fprintf (stderr, "       -time:    produce output files with time information\n");
					fprintf (stderr, "       -comment: leave ADIF commemt lines in the output files\n");
					fprintf (stderr, "       -lower:   create output files with lower case filenames\n");
					fprintf (stderr, "For Internet logs it is recommended to use the default -nodate and -notime parameters\n");
					exit (1);
				}
			}
		}
	}

	strcpy (filename, "");

	for (i=1; i < argc; i++)
	{
		if (argv[i][0] != '-')
			strcpy (filename, argv[i]);
	}

	/* Open the log file                            */
	if ( (in = fopen(filename, "r")) == NULL)
	{
		fprintf (stderr, "unable to open file %s\n", filename);
		exit (1);
	}

	i = 0;
	/* Open the temporary files                     */
	for (ch='0'; ch<='Z'; ch++)
	{
		if (!isalnum(ch))
			continue;

		if (lower_case_filenames_flag == TRUE)
			sprintf (txt_file,"%c.tmp",tolower(ch));
		else
			sprintf (txt_file,"%c.TMP",ch);


		/* Create the temporary file             */
		if ( (out = fopen (txt_file, "a")) == NULL)
		{
			fprintf(stderr,"unable to create file %s\n", txt_file);
			exit (1);
		}

		/* If this has been assigned one of the "common"        */
		/* callsigns then remember the stream for later,        */
		/* otherwise close the stream.                          */
		switch (ch)
		{
			case 'K':
			case 'k':
				temp_files[0] = out;
				break;
			case 'O':
			case 'o':
				temp_files[1] = out;
				break;
			case 'W':
			case 'w':
				temp_files[2] = out;
				break;
			case 'D':
			case 'd':
				temp_files[3] = out;
				break;
			case 'U':
			case 'u':
				temp_files[4] = out;
				break;
			case 'S':
			case 's':
				temp_files[5] = out;
				break;
			case 'N':
			case 'n':
				temp_files[6] = out;
				break;
			case 'R':
			case 'r':
				temp_files[7] = out;
				break;
			case 'H':
			case 'h':
				temp_files[8] = out;
				break;
			case 'Y':
			case 'y':
				temp_files[9] = out;
				break;
			case 'I':
			case 'i':
				temp_files[10] = out;
				break;
			case 'L':
			case 'l':
				temp_files[11] = out;
				break;
			case 'G':
			case 'g':
				temp_files[12] = out;
				break;
			default:
				fclose (out);
		}

		i++;
	}

	/* Read line 1 to guess the format              */
	fgets (ipline, sizeof(ipline), in);

	/* Skip invalid lines                           */
	while (!check_valid_line (ipline))
		if (fgets (ipline, sizeof(ipline), in) == NULL)
			exit (0);

	if (ADIF_format)
	{
		/* Read ADIF format file into temporary files   */
		process_ADIF_format (in, ipline);

		fclose (in);

		/* Close the temporary files                    */
		close_temp_file ();

		fprintf (stderr, "\nSorting logs....\n");

		sort_logs ();

		/* We're done                                   */
		exit (0);
	}

	if (Cabrillo_format)
	{
		/* Read Cabrillo format file into temporary files   */
		process_Cabrillo_format (in, ipline);

		fclose (in);

		/* Close the temporary files                    */
		close_temp_file ();

		fprintf (stderr, "\nSorting logs....\n");

		sort_logs ();

		/* We're done                                   */
		exit (0);
	}

	/* Convert to upper case                        */
	for (i=0; i <= (int) strlen(ipline); i++)
		line[i] = toupper (ipline[i]);

	p = strtok (line, " ");
	strcpy (data[column], p);

	parse_line (p);

	while ( (p = strtok (NULL, " ")) != NULL)
	{
		column++;
		strcpy (data[column], p);
		parse_line (p);
	}

	/* Trim frequency, e.g 14.200 to 14. This saves */
	/* space!                                       */
	if (strcmp(data[band_pos],"1.8") &&
	    strcmp(data[band_pos],"3.5") &&
	    strcmp(data[band_pos],"3.6") &&
	    strcmp(data[band_pos],"3.8") &&
	    strcmp(data[band_pos],"10.1") &&
	    strcmp(data[band_pos],"24.5"))
	{
		p = strchr (&data[band_pos][0], '.');
		if (p)
			*p = '\0';
	}

	/* If a column was not found then just display  */
	/* blank characters                             */
	if (date_pos == -1)
		strcpy (data[date_pos],"");
	if (time_pos == -1)
		strcpy (data[time_pos],"");
	if (band_pos == -1)
		strcpy (data[band_pos],"");
	if (mode_pos == -1)
		strcpy (data[mode_pos],"");
	if (call_pos == -1)
		strcpy (data[call_pos],"");

	/* Convert the mode to one of the following as per LoTW - CW/PHO/IMG/DAT				*/
	convert_mode (&data[mode_pos], &data[mode_pos]);

	/* Write first line to the temp file            */
	if (no_date_flag == TRUE && no_time_flag == TRUE)
	{
		sprintf (opline,"% 3s% 4s %s\n",
			data [band_pos],
			data [mode_pos],
			data [call_pos]);

		callsign_column = 9;
	}

	if (no_date_flag == TRUE && no_time_flag == FALSE)
	{
		sprintf (opline,"% 4s % 3s% 4s %s\n",
			data [time_pos],
			data [band_pos],
			data [mode_pos],
			data [call_pos]);

		callsign_column = 14;
	}

	if (no_date_flag == FALSE && no_time_flag == TRUE)
	{
		sprintf (opline,"% 9s % 3s% 4s %s\n",
			data [date_pos],
			data [band_pos],
			data [mode_pos],
			data [call_pos]);

		callsign_column = 19;
	}

	if (no_date_flag == FALSE && no_time_flag == FALSE)
	{
		sprintf (opline,"% 9s % 4s % 3s% 4s %s\n",
			data [date_pos],
			data [time_pos],
			data [band_pos],
			data [mode_pos],
			data [call_pos]);

		callsign_column = 24;
	}

	write_temp_file (data [call_pos][0], opline);

	qso_numb++;

	fprintf (stderr, "\nEach dot is 100 QSOs. Writing logs\n");

	/* Read in the rest of the input file           */
	while (fgets (ipline, sizeof(ipline), in) != NULL)
	{
		if (!check_valid_line (ipline))
			continue;

		qso_numb++;

		/* Display some progress!               */
		if (qso_numb % 100 == 0)
			putchar ('.');

		if (qso_numb % 8000 == 0)
			putchar ('\n');

		/* Convert to upper case                        */
		for (i=0; i <= (int) strlen(ipline); i++)
			line[i] = toupper (ipline[i]);

		column = 0;
		p = strtok (line, " ");
		strcpy (data[column], p);

		while ( (p = strtok (NULL, " ")) != NULL)
		{
			column++;
			strcpy (data[column], p);
		}

		/* Trim frequency, e.g 14.200 to 14 This saves  */
		/* space!                                       */
		if (strcmp(data[band_pos],"1.8") &&
		    strcmp(data[band_pos],"3.5") &&
	        strcmp(data[band_pos],"3.6") &&
		    strcmp(data[band_pos],"3.8") &&
		    strcmp(data[band_pos],"10.1") &&
			strcmp(data[band_pos],"24.5"))
		{
			p = strchr (&data [band_pos][0],'.');
			if (p)
				*p = '\0';
		}
		/* If a column was not found then just display  */
		/* blank characters                             */
		if (date_pos == -1)
			strcpy (data[date_pos],"");
		if (time_pos == -1)
			strcpy (data[time_pos],"");
		if (band_pos == -1)
			strcpy (data[band_pos],"");
		if (mode_pos == -1)
			strcpy (data[mode_pos],"");
		if (call_pos == -1)
			strcpy (data[call_pos],"");
		
		/* Trim PSK31 to PSK to fit into 4 bytes and avoid	*/
		/* code re-write. Sorry I am lazy!					*/
		if (! strcmp (data[mode_pos],"PSK31") || ! strcmp (data[mode_pos],"PSK63") || !strcmp (data[mode_pos],"BPSK63") )
			strcpy (data[mode_pos],"PSK");

		/* ditto RTTY										*/
		if (! strcmp (data[mode_pos],"RTTY"))
			strcpy (data[mode_pos],"RY");

		/* ditto MFSK16										*/
		if (! strcmp (data[mode_pos],"MFSK16"))
			strcpy (data[mode_pos],"MFSK");

		/* ditto all the WSJT modes							*/
		if (! strcmp (data[mode_pos],"WSJT") || ! strcmp (data[mode_pos],"FSK441") || ! strcmp (data[mode_pos],"JT6M") ||
			! strcmp (data[mode_pos],"JT44"))
			strcpy (data[mode_pos], "JT");

		/* Write each line to the temp file            */
		if (no_date_flag == TRUE && no_time_flag == TRUE)
		{
			sprintf (opline,"% 3s% 4s %s\n",
				data [band_pos],
				data [mode_pos],
				data [call_pos]);

			callsign_column = 9;
		}

		if (no_date_flag == TRUE && no_time_flag == FALSE)
		{
			sprintf (opline,"% 4s % 3s% 4s %s\n",
				data [time_pos],
				data [band_pos],
				data [mode_pos],
				data [call_pos]);

			callsign_column = 14;
		}

		if (no_date_flag == FALSE && no_time_flag == TRUE)
		{
			sprintf (opline,"% 9s % 3s% 4s %s\n",
				data [date_pos],
				data [band_pos],
				data [mode_pos],
				data [call_pos]);

			callsign_column = 19;
		}

		if (no_date_flag == FALSE && no_time_flag == FALSE)
		{
			sprintf (opline,"% 9s % 4s % 3s% 4s %s\n",
				data [date_pos],
				data [time_pos],
				data [band_pos],
				data [mode_pos],
				data [call_pos]);

			callsign_column = 24;
		}

		write_temp_file (data [call_pos][0], opline);

	}

	fprintf (stderr,"\n\n%ld QSOs read\n", qso_numb);

	fclose (in);

	/* Close the temporary files                    */
	close_temp_file ();

	fprintf (stderr, "\nSorting logs....\n");

	sort_logs ();

	return (0);
}


