
static char acRcs[] =
    "$Header: spr.c,v 6.20 96/05/10 10:59:57 k2mm Exp $";

#include <stdio.h>

#define nHASHLEN	4		/* max # string chars hashed */
#define nHASHBIT	6		/* # bits hashed from each char */
#define nHASHCHAR	(1 << (nHASHBIT))
#define mHASHCHAR	((nHASHCHAR) - 1)		/* char mask */
#define nHASHENT	((nHASHCHAR) * (nHASHCHAR))
#define mHASHENT	((nHASHENT) - 1)		/* entry mask */
#define ALLOCSIZ	(64 * 1024)
#define LINSIZ		1000
#define nMISSQ		256
#define nERRBIT		16
#define iMATCHSTART	36 /* starting qso-match threshold */
#define iTIMEUNK	9999

#define iADJTIME( x)	((x)->iTime == iTIMEUNK ? iTIMEUNK : \
			    (x)->iTime + (x)->pcallTx->iTimeAdj)
#define cMISSQ_CHR( x)	acMissQ[ (unsigned)(x) >> 3]
#define mMISSQ_BIT( x)	(1 << ((unsigned)(x) & 7))
#define bMISSQ( x, y)	((x)->cMISSQ_CHR( y) & mMISSQ_BIT( y))

#define opDISQUAL	(1 << 0x0)
#define opFLAKE		(1 << 0x1)
#define opDCLBUST	(1 << 0x2)
#define opMISSQ		(1 << 0x3)
#define opLOGSUB	(1 << 0x4)
#define opNOCLUE	(1 << 0x5)
#define opREF		(1 << 0x6)

#define eRXCALL		0x0 /* mis-copied rx'd call  */
#define eRXNR		0x1 /* mis-copied rx'd number */
#define eRXNAME		0x2 /* mis-copied rx'd name */
#define eRXQTH		0x3 /* mis-copied rx'd qth */
#define eTXCALL		0x4 /* tx'd call mis-copied  */
#define eTXNR		0x5 /* tx'd number mis-copied */
#define eTXNAME		0x6 /* tx'd name mis-copied */
#define eTXQTH		0x7 /* tx'd qth mis-copied */
#define eTXNIL		0x8 /* tx'd qso not in other stn's log */
#define eDUPE		0x9 /* stn wkd in prev 3 non-dupe qsos */
#define eCHGNAME	0xA /* tx'd name other than prev rx */
#define eBADNR		0xB /* tx'd nr dupe or backward */
#define eNUQNR		0xC /* tx'd nr not unique, only 1st qso ok */
#define eNOLOG		0xD /* stn wkd did not submit log */
#define eUNIQUE		0xE /* stn wkd is unique */
#define eDCLBUST	0xF /* log declares qso busted */

#define eRXTRC		0x10 /* rx'd name has been traced */
#define eTXTRC		0x11 /* tx'd name has been traced */
#define eMISSQ		0x12 /* amnesty for missing qso */
#define eCLONE		0x13 /* no-match qso w/ non-sub */

#define mRXNR		(1 << eRXNR)
#define mRXCALL		(1 << eRXCALL)
#define mRXNAME		(1 << eRXNAME)
#define mRXQTH		(1 << eRXQTH)
#define mTXNR		(1 << eTXNR)
#define mTXCALL		(1 << eTXCALL)
#define mTXNAME		(1 << eTXNAME)
#define mTXQTH		(1 << eTXQTH)
#define mTXNIL		(1 << eTXNIL)
#define mDUPE		(1 << eDUPE)
#define mCHGNAME	(1 << eCHGNAME)
#define mBADNR		(1 << eBADNR)
#define mNUQNR		(1 << eNUQNR)
#define mNOLOG		(1 << eNOLOG)
#define mUNIQUE		(1 << eUNIQUE)
#define mDCLBUST	(1 << eDCLBUST)
#define mRXTRC		(1 << eRXTRC)
#define mTXTRC		(1 << eTXTRC)
#define mMISSQ		(1 << eMISSQ)
#define mCLONE		(1 << eCLONE)

#define mRXNG		(mRXNR | mRXCALL | mRXNAME | mRXQTH)
#define mTXNG		(mTXNR | mTXCALL | mTXNAME | mTXQTH | mTXNIL)
#define mBADQ		(mRXNG | mTXNG | mDUPE | mDCLBUST)

typedef FILE*		pFILE;
typedef int*		pINT;
typedef char*		TEXT;
typedef TEXT*		pTEXT;
typedef struct str *	pSTR;
typedef struct score *	pSCORE;
typedef struct call *	pCALL;
typedef struct qso *	pQSO;
typedef struct str
{   pSTR		pstrNext;
    pCALL		pcall;
    int			nRef;
    char		ac[ 4];
}   STR;
typedef struct score
{   pCALL		pcall;
    int			nOk;
    int			nNg;
    int			nDupe;
    int			nRxNg;
    int			nTxNg;
    int			nBandChg;
    int			anErr[ nERRBIT];
}   SCORE;
typedef struct call
{   pCALL		pcallNext;
    pSTR		pstrCall;
    pSTR		pstrName;
    pSTR		pstrQth;
    pSTR		pstrOp;
    pQSO		pqsoTxHead;
    pQSO		pqsoRxHead;
    pQSO		pqsoTxTail;
    pQSO		pqsoRxTail;
    pQSO		*apqsoTx;
    pSCORE		pscore;
    int			nBandChg;
    int			iTimeAdj;
    int			iFlags;
    int			iLog;
    int			nRef;
    int			nQso;
    char		acMissQ[ nMISSQ / 8];
}   CALL;
typedef struct qso
{   pQSO		pqsoTxPrev;
    pQSO		pqsoTxNext;
    pQSO		pqsoRxNext;
    pQSO		pqsoMatch;
    pQSO		pqsoTrNext;
    pQSO		pqsoTrPair;
    pQSO		pqsoTxSkip;
    pQSO		pqsoRxSkip;
    int			iFlags;
    int			iLine;
    int			iBand;
    int			iTime;
    int			iTxNr;
    int			iRxNr;
    int			iQso;
    pCALL		pcallTx;
    pCALL		pcallRx;
    pSTR		pstrTxName;
    pSTR		pstrRxName;
    pSTR		pstrTxQth;
    pSTR		pstrRxQth;
    pSTR		pstrTxJump;
}   QSO;

