/* scorenum.c
 * C language replacement for score.numerical in PCWC kit
 */

/* Version 2.0 - updated after field problems      
 * usability issues with file handling
 * 1. confusion with fore-slash and back-slash in wild-card
 * 2. Remove . and .. from list of searched files
 * 15 November, 1998 - Phil Rose
 */

#include <stdio.h>
#include <io.h>
/* <io.h> included for _find functions - these appear to be Win32 
 * functions only - check your C compiler for how to implement
 * similar functionality for wild-card filenames
 */
#include <direct.h>
/* <direct.h> included for _chdir */
#include <string.h>
#include <stdlib.h>
#include <malloc.h>

struct stringlist
{
	struct stringlist * next;	/* pointer to next item on list */
	char * string;	/* pointer to string item */
};

struct vote
{
	float fValue;	/* vote given */
	float fNormalised;	/* normalised between 0 & 1 */
	int bValid;	/* set when vote first validated */
	int bInvalid; /* set if vote subsequently found to be spoilt */
};

struct ranking
{
	struct ranking * next;	/* pointer to next item in list */
	int clue;	/* clue number */
	float score;	/* averaged score over valid votes */
	int bScored;	/* set if any valid votes received */
	int iFiller;	/* test to check malloc(16) fail */
};		/* used to sort clues into ranked order */

int ProcessClues (char * psClues,
				   int * iClue,
				   int * iBonus
				   );

/* Some of the following procedures update the supplied pointer
 * which is why it is passed as a pointer to pointer
 *
 * In hindsight it would have been better to make these global
 * variables, but I prefer to use parameters to interface data
 */

int AddVPList (char * pVArg,
				struct stringlist * * psList
				);

int AddStringList (char * pFoundFilename,
					struct stringlist * * psList
					);

int GetStringListLength ( struct stringlist * psList );

int ProcessVP (char * pVotingPaper,
				int iClues,
				struct vote * psThisVote
				);

void EvaluateAndRankScore (
	struct ranking * * psRankedScores,
	struct vote * psTheVotes,
	int iPapers,
	int iClues,
	int clue
	);

void RankScore (
	struct ranking * * psRankScores,
	struct ranking * psThisScore
	);

int ConstructTheVotes (
	struct vote * * psTheVotes,
	int iPapers,
	int iClues
	);

void PrintClue ( 
	FILE * pfOutput, 
	char * summary, 
	int clue ); 

void PrintEntrant ( 
	FILE * pfOutput, 
	char * summary, 
	int clue );

void PrintScores ( 
	FILE * pfOutput, 
	struct vote * psVotesForClue, 
	int iPapers, 
	int iClues );

void PrintComments ( 
	struct stringlist * psVPList, 
	FILE * pfOutput, 
	int clue );

void DestructStringList ( 
	struct stringlist * pStringList );

void DestructRanking (
	struct ranking * pRanking );

void DestructTheVotes (
	struct vote * pTheVotes );

void DumpScores ( 
	struct vote * pTheVotes,
	int iClues,
	int iPapers );

