/*
 *		Gathering files into a single file.
 *								April 14, 1990, written by SHIMA
 *
 *	usage: gather -{a|e|u|d|p|v|x}[b][i] file [file_1 file_2 ...]
 *
 *  slightly changed by sempa for spell miss "completed"
 *
 *  supported option parameters  b  and  i  by SHIMA, Sep. 1992
 *
 *  corrected the spell miss "Erace" Aug, 1993
 */

#include	<stdio.h>
#include	<stdlib.h>
#include	<fcntl.h>
#include	<string.h>
#include	<io.h>
#include	<dir.h>
#include	<fcntl.h>
#include	<sys\stat.h>

#define		ERROR		-1
#define		BUF_SIZE	0x1000
#define		BUF2_SIZE	0x4000
#define		MAX_FILE	1000
#define		F_NEW		0
#define		F_UP		1

struct finfo {	/* 27 bytes */
	char f_atrib;
	unsigned int f_time;
	unsigned int f_date;
	long f_size;
	char f_name[14];
	long f_pos;
};

#define	ptr_long(x)	(*((long *)(x)))
#define	ptr_int(x)	(*((int *)(x)))

#define	FINFO_SIZE	sizeof(struct finfo)

char buf[BUF_SIZE];
char buf2[BUF2_SIZE];
struct finfo gth_fat[MAX_FILE];	/* 27*MAX_FILE bytes */
char *addfile[MAX_FILE];	/*  2*MAX_FILE bytes */
int sortfile[MAX_FILE];	/*  2*MAX_FILE bytes */

int fnum;
int fall;
int f_stdout;
int f_backup;
int f_inquire;

unsigned int buf2_pos;

char path[MAXPATH + 4];
char drive[MAXDRIVE];
char dir[MAXDIR];
char file[MAXFILE];
char ext[MAXEXT];

void usage(void);
int get_info(char *);
long get_long(char *);
void show_dir(void);
void f_up(char *, int);
void f_del(char *);
void f_ext(char *);
int fcmp(char *, char *);
void finish(int, char *);
void extract(int);
int comp(int *, int *);
int cpyfile(int, int, long);
void cpyffblk(char *, struct ffblk *);
void fsort(int);

void main(int argc, char **argv)
{
	FILE *fp;
	int ch, i, fh;

	if (argc < 3 || *argv[1] != '-' || argv[1][1] == 0)
		usage();

	strncpy(path, argv[2], MAXPATH - 1);
	fh = get_info(path);

	for (i = 2; (ch = tolower(argv[1][i])) != 0; i++) {
		switch (ch) {

		  case ('b'):
			  f_backup = 1;
			  break;

		  case ('i'):
			  f_inquire = 1;
			  break;

		  default:
			  usage();

		}
	}

	switch ((int)(tolower(argv[1][1]))) {

	  case ('a'):
		  if (fh == ERROR)
			  printf("Creating -> %s\n", path);

		  for (i = 3; i < argc; i++) {
			  f_up(argv[i], F_NEW);
		  }
		  finish(fh, path);

	  case ('u'):
		  if (fh == ERROR) {
			no_gth:printf("%s doesn't exist.\n", path);
			  exit(1);
		  }
		  for (i = 3; i < argc; i++) {
			  f_up(argv[i], F_UP);
		  }
		  finish(fh, path);

	  case ('d'):
		  if (fh == ERROR)
			  goto no_gth;
		  for (i = 3; i < argc; i++) {
			  f_del(argv[i]);
		  }
		  finish(fh, path);

	  case ('p'):
		  f_stdout = 1;

	  case ('e'):
	  case ('x'):
		  if (fh == ERROR)
			  goto no_gth;
		  for (i = 3; i < argc; i++) {
			  f_ext(argv[i]);
		  }
		  extract(fh);

	  case ('v'):
		  if (fh == ERROR)
			  goto no_gth;
		  show_dir();
		  exit(0);

	  default:
		  usage();
	}
}

