/* gsx.c: game sorter extended (PGN reduced export format only) */

/* Revised: 1994.01.21 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#ifdef NULL
#undef NULL
#endif
#define NULL ((void *) 0)

#define nonstatic

typedef short int siT;
typedef long int liT;

typedef void *voidptrT;
typedef char *charptrT;

#define tL 256

#define giL 7

#define gi_event  0
#define gi_site   1
#define gi_date   2
#define gi_round  3
#define gi_white  4
#define gi_black  5
#define gi_result 6

typedef struct gS
	{
	struct gS *g_lptr;
	struct gS *g_rptr;
	charptrT g_tagv[giL];
	charptrT g_score;
	} gT, *gptrT;

static charptrT pgn0_fn;
static charptrT pgn1_fn;

static FILE *pgn0_fp;
static FILE *pgn1_fp;

static charptrT progname;
static charptrT gisv[giL];

static liT line_n, game_n;
static gptrT grptr;

static char tv[tL];

/*--> Fatal: report fatal error and exit */
static
void
Fatal(charptrT s)
{
fprintf(stderr, "%s: fatal error: %s\n", progname, s);
exit(1);

return;
}

/*--> FatalLoc: report fatal error with location and exit */
static
void
FatalLoc(charptrT s)
{
fprintf(stderr, "%s: fatal error: %s (line: %ld   game: %ld)\n",
	progname, s, line_n, game_n);
exit(1);

return;
}

/*--> MemGrab: memory grab */
static
voidptrT
MemGrab(size_t n)
{
size_t m;
voidptrT ptr;

if (n == 0)
	m = 1;
else
	m = n;

ptr = malloc(m);
if (ptr == NULL)
	Fatal("Out of memory");

return (ptr);
}

/*--> MemFree: free memory */
static
void
MemFree(voidptrT ptr)
{
if (ptr != NULL)
	free(ptr);

return;
}

/*--> StrGrab: allocate a string */
static
charptrT
StrGrab(charptrT s)
{
charptrT p;

p = MemGrab(strlen(s) + 1);

strcpy(p, s);

return (p);
}

/*--> ReadLine: read a line from input */
static
charptrT
ReadLine(void)
{
charptrT ptr;

ptr = fgets(tv, tL, pgn0_fp);
if (ptr != NULL)
	line_n++;

return (ptr);
}

/*--> ConcatStr: special concatenation */
static
charptrT
ConcatStr(charptrT sptr0, charptrT sptr1)
{
charptrT sptr;
liT length;

length = strlen(sptr0) + strlen(sptr1);
sptr = MemGrab(length + 1);

strcpy(sptr, sptr0);
strcat(sptr, sptr1);
MemFree(sptr0);

return (sptr);
}

/*--> NewGame: allocate a new game record */
static
gptrT
NewGame(void)
{
gptrT gptr;
siT gi;

gptr = (gptrT) MemGrab(sizeof(gT));

gptr->g_lptr = gptr->g_rptr = NULL;
for (gi = 0; gi < giL; gi++)
	gptr->g_tagv[gi] = NULL;
gptr->g_score = NULL;

return (gptr);
}

/*--> ReleaseGame: deallocate a game record */
static
void
ReleaseGame(gptrT gptr)
{
siT gi;

if (gptr != NULL)
	{
	ReleaseGame(gptr->g_lptr);
	ReleaseGame(gptr->g_rptr);
	MemFree(gptr->g_score);
	for (gi = 0; gi < giL; gi++)
		MemFree(gptr->g_tagv[gi]);
	MemFree(gptr);
	};

return;
}

/*--> CompGame: compare two games */
static
siT
CompGame(gptrT gptr0, gptrT gptr1)
{
siT result, result_save;
siT c0, c1;
liT r0, r1;
liT l0, l1;

result = 0;

if (result == 0)
	result = strcmp(gptr0->g_tagv[gi_date], gptr1->g_tagv[gi_date]);

if (result == 0)
	result = strcmp(gptr0->g_tagv[gi_event], gptr1->g_tagv[gi_event]);

if (result == 0)
	{
	result = strcmp(gptr0->g_tagv[gi_round], gptr1->g_tagv[gi_round]);
	if (result != 0)
		{
		result_save = result;
		l0 = strlen(gptr0->g_tagv[gi_round]);
		l1 = strlen(gptr1->g_tagv[gi_round]);
		if ((l0 < 1) || (l1 < 1))
			result = 0;
		else
			{
			c0 = sscanf(gptr0->g_tagv[gi_round], "%ld", &r0);
			c1 = sscanf(gptr1->g_tagv[gi_round], "%ld", &r1);

			if (c0 == 1)
				if (c1 == 1)
					{
					if (r0 == r1)
						result = 0;
					else
						{
						if (r0 < r1)
							result = -1;
						else
							result = 1;
						};
					}
				else
					result = 1;
			else
				{
				if (c1 == 1)
					result = -1;
				else
					result = result_save;
				};
			};
		};
	};

if (result == 0)
	result = strcmp(gptr0->g_tagv[gi_site], gptr1->g_tagv[gi_site]);

if (result == 0)
	result = strcmp(gptr0->g_tagv[gi_white], gptr1->g_tagv[gi_white]);

if (result == 0)
	result = strcmp(gptr0->g_tagv[gi_black], gptr1->g_tagv[gi_black]);

if (result == 0)
	result = strcmp(gptr0->g_tagv[gi_result], gptr1->g_tagv[gi_result]);

if (result == 0)
	result = strcmp(gptr0->g_score, gptr1->g_score);

return (result);
}