TEXT			tLogIn;
TEXT			tMatch;
pFILE			pfLogIn;
pFILE			pfLogOut;
pFILE			pfMatch;
int			iPrintScores;
int			iPrintErrs;
int			iPrintCalls;
int			nLogTop;
TEXT			tLogCall;
TEXT			tLogDir;
TEXT			tTraceCall;
int			iTraceCallNr;
int			iTraceNames;
int			iPrintQth;
int			iMatchThresh;
int			iUniqThresh = 1;
int			iTimeStart;
int			iTraceInteractive;

pSTR			apstr[ nHASHENT];
pSTR			pstrBusted;
pCALL			pcallBusted;
pCALL			pcallHead;
pCALL			pcallTail;
pQSO			pqsoFirst;
pQSO			pqsoLast;
pSCORE			ascore;

int			nLog;
int			nCall;
int			nStr;
int			nQso;
int			nLine;

int			mRxNg = mRXNG;
int			mTxNg = mTXNG;
int			mDupe = mDUPE;
int			mBadQ = mBADQ;
int			iLenCall;
int			iLenName;
int			iLenQth;
char			acFmtNrInt[]	= " %3d";
char			acFmtNrStr[]	= " %3s";
char			acFmtCall[]	= " %-8s";
char			acFmtName[]	= " %-12s";
char			acFmtQth[]	= " %-2s";
TEXT			atFmt[] =
			{   acFmtCall, acFmtNrStr, acFmtName, acFmtQth,
			    acFmtCall, acFmtNrStr, acFmtName, acFmtQth,
			};
int			amErr[] =
			{   mRXCALL,          mRXNR, mRXNAME, mRXQTH,
			    mTXCALL | mTXNIL, mTXNR, mTXNAME, mTXQTH,
			};

extern pQSO		pqsoFindTxNr();
extern pQSO		pqsoFindRxNr();
extern pSTR		pstrHash();
extern pSTR		pstrHashName();
extern pSTR		pstrHashQth();
extern pCALL		pcallHash();
extern TEXT		tAlloc();
extern TEXT		sbrk();
extern long		strtol();
extern TEXT		strchr();

main( nArg, atArg)
int			nArg;
TEXT			atArg[];
{   int			i;
    pCALL		pcall;
    pQSO		pqso;

    Init( nArg - 1, atArg + 1);
    ReadInput();
    if( iPrintCalls)
	PrintCalls();
    SetFmt();
    SetQth();
    MarkDupes();
    if( iMatchThresh)
    {   fprintf( stderr,
	    "\nnLog=%d nCall=%d nStr=%d nQso=%d nLine=%d sbrk=0x%X\n",
	    nLog, nCall, nStr, nQso, nLine, sbrk( 0));
	for( i = iMATCHSTART; i >= iMatchThresh; i--)
	    MatchQsos( i);
	WriteMatches();
	exit( 0);
    }
    else
	ReadMatches();
    TimeAdj();
    CheckQsos();
    CalcScores();
    if( iPrintScores)
	PrintScores();
    if( iPrintErrs)
	PrintErrs();
    if( nLogTop)
	for( i = 0; i < nLogTop && i < nLog; i++)
	    PrintLog( ascore[ i].pcall);
    if( tLogCall)
	PrintLog( pcallHash( tLogCall));
    if( tLogDir)
	for( i = 0; i < nLog && i < nLog; i++)
	{   char acCall[ LINSIZ], acLogFile[ LINSIZ];
	    strcpy( acCall, ascore[ i].pcall->pstrCall->ac);
	    ChangeSlashesToDots( acCall);
	    sprintf( acLogFile, "%s/%s", tLogDir, acCall);
	    pfLogOut = fopen( acLogFile, "w");
	    if( pfLogOut)
	    {   WriteLog( ascore[ i].pcall, pfLogOut);
		fclose( pfLogOut);
		pfLogOut = 0;
	    }
	}
    if( tTraceCall)
    {   pcall = pcallHash( tTraceCall);
	if( pqso = pqsoFindTxNr( pcall, iTraceCallNr))
	    NameTraceTx( pqso);
	else if( pqso = pqsoFindRxNr( pcall, iTraceCallNr))
	    NameTraceRx( pqso);
    }
    if( iTraceNames)
	TrAll( 0, 0);
    if( iTraceInteractive)
	NameTraceCmdLoop();
    exit( 0);
}

ChangeSlashesToDots( t)
TEXT			t;
{
    while( *t)
    {   if( *t == '/')
	    *t = '.';
	t++;
    }
}

#define IFARG( x)	if( strcmp( *ptArg, x) == 0)

Init( nArg, ptArg)
int			nArg;
TEXT			*ptArg;
{
    if( nArg < 3)
	Usage();
    ptArg[ nArg] = 0;
    tLogIn = *ptArg++;
    tMatch = *ptArg++;
    while( *ptArg)
    {   IFARG( "-res")
	    iPrintScores = 1;
	else IFARG( "-top")
	    nLogTop = *++ptArg ? atoi( *ptArg) : Usage();
	else IFARG( "-log")
	    tLogCall = *++ptArg ? *ptArg : (TEXT) Usage();
	else IFARG( "-logfiles")
	    tLogDir = *++ptArg ? *ptArg : (TEXT) Usage();
	else IFARG( "-err")
	    iPrintErrs = 1;
	else IFARG( "-calls")
	    iPrintCalls = 1;
	else IFARG( "-names")
	    iTraceNames = 1;
	else IFARG( "-trace")
	{   tTraceCall = *++ptArg ? *ptArg : (TEXT) Usage();
	    iTraceCallNr = *++ptArg ? atoi( *ptArg) : Usage();
	}
	else IFARG( "-qth")
	    iPrintQth = 1;
	else IFARG( "-match")
	    iMatchThresh = *++ptArg ? atoi( *ptArg) : Usage();
	else IFARG( "-uniq")
	    iUniqThresh = *++ptArg ? atoi( *ptArg) : Usage();
	else IFARG( "-tr")
	    iTraceInteractive = 1;
	else
	    Usage();
	*ptArg++;
    }
    pcallBusted = pcallHash( "--");
    pstrBusted = pcallBusted->pstrCall;
}

