/*
 * makepsres.c
 *
 * (c) Copyright 1991, 1994 Adobe Systems Incorporated.
 * All rights reserved.
 * 
 * Permission to use, copy, modify, distribute, and sublicense this software
 * and its documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notices appear in all copies and that
 * both those copyright notices and this permission notice appear in
 * supporting documentation and that the name of Adobe Systems Incorporated
 * not be used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.  No trademark license
 * to use the Adobe trademarks is hereby granted.  If the Adobe trademark
 * "Display PostScript"(tm) is used to describe this software, its
 * functionality or for any other purpose, such use shall be limited to a
 * statement that this software works in conjunction with the Display
 * PostScript system.  Proper trademark attribution to reflect Adobe's
 * ownership of the trademark shall be given whenever any such reference to
 * the Display PostScript system is made.
 * 
 * ADOBE MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THE SOFTWARE FOR
 * ANY PURPOSE.  IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
 * ADOBE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NON- INFRINGEMENT OF THIRD PARTY RIGHTS.  IN NO EVENT SHALL ADOBE BE LIABLE
 * TO YOU OR ANY OTHER PARTY FOR ANY SPECIAL, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE, STRICT LIABILITY OR ANY OTHER ACTION ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.  ADOBE WILL NOT
 * PROVIDE ANY TRAINING OR OTHER SUPPORT FOR THE SOFTWARE.
 * 
 * Adobe, PostScript, and Display PostScript are trademarks of Adobe Systems
 * Incorporated which may be registered in certain jurisdictions
 * 
 * Author:  Adobe Systems Incorporated
 */
/* $XFree86: xc/programs/makepsres/makepsres.c,v 1.10 2004/04/03 22:38:55 tsi Exp $ */

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

#ifdef XENVIRONMENT
#include <X11/Xos.h>
#else
#include <string.h>
#include <sys/types.h>
#endif

#include <sys/stat.h>

#include <dirent.h>

#ifndef S_ISDIR
#ifdef S_IFDIR
#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
#else
#define S_ISDIR(mode) (((mode) & _S_IFMT) == _S_IFDIR)
#endif /* S_IFDIR */
#endif /* S_ISDIR */

#define	true	 1
#define	false	 0

/* The max line length is really 256, but why make things that are hard
   to read??? */
#define MAXLINELEN 79

#ifndef DEBUG
#define DEBUG false
#endif

#define	QUOTE 042

#define HASHSIZE 2048

/* Command line information */

char **directories;
int *directoryLen;
int directoryCount;
int recursive;
int discard;
int keep;
char *outputFilename;
char **inputFiles;
int inputCount;
int makeExclusive;
int interactive;
int strict;
int noPrefix;
int issueWarnings;
int noBackup;
int noSuffix;

typedef struct _t_Resource {
  char *name;
  char *file;
  int noPrefix;
  struct _t_Resource *next;
} Resource;

typedef struct _t_Duplicate {
  char *name;
  char *file1;
  char *file2;
  struct _t_Duplicate *next;
} Duplicate;

typedef struct _t_Category {
  char *name;
  Resource *list;
  Resource **hash;	/* Currently used only for mkpsresPrivate */
  Duplicate *duplicates;
  struct _t_Category *next;
} Category;

char *program;

#if 0
extern char *malloc(), *realloc();
extern char *sys_errlist[];
extern int errno;
#endif

#define BUFFER_SIZE 1024
static char lineBuffer[BUFFER_SIZE];

Category *categories;

static char *ckmalloc(int size, char *whynot)
{
    char *result;

    if (size == 0) size = 1;
    result = malloc(size);
    if (result == NULL) {
	fprintf(stderr, "%s:  %s\n", program, whynot);
	exit(1);
    }
    return result;
}

static char *ckrealloc(char *ptr, int size, char *whynot)
{
    char *result;

    if (size == 0) size = 1;
    result = realloc(ptr, size);
    if (result == NULL) {
	fprintf(stderr, "%sf : %s\n", program, whynot);
	exit(1);
    }
    return result;
}

static char *ckcalloc(int count, int size, char *whynot)
{
    char *result;

    if (size == 0) size = 1;
    if (count == 0) count = 1;
    result = (char *) calloc(count, size);
    if (result == NULL) {
	fprintf(stderr, "%s:  %s\n", program, whynot);
	exit(1);
    }
    return result;
}


static Category *AddCategory (
  char *name)
{
  Category *newCategory = (Category *) ckcalloc (1, sizeof (Category),
		"Failed to allocate Category record.");

  newCategory->name = (char *) ckmalloc (strlen (name) + 1,
					 "Failed to allocate Category name.");

  strcpy (newCategory->name, name);

  if (categories == NULL) {
    categories = newCategory;
  } else {
    /* Insert into alphabetical position */
    Category *current,
           *previous;

    current = previous = categories;

    while (current != NULL) {
      if (strcmp (current->name, name) > 0) {
	break;
      } else {
	previous = current;
	current = current->next;
      }
    }

    if (current == NULL) {
      newCategory->next = NULL;
      previous->next = newCategory;
    } else {
      newCategory->next = current;

      if (current == categories) {
	categories = newCategory;
      } else {
	previous->next = newCategory;
      }
    }
  }

  return (newCategory);
}


static Category *FindCategory (
  char *name)
{
  Category *category = categories;

  while (category != NULL) {
    if (strcmp (category->name, name) == 0)
      break;
    else
	category = category->next;
  }

   if (category == NULL)
     category = AddCategory (name);

  return (category);
}


static int Hash(
    char *string)
{
    int hash = 0;
    unsigned char *ch = (unsigned char *) string;

    while (1) {
	if (*ch == '\0') return hash % HASHSIZE;
	if (*(ch+1) == '\0') {
	    hash += *ch;
	    return hash % HASHSIZE;
	}
	hash += *ch++;
	hash += (*ch++ << 8);
    }
}

static void AddHashedResource(
    Resource *resource,
    Category *category)
{
    Resource *current, *previous;
    int     comparison, hash;

    if (category->hash == NULL) {
	category->hash = (Resource **) ckcalloc(HASHSIZE, sizeof(Resource *),
					"Failed to allocate hash table.");
    }

    hash = Hash(resource->file);
    current = previous = category->hash[hash];

    while (current != NULL) {
	comparison = strcmp (current->file, resource->file);
	if (comparison > 0) break;

	if (comparison == 0 &&
	    strcmp(current->name, resource->name) != 0) break;

	previous = current;
	current = current->next;
    }

    if (category->hash[hash] == NULL) {
	category->hash[hash] = resource;
	resource->next = NULL;

    } else if (current == NULL) {
	resource->next = NULL;
	previous->next = resource;

    } else {
	resource->next = current;

	if (current == category->hash[hash]) {
	    category->hash[hash] = resource;
	} else {
	    previous->next = resource;
	}
    }
}

static void EnterDuplicateWarning(
    Category *category,
    Resource *res1,
    Resource *res2)
{
    Duplicate *dup, *previous, *current;

    if (!issueWarnings) return;

    dup = (Duplicate *) ckcalloc(1, sizeof(Duplicate),
				 "Failed to allocate Duplicate record.");

    dup->name = res1->name;
    dup->file1 = res1->file;
    dup->file2 = res2->file;

    current = previous = category->duplicates;

    while (current != NULL) {
	if (strcmp (current->name, res1->name) >= 0) break;
	previous = current;
	current = current->next;
    }

    if (category->duplicates == NULL) category->duplicates = dup;
    else if (current == NULL) {
	dup->next = NULL;
	previous->next = dup;
    } else {
	dup->next = current;

	if (current == category->duplicates) category->duplicates = dup;
	else previous->next = dup;
    }
}

