// What? FCC --> QRZ conversion
//
//  Who? Steve Dimse K4HG  (sdimse@bridge.net
//
// When? Copyright 1997
//
//  Why? The present FCC data format consists of 11 separate files split on zip code, and sorted
//			by call, plus an "indiv" file containing prior call and license class, and a file
//			crm.dat which contains the club name for club stations. To be used by
//			Mac/WinAPRS, they must be converted into a format supported by the programs. 
//			I chose the QRZ format, because I am most familiar with that, and have
//			written a Macintosh browser.
//
//  How? There are several phases to this program. First I load into memory all the 
//			callsigns in the FCC database. I store this as a binary tree. No effort is 
//			made to balance the tree, there may be a slight speed improvement realized 
//			by implementing this as an AVL balanced tree, but who has the time...
//
//			Keeping this data in memory speeds things up at the expense of requiring a 36 MB
//			 block of memory for the tree...
//
//			A pass is then made reading in an old QRZ file "CALLBKC.DAT", and the birthdates
//			are extracted and placed into the tree. This is necessary because the FCC no 
//			longer places the birthdates in the data files. Hams without a birthdate in the 
//			old QRZ file will not have any data in that field. If the program does not find
//			the CALLBKC.DAT file, it continues, but the output will no have birthdates.
//
//			Next, each of the 11 zip code files are read sequentially, and the offset for each
//			record is added to the tree info. I do this because storing all the data in memory
//			would require far too much memory, even for me. The crm file is read and each call
//			with a crm has the offset stored.
//
//			In the final phase, the tree is walked in order, and as each call is reached,
//			the data is retrieved from the fcc files, and a QRZ record written. At the same
//			time, the old QRZ file is merged in, allowing the international listings to
//			remain in the database. This is the slow phase as each of the records requires 
//			a disk seek...be patient...
//
//			Run time on my PowerMac 8500/120 is about 1.5 hours...your mileage may vary.
//
//********************* W A R N I N G **************************/
//
//  The recursive nature of this program, coupled with the unbalanced nature of the tree,
//    puts severe strain on the stack, as the recursion can reach 200 deep. I have moved
//    the local variables to be global so that they are not created on the stack. 
//    Still, this program would not run until I increased the stack size on the
//    PowerMac version to 128k from the default 64k. I have not been able to get the program
//    to run as 68K code at all. If you compile this for another system and it crashes during
//    the write phase, this is most likely the culprit. Increase the allocated stack size if
//    your system allows you to. If you can't, the only answer is to re-write the 
//    tree-creating code to produced a balanced tree. 
//  My purpose in writing this is to get something that works for me, and it does. Please do
//    not bother me to make it require less memory or balance the tree...unless you are 
//    willing to pay me my hourly rate...

// *** ANSI includes ***
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

// *** Constants ***

#define maxindiv 900000  //maximum number of lines in the indiv.dat file, as of 3/3 821,000 needed

// *** Structures ***

typedef struct indivT indivT; // Forward declaration for structure

typedef struct indivT 	// structure for the individual record kept in memory
{
	char	call[6];
	char	prevCall[6]; // space if no prior call
	char	clas;     // N T P G A E (no data == ?)
	char	prevClas; // (no data == space)
	char	vanity;   // V if vanity call
	char	isPrior;  // True if record is old call, prevCall contains new call
	char	dob[5];   // DOB in QRZ format (yyjjj) (jjj = julian day of year)
	unsigned char	fileNum;   // Appl.dat file number containing FCC data (0-8, 90, 94)
	long	fileIndex;// offset to data in file Applx.dat
	long	clubIndex;// offset to data in file crm.dat (Club info)
	indivT	*left;    // pointer to calls lower in order
	indivT	*right;   // pointer to calls higher in order
} indivT;

typedef indivT *indivTptr;
typedef	FILE   *fPtr;
typedef char   *cPtr;

//  **** Global Variables  **** 

	indivT		*theIndivTree;		// Array of Indiv records, not accessed directly
	indivT		*freePtr;			// Next free indiv record in theIndivTree Array
	long		nIndiv;   			// number of indiv records, only used for display
	indivTptr	indivTreeRoot; 		// Pointer to the root of a binary tree containing all 
									//   the records in a callsign area. Sort critera is
									//   the QRZ order, based on suffix, number, finally prefix
	fPtr		inFile[100];		// Array of input files, got lazy and handled the 90 / 94
									//    files in the same array instead of special case
	long		masterCount;		// Used for count of records written
	char		rbuf[255],routBuf[255],rtemp[40]; // Local variables for writeTree proc, made
	cPtr		rs[20];							  // global to avoid placing them on stack
	int			i;					// More of the same
	char		*p,*rp,rbuf1[255];	// More of the same
	FILE		*clubFile;			// File for club names
	FILE		*QRZfile;			// File for QRZ merging
	char		QRZbuf[255];		// buffer for QRZ merging