Usage()
{   static TEXT		*ptMsg, atMsg[] = {
    "usage: spr log-file match-file option [ option ... ]",
    "options:",
    "	-match n	write qso-match file -- specify threshold",
    "	-res		final-score results",
    "	-top n		annotated logs for top n finishers",
    "	-log call	annotated log for call",
    "	-logfiles y	annotated logs for all in directory y",
    "	-err		qso-error statistics",
    "	-calls		list calls of submitted logs",
    "	-names		trace name permutations",
    "	-trace call n	trace name sent by call in qso n",
    "	-qth		non-submitter qth inferences",
    "	-uniq n		unique-call threshold",
    "	-tr		trace names interactively",
    0 };

    for( ptMsg = atMsg; *ptMsg; ptMsg++)
	fprintf( stderr, "%s\n", *ptMsg);
    exit( -1);
}

ReadInput()
{   char		acLine[ LINSIZ];
    int			iBand, iTime, iTxNr, iRxNr, nScan;
    char		acTxCall[ LINSIZ], acRxCall[ LINSIZ];
    char		acTxName[ LINSIZ], acRxName[ LINSIZ];
    char		acTxQth[ LINSIZ], acRxQth[ LINSIZ];
    register pQSO	pqso;
    register pCALL	pcallTx, pcallRx;

    pfLogIn = fopen( tLogIn, "r");
    if( pfLogIn == 0)
    {   fprintf( stderr, "spr: can't read log-file \"%s\"\n", tLogIn);
	exit( 1);
    }
    while( fgets( acLine, LINSIZ, pfLogIn))
    {   nLine++;
	if( acLine[ 0] == '#')
	    continue;
	if( acLine[ 0] == '%')
	{   Directive( acLine);
	    continue;
	}
	nScan = sscanf( acLine, "%d %d %d %s %s %s %d %s %s %s",
	    &iBand, &iTime,
	    &iTxNr, acTxCall, acTxName, acTxQth,
	    &iRxNr, acRxCall, acRxName, acRxQth);
	if( nScan != 10)
	{   fprintf( stderr, "bad decode, line %d\n", nLine);
	    continue;
	}
	nQso++;
	pqso = (pQSO) tAlloc( sizeof( QSO));
	if( pqsoFirst == 0)
	    pqsoFirst = pqso;
	pqso->iLine = nLine;
	pqso->iBand = iBand;
	pqso->iTime = iHrMinToTime( iTime);
	pqso->iTxNr = iTxNr;
	pqso->iRxNr = iRxNr;
	pqso->pcallTx = pcallTx = pcallHash( acTxCall);
	pqso->pcallRx = pcallRx = pcallHash( acRxCall);
	pqso->pstrTxName = pstrHashName( acTxName);
	pqso->pstrRxName = pstrHashName( acRxName);
	pqso->pstrTxQth = pstrHashQth( acTxQth);
	pqso->pstrRxQth = pstrHashQth( acRxQth);
	pqso->pqsoTxPrev = pcallTx->pqsoTxTail;
	if( pcallTx->pqsoTxHead == 0)
	{   if( pqsoLast)
		LastQsoByOp( pqsoLast->pcallTx);
	    pcallTx->iLog = nLog++;
	    pcallTx->pqsoTxHead = pcallTx->pqsoTxTail = pqso;
	    pcallTx->pstrQth = pqso->pstrTxQth;
	    pcallTx->iFlags |= opLOGSUB | opREF;
	}
	else
	{   pcallTx->pqsoTxTail->pqsoTxNext = pqso;
	    pcallTx->pqsoTxTail = pqso;
	    if( iBand != pqsoLast->iBand)
		pcallTx->nBandChg++;
	}
	if( pcallRx->pqsoRxHead == 0)
	    pcallRx->pqsoRxHead = pcallRx->pqsoRxTail = pqso;
	else
	{   pcallRx->pqsoRxTail->pqsoRxNext = pqso;
	    pcallRx->pqsoRxTail = pqso;
	}
	pcallRx->iFlags |= opREF;
	pqsoLast = pqso;
    }
    LastQsoByOp( pqsoLast->pcallTx);
    fclose( pfLogIn);
    pfLogIn = 0;
}

#define IFCMD( x, y)	if( nDecode >= (x) && strcmp( ac1, y) == 0)

Directive( tLine)
TEXT		tLine;
{   char	ac1[ LINSIZ], ac2[ LINSIZ], ac3[ LINSIZ], ac4[ LINSIZ];
    int		nDecode;
    unsigned	uBeg, uEnd, uQso;

    nDecode = sscanf( tLine, "%s %s %s %s", ac1, ac2, ac3, ac4);
    IFCMD( 2, "%dq")
	pcallHash( ac2)->iFlags |= opDISQUAL;
    else IFCMD( 2, "%flake")
	pcallHash( ac2)->iFlags |= opFLAKE;
    else IFCMD( 1, "%bustcall")
    {   if( pqsoLast)
	    pqsoLast->pcallRx->iFlags |= opDCLBUST;
    }
    else IFCMD( 2, "%missq")
    {   pCALL pcall = pcallHash( ac2);
	pcall->iFlags |= opMISSQ;
	uBeg = nDecode >= 3 ? atoi( ac3) : 0;
	uEnd = nDecode >= 4 ? atoi( ac4) : nMISSQ - 1;
	for( uQso = uBeg; uQso <= uEnd && uQso < nMISSQ; uQso++)
	    pcall->cMISSQ_CHR( uQso) |= mMISSQ_BIT( uQso);
    }
    else IFCMD( 2, "%logsub")
	pcallHash( ac2)->iFlags |= opLOGSUB;
    else IFCMD( 2, "%lognon")
	pcallHash( ac2)->iFlags &= ~opLOGSUB;
    else IFCMD( 2, "%bustqso")
    {   if( pqsoLast)
	    pqsoLast->iFlags |= mDCLBUST | strtol( ac2, 0, 0);
    }
    else IFCMD( 2, "%timestart")
	iTimeStart = iHrMinToTime( atoi( ac2));
    else IFCMD( 3, "%clueless")
    {   pcallHash( ac2)->iFlags |= opNOCLUE;
	pcallHash( ac2)->pstrName = pstrHash( ac3);
    }
    else IFCMD( 3, "%qth")
	pcallHash( ac2)->pstrQth = pstrHash( ac3);
    else IFCMD( 2, "%defbad")
    {   mBadQ = strtol( ac2, 0, 0);
	mRxNg = mBadQ & mRXNG;
	mTxNg = mBadQ & mTXNG;
	mDupe = mBadQ & mDUPE;
    }
    else IFCMD( 3, "%op")
	pcallHash( ac2)->pstrOp = pstrHash( ac3);
}