static void AddResource (
  char *categoryName,
  char *resourceName,
  char *fileName,
  int noPrefix)
{
  Category *category = FindCategory (categoryName);
  Resource *resource,
         *current,
         *previous;
  int     comparison;

  resource = (Resource *) ckcalloc (1, sizeof (Resource),
				    "Failed to allocate Resource record.");

  resource->name = ckmalloc (strlen (resourceName) + 1,
			     "Failed to allocate Resource name.");

  strcpy (resource->name, resourceName);

  if (fileName[0] == '.' && fileName[1] == '/') fileName+=2;

  resource->file = ckmalloc (strlen (fileName) + 1,
			     "Failed to allocate Resource filename.");

  strcpy (resource->file, fileName);

  resource->noPrefix = noPrefix;

  if (strcmp(categoryName, "mkpsresPrivate") == 0) {
      AddHashedResource(resource, category);
      return;
  }

  current = previous = category->list;

  while (current != NULL) {
      comparison = strcmp (current->name, resourceName);
      if (comparison > 0) break;
      else if (comparison == 0) {
	  comparison = strcmp (current->file, fileName);
	  if (comparison > 0) {
	      if (strcmp(categoryName, "FontBDFSizes") != 0 &&
		  strcmp(categoryName, "FontFamily") != 0 &&
		  strcmp(categoryName, "mkpsresPrivate") != 0) {
		  EnterDuplicateWarning(category, current, resource);
	      }
	      break;
	  } else if (comparison == 0) {		/* Same file */
	      free (resource->name);
	      free (resource->file);
	      free (resource);
	      return;
	  }
      }
      previous = current;
      current = current->next;
  }

  if (category->list == NULL) {
    category->list = resource;
  } else if (current == NULL) {
    resource->next = NULL;
    previous->next = resource;
  } else {
    resource->next = current;

    if (current == category->list) {
      category->list = resource;
    } else {
      previous->next = resource;
    }
  }
}

static int FindResource(
  char *categoryName,
  char *resourceName)
{
    Category *category = FindCategory (categoryName);
    Resource *resource;
    int i;

    for (resource = category->list;
	 resource != NULL && strcmp(resource->name, resourceName) != 0;
	 resource = resource->next) {}

    if (resource != NULL) return true;

    if (category->hash == NULL) return false;

    for (i = 0; i < HASHSIZE; i++) {
	for (resource = category->hash[i];
	     resource != NULL && strcmp(resource->name, resourceName) != 0;
	     resource = resource->next) {}
	if (resource != NULL) return true;
    }

    return false;
}


typedef struct _t_UPRResource {
  char *name;
  char *file;
  char *category;
  int found;
  int noPrefix;
  struct _t_UPRResource *next;
} UPRResource;

UPRResource *UPRresources[HASHSIZE];

#if DEBUG
int bucketCount[HASHSIZE];
int totalHashed = 0;
#endif

static void AddUPRResource (
  char *categoryName,
  char *resourceName,
  char *fileName,
  char *prefix,
  int noPrefix)
{
  UPRResource *resource, *current, *previous;
  int     comparison, hash;

  if (noPrefix || prefix == NULL) {
      prefix = "";
      if (fileName[0] == '.' && fileName[1] == '/') fileName+=2;
  } else {
      prefix++; /* Skip over leading / */
      if (prefix[0] == '.' && prefix[1] == '/') prefix += 2;
  }

  resource = (UPRResource *) ckcalloc (1, sizeof (UPRResource),
				    "Failed to allocate Resource record.");

  resource->name = ckmalloc (strlen (resourceName) + 1,
			     "Failed to allocate Resource name.");

  strcpy (resource->name, resourceName);

  resource->file = ckmalloc (strlen (fileName) + strlen(prefix) + 2,
			     "Failed to allocate Resource filename.");

  if (prefix != NULL && prefix[0] != '\0') {
      strcpy (resource->file, prefix);
      strcat (resource->file, "/");
      strcat (resource->file, fileName);
  } else strcpy (resource->file, fileName);

  resource->category = ckmalloc (strlen (categoryName) + 1,
				 "Failed to allocate Resource name.");

  strcpy(resource->category, categoryName);

  resource->noPrefix = noPrefix;
  resource->found = false;

  hash = Hash(resource->file);
  current = previous = UPRresources[hash];

  while (current != NULL) {
      comparison = strcmp (current->file, resource->file);
      if (comparison > 0) break;

      if (comparison == 0) {
	  if (noPrefix) break;

	  if (strcmp(current->name, resource->name) != 0 ||
	      strcmp(current->category, resource->category) != 0) { /* Same */
	      if (strcmp(current->category, "mkpsresPrivate") == 0 &&
		  strcmp(current->name, "NONRESOURCE") == 0) {

		  /* Replace "NONRESOURCE" entry with resource one */
		  free(current->name);
		  current->name = resource->name;
		  free(current->category);
		  current->category = resource->category;
		  free(resource->file);
		  free (resource);
		  return;
	      }
	      fprintf(stderr,
		  "%s:  Warning:  file %s identified as different resources\n",
		  program, resource->file);
	      fprintf(stderr, "            Using %s\n", current->category);
	  }
	  free (resource->name);
	  free (resource->file);
	  free (resource->category);
	  free (resource);
	  return;
      }
      previous = current;
      current = current->next;
  }

  if (UPRresources[hash] == NULL) {
      UPRresources[hash] = resource;
      resource->next = NULL;

  } else if (current == NULL) {
      resource->next = NULL;
      previous->next = resource;
  } else {
      resource->next = current;

      if (current == UPRresources[hash]) {
	  UPRresources[hash] = resource;
      } else {
	  previous->next = resource;
      }
  }
#if DEBUG
  totalHashed++;
  bucketCount[hash]++;
#endif
}


static void AddUPRResourceBDF(
    char *font,
    char *sizes)
{
    char *ch = sizes;
    char *buf;

    while (*ch != '\0') {
	while (*ch != '\0' && *ch != ',') ch++;
	if (*ch == ',') {
	    *ch = '\0';
	    if (*sizes != '\0') {
		/* Stick in the font size to spread out the hash table */

		buf = ckmalloc(strlen(font) + strlen(sizes) + 2,
			       "Failed to allocate BDF string");
		sprintf(buf, "%s,%s", font, sizes);
		AddUPRResource("FontBDFSizes", font, buf, NULL, true);
		free(buf);
	    }
	    sizes = ++ch;
	}
    }
}

static void AddUPRResourceFontFamily(
    char *family,
    char *faces)
{
    char *ch = faces, *chunk = faces;
    char old;

    while (true) {
	while (true) {
	    while (*ch != '\0' && *ch != ',') ch++;
	    if (*ch == '\0') return;
	    if (ch > faces && *(ch-1) == '\\') ch++;
	    else break;
	}
	/* Found the first , look for the second */
	ch++;
	while (true) {
	    while (*ch != '\0' && *ch != ',') ch++;
	    if (*ch == '\0') break;
	    if (*(ch-1) == '\\') ch++;
	    else break;
	}

	old = *ch;
	*ch = '\0';
	AddUPRResource("FontFamily", family, chunk, NULL, true);
	if (old == '\0') return;
	chunk = ++ch;
    }
}

static int SkipWhiteSpace(
    FILE *file)
{
    int c;

    while (1) {
	c = fgetc(file);
	if (c == ' ' || c == '\t') continue;
	if (c == EOF) return false;
	ungetc(c, file);
	return true;
    }
}


static int ReadItem(
    FILE *file,
    char *buf,
    int size)
{
    int c;
    char closechar, openchar;
    int count = 0, nesting = 0;;

    openchar = '\0';

    c = fgetc(file);
    if (c == EOF) return false;
    if (c == '(') {closechar = ')'; openchar = c;}
    else if (c == '[') {closechar = ']'; openchar = c;}
    else if (c == QUOTE) closechar = QUOTE;
    else if (c == '/') closechar = '\0';
    else {
	closechar = '\0';
	ungetc(c, file);
    }

    while (count < size) {
	c = fgetc(file);
	if (openchar != '\0' && c == openchar) nesting++;
	if (c == EOF) break;
	if (c == closechar) {
	    if (nesting == 0) break;
	    else nesting--;
	}
	if (closechar == '\0' && strchr(" \t\n\r", c) != NULL) break;
	buf[count++] = c;
    }

    buf[count] = '\0';
    return true;
}