//  **** Procedures ****

void 	main(void);							// duuh
int 	indivTree(void); 					// Creates the indiv array, and loads it
void 	loadDOB(void);						// extracts DOBs from a QRZ file and storea in indiv tree
void 	datetojul(char *dob,char *julyr);	// converts date in form yymmdd to yyjjj as used by QRZ
short 	callCmp(const char *call1, const char *call2); // Compares two calls using QRZ order
void 	oneFile(int fileNum);				// Processes a single FCC file into the indiv tree
void 	readClub(void);						// Reads club file offset into indiv tree
void 	convertCall(char *theCall);			// converts a call into QRZ format (number always third char,
											//   padded with spaces
void 	addToTree(indivT *theIndivT);		// Adds a single indiv to the tree
void	addIndiv(indivT *theIndiv, indivT *theRoot); // recursive call to add a record
void 	writeTree(indivT *theIndiv,FILE *theFile); // recursive routine to write the data
indivT 	*searchTree(char *call, indivT *theTree); // recursive routine for searching the tree

indivT *searchTree(char *call, indivT *theTree) 
{
	int cmp;
	if (theTree == nil) return nil;  // Special case for empty tree
	cmp = callCmp(call,theTree->call);  
	if (cmp == 0) return theTree;  // Found matching record
	if (cmp < 0) searchTree(call,theTree->right);  // search right tree
	else searchTree(call,theTree->left);  		   // Search left tree
}
		
void addIndiv(indivT *theIndiv, indivT *theRoot)
{
	int cmp = callCmp(theIndiv->call,theRoot->call);
	if (cmp == 0) // oops, already have one in tree....
	{
//		printf("dup call %12.12s and %12.12s\n",theIndiv->call,theRoot->call);
	}
	else if (cmp<0) 
	{
		if (theRoot->right == nil) theRoot->right = theIndiv; // If no branch here add record
		else addIndiv(theIndiv,theRoot->right);  // else search right
	}
	else
	{
		if (theRoot->left == nil) theRoot->left = theIndiv;
		else addIndiv(theIndiv,theRoot->left);
	}
}

void addToTree(indivT *theIndivT)
{
	if (isdigit(theIndivT->call[2])) // Check for valid callsign
	{
		if (indivTreeRoot == nil) indivTreeRoot = theIndivT; // special case for first element
		else addIndiv(theIndivT,indivTreeRoot); // start recursive search/add
	}
	else printf("Invalid callsign: %s\n",theIndivT->call);
}