LastQsoByOp( pcall)
pCALL			pcall;
{   pQSO		pqso;

    for( pqso = pcall->pqsoTxHead; pqso; pqso = pqso->pqsoTxNext)
	pqso->iQso = pcall->nQso++;
    for( pcall = pcallHead; pcall; pcall = pcall->pcallNext)
    {   if( pcall->iFlags & opREF)
	    pcall->nRef++;
	pcall->iFlags &= ~opREF;
    }
}

SetFmt()
{
    sprintf( acFmtCall, " %%-%ds", iLenCall);
    sprintf( acFmtName, " %%-%ds", iLenName);
    sprintf( acFmtQth, " %%-%ds", iLenQth);
}

SetQth()
{   pCALL		pcall;
    pQSO		pqso;
    pSTR		pstrMax1, pstrMax2;
    int			nRefMax1, nRefMax2, nRef, iOk;

    for( pcall = pcallHead; pcall; pcall = pcall->pcallNext)
	if( pcall != pcallBusted && pcall->pqsoTxHead == 0)
	{   nRefMax1 = nRefMax2 = 0;
	    pstrMax1 = pstrMax2 = 0;
	    for( pqso = pcall->pqsoRxHead; pqso; pqso = pqso->pqsoRxNext)
		pqso->pstrRxQth->nRef = 0;
	    for( pqso = pcall->pqsoRxHead; pqso; pqso = pqso->pqsoRxNext)
		pqso->pstrRxQth->nRef++;
	    for( pqso = pcall->pqsoRxHead; pqso; pqso = pqso->pqsoRxNext)
	    {   nRef = pqso->pstrRxQth->nRef;
		if( nRef > nRefMax1)
		{   if( pstrMax1 != pqso->pstrRxQth)
		    {   nRefMax2 = nRefMax1;
			pstrMax2 = pstrMax1;
			pstrMax1 = pqso->pstrRxQth;
		    }
		    nRefMax1 = nRef;
		}
		else if( nRef > nRefMax2 && pstrMax1 != pqso->pstrRxQth)
		{   nRefMax2 = nRef;
		    pstrMax2 = pqso->pstrRxQth;
		}
	    }
	    iOk = pstrMax1 != pstrBusted && (
		nRefMax2 == 0 ||
		nRefMax2 == 1 && nRefMax1 >= 3 ||
		nRefMax2 >= 2 && nRefMax1 >= nRefMax2 + 3);
	    if( iPrintQth)
		printf( "qth: %s\t%s=%d\t%s=%d\t%s%s\n",
		    pcall->pstrCall->ac,
		    pstrMax1 ? pstrMax1->ac : "--", nRefMax1,
		    pstrMax2 ? pstrMax2->ac : "--", nRefMax2,
		    pcall->pstrQth ? "%%" : iOk ? "ok" : "NG",
		    pcall->iFlags & opDCLBUST ? "\tDclBust" : "");
	    if( pcall->pstrQth == 0)
		if( iOk)
		    pcall->pstrQth = pstrMax1;
		else if( (pcall->iFlags & opDCLBUST) == 0)
		    fprintf( stderr, "qth: %s\t%s=%d\t%s=%d\n",
			pcall->pstrCall->ac,
			pstrMax1 ? pstrMax1->ac : "--", nRefMax1,
			pstrMax2 ? pstrMax2->ac : "--", nRefMax2);
	}
}

MarkDupes()
{   pCALL		pcallTx, pcall, pcall1, pcall2, pcall3;
    pQSO		pqso;

    for( pcallTx = pcallHead; pcallTx; pcallTx = pcallTx->pcallNext)
    {   pqsoLast = 0;
	pcall1 = pcall2 = pcall3 = 0;
	for( pqso = pcallTx->pqsoTxHead; pqso; pqso = pqso->pqsoTxNext)
	{   pcall = pqso->pcallRx;
	    if( pcall == pcall1 || pcall == pcall2 || pcall == pcall3)
		pqso->iFlags |= mDUPE;
	    else
	    {   pcall3 = pcall2;
		pcall2 = pcall1;
		pcall1 = pcall;
	    }
	    if( pcallTx->iFlags & opDISQUAL)
		pqso->iFlags |= mDCLBUST;
	    if( pqsoLast)
	    {   if( pqso->pstrTxName != pqsoLast->pstrRxName)
		    pqso->iFlags |= mCHGNAME;
		if( pqso->iTxNr == pqsoLast->iTxNr )
		{   pqso->iFlags |= mNUQNR | mBADNR;
		    pqsoLast->iFlags |= mNUQNR;
		}
		else if( pqso->iTxNr < pqsoLast->iTxNr )
		{   fprintf( stderr, "%s bkwd nr: %d, line %d\n",
			pcallTx->pstrCall->ac, pqso->iTxNr, pqso->iLine);
		    pqso->iFlags |= mNUQNR | mBADNR;
		    for( pqsoLast = pcallTx->pqsoTxHead;
			pqsoLast != pqso;
			pqsoLast = pqsoLast->pqsoTxNext)
			    if( pqsoLast->iTxNr == pqso->iTxNr)
				pqsoLast->iFlags |= mNUQNR;
		}
	    }
	    pqsoLast = pqso;
	}
    }
}