int main( int argc, char *argv[] )
{

	/* To start with we validate the parameter list 
	 * function call is
	 * scorenum <summary> <vote-files> ... <output>
	 */
	if ( argc < 3 )
	{
		fprintf ( stderr, "Usage: scorenum <summary> <vote-files> ... <output>\n" );
	}
	else
	{
		FILE *pfOutput;
		char *outfile = argv[ argc - 1 ];
		char *summary = argv[ 1 ];
		struct stringlist * psVPList = NULL;
		struct vote * psTheVotes = NULL;
		struct ranking * psRankedScores = NULL;
		char * pcSeparator = "--------------------------------------------------";

		int iClues;
		int iBonus;

		fprintf ( stderr, "PCWC vote analyser - V2.0\n" );
		/* We attempt to open the output file (last parameter on the list ) */

		if ( ( pfOutput = fopen ( outfile, "w" ) ) == NULL )
		{
			fprintf ( stderr, "Cannot open file %s to create output\n",
				outfile );
		}
		else 
		{
			int iFirstVArg = 2;
			int iLastVArg = argc - 2;
			int ia;
			int iThisVote;
			int iPapers;
			int iRank;
			struct stringlist * psProcessList;
			struct ranking * psPrintList;
			if ( ProcessClues ( summary, &iClues, &iBonus ))
				return 1;

				/* Now creating a list of voting files from the filename 
				 * wild cards in the argument list 
				 */

			/* for ( from second parameter to last but one by one ) */
			for ( ia = iFirstVArg; ia <= iLastVArg; ia++ )
			{
				char * pVArg = argv[ia];
				if ( AddVPList ( pVArg, &psVPList ) )
					return 1;
			};

			/* iPapers represents the number of voting papers in
			 * in parameter list
			 */
			iPapers = GetStringListLength ( psVPList );
			fprintf ( stderr, "Processing %d voting papers\n", iPapers );

			/* Construct an array (psTheVotes) of struct vote
			 * iPapers by iClues
			 * As this is a fixed size array it is constructed
			 * as a 1-dimension array
			 * So Vote for clue c in paper p is represented by
			 * psTheVotes[ p * iClues + c ]
			 */
			if ( ConstructTheVotes ( &psTheVotes, iPapers, iClues ) )
				return 1;

			/* for (each voting paper) process it */
			iThisVote = 0;
			for ( psProcessList = psVPList; psProcessList != NULL; psProcessList = psProcessList->next )
			{
				ProcessVP ( psProcessList->string, iClues, & psTheVotes[ iThisVote ] );
				/* the following allows us to step through TheVotes a VP at a time */
				iThisVote += iClues;
			};

			/* Start printing comments on "clue 0" - the contest */
			fprintf ( pfOutput, "%s\n", pcSeparator );
			fprintf ( pfOutput, "\nOverall Comments on the Contest\n\n%s\n\n", pcSeparator );
			PrintComments ( psVPList, pfOutput, 0 );
			fprintf ( pfOutput, "%s\n", pcSeparator );
			fprintf ( pfOutput, "%s\n", pcSeparator );
			fprintf ( pfOutput, "\nResults of the Voting\n\n" );
			fprintf ( pfOutput, "%s\n", pcSeparator );

			/* for (each clue) evaluate score and rank it */
			for ( ia = 0; ia < iClues; ia ++ )
			{
				EvaluateAndRankScore ( &psRankedScores, psTheVotes, iPapers, iClues, ia );
			};

			iRank = 1;

			/* for each clue in ranked order print clue & details */
			for ( psPrintList = psRankedScores; psPrintList != NULL; psPrintList = psPrintList->next )
			{
				int iClue = psPrintList->clue ;
				float fScore = psPrintList->score;
				char pcSuffix [3];
				int iRankMod10 = iRank % 10;
				int iRankMod100 = iRank % 100;

				/* evaluate ordinal suffix - 1st, 2nd, 3rd, 4th etc */
				if ( iRankMod100 > 10 && iRankMod100 < 14 )
					strcpy ( pcSuffix, "th");
				else if ( iRankMod10 == 1 )
					strcpy ( pcSuffix, "st");
				else if ( iRankMod10 == 2 )
					strcpy ( pcSuffix, "nd");
				else if ( iRankMod10 == 3 )
					strcpy ( pcSuffix, "rd");
				else strcpy ( pcSuffix, "th");
;

				/* Nth place (score ##.##%) - clue ## */
				fprintf ( pfOutput, "%d%s place (score %.2f%%) - clue %d\n",
					iRank, 
					pcSuffix,
					fScore * 100.0f,
					iClue  );

				/* only print entrant's name for top third of entrants */
				if ( iRank < ( (iClues + 2) / 3 ) )
					PrintEntrant ( pfOutput, summary, iClue );

				/* Print clue separated by two spaces and indented 1 tab */
				PrintClue ( pfOutput, summary, iClue ); 

				/* Print scores "Votes (normalised): ## ## ##" */
				PrintScores ( pfOutput, & psTheVotes[iClue - 1], iPapers, iClues );
				PrintComments ( psVPList, pfOutput, iClue );
				fprintf ( pfOutput, "%s\n", pcSeparator );
				iRank ++;

			};
			/* Print bonus clue details */
			fprintf ( pfOutput, "\nComments on the Bonus Clues\n\n%s\n\n", pcSeparator );
			if ( !iBonus )
				fprintf ( pfOutput, "No bonus clues received\n\n" );
			else
			{
				for ( ia = -1; ia >=iBonus; ia-- )
				{
					fprintf ( pfOutput, "Bonus clue %d\n\n", ia );
					PrintComments ( psVPList, pfOutput, ia );
					fprintf ( pfOutput, "\n\n%s\n\n", pcSeparator );
				};
			};

			/*	DumpScores ( psTheVotes, iClues, iPapers );
			 */

			DestructTheVotes ( psTheVotes );
			DestructStringList ( psVPList );
			DestructRanking ( psRankedScores );

			fclose ( pfOutput );
		};

	};

	return 0;
};