void extract(int fh)
{
	struct ffblk ffblk;
	char *fname;
	int i, code, mode, fd;

	if (f_stdout == 1)
		fd = fileno(stdout);

	for (i = 0; i < fnum; i++) {
		if (sortfile[i] == 0)
			continue;
		if (f_stdout == 1) {
			fprintf(stderr, "%-13s -> stdout\n", gth_fat[i].f_name);
			lseek(fh, gth_fat[i].f_pos, SEEK_SET);
			setmode(fd, O_BINARY);
			cpyfile(fd, fh, gth_fat[i].f_size);
			setmode(fd, O_TEXT);
			continue;
		}

		/* Check the existence of the file in the current directory
         */
		if (findfirst(fname = gth_fat[i].f_name, &ffblk, 0) == ERROR)
			goto get_f;

		/* Compare the time stamps */
		if (f_inquire || gth_fat[i].f_date < ffblk.ff_fdate)
			continue;
		if (gth_fat[i].f_date > ffblk.ff_fdate)
			goto ask;
		if (gth_fat[i].f_time <= ffblk.ff_ftime)
			continue;

	  ask:printf("\n%s exist! Extract it (y/n)?", gth_fat[i].f_name);
		if ((code = getch()) != 'y' && code != 'Y')
			continue;

	  get_f:if ((fd = open(fname, O_CREAT | O_RDWR | O_BINARY, S_IREAD | S_IWRITE))
			== -1) {
			printf("\nCannot create %s\n", fname);
			exit(1);
		}
		lseek(fh, gth_fat[i].f_pos, SEEK_SET);
		printf("\nExtracting: %s", fname);
		if ((mode = cpyfile(fd, fh, gth_fat[i].f_size)) == 0) {
			if (setftime(fd, (struct ftime *)(&gth_fat[i].f_time)) == ERROR) {
				printf("\nCannot copy created time\n");
				exit(1);
			}
			close(fd);
			continue;
		}
		if (mode == -1) {
			printf("\nWriting error!");
		}
		else {
			printf("\nError in reading!");
		}
		exit(1);
	}
	exit(0);
}

void finish(int fh, char *fname)
{
	char filebak[MAXPATH];
	int i, j, mode, ftotal, fhback, fhadd;
	long pos;

	filebak[0] = 0;
	for (i = ftotal = 0; i < fall; i++) {
		if (gth_fat[i].f_size != 0)
			sortfile[ftotal++] = i;
	}
	if (ftotal == fall && fall == fnum) {
		printf("Noting to do.\n");
		exit(1);
	}
/************
	qsort( sortfile[0], sizeof(int), ftotal, comp );
 ************/
	fsort(ftotal);

	if (!f_inquire) {
		printf("Perform the above (y/n)?");
		if ((mode = getch()) != 'y' && mode != 'Y') {
			printf("\nTerminated.\n");
			exit(1);
		}
		printf("\n");
	}

	if (fh != ERROR) {			/* If there exists an old GTH file */
		close(fh);
		fnsplit(fname, drive, dir, file, ext);
		fnmerge(filebak, drive, dir, file, ".bak");
		remove(filebak);		/* remove the old BACK file if exists */
		if (rename(fname, filebak) != 0) {
			printf("Cannot rename %s\n", fname);
			exit(1);
		}
		if ((fhback = open(filebak, O_RDONLY | O_BINARY)) == ERROR) {
		  read_err:printf("Error in reading %s.", fname);
			close(fhback);
			goto recov;
		}
	}
	if ((fh = open(fname, O_CREAT | O_RDWR | O_BINARY, S_IREAD | S_IWRITE)
		) == ERROR) {
		printf("Cannot create %s.\n", fname);
	  recov:remove(fname);
		rename(filebak, fname);	/* recover the original GTH file */
		exit(1);
	}
	pos = 16L;
	for (i = 0; i < ftotal; i++)
		pos += gth_fat[sortfile[i]].f_size;
	ptr_long(buf) = pos;
	ptr_int(buf + 4) = ftotal;
	strcpy(buf + 8, "gather");
	if (write(fh, buf, 16) != 16) {
	  write_err:printf("Error in writing a file! (Enough disk space?)\n");
		close(fh);
		goto recov;
	}

	for (i = 0; i < ftotal; i++) {
		j = sortfile[i];
		if (addfile[j] == NULL) {	/* Copy from the original GTH file */
			if (lseek(fhback, gth_fat[j].f_pos, SEEK_SET) == -1L) {
				goto read_err;
			}
			if ((mode = cpyfile(fh, fhback, gth_fat[j].f_size)) == 0) {
				continue;
			}
			if (mode == -1)
				goto write_err;
			goto read_err;
		}
		else {
			if ((fhadd = open(addfile[j], O_RDONLY | O_BINARY)) == ERROR) {
			  err_readnew:printf("Error in reading %s\n", addfile[j]);
				goto recov;
			}
			if ((mode = cpyfile(fh, fhadd, gth_fat[j].f_size)) == 0
				&& eof(fhadd)) {
				close(fhadd);
				continue;
			}
			if (mode == -1)
				goto write_err;
			goto err_readnew;
		}
	}
	pos = 16L;
	for (i = 0; i < ftotal; i++) {
		gth_fat[sortfile[i]].f_pos = pos;
		pos += gth_fat[sortfile[i]].f_size;
	}
	for (i = 0; i < ftotal; i++) {
		if (write(fh, &(gth_fat[sortfile[i]]), FINFO_SIZE) != FINFO_SIZE)
			goto write_err;
	}
	printf("Completed.\n");
	if (f_backup)
		remove(filebak);
	else if (!f_inquire && filebak[0]) {
		printf("Erase a backup file %s (y/n)?", filebak);
		if ((mode = getch()) == 'y' || mode == 'Y') {
			printf("\nEracing %s.\n", filebak);
			remove(filebak);
		}
	}
	exit(0);
}