MatchQsos( iMatchMin)
int			iMatchMin;
{   pCALL		pcallTx, pcallRx;
    pQSO		pqsoTx, pqsoRx, pqsoMatch;
    int			nMatch, nMatchOld, nMatchNew, nNoMatch, nOvMatch;

    nMatchOld = nMatchNew = nNoMatch = nOvMatch = 0;
    for( pcallTx = pcallHead; pcallTx; pcallTx = pcallTx->pcallNext)
	for( pqsoTx = pcallTx->pqsoTxHead; pqsoTx; pqsoTx = pqsoTx->pqsoTxNext)
	{   if( pqsoTx->pqsoMatch)
	    {   nMatchOld++;
		continue;
	    }
	    pcallRx = pqsoTx->pcallRx;
	    if( pcallRx->pqsoTxHead == 0)
		continue;
	    nMatch = 0;
	    for( pqsoRx = pcallRx->pqsoTxHead; pqsoRx; pqsoRx = pqsoRx->pqsoTxNext)
	    {   if( pqsoRx->pqsoMatch)
		    continue;
		if( iMatchVal( pqsoRx, pqsoTx) >= iMatchMin)
		{   pqsoMatch = pqsoRx;
		    nMatch++;
		}
	    }
	    if( nMatch == 0)
	    {   if( bMISSQ( pcallRx, pqsoTx->iRxNr))
		    pqsoTx->iFlags |= mMISSQ;
		else
		    pqsoTx->iFlags |= mTXNIL;
		nNoMatch++;
	    }
	    else if( nMatch == 1)
	    {   pqsoTx->iFlags &= ~mTXNIL;
		pqsoTx->pqsoMatch = pqsoMatch;
		pqsoMatch->iFlags &= ~mTXNIL;
		pqsoMatch->pqsoMatch = pqsoTx;
		nMatchNew++;
	    }
	    else
	    {   pqsoTx->iFlags &= ~mTXNIL;
		nOvMatch++;
	    }
	}
    printf( "match %2d: %4d=old %4d=new %4d=both %4d=no %4d=ov %4d=tot\n",
	iMatchMin, nMatchOld, nMatchNew, nMatchOld + nMatchNew,
	nNoMatch, nOvMatch, nMatchOld + nMatchNew + nNoMatch + nOvMatch);
}

WriteMatches()
{   pCALL		pcall;
    pQSO		pqso;

    pfMatch = fopen( tMatch, "w");
    if( pfMatch == 0)
    {   fprintf( stderr, "spr: can't write match-file \"%s\"\n", tMatch);
	return;
    }
    for( pcall = pcallHead; pcall; pcall = pcall->pcallNext)
	for( pqso = pcall->pqsoTxHead; pqso; pqso = pqso->pqsoTxNext)
	    if( pqso->pqsoMatch && pqso->pqsoMatch->pcallTx->iLog > pcall->iLog)
		fprintf( pfMatch, "%s %d %s %d\n",
		    pqso->pcallTx->pstrCall->ac, pqso->iQso,
		    pqso->pqsoMatch->pcallTx->pstrCall->ac, pqso->pqsoMatch->iQso);
    fclose( pfMatch);
}

ReadMatches()
{   pQSO		*apqso, pqso, pqsoTx, pqsoRx;
    pCALL		pcall, pcallTx, pcallRx;
    char		acLine[ LINSIZ], acTxCall[ LINSIZ], acRxCall[ LINSIZ];
    int			nScan, iTxQso, iRxQso;

    pfMatch = fopen( tMatch, "r");
    if( pfMatch == 0)
    {   fprintf( stderr, "spr: can't read match-file \"%s\"\n", tMatch);
	return;
    }
    apqso = (pQSO*) tAlloc( nQso * sizeof( pQSO));
    for( pcall = pcallHead; pcall; pcall = pcall->pcallNext)
    {   pcall->apqsoTx = apqso;
	for( pqso = pcall->pqsoTxHead; pqso; pqso = pqso->pqsoTxNext)
	    *apqso++ = pqso;
    }
    while( fgets( acLine, LINSIZ, pfMatch))
    {   nScan = sscanf( acLine, "%s %d %s %d",
	    acTxCall, &iTxQso, acRxCall, &iRxQso);
	if( nScan != 4)
	    continue;
	pcallTx = pcallHash( acTxCall);
	pcallRx = pcallHash( acRxCall);
	if( iTxQso >= pcallTx->nQso || iRxQso >= pcallRx->nQso)
	    continue;
	pqsoTx = pcallTx->apqsoTx[ iTxQso];
	pqsoRx = pcallRx->apqsoTx[ iRxQso];
	pqsoTx->pqsoMatch = pqsoRx;
	pqsoRx->pqsoMatch = pqsoTx;
    }
    fclose( pfMatch);
    for( pcall = pcallHead; pcall; pcall = pcall->pcallNext)
	for( pqso = pcall->pqsoTxHead; pqso; pqso = pqso->pqsoTxNext)
	    if( pqso->pcallRx->pqsoTxHead && pqso->pqsoMatch == 0)
		if( bMISSQ( pqso->pcallRx, pqso->iRxNr))
		    pqso->iFlags |= mMISSQ;
		else
		    pqso->iFlags |= mTXNIL;
}

int
iMatchVal( pqsoRx, pqsoTx)
pQSO		pqsoRx, pqsoTx;
{   int			iMatch1, iMatch2, iMatch3, iMatch4;
    int			iMatch5, iMatch6, iMatch7, iMatch;

    iMatch1 = pqsoRx->pcallRx == pqsoTx->pcallTx;
    iMatch2 = pqsoRx->iRxNr == pqsoTx->iTxNr && ! (pqsoTx->iFlags & mNUQNR);
    iMatch3 = pqsoRx->iTxNr == pqsoTx->iRxNr && ! (pqsoRx->iFlags & mNUQNR);
    iMatch4 = pqsoRx->pstrRxName == pqsoTx->pstrTxName;
    iMatch5 = pqsoRx->pstrTxName == pqsoTx->pstrRxName;
    iMatch6 = pqsoRx->pstrRxQth == pqsoTx->pstrTxQth;
    iMatch7 = pqsoRx->pstrTxQth == pqsoTx->pstrRxQth;
    iMatch
	= 8 * (iMatch1 && iMatch2)  /* both calls & tx nr */
	+ 8 * (iMatch1 && iMatch3)  /* both calls & rx nr */
	+ 6 * (iMatch2 && iMatch3)  /* rx call & both nrs */
	+ 5 * (iMatch4 && iMatch5)  /* rx call & both names */
	+ 2 * (iMatch6 && iMatch7)  /* rx call & both qths */
	+ 2 * iMatch4  /* rx call & tx name */
	+ 2 * iMatch5  /* rx call & rx name */
	+ 1 * iMatch6  /* rx call & tx qth */
	+ 1 * iMatch7  /* rx call & rx qth */
	+ 2 * (iMatch2 && iMatch3 && iMatch4)  /* rx call & both nrs & tx name */
	+ 2 * (iMatch2 && iMatch3 && iMatch5)  /* rx call & both nrs & rx name */
	;
    return iMatch;
}