/*--> InsertGame: insert a game into the tree */
static
void
InsertGame(gptrT gptr)
{
siT flag, result;
gptrT xptr;

if (grptr == NULL)
	grptr = gptr;
else
	{
	flag = 0;
	xptr = grptr;
	while (!flag)
		{
		result = CompGame(xptr, gptr);
		if (result != 0)
			{
			if (result > 0)
				{
				if (xptr->g_lptr != NULL)
					xptr = xptr->g_lptr;
				else
					{
					xptr->g_lptr = gptr;
					flag = 1;
					};
				}
			else
				{
				if (xptr->g_rptr != NULL)
					xptr = xptr->g_rptr;
				else
					{
					xptr->g_rptr = gptr;
					flag = 1;
					};
				};
			}
		else
			{
			fprintf(stderr,
				"%s: duplicate game (line: %ld / game: %ld) deleted\n",
				progname, line_n, game_n);
			ReleaseGame(gptr);
			flag = 1;
			};
		};
	};

return;
}

/*--> ReadGame: read a game and return record */
static
gptrT
ReadGame(void)
{
gptrT gptr;
siT flag;
siT gi;
liT length0, length1, length2;

flag = 1;
gptr = NewGame();
game_n++;

gi = 0;
while (flag && (gi < giL))
	{
	if (ReadLine() == NULL)
		if (gi == 0)
			flag = 0;
		else
			FatalLoc("unexpected EOF");

	if (flag)
		{
		length0 = strlen(tv);
		length1 = strlen(gisv[gi]);
		length2 = length0 - (length1 + 6);
		if (length2 < 0)
			FatalLoc("tag fault");
		};

	if (flag)
		{
		gptr->g_tagv[gi] = MemGrab(length2 + 1);
		strncpy(gptr->g_tagv[gi], &tv[length1 + 3], length2);
		*(gptr->g_tagv[gi] + length2) = '\0';
		gi++;
		};
	};

if (flag)
	{
	if (ReadLine() == NULL)
		FatalLoc("unexpected EOF");

	if (strlen(tv) != 1)
		FatalLoc("expected blank line");
	};

if (flag)
	{
	gptr->g_score = StrGrab("");
	do
		{
		if (ReadLine() == NULL)
			FatalLoc("unexpected EOF");

		gptr->g_score = ConcatStr(gptr->g_score, tv);
		}
	while (flag && (strlen(tv) > 1));
	};

if (!flag)
	{
	ReleaseGame(gptr);
	gptr = NULL;
	};

if (gptr == NULL)
	game_n--;

return (gptr);
}

/*--> WriteGame: write a game from a record */
static
void
WriteGame(gptrT gptr)
{
siT gi;

for (gi = 0; gi < giL; gi++)
	fprintf(pgn1_fp, "[%s \"%s\"]\n", gisv[gi], gptr->g_tagv[gi]);
fprintf(pgn1_fp, "\n");
fprintf(pgn1_fp, "%s", gptr->g_score);

return;
}

/*--> WriteNode: write the game tree from a node */
static
void
WriteNode(gptrT gptr)
{
if (gptr != NULL)
	{
	WriteNode(gptr->g_lptr);
	WriteGame(gptr);
	WriteNode(gptr->g_rptr);
	};

return;
}

/*--> CheckChar: check for a character */
static
siT
CheckChar(charptrT p, char ch)
{
siT flag;
liT i, length;

length = strlen(p);
flag = 0;
i = 0;
while (!flag && (i < length))
	if (*(p + i) == ch)
		flag = 1;
	else
		i++;

return (flag);
}

/*--> DelBoundSpaces: delete boundary spaces */
static
charptrT
DelBoundSpaces(charptrT p0)
{
charptrT p1, p2;
liT i, length;

p1 = p0;

length = strlen(p1);
i = 0;
while ((i < length) && isspace(*(p1 + i)))
	i++;
if (i > 0)
	{
	p2 = MemGrab(length - i + 1);
	strcpy(p2, (p1 + i));
	MemFree(p1);
	p1 = p2;
	};

length = strlen(p1);
i = 0;
while ((i < length) && isspace(*(p1 + length - i - 1)))
	i++;
if (i > 0)
	{
	p2 = MemGrab(length - i + 1);
	strcpy(p2, "");
	strncat(p2, p1, (length - i));
	MemFree(p1);
	p1 = p2;
	};

return (p1);
}