int cpyfile(int fhd, int fhs, long size)
{
	int tmpsize;

	while (size > 0) {
		if (size > BUF_SIZE)
			tmpsize = BUF_SIZE;
		else
			tmpsize = (int)size;
		if (read(fhs, buf, tmpsize) != tmpsize)
			return (-2);
		if (write(fhd, buf, tmpsize) != tmpsize)
			return (-1);
		size -= tmpsize;
	}
	return (0);
}

void fsort(int total)
{
	int i, j, k;

	i = 0;
	while (++i < total) {
		if (strcmp(gth_fat[sortfile[i - 1]].f_name,
				   gth_fat[sortfile[i]].f_name) <= 0)
			continue;
		k = i;
		do {
			j = sortfile[i];
			sortfile[i] = sortfile[i - 1];
			sortfile[--i] = j;
		} while (
					i > 0 &&
					strcmp(gth_fat[sortfile[i - 1]].f_name,
						   gth_fat[sortfile[i]].f_name) > 0
			);
		i = k;
	}
}

/************
int		comp( int *pt1, int *pt2 )
{
	return( strcmp( gth_fat[*pt1].f_name, gth_fat[*pt2].f_name ) );
}
************/

int get_info(char *fname)
{
	long start;
	int i, fh;

	if (strchr(fname, '.') == NULL) {
		strcat(fname, ".gth");
	}
	if ((fh = open(fname, O_RDONLY | O_BINARY)) == ERROR)
		return (fh);
	read(fh, buf, 16);
	if (strcmp(buf + 8, "gather") != 0) {
		printf("%s is not a GTH file.\n", fname);
		exit(1);
	}
	start = ptr_long(buf);
	fnum = fall = ptr_int(buf + 4);
	if (fnum > MAX_FILE) {
		printf("Too many files exist in %s.\n", fname);
		exit(1);
	}
	if (fnum < 0 || start < 16)
		goto gth_err;

	lseek(fh, start, SEEK_SET);
	read(fh, gth_fat, fnum * FINFO_SIZE);

	for (start = 16L, i = 0; i < fnum; i++) {
		if (start != gth_fat[i].f_pos) {
		  gth_err:printf("Error exists in %s", fname);
			exit(1);
		}
		start += gth_fat[i].f_size;
	}
	return (fh);
}

void show_dir()
{
	int i;
	unsigned int tmp;
	struct ftime *ftime;

	for (i = 0; i < fnum; i++) {

		ftime = (struct ftime *)(&gth_fat[i].f_time);
		fnsplit(gth_fat[i].f_name, drive, dir, file, ext);
		printf("\n%-9s%-4s%10ld  %4d-%02d-%02d  %2d:%02d:%02d",
			   file, ext + 1, gth_fat[i].f_size,
			   ftime->ft_year + 1980, ftime->ft_month, ftime->ft_day,
			   ftime->ft_hour, ftime->ft_min, ftime->ft_tsec);
	}
	printf("\n\n%d files exist.\n", fnum);
}