void writeTree(indivT *theIndiv,FILE *theFile)
{
	if (theIndiv == nil) return; 
	writeTree(theIndiv->right,theFile); // in-order tree traverse
	if (theIndiv->isPrior)
	{
		if ((++masterCount % 500) == 0) printf("   Record: %ld\n",masterCount); // Progress display
		if (QRZfile != 0) // are there old QRZ records waiting to merge?
		{
			if (callCmp(theIndiv->call,QRZbuf) == 0) fgets(QRZbuf,250,QRZfile); // skip dup records
			while (callCmp(theIndiv->call,QRZbuf) > 0)  // write old recs
			{
				if ((++masterCount % 500) == 0) printf("   Record: %ld\n",masterCount); // Progress display
				fprintf(theFile,QRZbuf); // write old record
				if (feof(QRZfile))				// no more old recs?
				{
					sprintf(QRZbuf,"ZZ9ZZZ");   // highest possible call
					fclose(QRZfile);			// close the old file
					QRZfile = 0;				// clear file pointer
				}
				fgets(QRZbuf,250,QRZfile);		// read another old record
			}
			if (callCmp(theIndiv->call,QRZbuf) == 0) fgets(QRZbuf,250,QRZfile); // skip dup records
		}
		fprintf(theFile,"%6.6s,%6.6s\n",theIndiv->call,theIndiv->prevCall); // Prior call record
	}
	else
	{
		if ((++masterCount % 500) == 0) printf("   Record: %ld\n",masterCount); // Progress display
		if (theIndiv->fileNum < 100) // Valid Appl file number
		{
			fseek(inFile[theIndiv->fileNum],theIndiv->fileIndex,SEEK_SET); // Seek the record
			fgets(rbuf,250,inFile[theIndiv->fileNum]); // get the record
			p = rbuf;
			rs[0] = p;
			i = 1;
			while (*p != '\n') // set up an array of pointers to each field in the Appl record
			{
				while ((*p != '|') && (*p != '\n')) p++;
				if (i>16) break; // overflow, should never happen
				if (*p == '|') 
				{
					p++;
					rs[i++] = p;
				}
			}
			p = routBuf; // pointer for writing the output record
			strncpy(p,theIndiv->call,6); // write call
			p+=6;
			*p++ = ',';
			if (theIndiv->clubIndex != -1) 		// Club call, get club name from crm.dat
			{
				if (clubFile != 0) 
				{
					fseek(clubFile,theIndiv->clubIndex,SEEK_SET); // Seek the record
					fgets(rbuf1,250,clubFile); // get the record
					rp = rbuf1;
					while (*rp++ != '|') ;
					while (*rp++ != '|') ; // Third field is club name
					while (*rp != '|') *p++ = *rp++;  // Copy the name
					*p++ = ',';
					*p++ = ',';
					*p++ = ',';
					*p++ = ','; // Another extra comma for the obsolete middle initial field
				}
			}
			else
			{
				while (*(rs[4]) != '|') // write the name...a little obtuse, as the first and last
										//  fields are combined (separated by a comma) in the FCC
										//  file, but separate field in QRZ. Also, need extra comma
										//  in the output, for the obsolete suffix field 
				{
					*p++ = *(rs[4])++;
					if (*(rs[4]) == ',') 
					{
						*p++ = ',';
						*p++ = ',';
						(rs[4])++;
						if (*(rs[4]) == ' ') (rs[4])++;
					}
				}
				*p++ = ',';
				*p++ = ','; // Another extra comma for the obsolete middle initial field
				if (theIndiv->dob[0] != ' ') // Do we know a dob???
				{ 
					strncpy(p,theIndiv->dob,5);
					p+=5;
				} 
			}
			*p++ = ',';
			datetojul(rs[11],rtemp); // convert lic date from yymmdd to yyjjj
			strncpy(p,rtemp,5); // write license date
			p+=5;
			*p++ = ',';
			datetojul(rs[12],rtemp);  // convert exp date from yymmdd to yyjjj
			strncpy(p,rtemp,5); // write license expiration date
			p+=5;
			*p++ = ',';
			while (*(rs[5]) != '|') *p++ = *(rs[5])++; // write street address
			*p++ = ',';
			while (*(rs[7]) != '|') *p++ = *(rs[7])++; // write city
			*p++ = ',';
			while (*(rs[8]) != '|') *p++ = *(rs[8])++; // write state
			*p++ = ',';
			while (*(rs[9]) != '|') *p++ = *(rs[9])++; // write zip
			*p++ = ',';
			if (theIndiv->clas != '?') *p++ = theIndiv->clas; // write class
			*p++ = ',';
			if (theIndiv->prevCall[0] != ' ')  // prior call???
			{
				strncpy(p,theIndiv->prevCall,6); // write prior call
				p+=6; 
			}
			*p++ = ',';
			if (theIndiv->prevClas != ' ') *p++ = theIndiv->prevClas; // write prior class
			*p++ = 0;
//			if (theIndiv->clubIndex != -1) printf("%s\n",routBuf);
			if (QRZfile != 0) // are there old QRZ records waiting to merge?
			{
				if (callCmp(routBuf,QRZbuf) == 0) fgets(QRZbuf,250,QRZfile); // skip dup records
				while (callCmp(routBuf,QRZbuf) > 0)  // write old recs
				{
					if ((++masterCount % 500) == 0) printf("   Record: %ld\n",masterCount); // Progress display
					fprintf(theFile,QRZbuf); // write old record
					if (feof(QRZfile))				// no more old recs?
					{
						sprintf(QRZbuf,"ZZ9ZZZ");   // highest possible call
						fclose(QRZfile);			// close the old file
						QRZfile = 0;				// clear file pointer
					}
					fgets(QRZbuf,250,QRZfile);		// read another old record
				}
				if (callCmp(routBuf,QRZbuf) == 0) fgets(QRZbuf,250,QRZfile); // skip dup records
			}
			fprintf(theFile,"%s\n",routBuf); // finally write the new record to the file
		}
//	else printf("Oops...no Appl record found for callsign %6.6s\n",theIndiv->call);
	}
	writeTree(theIndiv->left,theFile);  // in-order traverse
}