static char *FindKeyValue (
  FILE *file,
  char *key)
{
  char    lineKey[64];
  char   *result = NULL;

  while (true) {
    if (fgets (lineBuffer, BUFFER_SIZE, file) == NULL)
      break;

    sscanf (lineBuffer, "%63[%a-zA-Z]", lineKey);
    if (strcmp (key, lineKey) == 0) {
      result = strchr (lineBuffer, ' ');
      if (result != NULL) {
	result++;
	break;
      }
    }
  }

  return (result);
}

static void StripName (
  char *name)
{
  char    closeCharacter = '\0';
  char   *pointer;

  if (name[0] == '/') strcpy(name, name+1);

  while (true) {
    if (name[0] == '(') closeCharacter = ')';
    else if (name[0] == QUOTE) closeCharacter = QUOTE;
    else break;

    pointer = strrchr (name, closeCharacter);

    if (pointer != NULL) {
      *pointer = '\0';
      strcpy (name, name + 1);
    } else break;			/* No close character */
  }

  pointer = strrchr (name, '\r');

  if (pointer != NULL) *pointer = '\0';
  else {
    pointer = strrchr (name, '\n');
    if (pointer != NULL) *pointer = '\0';
  }
}


static char *bugFamilies[] = {
    "Berkeley", "CaslonFiveForty", "CaslonThree", "GaramondThree",
    "Music", "TimesTen", NULL
	};

static char *fixedFamilies[] = {
    "ITC Berkeley Oldstyle", "Caslon 540", "Caslon 3", "Garamond 3",
    "Sonata", "Times 10", NULL
};

static char *missingFoundries[] = {
    "Berthold ", "ITC ", "Linotype ", NULL
};

static int missingFoundryLen[] = {
    9, 4, 9, 0
};

static void MungeFontNames(
    char *name,
    char *family,
    char *fullname,
    char *weight,
    char *familyReturn,
    char *fullnameReturn,
    char *faceReturn)
{
    register char *src, *dst, prev;
    char buf[256];
    int digits = 0;
    int i, diff;

    /* Copy the fullname into buf, enforcing one space between words.
       Eliminate leading digits and spaces, ignore asterisks, if the
       full name ends with 5 digits strip them, and replace periods that
       aren't followed by a space with a space.  If leading digits are
       followed by " pt " skip that too. */

    dst = buf;
    prev = ' ';
    src = fullname; 
    while (isdigit(*src)) src++;
    while (*src == ' ' || *src == '\t') src++;
    if (strncmp(src, "pt ", 3) == 0) src += 3;
    else if (strncmp(src, "pt. ", 4) == 0) src += 4;

    while (*src != '\0') {
	if (*src == '*') {
	    src++;
	    continue;
	}

	if (*src == '.') {
	    if (*(src+1) != ' ') {
		prev = *dst++ = ' ';
	    } else prev = *dst++ = '.';
	    src++;
	    continue;
	}

	if (isdigit(*src)) digits++;
	else digits = 0;

	if (isupper(*src)) {
	    if (prev != ' ' && (islower(*(src+1)) || islower(prev))) {
		*dst++ = ' ';
		prev = *dst++ = *src++;
	    } else prev = *dst++ = *src++;

	} else if (*src == ' ' || *src == '\t') {
	    if (prev == ' ') {
		src++;
		continue;
	    }
	    prev = *dst++ = ' ';
	    src++;

	} else prev = *dst++ = *src++;
    }

    if (digits == 5) {
	dst -= 5;
    }
    if (dst > buf && *(dst-1) == ' ') dst--;

    *dst = '\0';

    if (strcmp(name, "FetteFraktur-Dfr") == 0) strcat(buf, " Black Dfr");
    else if (strcmp(name, "Linotext-Dfr") == 0) strcat(buf, " Dfr");

    if (strncmp(fullname, "pt ", 3) == 0) {
	src = buf + 2;
	while (*++src != '\0') *(src-3) = *src;
	*(src-3) = '\0';
    }

    strcpy(fullnameReturn, buf);

    /* From here on fullname should not be used */

    /* Done with the full name; now onto the family */

    for (i = 0; bugFamilies[i] != NULL; i++) {
	diff = strcmp(family, bugFamilies[i]);
	if (diff < 0) break;
	if (diff == 0) {
	    strcpy(familyReturn, fixedFamilies[i]);
	    goto FAMILY_DONE;
	}
    }

    /* Copy the family into buf, enforcing one space between words */

    dst = buf;
    prev = ' ';
    src = family; 

    while (*src != '\0') {
	if (isupper(*src)) {
	    if (prev != ' ' && (islower(*(src+1)) || islower(prev))) {
		*dst++ = ' ';
		prev = *dst++ = *src++;
	    } else prev = *dst++ = *src++;

	} else if (*src == ' ' || *src == '\t') {
	    if (prev == ' ') {
		src++;
		continue;
	    }
	    prev = *dst++ = ' ';
	    src++;

	} else prev = *dst++ = *src++;
    }

    if (dst > buf && *(dst-1) == ' ') dst--;
    *dst = '\0';

    /* Compensate for fonts with foundries in the full name but not the
       family name by adding to the family name */
 
    for (i = 0; missingFoundries[i] != NULL; i++) {
	diff = strncmp(fullnameReturn, missingFoundries[i],
		       missingFoundryLen[i]);
	if (diff > 0) continue;
	if (diff == 0 && strncmp(buf, missingFoundries[i],
		       missingFoundryLen[i] != 0)) {
	    while (dst >= buf) {
		*(dst+missingFoundryLen[i]) = *dst;
		dst--;
	    }
	    strncpy(buf, missingFoundries[i], missingFoundryLen[i]);
	}
	break;
    }

    /* From here on dst no longer points to the end of the buffer */

    if (strncmp(fullnameReturn, "Helvetica Rounded ", 18) == 0) {
	strcat(buf, " Rounded");
    }

    strcpy(familyReturn, buf);

FAMILY_DONE:

    /* From here on family should not be used */

    /* Now to find the face in all this */

    src = fullnameReturn;
    dst = familyReturn;
    while (*dst == *src && *dst != '\0') {
        src++;
        dst++;
    }
    if (*src == ' ') src++;

    if (*src == '\0') {
	if (*weight != '\0') {
	    /* Handle Multiple Master fonts */
	    if (strcmp(weight, "All") == 0) src = "Roman";
	    else {
		if (islower(weight[0])) weight[0] = toupper(weight[0]);
		src = weight;
	    }
	} else src = "Medium";
    }

    strcpy(faceReturn, src);
}


static void StripComments(
    char *buf)
{
    register char *ch = buf;

    while (true) {
	while (*ch != '%' && *ch != '\0') ch++;
	if (*ch == '\0') break;
	if (ch == buf || *(ch-1) != '\\') {
	    *ch = '\0';
	    break;
	}
	ch++;
    }

    /* ch points to '\0' right now */

    if (ch == buf) return;
    ch--;

    while (ch > buf && (*ch == ' ' || *ch == '\t' || *ch == '\n')) {
	*ch = '\0';
	ch--;
    }

    if (ch == buf && (*ch == ' ' || *ch == '\t' || *ch == '\n')) *ch = '\0';
}

/* Caller must free returned line */