int ProcessClues (char * psClues,
				   int * iClue,
				   int * iBonus
				   )
/* This procedure processes the file "clues" (parameter 1)
 * and returns the number of clues & bonus clues
 */

{
	/* Open the file "clues" */
	FILE * pfClues = fopen ( psClues, "r" );

	if ( pfClues == NULL )
	{
		fprintf ( stderr, "Cannot open clue summary file %s",
			psClues );
		return 1;
	}
	else
	{
		char record [128];
		char * pRecord;
		int iMinClue = 1;
		int iMaxClue = 0;
		int iMinBonus = 1;
		int iMaxBonus = 0;
		int bValid = 0;
		int bBonus = 0;


		/* while ( not end of file ) */
		while ( !feof ( pfClues ) )
		{
			if ( fgets ( record, 128, pfClues ) != NULL )
			{
				int bValid = 0;
				int bBonus = 0;
				int iThisClue = 0;

				pRecord = record; /* to enable processing */
				
				/* if record starts with "-" it's a bonus clue; skip to next character */
				if ( *pRecord == '-' )
				{
					bBonus = 1;
					pRecord ++;
				};
				/* while characters are numeric; accumulate clue number */
				while ( strchr ( "0123456789", *pRecord ) != NULL )
				{
					bValid = 1;
					iThisClue = iThisClue * 10 + *pRecord - '0';
					pRecord ++;
				};
				/* if it's neither "##)" nor "## Entrant:" line invalidate processing */
				if ( *pRecord != ')' &&
					strstr ( pRecord, "Entrant:" ) == NULL )
				{
					bValid = 0;
				};
				/* and if it's not valid say so */
				if ( ! bValid )
				{
					fprintf ( stderr, "Invalid record found in clue file\n" );
					fprintf ( stderr, record );
				}
				/* otherwise see if current clue number is a new max or min */
				else 
				{
					if ( bBonus )
					{
						if ( iThisClue > iMaxBonus ) 
							iMaxBonus = iThisClue;
						if ( iThisClue < iMinBonus )
							iMinBonus = iThisClue;
					}
					else
					{
						if ( iThisClue > iMaxClue ) 
							iMaxClue = iThisClue;
						if ( iThisClue < iMinClue )
							iMinClue = iThisClue;
					};
				};
			};
		};
		/* iMaxClue will stay at 0 if no valid clues found */
		if ( iMaxClue == 0 )
			fprintf ( stderr, "No valid clues found!\n" );
		else
		{
			/* print information about clue file */
			fprintf ( stderr, "Clues read: lowest #%d, highest #%d\n",
				iMinClue, iMaxClue );
			if ( iMaxBonus == 0 )
			{
				fprintf ( stderr, "No bonus clues found, but carrying on\n" );
			}
			else
				fprintf ( stderr, "Bonus clues read: lowest #%d, highest #%d\n",
				- iMinBonus, - iMaxBonus );

			/* return information from clue file to program */
			*iBonus = 0 - iMaxBonus;
			*iClue = iMaxClue;
		}
		fclose ( pfClues );
		return 0;
	};
};