TimeAdj()
{   pCALL		pcall;
    pQSO		pqso;
    float		fTimeErr;
    int			nTime;

    for( pcall = pcallHead; pcall; pcall = pcall->pcallNext)
	if( pcall->pqsoTxHead)
	{   fTimeErr = nTime = 0;
	    for( pqso = pcall->pqsoTxHead; pqso; pqso = pqso->pqsoTxNext)
	    {   if( pqso->iTime == iTIMEUNK)
		    continue;
		if( pqso->pqsoMatch == 0)
		    continue;
		if( pqso->pqsoMatch->iTime == iTIMEUNK)
		    continue;
		fTimeErr += pqso->iTime - pqso->pqsoMatch->iTime;
		nTime++;
	    }
	    if( nTime == 0)
		continue;
	    if( fTimeErr >= 0.0)
		pcall->iTimeAdj = - (fTimeErr / nTime + 0.5);
	    else
		pcall->iTimeAdj = -fTimeErr / nTime + 0.5;
	    if( pcall->iTimeAdj >= 10 || pcall->iTimeAdj <= -10) 
		printf( "%s timeadj: %d min\n",
		    pcall->pstrCall->ac, pcall->iTimeAdj);
	}
}

CheckQsos()
{   pCALL		pcallTx, pcallRx;
    pQSO		pqsoTx, pqsoRx;
    int			iDiff, iFlags;

    for( pcallTx = pcallHead; pcallTx; pcallTx = pcallTx->pcallNext)
	for( pqsoTx = pcallTx->pqsoTxHead; pqsoTx; pqsoTx = pqsoTx->pqsoTxNext)
	{   pqsoRx = pqsoTx->pqsoMatch;
	    if( pqsoRx == 0)
	    {   pcallRx = pqsoTx->pcallRx;
		if( pqsoTx->pstrRxName == pstrBusted)
		    pqsoTx->iFlags |= mRXNAME;
		if( pqsoTx->pstrRxQth == pstrBusted)
		    pqsoTx->iFlags |= mRXQTH;
		if( pqsoTx->pstrRxQth != pcallRx->pstrQth && pcallRx->pstrQth != 0)
		    pqsoTx->iFlags |= mRXQTH;
		if( pcallRx == pcallBusted || (pcallRx->iFlags & opDCLBUST))
		{   pqsoTx->iFlags |= mDCLBUST | mRXCALL;
		    continue;
		}
		if( pcallRx->pqsoTxHead == 0)
		    pqsoTx->iFlags |= mNOLOG;
		if( pcallRx->nRef <= iUniqThresh && ! (pcallRx->iFlags & opDCLBUST))
		    pqsoTx->iFlags |= mUNIQUE;
		continue;
	    }
	    iFlags = 0;
	    iDiff = pqsoTx->iRxNr - pqsoRx->iTxNr;
	    if( iDiff < -1 || iDiff > 1)
		iFlags |= mRXNR;
	    iDiff = pqsoTx->iTxNr - pqsoRx->iRxNr;
	    if( iDiff < -1 || iDiff > 1)
		iFlags |= mTXNR;
	    if( pqsoTx->pcallRx != pqsoRx->pcallTx)
		iFlags |= mRXCALL;
	    if( pqsoTx->pcallTx != pqsoRx->pcallRx)
		iFlags |= mTXCALL;
	    if( pqsoTx->pstrRxName != pqsoRx->pstrTxName)
		iFlags |= mRXNAME;
	    if( pqsoTx->pstrTxName != pqsoRx->pstrRxName)
		iFlags |= mTXNAME;
	    if( pqsoTx->pstrRxQth != pqsoRx->pstrTxQth)
		iFlags |= mRXQTH;
	    if( pqsoTx->pstrTxQth != pqsoRx->pstrRxQth)
		iFlags |= mTXQTH;
	    pqsoTx->iFlags |= iFlags;
	}
}

int
iCmpScore( pscore1, pscore2)
pSCORE			pscore1, pscore2;
{
    if( pscore2->nOk != pscore1->nOk)
	return pscore2->nOk - pscore1->nOk;
    else
	return pscore1->nNg - pscore2->nNg;
}

CalcScores()
{   pCALL		pcall;
    pQSO		pqso;
    pSCORE		pscore;
    int			iErr, iFlags;

    pscore = ascore = (pSCORE) tAlloc( nLog * sizeof( SCORE));
    for( pcall = pcallHead; pcall; pcall = pcall->pcallNext)
	if( pcall->pqsoTxHead && (pcall->iFlags & opDISQUAL) == 0)
	{   for( pqso = pcall->pqsoTxHead; pqso; pqso = pqso->pqsoTxNext)
	    {   iFlags = pqso->iFlags;
		for( iErr = 0; iErr < nERRBIT; iErr++)
		    if( iFlags & 1 << iErr)
			pscore->anErr[ iErr]++;
		if( iFlags & mRxNg) pscore->nRxNg++;
		if( iFlags & mTxNg) pscore->nTxNg++;
		if( iFlags & mDupe) pscore->nDupe++;
		if( iFlags & mBadQ) pscore->nNg++; else pscore->nOk++;
	    }
	    pscore->nBandChg = pcall->nBandChg;
	    pscore->pcall = pcall;
	    pcall->pscore = pscore++;
	}
    qsort( ascore, nLog, sizeof( SCORE), iCmpScore);
}