static char *GetWholeLine(
    FILE *file)
{
    char *line;
    int len, oldlen;

    if (fgets (lineBuffer, BUFFER_SIZE, file) == NULL) return NULL;

    StripComments(lineBuffer);
    
    len = strlen(lineBuffer); 
    line = ckmalloc(len+1, "Failed to allocate input line.");
    strcpy(line, lineBuffer);

    if (line[len-1] == '\\') {	/* Continued... */
	line[len-1] = '\0';
	oldlen = len-1;
	while (true) {
	    if (fgets(lineBuffer, BUFFER_SIZE, file) == NULL) {
		return line;
	    }

	    StripComments(lineBuffer);
	    if (lineBuffer[0] == '\0') return line;

	    len = strlen(lineBuffer);
	    line = ckrealloc(line, oldlen+len+1,
			     "Failed to reallocate input line.");
	    strcat(line, lineBuffer);

	    oldlen += len;
	    if (line[oldlen-1] != '\\') break;
	    line[oldlen-1] = '\0';
	    oldlen--;
	}
    }
    return line;
}

static void HandleUnopenableUPRFile(
    char *filename,
    int err)
{
    if (issueWarnings) {
	fprintf (stderr, "%s:  Could not open file %s (%s).\n",
		 program, filename, strerror(err));
    }

    if (strict) exit(1);
}


static void PreprocessResourceDirectory(
    char *fullname)
{
    char *category;
    FILE *file;
    char *line;
    char *directoryPrefix = NULL;
    int noPrefix;

    file = fopen (fullname, "r");

    if (file == NULL) {
	HandleUnopenableUPRFile(fullname, errno);
	return;
    }

    /* Skip over list of categories */

    while (true) {
	if (fgets (lineBuffer, BUFFER_SIZE, file) == NULL) return;

	if (lineBuffer[0] == '.') break;
    }

    while (true) {
	/* Process category */
     
	line = GetWholeLine(file);
	if (line == NULL) {
	    if (directoryPrefix != NULL) free(directoryPrefix);
	    return;
	}
      
	if (line[0] == '/') { /* Handle optional directory prefix */
	    directoryPrefix = line;
	    continue;
	}

	category = line;

	while (true) {
	    char *resourceFile;

	    line = GetWholeLine(file);
	    if (line == NULL) {
		if (directoryPrefix != NULL) free(directoryPrefix);
		free(category);
	    }

	    if (line[0] == '.') {
		free(category);
		free(line);
		break;
	    }

	    resourceFile = line;
	    while (true) {
		if ((resourceFile = strchr(resourceFile, '=')) != NULL) {
		    if (resourceFile != line && *(resourceFile-1) != '\\') {
			*resourceFile++ = '\0';
			noPrefix = (*resourceFile == '=');
			if (noPrefix) resourceFile++;
			if (strcmp(category, "FontBDFSizes") == 0) {
			    AddUPRResourceBDF(line, resourceFile);
			} else if (strcmp(category, "FontFamily") == 0) {
			    AddUPRResourceFontFamily(line, resourceFile);
			} else AddUPRResource (category, line,
					       resourceFile,
					       (noPrefix ? NULL :
						           directoryPrefix),
					       noPrefix);
			break;
		    }
		    resourceFile++;
		} else break;	/* Bogus line */
	    }
	    free(line);
	}
    }
}

static int SkipToCharacter (
  FILE *file,
  char character)
{
  int c;

  while ((c = fgetc (file)) != EOF) {
    if (c == character)
      return (true);
  }

  return (false);
}

static int SkipToEitherCharacter (
  FILE *file,
  char character1,
  char character2,
  char *outchar)
{
  register int c;

  while ((c = fgetc (file)) != EOF) {
    if (c == character1 || c == character2) {
	*outchar = c;
	return (true);
    }
  }

  return (false);
}


static void ProcessFont (
    FILE *file,
    char *fileName)
{
    char fontName[256], fontFamily[256], fontFullName[256], fontWeight[256];
    char key[256], buf[513];
    char familyReturn[256], fullnameReturn[256], faceReturn[256];
    char blendDesignPositions[256], blendDesignMap[256], blendAxisTypes[256];  
    char out;
    int found = 0;

    fontName[0] = fontFamily[0] = fontFullName[0] = fontWeight[0] = '\0';
    blendDesignPositions[0] = blendDesignMap[0] = blendAxisTypes[0] = '\0';  

    while (found != 0x7F && SkipToEitherCharacter (file, '/', 'e', &out)) {
	/* If we encounter an eexec, skip the rest of the file */
	if (out == 'e') {
	    if (fscanf (file, "%255s", key) != 1) continue;
	    if (strcmp(key, "exec") == 0) break;
	    continue;
	}

	if (fscanf (file, "%255s", key) != 1) continue;
	if (!SkipWhiteSpace(file)) break;
	if (!ReadItem(file, buf, 256)) break;

	if ((found & 0x1) == 0 && strcmp(key, "FullName") == 0) {
	    strcpy(fontFullName, buf);
	    found |= 0x1;
	    continue;
	}
	if ((found & 0x2) == 0 && strcmp(key, "FamilyName") == 0) {
	    strcpy(fontFamily, buf);
	    found |= 0x2;
	    continue;
	}
	if ((found & 0x4) == 0 && strcmp(key, "Weight") == 0) {
	    strcpy(fontWeight, buf);
	    found |= 0x4;
	    continue;
	}
	if ((found & 0x8) == 0 && strcmp(key, "FontName") == 0) {
	    strcpy(fontName, buf);
	    found |= 0x8;
	    continue;
	}
	if ((found & 0x10) == 0 && strcmp(key, "BlendDesignPositions") == 0) {
	    strcpy(blendDesignPositions, buf);
	    found |= 0x10;
	    continue;
	}
	if ((found & 0x20) == 0 && strcmp(key, "BlendDesignMap") == 0) {
	    strcpy(blendDesignMap, buf);
	    found |= 0x20;
	    continue;
	}
	if ((found & 0x40) == 0 && strcmp(key, "BlendAxisTypes") == 0) {
	    strcpy(blendAxisTypes, buf);
	    found |= 0x40;
	    continue;
	}
    }

    if (fontName[0] != '\0') {
	if (fontFullName[0] == '\0') {
	    if (fontFamily[0] != '\0') strcpy(fontFullName, fontFamily);
	    else strcpy(fontFullName, fontName);
	}
	if (fontFamily[0] == '\0') strcpy(fontFamily, fontFullName);

	MungeFontNames(fontName, fontFamily, fontFullName, fontWeight,
		       familyReturn, fullnameReturn, faceReturn);
#if DEBUG
	printf("Found font %s\n", fontName);
	printf("Munged to (%s) (%s)\n", familyReturn, faceReturn);
#endif
	sprintf(buf, "%s,%s", faceReturn, fontName);
	AddResource ("FontOutline", fontName, fileName, false);
	AddResource("FontFamily", familyReturn, buf, true);
	if (blendDesignPositions[0] != '\0' && blendDesignMap[0] != '\0' &&
	    blendAxisTypes[0] != '\0') {
#if DEBUG
	    printf("Font %s is multiple master\n", fontName);
#endif
	    AddResource("FontBlendPositions", fontName, blendDesignPositions, true);
	    AddResource("FontBlendMap", fontName, blendDesignMap, true);
	    AddResource("FontAxes", fontName, blendAxisTypes, true);
	}
    }
}


static void ProcessResource (
    FILE *file,
    char *fileName)
{
    char    resourceType[256];
    char    resourceName[256];
    char   *pointer;

    sscanf (lineBuffer, "%%!PS-Adobe-%*[0123456789.] Resource-%128s",
	    resourceType);

    StripName (resourceType);

    if (strcmp(resourceType, "Font") == 0) {
	ProcessFont(file, fileName);
	return;
    }

    pointer = FindKeyValue (file, "%%BeginResource");

    if (pointer == NULL) return;

    sscanf (pointer, "%*256s%255s", resourceName);
    StripName (resourceName);

    AddResource (resourceType, resourceName, fileName, false);
}