int AddVPList (char * pVArg,
				struct stringlist * * psList
				)
{
	struct _finddata_t sFiles;
	char * pPath;
	char * pFileTemplate;
	char * pTemp;
	char pOldPath [1024];
	int i1;
	long hFiles;
	/* the above appear to be Win32 specific
	 * sFiles is a structure used by the _find functions
	 * hFiles is a handle used by them
	 */

	fprintf ( stderr, "Expanding file template: %s\n", pVArg );

	/* Get current working directory */
	_getcwd ( pOldPath, 1024 );

	/* Looking for last DOS/W95 directory marker "\" */
	/* after replacing fore-slashes with back-slashes */
	for ( i1 = 0; i1 < strlen(pVArg) ; i1 ++ )
		if ( pVArg[i1] == '/' ) pVArg[i1] = '\\';

	pTemp = strrchr ( pVArg, '\\' );

	/* if voting papers in a different directory 
	 * make this the working directory as _find functions
	 * don't return path names
	 */
	if ( pTemp != NULL )
	{
		int iPathLength = pTemp - pVArg;
		pFileTemplate = pTemp + 1;
		pPath = ( char * ) malloc ( iPathLength + 1 );
		strncpy ( pPath, pVArg, iPathLength );
		pPath[ iPathLength ] = '\0';
		fprintf ( stderr, "Changing directory to: %s\n", pPath );
		_chdir ( pPath );
	}
	else
	{
		pFileTemplate = pVArg;
		pPath = ( char * ) malloc ( 1 );
		strcpy ( pPath , "" );
	};


	/* The call _findfirst looks for a file that matches
	 * the first argument in the for loop
	 */

	if ( ( hFiles = _findfirst ( pFileTemplate, & sFiles ) ) == -1L )
	{
		fprintf ( stderr, "No files match the argument %s\n",
			pVArg );
	}
	else
	{
		char * pFullName = ( char * ) malloc ( strlen(pPath) + strlen(sFiles.name) +10 );
		strcpy ( pFullName, "" );
		strcat ( pFullName, pPath );
		strcat ( pFullName, "\\" );
		strcat ( pFullName, sFiles.name );


		/* Add the file name to list of files in **psList */
		if ( *(sFiles.name) == '.' )
			fprintf ( stderr, "Ignoring directory entry %s\n", pFullName );
		else
			if ( AddStringList ( pFullName, psList ) )
				return 1;

		/* _findnext finds the next matching file name */
		while ( _findnext ( hFiles, &sFiles ) == 0 )
		{
			char * pFullName = ( char * ) malloc ( strlen(pPath) + strlen(sFiles.name) +10 );
			strcpy ( pFullName, "" );
			strcat ( pFullName, pPath );
			strcat ( pFullName, "\\" );
			strcat ( pFullName, sFiles.name );

			/* Add the file name to list of files in **psList */
			if ( *(sFiles.name) == '.' )
				fprintf ( stderr, "Ignoring directory entry %s\n", pFullName );
			else
				if ( AddStringList ( pFullName, psList ) )
					return 1;

			free ( pFullName );
		};
		/* Closes the search engine 
		 * NB no files have been opened in this process
		 * only the directories have been searched
		 * All filenames that match *pVArg have been added to **psList 
		 */
		_findclose ( hFiles );
		free ( pFullName );
	};

	free ( pPath );

	/* Revert to saved working directory */
	_chdir ( pOldPath );

};

int AddStringList (char * pFoundFilename,
					struct stringlist * * psList
					)
/* Adds the string *pFoundFilename to list *psList */
{
	/* if psList doesn't yet exist make it! */
	if ( (*psList) == NULL )
	{
		(*psList) = ( struct stringlist * ) malloc ( sizeof ( struct stringlist ) );
		if ( (*psList) == NULL )
		{
			fprintf (stderr, "Memory problems\n" );
			return 1;
		}
		else 
		{
			(*psList)->next = NULL;
			/* create storage to contain string */
			(*psList)->string = ( char * ) malloc ( strlen ( pFoundFilename ) + 1 );
			if ( (*psList)->string == NULL )
			{
				fprintf ( stderr, "Memory problems\n" );
				return 1;
			}
			else
			{
				/* and copy string into new storage location */
				strcpy ( (*psList)->string, pFoundFilename );
			};
			return 0;
		}
	}
	/* Find the end of the list
	 * stringlist is a linked list with a pointer to a string
	 * and a pointer to the next link in the list
	 */

	else if ( (*psList)->next != NULL )
		AddStringList ( pFoundFilename, &((*psList)->next) );
	else
	{
		/* when at the end of the list
		 * create storage to hold the next link 
		 */
		struct stringlist * psTemp = ( struct stringlist * )
			malloc ( sizeof ( struct stringlist ) );
		/* malloc errors are handled by returning straight out */
		if ( psTemp == NULL )
		{
			fprintf ( stderr, "Memory problems\n" );
			return 1;
		}
		else 
		{
			/* add new link on end of current list */
			(*psList)->next = psTemp;
			psTemp->next = NULL;
			/* create storage to contain string */
			psTemp->string = ( char * ) malloc ( strlen ( pFoundFilename ) + 1 );
			if ( psTemp->string == NULL )
			{
				fprintf ( stderr, "Memory problems\n" );
				return 1;
			}
			else
			{
				/* and copy string into new storage location */
				strcpy ( psTemp->string, pFoundFilename );
			};
			return 0;
		};
	};
};