int indivTree(void)   // Loads the array of prior calls and license classes 
{
	FILE	*theFile;
	char	buf[100],*p1,*p2,*p3;
	int		i;
	
	theIndivTree = malloc(maxindiv*sizeof(indivT)); // allocate the BIG block of memory
	if (theIndivTree != 0)
	{
		printf("allocated space for indiv tree of %ld bytes.\n",maxindiv*sizeof(indivT));
	}
	else 
	{
		printf("error allocating indiv table\n");
		exit(0); // we're outta here!
	}
	indivTreeRoot = nil; // zero the root
	theFile = fopen("indiv.dat","r+"); // open the indiv file
	if (theFile != 0) 
	{
		nIndiv = 0; // counter
		freePtr = theIndivTree; // as we add new records they are allocated sequentially from the
							//  array using freePtr, and then added to the tree
		while (!feof(theFile)) 
		{
			fgets(buf,90,theFile);
			nIndiv++;
			if (nIndiv >= maxindiv) return maxindiv; // exceeded memory size, a good debug tool!
			p1 = (char *)&buf; // pointer to read line 
			p2 = (char *)freePtr;  // pointer to output record (depends on order of indiv structure
			p3 = p2;		   // used to revent overrun
			while (*p1 != '|') *p2++ = *p1++; //load call
			while (p2-p3 < 6) *p2++ = ' ';
			p1++;
			while (*p1++ != '|') ; //skip field
			while (*p1++ != '|') ; //skip field
			while (*p1 != '|') *p2++ = *p1++; //load previous call
			while (p2-p3 < 12) *p2++ = ' ';
			p1++;
			if (*p1 == '|') *p2++ = '?'; // load license class
			else *p2++ = *p1;
			p1++;
			p1++;
			while (*p1++ != '|') ; //skip field
			if (*p1 == '|') *p2++ = ' '; // no prior call...store space
			else *p2++ = *p1; // load prior license class
			p1++;
			while (*p1++ != '|') ; //skip field
			while (*p1++ != '|') ; //skip field
			if (*p1 != 'V') *p2++ = ' '; // load vanity flag
			else 
			{
				*p2++ = *p1;
			}
			*p2++ = 0; //dummy for read
			convertCall(freePtr->call); // set up as QRZ call
			freePtr->left = nil; // initialize record
			freePtr->right = nil;
			freePtr->isPrior = 0;
			freePtr->fileIndex = -1;
			freePtr->clubIndex = -1;
			freePtr->fileNum = 101;
			freePtr->dob[0] = ' ';
			addToTree(freePtr);
			if (freePtr->prevCall[0] != ' ') // if there is a prior call, we need an indiv record for that also
			{
				indivT	*ptr1 = freePtr+1;
				strncpy(ptr1->call,freePtr->prevCall,6); // call and prevCall have opposite meanings
				strncpy(ptr1->prevCall,freePtr->call,6); //   in a prior call record
				convertCall(ptr1->call); // converts to QRZ format
				ptr1->left = nil;  // initialze record
				ptr1->right = nil;
				ptr1->isPrior = 1;
				freePtr->fileIndex = -1;
				freePtr->clubIndex = -1;
				freePtr->fileNum = 101;
				freePtr->dob[0] = ' ';
				addToTree(ptr1);
				if (nIndiv%25000 == 0) printf("record %ld \n",nIndiv); // progress report
				nIndiv++;
				freePtr++;
			}
			if (nIndiv%25000 == 0) printf("record %ld \n",nIndiv);
			freePtr++;
		}
		printf("total indiv.dat records: %ld\n",nIndiv);
	}
	else 
	{
		printf("error opening indiv.dat\n");
		exit(0);
	}
	fclose(theFile);
	return nIndiv;
}