PrintCalls()
{   pCALL		pcall;
    char		acCall[ LINSIZ];

    for( pcall = pcallHead; pcall; pcall = pcall->pcallNext)
	if( pcall->pqsoTxHead)
	{   strcpy( acCall, pcall->pstrCall->ac);
	    ChangeSlashesToDots( acCall);
	    printf( "%s\n", acCall);
	}
}

PrintScores()
{   pCALL		pcall;
    pSCORE		pscore;
    int			iScore, nQSo, nStar;
    float		fAccur;
    char		acCall[ LINSIZ];

    printf( "\nCALL            #OK  #NG RXNG TXNG DUPE BCHG ACC'Y (each * is 1%% over 80%%)\n");
    for( iScore = 0, pscore = ascore; iScore < nLog; iScore++, pscore++)
    {   pcall = pscore->pcall;
	if( pcall->pstrOp)
	    sprintf( acCall, "%s (%s)", pcall->pstrCall->ac, pcall->pstrOp->ac);
	else
	    sprintf( acCall, "%s", pcall->pstrCall->ac);
	nQso = pscore->nOk + pscore->nNg;
	fAccur = nQso == 0 ? 0.0 : 100.0 * (float) pscore->nOk / nQso;
	nStar = (fAccur - 80.0) / 1.0;
	printf( "%-15s %3d %4d %4d %4d %4d %4d %5.1f ",
	    acCall,
	    pscore->nOk,
	    pscore->nNg,
	    pscore->nRxNg,
	    pscore->nTxNg,
	    pscore->nDupe,
	    pscore->nBandChg,
	    fAccur);
	if( pscore->pcall->iFlags & opDISQUAL)
	    printf( "DQ'ed");
	while( nStar-- > 0)
	    printf( "*");
	printf( "\n");
    }
}

PrintErrs()
{   pSCORE		pscore;
    int			iScore, iErr, anErr[ nERRBIT];

    printf( "\n          ");
    for( iErr = 0; iErr < nERRBIT; iErr++)
    {   anErr[ iErr] = 0;
	printf( " 0x%X", iErr);
    }
    printf( "\n");
    for( iScore = 0, pscore = ascore; iScore < nLog; iScore++, pscore++)
    {   printf( "%-10s", pscore->pcall->pstrCall->ac);
	for( iErr = 0; iErr < nERRBIT; iErr++)
	{   anErr[ iErr] += pscore->anErr[ iErr];
	    printf( " %3d", pscore->anErr[ iErr]);
	}
	printf( "\n");
    }
    printf( "TOTALS    ");
    for( iErr = 0; iErr < nERRBIT; iErr++)
	printf( " %3d", anErr[ iErr]);
    printf( "\n");
}

PrintLog( pcall)
pCALL			pcall;
{
    WriteLog( pcall, stdout);
}

WriteLog( pcall, pfLog)
pCALL			pcall;
pFILE			pfLog;
{   pQSO		pqso;

    fprintf( pfLog, "\n######## log %s\n\nBand UTC", pcall->pstrCall->ac);
    fprintf( pfLog, acFmtCall, "TxCall");
    fprintf( pfLog, acFmtNrStr, "Tx#");
    fprintf( pfLog, acFmtName, "TxName");
    fprintf( pfLog, acFmtQth, "St");
    fprintf( pfLog, acFmtCall, "RxCall");
    fprintf( pfLog, acFmtNrStr, "Rx#");
    fprintf( pfLog, acFmtName, "RxName");
    fprintf( pfLog, acFmtQth, "St");
    fprintf( pfLog, " Flags\n");
    for( pqso = pcall->pqsoTxHead; pqso; pqso = pqso->pqsoTxNext)
	WriteQsoPair( pqso, pfLog);
}

PrintQsoPair( pqsoTx)
pQSO			pqsoTx;
{
    WriteQsoPair( pqsoTx, stdout);
}

WriteQsoPair( pqsoTx, pfLog)
pQSO			pqsoTx;
pFILE			pfLog;
{   pQSO		pqsoRx;
    int			iField;

    fprintf( pfLog, "\n");
    WriteQso( pqsoTx, 1, pfLog);
    pqsoRx = pqsoTx->pqsoMatch;
    if( pqsoRx)
    {   WriteQso( pqsoRx, 0, pfLog);
	if( pqsoTx->iFlags & (mRxNg | mTxNg))
	    WriteQsoErr( pqsoTx->iFlags & (mRxNg | mTxNg), pfLog);
	return;
    }
    pqsoRx = pqsoFindTxNr( pqsoTx->pcallRx, pqsoTx->iRxNr);
    if( pqsoRx)
    {   WriteQso( pqsoRx, 0, pfLog);
	if( mTXNIL & mTxNg)
	    WriteQsoErr( mTXNIL, pfLog);
	return;
    }
    if( pqsoTx->iFlags & mRxNg)
	WriteQsoErr( (pqsoTx->iFlags & mRxNg) << 4, pfLog);
}

PrintQso( pqso, iGrade)
pQSO			pqso;
int			iGrade;
{
    WriteQso( pqso, iGrade, stdout);
}