int GetStringListLength ( struct stringlist * psList )
/* returns the number of items in the list by recursively
 * calling itself until the end of the list 
 */
{
	if ( psList == NULL )
		return 0;
	else
		return GetStringListLength ( psList->next ) + 1;
};

int ProcessVP (char * pVotingPaper,
				int iClues,
				struct vote * psThisVP
				)
/* Process a voting paper
 * psThisVP on input points to psTheVotes[ paper * #clues + 0 ]
 * iClues is used to validate clue number
 */
{
	/* Open the voting paper */
	FILE * pfVotingPaper = fopen ( pVotingPaper, "r" );
	if ( pfVotingPaper == NULL )
	{
		fprintf ( stderr, "Cannot open voting paper %s",
			pVotingPaper );
		return 1;
	}
	else
	{
		char record [128] ;
		char * pRecord;
		float fMaxVote = 0.0F;
		float fMinVote = 0.0F;
		int bFirstVote = 1;
		int iValidVotes = 0;
		int ib;
		/* for (each line of the voting paper) */
		while ( !feof ( pfVotingPaper ) )
		{
			int iClue = 0;
			int bBonus = 0;
			int bValid = 0;
			float fValue = 0.0F;
			if ( fgets ( record, 128, pfVotingPaper ) != NULL )
			{
				pRecord = record;
				/* if the line starts with "-" assume it's a bonus clue & skip character */
				if ( *pRecord == '-' )
				{
					bBonus = 1;
					pRecord ++;
				};
				/* while input is numeric convert it to a number */
				while ( strchr ( "0123456789", *pRecord ) != NULL )
				{
					bValid = 1;
					iClue = iClue * 10 + *pRecord - '0';
					pRecord ++;
				};
				/* If it's a bonus number publicly ignore it */
				if ( bBonus && bValid )
				{
					fprintf ( stderr, "Voting Paper %s: vote on bonus clue %d ignored\n",
						pVotingPaper, -iClue );
				}
				/* If it's out of range publicly ignore it */
				else if ( bValid && iClue > iClues )
				{
					fprintf ( stderr, "Voting Paper %s: has vote on non-existing clue %d\n",
						pVotingPaper, iClue );
				}
				else 
				{				
					/* ignore spaces & tabs */
					while ( *pRecord == ' ' || *pRecord == '\t' )
						pRecord ++;
					
					/* checking "## = ##.#" record */
					if ( *pRecord == '=' )
					{
						/* create pointer to voting table entry [paper, clue]*/
						struct vote * psThisVote = psThisVP + iClue - 1;

						/* skip equal sign */
						pRecord ++;

						/* ignore spaces & tabs */
						while ( *pRecord == ' ' || *pRecord == '\t' )
							pRecord ++;
	
						/* read in floating point value */
						sscanf ( pRecord, "%f", &fValue );

						/* if a vote has already been cast on this paper 
						 * for this clue, and it's different, publicly 
						 * declare spoilt vote
						 */
						if ( psThisVote->bValid && psThisVote->fValue != fValue )
						{
							fprintf ( stderr, "Voting Paper %s: has conflicting votes for clue %d\n",
								pVotingPaper, iClue );
							psThisVote->bInvalid = 1;
						}
						else
						{
							/* adjust high and low votes if necessary */
							psThisVote->bValid = 1;
							psThisVote->fValue = fValue;
							if ( bFirstVote )
							{	
								fMaxVote = fValue;
								fMinVote = fValue;
								bFirstVote = 0;
							}
							else
							{
								if ( fValue > fMaxVote )
									fMaxVote = fValue;
								if ( fValue < fMinVote )
									fMinVote = fValue;
							};
						};
					};
				};
			};
		};
		/* Count number of valid votes cast in this paper */
		for ( ib = 0; ib < iClues; ib++ )
			if ( psThisVP [ib].bValid && !psThisVP [ib].bInvalid )
				iValidVotes++;
		/* if ( no valid votes ) */
		if ( !iValidVotes )
		{
			fprintf ( stderr, "Voting Paper %s: has no valid votes",
				pVotingPaper );
		}
		/* if (all votes on paper are the same) */
		else if ( fMaxVote == fMinVote )
		{
			fprintf ( stderr, "Voting Paper %s: all votes are equal",
				pVotingPaper );
		}
		else
		{
			/* for (all votes on this paper) */
			for ( ib = 0; ib < iClues; ib ++ )
			{
				struct vote * psThisVote = psThisVP + ib;
				if ( psThisVote->bValid && !psThisVote->bInvalid )
				{
					/* Normalise the vote to between 0 & 1 
					 * The paper's lowest vote gets 0
					 * the paper's highest vote gets 1
					 * intermediate votes are interpolated linearly
					 * value held as single precision floating point
					 */
					psThisVote->fNormalised =
						( psThisVote->fValue - fMinVote ) / ( fMaxVote - fMinVote );
				};
			};
		};
		fclose ( pfVotingPaper );
	};
};