void loadDOB(void) // Process an old QRZ file, extracting dates into database
{
	FILE	*theFile;
	indivT 	*theIndiv;
	char	buf[255],*p;
	long	cnt = 0;
	int		i;
	
	theFile = fopen("CALLBKC.DAT","r");
	if (theFile != 0) 
	{
		printf("Processing DOB file\n");
		while (!feof(theFile))
		{
			fgets(buf,250,theFile); // read a line from the QRZ file
			if ((++cnt)%10000 == 0) printf("  Record: %ld\n",cnt); // progress report
			theIndiv = searchTree(buf,indivTreeRoot); // find the matching indiv tree entry
			while ((theIndiv != nil) && (theIndiv->isPrior)) // follow the prev call until we find the
				theIndiv = searchTree(theIndiv->prevCall,indivTreeRoot); //  real call 
			if (theIndiv != nil) // we have a match
			{
				p = buf;
				i = 0;	
				while (i<5) // DOB is the fifth field in the QRZ record
				{
					if (*p==',') i++;
					if (*p==0) i = 99; // if a null is encountered before 5th field, this is a prior
									   //  call QRZ record, and we don't want it
					p++;
				}
				if (i<10) strncpy(theIndiv->dob,p,5); // copy the dob (yyjjj format)
			}
		}
	fclose(theFile);
	}
	else printf("Error opening callbook file!\n");
}

void oneFile(int theFileNum)	// Reads one of the Appl(n).dat files from the FCC,
								//  and stores the file number and offset into the tree
{
	FILE	*theFile;
	indivT 	*theIndiv;
	char 	fName[20];
	long	cnt = 0;
	
	sprintf(fName,"Appl%d.dat",theFileNum);
	theFile = fopen(fName,"r");
	if (theFile != 0) 
	{
		printf("Processing file: %s\n",fName);
		while (!feof(theFile)) // loop through the file
		{
			char *p1,*p2,call[8];
			char buf[255];
			long fpos = ftell(theFile); // get the file position of the start of the record
			fgets(buf,250,theFile);		// get the record
			if ((++cnt)%2500 == 0) printf("  Record: %ld\n",cnt); // progress report
			p1 = call;			// initialize the callsign pointers
			p2 = buf;
			call[4] = ' ';		// pad call with spaces
			call[5] = ' ';
			call[6] = 0;
			while ((p1-call<7) && (*p2 != '|'))
			{
				*p1++ = *p2++; // copy callsign into call
			}
			convertCall(call); // change to QRZ format
			theIndiv = searchTree(call,indivTreeRoot); // find the record
			if (theIndiv != nil)
			{
				theIndiv->fileIndex = fpos;			// store the data
				theIndiv->fileNum = theFileNum;
			}
			else 
			{
//				printf("Error...call %6.6s not in indiv.dat!\n",call);
				freePtr++;
				nIndiv++;
				strncpy(freePtr->call,call,6);
				freePtr->left = nil; // initialize record
				freePtr->right = nil;
				freePtr->isPrior = 0;
				freePtr->fileIndex = fpos;
				freePtr->clubIndex = -1;
				freePtr->fileNum = theFileNum;
				freePtr->dob[0] = ' ';
				freePtr->prevCall[0] =  ' ';
				freePtr->prevClas = ' ';
				freePtr->clas = '?';
				addToTree(freePtr);
			}
		}
	}
	else 
	{
		printf("error opening %s\n",fName);
		return;
	}
	fclose(theFile);
}


void readClub(void)				// Reads club files
{
	FILE	*theFile;
	indivT 	*theIndiv;
	long	cnt = 0;
	
	theFile = fopen("crm.dat","r");
	if (theFile != 0) 
	{
		printf("Processing club file.\n");
		while (!feof(theFile)) // loop through the file
		{
			char *p1,*p2,call[8];
			char buf[255];
			long fpos = ftell(theFile); // get the file position of the start of the record
			fgets(buf,250,theFile);		// get the record
			if ((++cnt)%2500 == 0) printf("  Record: %ld\n",cnt); // progress report
			p1 = call;			// initialize the callsign pointers
			p2 = buf;
			call[4] = ' ';		// pad call with spaces
			call[5] = ' ';
			call[6] = 0;
			while ((p1-call<7) && (*p2 != '|'))
			{
				*p1++ = *p2++; // copy callsign into call
			}
			convertCall(call); // change to QRZ format
			theIndiv = searchTree(call,indivTreeRoot); // find the record
			if (theIndiv != nil)
			{
				theIndiv->clubIndex = fpos;			// store the offset data
			}
			else 
			{
				printf("Error...call %6.6s not in indiv.dat!\n",call);
			}
		}
	}
	else 
	{
		printf("error opening club file\n");
		return;
	}
	fclose(theFile);
}


//                       J  F  M  A  M   J   J   A   S   O   N   D
const int n_year[] = { 0,0,31,59,90,120,151,181,212,243,273,304,334 };
const int l_year[] = { 0,0,32,60,91,121,152,182,213,244,274,305,335 };