static void ProcessBDF (
    FILE *file,
    char *fileName)
{
    char    fontName[256];
    char    psFontName[256];
    char    key[256];
    unsigned int found = 0;
    char	  nameSize[300];
    int     resx, resy, size;
    char    sizebuf[50];

    fontName[0] = psFontName[0] = '\0';
    resx = resy = size = 0;

    while (SkipToCharacter(file, '\n')) {
	if (!SkipWhiteSpace(file)) break;
	if (fscanf (file, "%255s", key) != 1) continue;
	if (!SkipWhiteSpace(file)) break;

	if ((found & 1) == 0 && strcmp(key, "FONT") == 0) {
	    if (!ReadItem(file, fontName, 256)) break;
	    found |= 1;
	    continue;
	}
	if ((found & 2) == 0 && strcmp(key, "RESOLUTION_X") == 0) {
	    if (fscanf (file, "%d", &resx) != 1) break;
	    found |= 2;
	    continue;
	}
	if ((found & 4) == 0 && strcmp(key, "RESOLUTION_Y") == 0) {
	    if (fscanf (file, "%d", &resy) != 1) break;
	    found |= 4;
	    continue;
	}
	if ((found & 8) == 0 && strcmp(key, "_ADOBE_PSFONT") == 0) {
	    if (!ReadItem(file, psFontName, 256)) break;
	    found |= 8;
	    continue;
	}
	if ((found & 16) == 0 && strcmp(key, "SIZE") == 0) {
	    if (fscanf(file, "%d %d %d", &size, &resx, &resy) != 3) break;
	    found |= 16;
	    continue;
	}
	if ((found & 32) == 0 && strcmp(key, "POINT_SIZE") == 0) {
	    if (fscanf(file, "%d", &size) != 1) break;
	    size /= 10;
	    found |= 32;
	}
	if (strcmp(key, "ENDPROPERTIES") == 0) break;
	if (strcmp(key, "STARTCHAR") == 0) break;
    }
	
    if (psFontName[0] != '\0') strcpy(fontName, psFontName);

    if (size == 0 || fontName[0] == '\0') return;
    if (resx == 0 || resy == 0) sprintf(sizebuf, "%d", size);
    else sprintf(sizebuf, "%d-%d-%d", size, resx, resy);
	
    sprintf(nameSize, "%s%s", fontName, sizebuf);

    AddResource ("FontBDF", nameSize, fileName, false);
    AddResource ("FontBDFSizes", fontName, sizebuf, true);
}

static void ProcessAFM (
    FILE *file,
    char *fileName)
{
    char    fontName[256];
    char   *pointer;
    char   *extraCr;

    pointer = FindKeyValue (file, "FontName");

    if (pointer == NULL)
	    return;

    sscanf (pointer, "%255s", fontName);

    extraCr = strchr (fontName, '\r'); /* Handle DOS newlines */

    if (extraCr != NULL) *extraCr = '\0';

    AddResource ("FontAFM", fontName, fileName, false);
}

/* ARGSUSED */
static void ProcessResourceDirectory (
    FILE *file,
    char *fileName)
{
}

static void MarkAsNonResource(
    char *filename)
{
    AddResource("mkpsresPrivate", "NONRESOURCE", filename, false);
}

char *userCategories[] = {
    "Encoding",
    "File",
    "FontAFM",
    "FontBDF",
    "FontOutline",
    "FontPrebuilt",
    "Form",
    "Pattern",
    "ProcSet",
    NULL
};

static void IdentifyFromUser(
    char *filename,
    FILE *file)
{
    int i, numCats, choice, size;
    char buf[256], name[256];
    static int stdinEOF = false;

    if (stdinEOF) return;

    if (file != NULL) rewind(file);

    while (1) {
	printf("Please indentify the file\n");
	printf("  0 - Not a resource file\n");
	i = 0;
	while (userCategories[i] != NULL) {
	    printf("  %d - %s\n", i+1, userCategories[i]);
	    i++;
	}
	numCats = i;
	printf("  %d - Other\n", numCats+1);
	if (file != NULL) printf("  %d - Show some of file\n", numCats+2);
	printf("> ");
	fflush(stdout);
	if (scanf("%d", &choice) != 1) {
	    stdinEOF = true;
	    return;
	}
	/* Skip the last of the number input line */
	if (fgets(buf, 256, stdin) == NULL) {
	    stdinEOF = true;
	    return;
	}
	if (choice < 0 || (file != NULL && choice > numCats+2) ||
	    (file == NULL && choice > numCats+1)) {
	    printf("Invalid choice\n\n");
	    continue;
	}
	if (choice == numCats+2) {
	    printf("\n");
	    for (i = 0; i < 10; i++) {
		if (fgets(buf, 256, file) != NULL) fputs(buf, stdout);
	    }
	    printf("\n");
	    continue;
	}
	if (choice == 0) {
	    MarkAsNonResource(filename);
	    return;
	}
	if (choice == numCats+1) {
	    printf("Please enter resource category:  ");
	    fflush(stdout);
	    if (fgets(buf, 256, stdin) == NULL) {
		stdinEOF = true;
		return;
	    }
	    if (buf[0] == '\0') continue;
	    printf("Please enter resource name:  ");
	    fflush(stdout);
	    if (fgets(name, 256, stdin) == NULL) {
		stdinEOF = true;
		return;
	    }
	    if (name[0] == '\0') continue;
	    StripName(buf);
	    StripName(name);
	    AddResource(buf, name, filename, false);
	    return;
	}
	if (strcmp(userCategories[choice-1], "FontBDF") == 0) {
	    printf("Please enter font name:  ");
	    fflush(stdout);
	    if (fgets(name, 256, stdin) == NULL) {
		stdinEOF = true;
		return;
	    }
	    if (name[0] == '\0') continue;
	    StripName(name);
	    printf("Please enter font size:  ");
	    fflush(stdout);
	    if (scanf("%d", &size) != 1) {
		stdinEOF = true;
		return;
	    }
	    /* Skip the last of the number input line */
	    if (fgets(buf, 256, stdin) == NULL) {
		stdinEOF = true;
		return;
	    }
	    if (size <= 0) continue;
	    sprintf(buf, "%d", size);
	    AddResource("FontBDFSizes", name, buf, false);
	    sprintf(buf, "%s%d", name, size);
	    AddResource("FontBDF", buf, filename, true);
	    return;
	}
	if (strcmp(userCategories[choice-1], "FontOutline") == 0) {
	    char family[256], face[256];
	    printf("Please enter font name:  ");
	    fflush(stdout);
	    if (fgets(name, 256, stdin) == NULL) {
		stdinEOF = true;
		return;
	    }
	    if (name[0] == '\0') continue;
	    StripName(name);
	    printf("Please enter font family:  ");
	    fflush(stdout);
	    if (fgets(family, 256, stdin) == NULL) {
		stdinEOF = true;
		return;
	    }
	    if (name[0] == '\0') continue;
	    StripName(family);
	    printf("Please enter font face:  ");
	    fflush(stdout);
	    if (fgets(face, 256, stdin) == NULL) {
		stdinEOF = true;
		return;
	    }
	    if (name[0] == '\0') continue;
	    StripName(face);
	    AddResource("FontOutline", name, filename, true);
	    sprintf(buf, "%s,%s", face, name);
	    AddResource("FontFamily", family, buf, false);
	    return;
	}

	printf("Please enter %s name:  ", userCategories[choice-1]);
	fflush(stdout);
	if (fgets(name, 256, stdin) == NULL) {
	    stdinEOF = true;
	    return;
	}
	if (name[0] == '\0') continue;
	StripName(name);
	AddResource(userCategories[choice-1], name, filename, true);
	return;
    }
}

static int IdentifyFromUPRList(
    char *filename)
{
    UPRResource *resource = UPRresources[Hash(filename)];

    while (resource != NULL && strcmp(filename, resource->file) != 0) {
	resource = resource->next;
    }
    if (resource == NULL) return false;
    AddResource(resource->category, resource->name, resource->file,
		resource->noPrefix);
    return true;
}