WriteQso( pqso, iGrade, pfLog)
pQSO			pqso;
int			iGrade;
pFILE			pfLog;
{
    fprintf( pfLog, " %2d %04d", pqso->iBand,
	iTimeToHrMin( iGrade ? pqso->iTime : iADJTIME( pqso)));
    fprintf( pfLog, acFmtCall, pqso->pcallTx->pstrCall->ac);
    fprintf( pfLog, acFmtNrInt, pqso->iTxNr);
    fprintf( pfLog, acFmtName, pqso->pstrTxName->ac);
    fprintf( pfLog, acFmtQth, pqso->pstrTxQth->ac);
    fprintf( pfLog, acFmtCall, pqso->pcallRx->pstrCall->ac);
    fprintf( pfLog, acFmtNrInt, pqso->iRxNr);
    fprintf( pfLog, acFmtName, pqso->pstrRxName->ac);
    fprintf( pfLog, acFmtQth, pqso->pstrRxQth->ac);
    if( iGrade)
    {   fprintf( pfLog, " %s", pqso->iFlags & mBadQ ? "-NG-" : "-ok-");
	if( pqso->iFlags & mTXNIL) fprintf( pfLog, " NoMatch");
	if( pqso->iFlags & mDupe) fprintf( pfLog, " Dupe");
	if( pqso->iFlags & mBADNR) fprintf( pfLog, " BadTxNr");
	if( pqso->iFlags & mCHGNAME) fprintf( pfLog, " ChgTxName");
	if( pqso->iFlags & mDCLBUST) fprintf( pfLog, " DclBust");
	if( pqso->iFlags & mUNIQUE) fprintf( pfLog, " Unique");
	if( (pqso->iFlags & mNOLOG) && pqso->pcallRx->pqsoTxHead)
	    fprintf( pfLog, " NoLog"); /* for Qs added by hand */
	if( pqso->iFlags & mMISSQ) fprintf( pfLog, " MissQ");
    }
    fprintf( pfLog, "\n");
}

PrintQsoErr( iFlags)
int			iFlags;
{
    WriteQsoErr( iFlags, stdout);
}

WriteQsoErr( iFlags, pfLog)
int			iFlags;
pFILE			pfLog;
{   int			iField;

    fprintf( pfLog, "        ");
    for( iField = 0; iField < 8; iField++)
	fprintf( pfLog, atFmt[ iField], iFlags & amErr[ iField] ? "^^" : "");
    fprintf( pfLog, "\n");
}

pQSO
pqsoFindTxNr( pcall, iTxNr)
pCALL			pcall;
int			iTxNr;
{   pQSO		pqso;

    if( pcall == 0)
	return 0;
    for( pqso = pcall->pqsoTxHead; pqso; pqso = pqso->pqsoTxNext)
	if( pqso->iTxNr == iTxNr)
	    return pqso;
    return 0;
}

pQSO
pqsoFindRxNr( pcall, iRxNr)
pCALL			pcall;
int			iRxNr;
{   pQSO		pqso;

    if( pcall == 0)
	return 0;
    for( pqso = pcall->pqsoRxHead; pqso; pqso = pqso->pqsoRxNext)
	if( pqso->iRxNr == iRxNr)
	    return pqso;
    return 0;
}

pSTR
pstrHash( tStr)
TEXT			tStr;
{   register TEXT	tSrc, tEnd;
    register int	iHash, iChar, iLen;
    register pSTR	pstr;

    tSrc = tStr;
    tEnd = tSrc + nHASHLEN;
    iHash = 0;
    while( (iChar = *tSrc++) && tSrc <= tEnd)
	iHash = iHash << nHASHBIT | iChar & mHASHCHAR;
    iHash &= mHASHENT;
    pstr = apstr[ iHash];
    while( pstr && strcmp( pstr->ac, tStr) != 0)
	pstr = pstr->pstrNext;
    if( pstr)
	return pstr;
    iLen = strlen( tStr) - 3;
    if( iLen < 0)
	iLen = 0;
    pstr = (pSTR) tAlloc( sizeof( STR) + iLen);
    strcpy( pstr->ac, tStr);
    pstr->pstrNext = apstr[ iHash];
    apstr[ iHash] = pstr;
    nStr++;
    return pstr;
}

pCALL
pcallHash( tCall)
TEXT			tCall;
{   pSTR		pstrCall;
    pCALL		pcall;
    int			iLen;

    iLen = strlen( tCall);
    if( iLenCall < iLen)
	iLenCall = iLen;
    pstrCall = pstrHash( tCall);
    if( (pcall = pstrCall->pcall) == 0)
    {   nCall++;
	pcall = (pCALL) tAlloc( sizeof( CALL));
	pcall->pstrCall = pstrCall;
	pstrCall->pcall = pcall;
	if( pcallHead == 0)
	    pcallHead = pcallTail = pcall;
	else
	{   pcallTail->pcallNext = pcall;
	    pcallTail = pcall;
	}
    }
    return pcall;
}

pSTR
pstrHashName( tName)
TEXT			tName;
{   int			iLen;

    iLen = strlen( tName);
    if( iLenName < iLen)
	iLenName = iLen;
    return pstrHash( tName);
}

pSTR
pstrHashQth( tQth)
TEXT			tQth;
{   int			iLen;

    iLen = strlen( tQth);
    if( iLenQth < iLen)
	iLenQth = iLen;
    return pstrHash( tQth);
}

TEXT
tAlloc( nBytes)
int			nBytes;
{   static int		nFree;
    static TEXT		tFree;

    nBytes = (nBytes + 3) & ~3;
    if( nBytes > nFree)
    {   nFree = ALLOCSIZ;
	tFree = sbrk( nFree);
	if( tFree == 0)
	{   fprintf( stderr, "tAlloc: out of memory, line %d\n", nLine);
	    exit( 1);
	}
	bzero( tFree, nFree);
    }
    nFree -= nBytes;
    tFree += nBytes;
    return tFree - nBytes;
}

DumpHash()
{   int			iHash;
    pSTR		pstr;

    for( iHash = 0; iHash < nHASHENT; iHash++)
	for( pstr = apstr[ iHash]; pstr; pstr = pstr->pstrNext)
	    printf( "%s\n", pstr->ac);
    printf( "%d\n", iHash);
}

int
iHrMinToTime( iHrMin)
int			iHrMin;
{   int			iHr, iMin, iTime;

    if( iHrMin == iTIMEUNK)
	return( iTIMEUNK);
    iHr = iHrMin / 100;
    iMin = iHrMin % 100;
    iTime = iHr * 60 + iMin - iTimeStart;
    if( iTime < 0)
	iTime += 24 * 60;
    return iTime;
}

int
iTimeToHrMin( iTime)
int			iTime;
{   int			iHr, iMin;

    if( iTime == iTIMEUNK)
	return( iTIMEUNK);
    iTime += iTimeStart;
    if( iTime >= 24 * 60)
	iTime -= 24 * 60;
    iHr = iTime / 60;
    iMin = iTime % 60;
    return iHr * 100 + iMin;
}

#include "spt.c"