int ConstructTheVotes (
	struct vote * * psTheVotes,
	int iPapers,
	int iClues
	)
/* Creates voting table and initialises it */
{

	/* allocate storage area */
	( *psTheVotes ) = ( struct vote * ) malloc ( sizeof ( struct vote ) * iPapers * iClues );

	if ( (*psTheVotes) == NULL )
		return 1;
	else 
	{
		int ia;
		int ib;
		for ( ia = 0; ia<iPapers; ia ++ )
		{
			for ( ib = 0; ib<iClues; ib ++ )
			{ 
				/* initialise each element of the table */
				struct vote * psThisVote = & ((*psTheVotes) [ia * iClues + ib]);
				psThisVote->bValid = 0;
				psThisVote->bInvalid = 0;
				psThisVote->fValue = 0.0F;
				psThisVote->fNormalised = 0.0F;

			}
		};
		return 0;
	};
};

void PrintComments (
	struct stringlist * psVPList,
	FILE * pfOutput,
	int iClue
	)
/* This file opens all files in *psVPList
 * and copies all lines starting "##)"
 * to output file
 */
{
	struct stringlist * psProcessList = NULL;
	/* for (each voting paper) process it */
	for ( psProcessList = psVPList; psProcessList != NULL; psProcessList = psProcessList->next )
	{
		char * pVotingPaper = psProcessList->string;
		FILE * pfVotingPaper = fopen ( pVotingPaper, "r" );
		int bFound = 0;
		if ( pfVotingPaper == NULL )
		{
			fprintf ( stderr, "Cannot open voting paper %s\n",
				pVotingPaper );
		}
		else
		{
			char record [128] ;
			char * pRecord;
			bFound = 0;
			
			/* for (each line of the voting paper) */
			while ( !feof ( pfVotingPaper ) )
			{
				int iThisClue = 0;
				int bValid = 0;
				int bBonus = 0;


				if ( fgets ( record, 128, pfVotingPaper ) != NULL )
				{
					pRecord = record;

					/* skip leading spaces & tabs */
					while ( *pRecord == ' ' || *pRecord == '\t' )
						pRecord++;

					/* if the line starts with "-" assume it's a bonus clue & skip character */
					if ( *pRecord == '-' )
					{
						bBonus = 1;
						pRecord ++;
					};
					/* while input is numeric convert it to a number */
					while ( strchr ( "0123456789", *pRecord ) != NULL )
					{
						bValid = 1;
						iThisClue = iThisClue * 10 + *pRecord - '0';
						pRecord ++;
					};
					if ( bBonus )
						iThisClue = 0 - iThisClue;
					/* If ( line is "##)" */
					if ( *pRecord == ')' && iThisClue == iClue )
					{ 
						pRecord++;
						bFound = 1;

						/* ignore spaces & tabs */
						while ( *pRecord == ' ' || *pRecord == '\t' )
							pRecord++;
						fprintf ( pfOutput, pRecord );
					};
				};
			};
			fclose ( pfVotingPaper );
		};
		if ( bFound )
			fprintf ( pfOutput, "\n" );
	};

};

void EvaluateAndRankScore (
	struct ranking * * psRankedScores,
	struct vote * psTheVotes,
	int iPapers,
	int iClues,
	int clue
	)

/* *psRankedScores will be built up as an ordered list of
 * average scores
 */