static int IdentifyFromFileSuffix(
    char *fileName)
{
    int len, len1;
    char fontName[256];
    register char *ch;

    /* The only files we can get anything useful from without looking inside
       are AFM files and prebuilt files */

    len = strlen(fileName);
    if (len < 5) return false;
    if (strcmp(".afm", fileName + len - 4) == 0) {
	len1 = 0;
	for (ch = fileName+len-5; ch > fileName && *ch != '/'; ch--) len1++;
	if (*ch == '/') ch++;
	else len1++;
	strcpy(fontName, ch);
	fontName[len1] = '\0';
	AddResource("FontAFM", fontName, fileName, false);
	return true;
    }
    if (len < 6) return false;
    if ((strcmp(".bepf", fileName + len - 5) == 0) ||
	(strcmp(".lepf", fileName + len - 5) == 0)) {
	len1 = 0;
	for (ch = fileName+len-6; ch > fileName && *ch != '/'; ch--) len1++;
	if (*ch == '/') ch++;
	else len1++;
	strcpy(fontName, ch);
	fontName[len1] = '\0';
	AddResource("FontPrebuilt", fontName, fileName, false);
	return true;
    }
    return false;
}

static void HandleUnopenableFile(
    char *filename,
    int err)
{
    if (IdentifyFromUPRList(filename)) return;

    if (!noSuffix && IdentifyFromFileSuffix(filename)) return;

    if (issueWarnings) {
	fprintf (stderr, "%s:  Could not open file %s (%s).\n",
		 program, filename, strerror(err));
    }

    if (strict) exit(1);

    if (interactive) IdentifyFromUser(filename, (FILE *) NULL);
    else MarkAsNonResource(filename);
}


static void HandleUnidentifiableFile(
    char *filename,
    FILE *file)
{
    if (IdentifyFromUPRList(filename)) return;

    if (!noSuffix && IdentifyFromFileSuffix(filename)) return;

    if (issueWarnings) {
	fprintf (stderr, "%s:  Could not identify file %s.\n",
		 program, filename);
    }

    if (strict) exit(1);

    if (interactive) IdentifyFromUser(filename, file);
    else MarkAsNonResource(filename);
}


typedef struct {
    char *key;
    int   keyLength;
    char *key2;
    int	  key2Length;
    void (*proc)(FILE * /* file */, char * /* fileName */);
} ResourceKey;

static ResourceKey resourceTypes[] = {
  {"%!PS-AdobeFont-",	15, NULL, 		 0, ProcessFont},
  {"%!PS-Adobe-",	11, " Resource-",	10, ProcessResource},
  {"STARTFONT",		 9, NULL,		 0, ProcessBDF},
  {"StartFontMetrics",	16, NULL,		 0, ProcessAFM},
  {"PS-Resources-",	13, NULL,		 0, ProcessResourceDirectory},
  {NULL,		 0, NULL}
};

static void ProcessFile (
  char *fileName,
  char *filePath)
{
  FILE   *file;
  ResourceKey *resourceType;
  char	version[10];

  if (fileName[0] == '.')
    return;

  file = fopen (filePath, "r");

  if (file == NULL) {
      HandleUnopenableFile(filePath, errno);
      return;
  }

  fgets (lineBuffer, BUFFER_SIZE, file);

  for (resourceType = resourceTypes; resourceType->key != NULL;
       resourceType++) {
      if (strncmp (resourceType->key, lineBuffer,
		   resourceType->keyLength) == 0) {
	  if (resourceType->key2 == NULL) {
	      (*resourceType->proc) (file, filePath);
	      break;

	  } else {
	      if (sscanf(lineBuffer+resourceType->keyLength,
			 "%10[0123456789.]", version) != 1) continue;

	      if (strncmp(resourceType->key2,
			  lineBuffer + resourceType->keyLength +
				  strlen(version),
			  resourceType->key2Length) == 0) {
		  (*resourceType->proc) (file, filePath);
		  break;
	      }
	  }
      }
  }

  if (resourceType->key == NULL) HandleUnidentifiableFile(filePath, file);
  
  fclose (file);
}

static void ProcessUPRFile (
  char *fileName,
  char *filePath)
{
  int len;

  if (fileName[0] == '.')
    return;

  len = strlen(fileName);
  if (len < 4) return;
  if (strcmp(".upr", fileName + len - 4) != 0) return;

  PreprocessResourceDirectory(filePath);
}


static void ProcessDirectory (
  char *directoryName,
  int top,
  void (*fileFunction)(char *fileName, char *filePath) )
{
  DIR    *directory;
  struct dirent *directoryEntry;
  struct stat status;
  char *filePath, *fileName;

#if DEBUG
  fprintf (stderr, "Directory: %s\n", directoryName);
#endif

  directory = opendir (directoryName);

  if (directory == NULL) {
      /* Treat top level failures to open differently from subdirectories */
      if (top || issueWarnings) {
	  fprintf (stderr, "%s:  Could not open directory %s (%s).\n",
		   program, directoryName, strerror(errno));
      }
      if (strict) exit(1);
      return;
  }
  while ((directoryEntry = readdir (directory)) != NULL) {
      fileName = directoryEntry->d_name;
      if (fileName[0] == '.') continue;
      filePath = ckmalloc (strlen (directoryName) + strlen (fileName) + 2,
		       "Failed to allocate file name string.");
      sprintf (filePath, "%s/%s", directoryName, fileName);
      if (stat(filePath, &status) == -1) {
	  if (issueWarnings) {
	      fprintf(stderr, "Couldn't get status of file %s (%s)\n",
		      filePath, strerror(errno));
	  }
	  if (strict) exit(1);
	  continue;
      }
      if (S_ISDIR(status.st_mode)) {
	  if (recursive) ProcessDirectory(filePath, false, fileFunction);
      } else (*fileFunction) (fileName, filePath);
      free (filePath);
  }

  closedir (directory);
}


static void GenerateEntriesFromUPRList(void)
{
  Category *category;
  Resource *resource;
  UPRResource *upr;
  int i, bucket;

  for (category = categories; category != NULL; category = category->next) {
      if (strcmp(category->name, "FontBDFSizes") == 0 ||
	  strcmp(category->name, "FontFamily") == 0) {
	  continue;
      }

      for (resource = category->list; resource != NULL;
	   resource = resource->next) {
	  for (upr = UPRresources[Hash(resource->file)];
	       upr != NULL && strcmp(upr->file, resource->file) < 0;
	       upr = upr->next) {}
	  if (upr != NULL) upr->found = true;
      }

      if (category->hash != NULL) {
	  for (bucket = 0; bucket < HASHSIZE; bucket++) {
	      for (resource = category->hash[bucket]; resource != NULL;
		   resource = resource->next) {
		  for (upr = UPRresources[Hash(resource->file)];
		       upr != NULL && strcmp(upr->file, resource->file) < 0;
		       upr = upr->next) {}
		  if (upr != NULL) upr->found = true;
	      }
	  }
      }
  }

  for (bucket = 0; bucket < HASHSIZE; bucket++) {
      for (upr = UPRresources[bucket]; upr != NULL; upr = upr->next) {
	  if (upr->found) continue;

	  if (strcmp(upr->category, "FontBDFSizes") != 0 &&
	      strcmp(upr->category, "FontFamily") != 0) {

	      if (!keep) {
		  /* If this file is in one of the input dirs, but wasn't
		     found, it must have been deleted since the previous run */

		  for (i = 0; i < directoryCount; i++) {
		      if (strncmp(upr->file, directories[i],
				  directoryLen[i]) == 0 &&
			  upr->file[directoryLen[i]+1] == '/') goto NEXT_UPR;
		  }
	      }
	      AddResource(upr->category, upr->name, upr->file, upr->noPrefix);
	  }
NEXT_UPR:	;
      }
  }

  /* Now do BDFSizes and Families */

  for (bucket = 0; bucket < HASHSIZE; bucket++) {
      for (upr = UPRresources[bucket]; upr != NULL; upr = upr->next) {
	  if (upr->found) continue;

	  if (strcmp(upr->category, "FontBDFSizes") == 0) {
	      char *buf, *ch = upr->file;

	      /* We know there's a comma since we put one in.  Anything
		 before the comma is just there to make hashing work better */

	      while (*ch != '\0') ch++;
	      while (ch >= upr->file && *ch != ',') ch--;
	      if (*ch == ',') ch++;

	      buf = ckmalloc(strlen(upr->name) + strlen(ch) + 1,
			     "Failed to allocate BDF name\n");
	      strcpy(buf, upr->name);
	      strcat(buf, ch);

	      if (FindResource("FontBDF", buf)) {
		  AddResource(upr->category, upr->name, ch, false);
	      }
	      free(buf);

	  } else if (strcmp(upr->category, "FontFamily") == 0) {
	      char *ch = upr->file;

	      while (true) {
		  while (*ch != '\0' && *ch != ',') ch++;
		  if (*ch == '\0') break;
		  if (ch > upr->file && *(ch-1) == '\\') ch++;
		  else break;
	      }
	      if (*ch == ',') {
		  ch++;
		  if (FindResource("FontOutline", ch)) {
		      AddResource(upr->category, upr->name, upr->file, false);
		  }
	      }
	  }
      }
  }
}