/*--> ProcessPlayer: process comma insertion */
static
charptrT
ProcessPlayer(charptrT p0)
{
charptrT p1, p2;
liT i, length;

p1 = p0;

#if (0)
/* token rotate and comma insertion */

if (!CheckChar(p1, ',') && CheckChar(p1, ' '))
	{
	printf("%s\n", p1);
	length = strlen(p1);
	i = length - 1;
	while (*(p1 + i) != ' ')
		i--;
	p2 = MemGrab(length + 2);
	strcpy(p2, (p1 + i + 1));
	strcat(p2, ", ");
	strncat(p2, p1, i);
	MemFree(p1);
	p1 = p2;
	};
#endif

/* initial formation */

#if (1)
length = strlen(p1);
if ((length >= 3) &&
	(isalpha(*(p1 + length - 1))) &&
	(*(p1 + length - 2) == ',') &&
	(isalpha(*(p1 + length - 3))))
	{
	printf("%s\n", p1);
	p2 = MemGrab(length + 3);
	*p2 = '\0';
	strncat(p2, p1, length - 1);
	strcat(p2, " ");
	strncat(p2, (p1 + length - 1), 1);
	strcat(p2, ".");
	MemFree(p1);
	p1 = p2;
	printf("p1: %s\n", p1);
	};
#endif

/* insert space before comma */

#if (1)
length = strlen(p1);
if ((length >= 3) && CheckChar(p1, ','))
	{
	i = length - 1;
	while (*(p1 + i) != ',')
		i--;
	if ((i < (length - 1)) && (i > 0) && isalpha(*(p1 + i + 1)) &&
		isalpha(*(p1 + i - 1)))
		{
		printf("isbc: %s\n", p1);
		p2 = MemGrab(length + 2);
		*p2 = '\0';
		strncat(p2, p1, (i + 1));
		strcat(p2, " ");
		strcat(p2, (p1 + i + 1));
		MemFree(p1);
		p1 = p2;
		printf("p1: %s\n", p1);
		};
	};
#endif

return (p1);
}

/*--> ProcessGameTag: general tag value processing */
static
charptrT
ProcessGameTag(charptrT p0)
{
charptrT p1;

p1 = DelBoundSpaces(p0);

if (strlen(p1) == 0)
	{
	MemFree(p1);
	p1 = StrGrab("?");
	};

return (p1);
}

/*--> ProcessGame: process a game record prior to insertion */
static
void
ProcessGame(gptrT gptr)
{
siT gi;


for (gi = 0; gi < giL; gi++)
	gptr->g_tagv[gi] = ProcessGameTag(gptr->g_tagv[gi]);

gptr->g_tagv[gi_white] = ProcessPlayer(gptr->g_tagv[gi_white]);
gptr->g_tagv[gi_black] = ProcessPlayer(gptr->g_tagv[gi_black]);

return;
}

/*--> main: main program for game sorter */
nonstatic
int
main(void)
{
gptrT gptr;

progname = "gsx";

printf("Enter input PGN file name: ");
gets(tv);
pgn0_fn = StrGrab(tv);
if ((pgn0_fp = fopen(pgn0_fn, "r")) == NULL)
	Fatal("Can't open input file");


printf("Enter output PGN file name: ");
gets(tv);
pgn1_fn = StrGrab(tv);
if ((pgn1_fp = fopen(pgn1_fn, "w")) == NULL)
	Fatal("Can't open output file");

/* initialize tags */

gisv[gi_event] = "Event";
gisv[gi_site] = "Site";
gisv[gi_date] = "Date";
gisv[gi_round] = "Round";
gisv[gi_white] = "White";
gisv[gi_black] = "Black";
gisv[gi_result] = "Result";

line_n = game_n = 0;
grptr = NULL;

/* read games */

while ((gptr = ReadGame()) != NULL)
	{
	putchar('0' + (game_n % 10));
	fflush(stdout);
	ProcessGame(gptr);
	InsertGame(gptr);
	};
putchar('\n');

/* write games */

WriteNode(grptr);

/* close files */

fclose(pgn0_fp);
fclose(pgn1_fp);

/* deallocate */

MemFree(pgn0_fn);
MemFree(pgn1_fn);

ReleaseGame(grptr);
grptr = NULL;

/* report */

printf("%ld games   %ld lines\n", game_n, line_n);

return (0);
}

/* gsx.c: EOF */