{
	int ia;
	float sum = 0.0F;
	int N = 0;
	float average = 0.0F;
	/* now create a storage location for the ranked score */
	struct ranking * psThisScore = NULL;

	/* firstly evaluate the average score for clue */
	for ( ia=0; ia<iPapers; ia++ )
	{
		struct vote * psThisVote = psTheVotes + clue + ia * iClues;
		if ( psThisVote->bValid && ! psThisVote->bInvalid )
		{
			sum += psThisVote->fNormalised;
			if ( psThisVote->fNormalised < 0.0F || psThisVote->fNormalised > 1.0F )
				fprintf(stderr, "Invalid normalised vote: paper %d clue %d value %f\n",
					ia, clue, psThisVote->fNormalised);

			N ++;
		};
	};
	/* if (any votes received for clue ) */
	psThisScore = ( struct ranking * ) malloc ( sizeof ( struct ranking ) );
	if ( N )
	{
		average = sum / N;
		psThisScore->bScored = 1;
	};
	/* put data in score list */
	psThisScore->clue = clue + 1;
	psThisScore->score = average;
	RankScore ( psRankedScores, psThisScore );

};

void RankScore (
	struct ranking * * psRankedScores,
	struct ranking * psThisScore
	)
{
	/* if ( list is empty ) create it */
	if ( (*psRankedScores) == NULL )
	{
		(*psRankedScores) = psThisScore;
		psThisScore->next = NULL;
	}
	/* see if new score goes to head of list
	 * i.e. the score is greater than the current head of list
	 * If it is hook it in at the head of the list
	 */
	else if ( (*psRankedScores)->score < psThisScore->score )
	{
		psThisScore->next = (*psRankedScores);
		psRankedScores = & psThisScore;
	}
	else /* scan down list to find place to hook it */
	{
		int bFound = 0;
		struct ranking * * psTemp = psRankedScores;
		while ( !bFound )
		{
			if ( (*psTemp)->score >= psThisScore->score )
			{
				/* next is null when whole list is scanned
				 * score is lower than current tail of list
				 */
				if ( (*psTemp)->next == NULL )
					bFound = 1;
				/* otherwise check if this is the place to hook the score */
				else if ( ((*psTemp)->next)->score < psThisScore->score )
					bFound = 1;
			};
			/* if this isn't the place to hook it carry on down the list */
			if ( !bFound )
			{
				psTemp = &((*psTemp)->next);
			};
		};
		/* Add this score to the list at the appropriate point */
		psThisScore->next = (*psTemp)->next;
		(*psTemp)->next = psThisScore;
	};
};

void PrintClue ( 
	FILE * pfOutput, 
	char * psClues, 
	int clue )
/* copy clues out of clue to output */

{
	/* Open the file "clues" */
	FILE * pfClues = fopen ( psClues, "r" );

	if ( pfClues == NULL )
	{
		fprintf ( stderr, "Cannot open clue summary file %s",
			psClues );
	}
	else
	{
		char record [128];
		char * pRecord;
		int bValid = 0;


		/* while ( not end of file ) */
		while ( !feof ( pfClues ) )
		{
			if ( fgets ( record, 128, pfClues ) != NULL )
			{
				int bValid = 0;
				int bBonus = 0;
				int iThisClue = 0;

				pRecord = record; /* to enable processing */
				
				/* if record starts with "-" it's a bonus clue; skip to next character */
				if ( *pRecord == '-' )
				{
					bBonus = 1;
					pRecord ++;
				};
				/* while characters are numeric; accumulate clue number */
				while ( strchr ( "0123456789", *pRecord ) != NULL )
				{
					bValid = 1;
					iThisClue = iThisClue * 10 + *pRecord - '0';
					pRecord ++;
				};
				if ( bBonus )
					iThisClue = 0 - iThisClue;

				/* if it's neither "##)" nor "## Entrant:" line invalidate processing */
				if ( *pRecord == ')' && iThisClue == clue )
				{
					pRecord ++;
					while ( *pRecord == ' ' || *pRecord == '\t' )
						pRecord++;
					/* Print clue separated by two spaces and indented 1 tab */
					fprintf ( pfOutput, "\n\t%s\n", pRecord );
				};
			};
		};
	};
};

void PrintEntrant ( 
	FILE * pfOutput, 
	char * psClues, 
	int clue )