static char *ExtractDirectoryPrefix(void)
{
  Category *category;
  Resource *resource;
  char *prefix = NULL;
  char *ch1, *ch2;
  int bucket;

  if (noPrefix) return NULL;

  category = categories;

  while (category != NULL) {
      if (strcmp(category->name, "FontBDFSizes") == 0 ||
	  strcmp(category->name, "FontFamily") == 0 ||
	  strcmp(category->name, "FontBlendPositions") == 0 ||
	  strcmp(category->name, "FontBlendMap") == 0 ||
	  strcmp(category->name, "FontAxes") == 0) {
	  category = category->next;
	  continue;
      }
      resource = category->list;
      
      while (resource != NULL) {
	  if (resource->noPrefix) {
	      resource = resource->next;
	      continue;
	  }
	  if (prefix == NULL) {
	      prefix = ckmalloc(strlen(resource->file) + 1,
				"Failed to allocate directory prefix");
	      strcpy(prefix, resource->file);
#if DEBUG
	      printf("New directory prefix: %s\n", prefix);
#endif
	  } else {
	      ch1 = prefix;
	      ch2 = resource->file;
	      while (*ch1 != '\0' && *ch2 != '\0' && *ch1 == *ch2) {
		  ch1++; ch2++;
	      }
#if DEBUG
	      if (*ch1 != '\0') {
		  *ch1 = '\0';
		  printf("New directory prefix: %s\n", prefix);
	      }
#endif
	      *ch1 = '\0';
	  }
	  resource = resource->next;
      }

      if (category->hash != NULL) {
	  for (bucket = 0; bucket < HASHSIZE; bucket++) {
	      for (resource = category->hash[bucket]; resource != NULL;
		   resource = resource->next) {

		  if (resource->noPrefix) continue;

		  if (prefix == NULL) {
		      prefix = ckmalloc(strlen(resource->file) + 1,
					"Failed to allocate directory prefix");
		      strcpy(prefix, resource->file);
#if DEBUG
		      printf("New directory prefix: %s\n", prefix);
#endif
		  } else {
		      ch1 = prefix;
		      ch2 = resource->file;
		      while (*ch1 != '\0' && *ch2 != '\0' && *ch1 == *ch2) {
			  ch1++; ch2++;
		      }
#if DEBUG
		      if (*ch1 != '\0') {
			  *ch1 = '\0';
			  printf("New directory prefix: %s\n", prefix);
		      }
#endif
		      *ch1 = '\0';
		  }
	      }
	  }
      }
      category = category->next;
  }
  if (prefix != NULL) {
      ch1 = ch2 = prefix;
      while (*ch1 != '\0') {
	  if (*ch1 == '/') ch2 = ch1;
	  ch1++;
      }
      *ch2 = '\0';
  }

  /* Prefixes must be absolute path names */
  if (prefix != NULL && prefix[0] != '/') prefix[0] = '\0';
  return prefix;
}


static void OutputChar(
    FILE *file,
    char c)
{
    static int len;	/* Rely upon being 0 initially */

    if (c == '\n') len = 0;
    else {
	len++;
	if (len == MAXLINELEN) {
	    fputs("\\\n", file);
	    len = 1;;
	}
    }
	
    putc(c, file);
}

static void OutputString(
    FILE *file,
    char *s)
{
    while (*s != '\0') OutputChar(file, *s++);
}


static void PrintResourceDirectory (
  char *directoryName,
  FILE *file)
{
  Category *category;
  Resource *resource;
  char *pname;
  int prefixlen;
  int bucket;
#define outs(s) OutputString(file, s)
#define outc(c) OutputChar(file, c)

  if (directoryName == NULL || *directoryName == '\0') prefixlen = 0;
  else prefixlen = strlen(directoryName) + 1;

  if (makeExclusive) outs("PS-Resources-Exclusive-1.0\n");
  else outs("PS-Resources-1.0\n");

  category = categories;

  while (category != NULL) {
    outs(category->name);
    outc('\n');
    category = category->next;
  }

  outs(".\n");
  if (prefixlen > 1) {
      outc('/');
      outs(directoryName);
      outc('\n');
  }

  category = categories;

  while (category != NULL) {
    resource = category->list;
    outs(category->name);
    outc('\n');
    if (strcmp(category->name, "FontBDFSizes") != 0 &&
	strcmp(category->name, "FontFamily") != 0) {
	while (resource != NULL) {
	    outs(resource->name);
	    outc('=');
	    if (resource->noPrefix) {
		outc('=');
		outs(resource->file);
	    } else outs(resource->file+prefixlen);
	    outc('\n');
	    resource = resource->next;
	}
    } else {
	while (resource != NULL) {
	    outs(resource->name);
	    outc('=');
	    outs(resource->file);
	    pname = resource->name;
	    resource = resource->next;
	    while (resource != NULL && strcmp(resource->name, pname) == 0) {
		outc(',');
		outs(resource->file);
		resource = resource->next;
	    }
	    outc('\n');
	}
    }
    if (category->hash != NULL) {
	for (bucket = 0; bucket < HASHSIZE; bucket++) {
	    for (resource = category->hash[bucket]; resource != NULL;
		 resource = resource->next) {
		outs(resource->name);
		outc('=');
		if (resource->noPrefix) {
		    outc('=');
		    outs(resource->file);
		} else outs(resource->file+prefixlen);
		outc('\n');
	    }
	}
    }
    outs(".\n");
    category = category->next;
  }
#undef outs
#undef outc
}


static void Usage(void)
{
    fprintf(stderr,
	   "Usage: %s [-o outputfile] [-f inputfile]... [-dir directory]...\n",
	    program);
    fprintf(stderr,
	   "        [-e] [-i] [-nr] [-s] [-p] [-d] [-k] [-q] directory...\n");
    exit(1);
}

int stdinDirectories = false;

static void ReadStdinDirectories(void)
{
    char buf[256];

    if (stdinDirectories) {
	fprintf(stderr, "%s:  Can only read stdin as directory list once.\n",
		program);
	Usage();
    }

    stdinDirectories = true;

    while (scanf("%255s", buf) == 1) {
	directoryCount++;
	directories = (char **) ckrealloc((char *) directories,
				  directoryCount * sizeof(char *),
				  "Failed to reallocate directory list.");
	directories[directoryCount-1] = ckmalloc(strlen(buf)+1,
				  "Failed to allocate directory name.");
	strcpy(directories[directoryCount-1], buf);
    }
}