void datetojul(char *dob,char *julyr) // convert yymmdd to yyjjj
{
	int		y;
	int		d;
	int		m;
	char	temp[7];
	
	strncpy(temp,dob,6);
	temp[6] = 0;
	sscanf(&temp[4],"%d",&d);
	temp[4] = 0;
	sscanf(&temp[2],"%d",&m);
	temp[2] = 0;
	sscanf(&temp[0],"%d",&y);
	if ((m>12) || (m<1))
	{
		strncpy(julyr,"00001",5);
		return;
	}
	if (y%4) sprintf(julyr,"%02d%03d",y,l_year[m]+d);
	else sprintf(julyr,"%02d%03d",y,n_year[m]+d);
}

short callCmp(const char *call1, const char *call2) // compares two calls in the QRZ order
													//  suffix, number, prefix
{
	short	theCmp;
	
	theCmp = strncmp(call1+3,call2+3,3);
	if (theCmp != 0) return(theCmp);
	if (*(call1+2) == *(call2+2)) return(strncmp(call1,call2,2));
	if (*(call1+2) > *(call2+2)) return(1);
	else return(-1);
	}

void convertCall(char *theCall)
{
	int i;
	if (!(isdigit(theCall[2])))   /* QRZ callsigns place digit in 3rd position always, so if 1x2 or 1x3 must pad with space */
	{
		theCall[5] = theCall[4];
		theCall[4] = theCall[3];
		theCall[3] = theCall[2];
		theCall[2] = theCall[1];
		theCall[1] = ' ';
	}
	if (!(isalpha(theCall[5]))) theCall[5] = ' '; /* QRZ calls have spaces in unused positions */
	if (!(isalpha(theCall[4]))) theCall[4] = ' ';
}

void main (void) {
	
	FILE	*theFile;
	int		i;
	char	temp[25];
	long	origCnt;
	
	printf("FCC --> QRZ Ham Radio Database Conversion\n");
	printf("  Copyright 1997 Steven S. Dimse  K4HG  (sdimse@bridge.net)\n");
	indivTree(); // set up tree
	origCnt = nIndiv; // save count of indiv.dat records
	for (i=0; i<9; i++) oneFile(i); // loads Appl0.dat to Appl8.dat
	oneFile(90);	// the two oddball files Appl90.dat and Appl94.dat
	oneFile(94);
	printf("Loaded %ld additional records, total number in tree %ld\n",nIndiv-origCnt,nIndiv);
	loadDOB();   // add dob data
	readClub();		// check for club names
	printf("Writing output file...\n");
	for (i=0; i<100; i++) inFile[i] = 0; // initialize the array of input FILES
	theFile = fopen("out.dat","wb"); // create the output file. It is opened binary, because
									 //   QRZ format uses 0x0a rather than 0x0d for EOL
	if (theFile != 0)				 // Open the Appl(n).dat files
	{
		for (i=0; i<9; i++)
		{
			sprintf(temp,"Appl%d.dat",i);
			inFile[i] = fopen(temp,"r");
		}
		inFile[90] = fopen("Appl90.dat","r");
		inFile[94] = fopen("Appl94.dat","r");
		QRZfile = fopen("CALLBKC.DAT","rb"); // open the old QRZ file for merging
		fgets(QRZbuf,250,QRZfile); // prime the old data buffer
		masterCount = 0;
		clubFile = fopen("crm.dat","r");
		writeTree(indivTreeRoot,theFile); // write the data
		fclose(clubFile);
		if (QRZfile != 0)	// take care of any left over data at the end of old QRZ file
		{
			while (!feof(QRZfile))
			{
				fprintf(theFile,"%s",QRZbuf);
				fgets(QRZbuf,250,QRZfile);
			}
			fclose(QRZfile);
		}
		fclose(theFile);
	}
	else printf("Unable to open output file!\n");
	printf("Total of %ld records written.\n",masterCount);
}

//  Format info
//
// indiv.dat format:
//CALL|x|x|prevCALL|class|x|prevClass|x|x|Vanity|x
//
// Appl(n).dat format:
//0    1          2 3 4                5           6 7              8  9      11     12     13     14  15
//K4HG|9609252934|S|I|DIMSE, STEVEN S|RT 6 BOX 433| |SUMMERLAND KEY|FL|33042||961104|061104|961104|SYS||
//
// QRZ Format
//KD9ZG ,PEARSON,,PEGGY L,,53216,86245,96246,116 E 117TH PL,CHICAGO,IL,60628,A,KA9GZI,G
//