/* copy entrant from clue to output */
{
	/* Open the file "clues" */
	FILE * pfClues = fopen ( psClues, "r" );

	if ( pfClues == NULL )
	{
		fprintf ( stderr, "Cannot open clue summary file %s\n",
			psClues );
	}
	else
	{
		char record [128];
		char * pRecord;
		int iMinClue = 0;
		int iMaxClue = 0;
		int iMinBonus = 0;
		int iMaxBonus = 0;
		int bValid = 0;


		/* while ( not end of file ) */
		while ( !feof ( pfClues ) )
		{
			if ( fgets ( record, 128, pfClues ) != NULL )
			{
				int bValid = 0;
				int bBonus = 0;
				int iThisClue = 0;

				pRecord = record; /* to enable processing */
				
				/* if record starts with "-" it's a bonus clue; skip to next character */
				if ( *pRecord == '-' )
				{
					bBonus = 1;
					pRecord ++;
				};
				/* while characters are numeric; accumulate clue number */
				while ( strchr ( "0123456789", *pRecord ) != NULL )
				{
					bValid = 1;
					iThisClue = iThisClue * 10 + *pRecord - '0';
					pRecord ++;
				};
				/* if it's neither "##)" nor "## Entrant:" line invalidate processing */
				pRecord = strstr ( pRecord, "Entrant:" );
				if ( pRecord != NULL && 
					iThisClue == clue )
				{
					/* skip "Entrant:" */
					pRecord += 8;

					/* skip spaces and tabs */
					while ( *pRecord == ' ' || *pRecord == '\t' )
						pRecord++;

					fprintf ( pfOutput, "Entrant %s\n", pRecord );
				};
			};
		};
	};
};


void PrintScores ( 
	FILE * pfOutput, 
	struct vote * psVotesForClue, 
	int iPapers, 
	int iClues )
/* "Votes (normalised): ## ## ##" */
{	
	int ia;
	int *pListScores = (int *) malloc ( sizeof ( int ) * iPapers );
	int iNoScores = 0;
	int bSorted = 0;
	int iNoChanges = 0;
	fprintf ( pfOutput, "Votes (normalised): " );

	/* gather individual normalised scores in a list of integers */
	for ( ia = 0; ia<iPapers; ia++ )
	{
		struct vote * psThisVote = psVotesForClue + ia * iClues;
		if ( psThisVote->bValid && ! psThisVote->bInvalid )
		{
			/* convert floating point 0->1 to perecntage and round to integer */
			pListScores[iNoScores] = (int)(psThisVote->fNormalised * 100.0F + 0.5F);
			iNoScores ++;
		};
	};

	/* simple ripple sort of the array
	 * by running through list swapping adjacent entries that are
	 * out of order - continue until no longer needed.
	 */
	while ( !bSorted )
	{	
		iNoChanges = 0;
		for ( ia = 0; ia < iNoScores - 1; ia++)
		{
			if (pListScores[ia] > pListScores[ia+1])
			{
				int temp = pListScores[ia];
				pListScores[ia] = pListScores[ia+1];
				pListScores[ia+1]= temp;
				iNoChanges++;
			};
		};
		if ( !iNoChanges )
			bSorted = 1;
	};

	/* print sorted normalised scores */
	for ( ia = 0; ia < iNoScores; ia++ )
		fprintf ( pfOutput, "%d ", pListScores[ia] );

	fprintf ( pfOutput, "\n\n" );
	free ( pListScores );

};

/* As this is C rather than C++, explicit destroy dynamically created
 * data areas
 */

void DestructStringList ( 
	struct stringlist * pStringList )
{
	/* Go to end of list */
	if ( pStringList->next != NULL )
		DestructStringList ( pStringList->next );

	/* and free memory allocated to tail of list */
	free ( pStringList->string );
	free ( pStringList );
};

void DestructRanking (
	struct ranking * pRanking )
{
	/* Go to end of list */
	if ( pRanking->next != NULL )
		DestructRanking ( pRanking->next );

	/* and free memory allocated to tail of list */
	free ( pRanking );
};

void DestructTheVotes ( struct vote * pTheVotes )
{
	free ( pTheVotes );
};

/* This is a diagnostic procedure to help debug the program */
void DumpScores (
	struct vote * pTheVotes,
	int iClues,
	int iPapers )
{
	int ia;
	int ib;
	FILE * pfDump = fopen ( "dump", "w" );
	for ( ia = 0; ia < iPapers; ia ++ )
	{
		fprintf ( pfDump, "Voting Paper %d\n", ia + 1 );
		for ( ib = 0; ib < iClues; ib ++ )
		{
			struct vote * pTemp = pTheVotes + ia*iClues + ib;
			fprintf ( pfDump, "Clue %2d Score %5.2f (norm %.4f) - valid %d%d\n",
				ib + 1,
				pTemp->fValue,
				pTemp->fNormalised,
				pTemp->bValid,
				pTemp->bInvalid);
		};
	};
	fclose ( pfDump );

}



					
						