static void ProcessArglist(
    int argc,
    char *argv[])
{
    int i = 1, j;

    if (argc > 0) {
	program = strrchr(argv[0], '/');
	if (program != NULL) program++;
	else program = argv[0];
    } else program = "makepsres";

    directories = (char **) ckmalloc(argc * sizeof(char *),
				     "Failed to allocate directory list.");
    while (i < argc) {
	if (strcmp(argv[i], "-help") == 0) Usage();

	else if (strcmp(argv[i], "-q") == 0) issueWarnings = false;

	else if (strcmp(argv[i], "-k") == 0) keep = true;

	else if (strcmp(argv[i], "-d") == 0) discard = true;

	else if (strcmp(argv[i], "-p") == 0) noPrefix = true;

	else if (strcmp(argv[i], "-s") == 0) strict = true;

	else if (strcmp(argv[i], "-nr") == 0) recursive = false;

	else if (strcmp(argv[i], "-nb") == 0) noBackup = true;

	else if (strcmp(argv[i], "-ns") == 0) noSuffix = true;

	else if (strcmp(argv[i], "-i") == 0) interactive = true;

	else if (strcmp(argv[i], "-e") == 0) makeExclusive = true;

	else if (strcmp(argv[i], "-f") == 0) {
	    i++;
	    if (i >= argc) Usage();
	    if (inputFiles == NULL) {
		inputFiles = (char **) ckmalloc(argc * sizeof(char *),
					"Failed to allocat input file list.");
	    }
	    inputFiles[inputCount++] = argv[i];
	}

	else if (strcmp(argv[i], "-o") == 0) {
	    i++;
	    if (i >= argc) Usage();
	    outputFilename = argv[i];
	}

	else if (strcmp(argv[i], "-dir") == 0) {
	    i++;
	    if (i >= argc) Usage();
	    directories[directoryCount++] = argv[i];
	}

	else directories[directoryCount++] = argv[i];

	i++;
    }

    if (directoryCount == 0) {
	directoryCount = 1;
	directories[0] = ".";
    } else {
	for (i = 0; i < directoryCount; i++) {
	    if (strcmp(directories[i], "-") == 0) ReadStdinDirectories();
	}
    }

    if (stdinDirectories) {
	j = 0;
	for (i = 0; i < directoryCount; i++) {
	    if (strcmp(directories[i], "-") != 0) {
		directories[j] = directories[i];
		j++;
	    }
	}
	directoryCount--;
    }

    for (i = 0; i < inputCount; i++) {
	if (strcmp(inputFiles[i], "-") == 0 && stdinDirectories) {
	    fprintf(stderr,
	    "%s:  Cannot read stdin as both directory list and input file\n",
		    program);
	    Usage();
	}
    }

#if DEBUG
    printf("Input directory list:\n");
    for (i = 0; i < directoryCount; i++) printf("  %s\n", directories[i]);
#endif

    directoryLen = (int *) ckmalloc(directoryCount * sizeof(int),
			    "Failed to allocate directory length list.");

    for (i = 0; i < directoryCount; i++) {
	directoryLen[i] = strlen(directories[i]);
    }
}


static void CheckBackup(
    char *filename)
{
    char *backupname;

    if (noBackup || strcmp(filename, "/dev/null") == 0) return;

    backupname = ckmalloc(strlen(filename)+2,
			  "Failed to allocate backup file name.'");
    strcpy(backupname, filename);
    strcat(backupname, "~");

    /* Effect "rename (filename, backupname)" in BSD/Sys-V independent way */

    (void) unlink (backupname); /* Ignore error */

#ifndef __UNIXOS2__
    if (link (filename, backupname) != 0) {
	if (errno != ENOENT) {
	    fprintf(stderr, "%s:  Could not back up output file %s\n",
		    program, filename);
	    fprintf(stderr, "            and will not write over it (%s)\n",
		    strerror(errno));
	    exit(1);
	}
    } else (void) unlink(filename);
#else
    if (rename(filename,backupname) != 0) {
	fprintf(stderr, "%s:  Could not back up output file %s\n",
		program, filename);
	fprintf(stderr, "            and will not write over it (%s)\n",
		strerror(errno));
	exit(1);
    }
#endif
    free(backupname);
}


static void IssueDuplicateWarnings(void)
{
    int headerIssued = false;
    Category *category;
    Duplicate *dup;

    for (category = categories; category != NULL; category = category->next) {
	if (category->duplicates == NULL) continue;
	if (!headerIssued) {
	    headerIssued = true;
	    fprintf(stderr, 
		    "%s:  Warning:  The output resource file contains the following\n",
		    program);
	    fprintf(stderr,
		    "            duplicates.  Edit them out if you wish.\n");
	}
	fprintf(stderr, "Category %s:\n", category->name);
	for (dup = category->duplicates; dup != NULL; dup = dup->next) {
	    fprintf(stderr, "  Resource:  %s\n", dup->name);
	    fprintf(stderr, "    First file:  %s\n", dup->file1);
	    fprintf(stderr, "    Second file:  %s\n", dup->file2);
	}
    }
}

int
main (
  int argc,
  char *argv[])
{
  FILE   *outputFile;
  int     i, len;
  char *prefix;

  directories = NULL;
  directoryCount = 0;
  recursive = true;
  discard = false;
  keep = false;
  outputFilename = NULL;
  inputFiles = NULL;
  inputCount = 0;
  makeExclusive = false;
  interactive = false;
  strict = false;
  noPrefix = false;
  issueWarnings = true;
  noBackup = false;
  noSuffix = false;

  ProcessArglist(argc, argv);

  for (i = 0; i < inputCount; i++) {
      PreprocessResourceDirectory(inputFiles[i]);
  }

  /* Make two passes; the first time we just look for .upr files, and the
     second time we look for everything.  This gives us a better chance at
     identifying files. */

  for (i = 0; i < directoryCount; i++) {
      len = strlen (directories[i]);
      if (directories[i][len - 1] == '/') directories[i][len - 1] = '\0';

      ProcessDirectory (directories[i], true, ProcessUPRFile);
  }

#if DEBUG
  {
      int i, biggestBucket, emptyCount;
      static int count[11];

      emptyCount = biggestBucket = 0;
      for (i = 0; i < HASHSIZE; i++) {
	  if (bucketCount[i] == 0) emptyCount++;
	  if (bucketCount[i] > biggestBucket) biggestBucket = bucketCount[i];
      }
      if (biggestBucket != 0) {
	  for (i = 0; i < HASHSIZE; i++) {
	      count[bucketCount[i] * 10 / biggestBucket]++;
	  }
      }
      printf("Total UPR entries: %d\n", totalHashed);
      printf("Buckets: %d\n", HASHSIZE);
      printf("Longest bucket: %d\n", biggestBucket);
      printf("Number of empty buckets: %d\n", emptyCount);
      if (biggestBucket != 0) {
	  for (i = 0; i < 10; i++) {
	      printf("Number of buckets <= %d entries: %d\n",
		     biggestBucket*(i+1) / 10, count[i]);
	  }
      }
  }
#endif

  for (i = 0; i < directoryCount; i++) {
      len = strlen (directories[i]);
      if (directories[i][len - 1] == '/') directories[i][len - 1] = '\0';

      ProcessDirectory (directories[i], true, ProcessFile);
  }

  if (outputFilename == NULL) outputFilename = "PSres.upr";

  if (strcmp(outputFilename, "-") == 0) outputFile = stdout;
  else {
      CheckBackup(outputFilename);
      outputFile = fopen (outputFilename, "w");
  }

  if (outputFile == NULL) {
      fprintf (stderr,
	       "%s: Failed to open %s for writing: %s\n",
	       program, outputFilename, strerror(errno));
      exit (1);
  }

  if (!discard) GenerateEntriesFromUPRList();

  prefix = ExtractDirectoryPrefix();
  PrintResourceDirectory (prefix, outputFile);
  IssueDuplicateWarnings();
  exit (0);
}