void f_up(char *fname, int mode)
{
	int i;
	char newfile[14];
	char newpath[MAXPATH], o_drive[MAXDRIVE], o_dir[MAXDIR];
	struct ffblk ffblk;

	if (findfirst(fname, &ffblk, 0) == ERROR)
		return;
	fnsplit(fname, drive, dir, file, ext);
	do {
		fnsplit(ffblk.ff_name, o_drive, o_dir, newfile, ext);
		fnmerge(newpath, drive, dir, newfile, ext);
		strcat(newfile, ext);
		for (i = 0; i < fall; i++) {
			if (strcmp(newfile, gth_fat[i].f_name) == 0) {
				if (i >= fnum || gth_fat[i].f_size == 0) {
					printf("Skipping:  %s\n", fname);
					goto next_file;
				}
				if (mode == F_NEW) {
				  replace:gth_fat[i].f_size = 0;
					cpyffblk(newpath, &ffblk);
					printf("Replacing: %s\n", newfile);
					goto next_file;
				}
				if (mode == F_UP) {	/* compare timestamp */
					if (ffblk.ff_fdate < gth_fat[i].f_date)
						goto next_file;
					if (ffblk.ff_fdate > gth_fat[i].f_date)
						goto replace;
					if (ffblk.ff_ftime > gth_fat[i].f_time)
						goto replace;
					goto next_file;
				}
			}
		}
		printf("Adding:    %s\n", newfile);
		cpyffblk(newpath, &ffblk);
	  next_file:{
		};
	} while (findnext(&ffblk) != ERROR);
}

void cpyffblk(char *pathname, struct ffblk *ffblk)
{
	char *pt, *pt2;
	int i;

	pt = (char *)(&(ffblk->ff_attrib));
	if (fall >= MAX_FILE - 1) {
		printf("Too many files.\n");
		exit(1);
	}
	pt2 = (char *)(&(gth_fat[fall]));
	for (i = 0; i < FINFO_SIZE - 4; i++)
		pt2[i] = pt[i];
	i = strlen(pathname);
	if (buf2_pos + i >= BUF2_SIZE - 1) {
		printf("No room for the internal buffer2\n");
		exit(1);
	}
	strcpy(buf2 + buf2_pos, pathname);
	addfile[fall++] = buf2 + buf2_pos;
	buf2_pos += i + 1;
}

void f_ext(char *fname)
{
	int i;

	for (i = 0; i < fnum; i++) {
		if (fcmp(gth_fat[i].f_name, fname) == 0)
			sortfile[i] = 1;
	}
}

void f_del(char *fname)
{
	int i;

	for (i = 0; i < fnum; i++) {
		if (fcmp(gth_fat[i].f_name, fname) == 0 &&
			gth_fat[i].f_size != 0L) {
			printf("Deleting: %s\n", gth_fat[i].f_name);
			gth_fat[i].f_size = 0L;
		}
	}
}

int fcmp(char *file_1, char *file_2)
{
	int i;

	for (;; file_1++, file_2++) {
		if (*file_1 == 0 || *file_2 == 0)
			return ((int)(*file_1 - *file_2));
		if (*file_2 == '?') {
			if (*file_1 != '.')
				continue;
			return (1);
		}
		if (*file_2 == '*') {
			while (*file_1 != '.') {
				if (*file_1 == 0) {
					file_1--;
					goto cmp_end;
				}
				file_1++;
			}
			file_1--;
			goto cmp_end;
		}
		if (toupper(*file_1) != toupper(*file_2))
			return (1);
	  cmp_end:{
		};
	}
}

void usage()
{
	printf(
			  "Gather files into a single file, Ver 1.2, "
			  "1990 - 1993, written by SHIMA\n"
			  "This is supported by TeX DEVICE DRIVERs dviout, dviprt for FONT files.\n\n"
	  "Usage:  gather -{a|e|u|d|p|v|x}[b][i] GTH_file [file_1 file_2 ...]\n"
			  "\t-a: add or create\n"
			  "\t-e: extract (same as -x)\n"
			  "\t-u: update\n"
			  "\t-d: delete\n"
			  "\t-p: print to stdout\n"
			  "\t-v: view file information\n"
			  "\t b: make no backup file\n"
			  "\t i: without inquiring\n");

	exit(1);
}
