/* ps_package.c */

#include "ps_package.h"
#include "pspager.h"
#include <string.h>
#include <ctype.h>
#ifdef AMIGA
#include <utilities.h>
#else
#include "utilities.h"
#endif


/*--- private global data --------------------------------------------------------*/

#define INBUF_SIZE   256*KB
#define SYMBUF_SIZE  1*KB
#define SYMTAB_SIZE  13
#define PAGE_LIMIT   71
#define IFSTK_SIZE   8

#define CpyStr(s1,s2) { \
  if(s1 < s2) { \
    if(wrPtr+((s2)-(s1)) >= wrEnd) \
      FatalExit("input buffer overflow"); \
    strncpy(wrPtr,s1,s2-s1); \
    wrPtr += s2-s1; \
  } \
}

#define CpyChar(ch) { \
  if(wrPtr == wrEnd) \
    FatalExit("input buffer overflow"); \
  *wrPtr = (ch), wrPtr++; \
}

static char *inBuf,*wrPtr,*rdPtr;
static char *symBuf,*symPtr,*symEnd,**symTab;


/*--- private functions ---------------------------------------------------------*/

static bool PSSymDefined(char *symName)
{
  char *cp;
  int h = 0,d = 1;

  if(!symTab)
    return FALSE;

  for(cp=symName; *cp!='\0'; cp++)
    h = (256*h + *cp) % SYMTAB_SIZE;
  FOREVER
    if(symTab[h])
      if(strcmp(symTab[h],symName) == 0)
        return TRUE;
      else {
        h += d; d += 2; /* collision */
        if(h >= SYMTAB_SIZE)
          h -= SYMTAB_SIZE;
        if(d == SYMTAB_SIZE)
          FatalExit("symbol table overflow");
      }
    else
      return FALSE; /* empty slot */
}


/*--- public functions ----------------------------------------------------------*/

/*  PSPrintHdr
**  ----------
**  prints a PostScript 3.0 header to stdout, parameters are: name of file, file
**  modification date or current date, number of pages, a string with PS font names
**  and orientation specification.
*/

void PSPrintHdr(char *fileName, time_t fileDate, int pages, char *fontNames,
                Orientation orient)
{
  char dateStr[80],*userStr,*orientStr;
  int paTop,paLft,paHgt,paWdt,iaHgt,iaWdt;

  strftime(dateStr,80,"%H:%M Uhr %A, %d. %B %Y",localtime(&fileDate)); /*or %c*/

  if(!(userStr = getenv("USER")))
    userStr = "Nobody";

  if(orient == ePortrait) {
    orientStr = "Portrait";
    paTop = -31;
    paLft = -30;
    paHgt = 761;
    paWdt = 582;
    iaHgt = 730;
    iaWdt = 552;
  }
  else {
    orientStr = "Landscape";
    paTop = -28;
    paLft = -30;
    paHgt = 566;
    paWdt = 811;
    iaHgt = 538;
    iaWdt = 781;
  }

  printf("%%!PS-Adobe-3.0\n"
    "%%%%Title: (%s)\n"
    "%%%%Creator: (pspager: himself)\n"
    "%%%%CreationDate: (%s)\n"
    "%%%%For: (%s)\n"
    "%%%%Pages: %d\n"
    "%%%%DocumentFonts: %s\n"
    "%%%%DocumentNeededFonts: %s\n"
    "%%%%DocumentSuppliedFonts:\n"
    "%%%%DocumentData: Clean7Bit\n"
    "%%%%PageOrder: Ascend\n"
    "%%%%Orientation: %s\n"
    "%%ADO_PaperArea: %d %d %d %d\n"
    "%%ADO_ImageableArea: 0 0 %d %d\n"
    "%%EndComments\n"
    "/md 138 dict def md begin\n"
    "/currentpacking where {pop /sc_oldpacking currentpacking def true setpacking}if\n",
    fileName,dateStr,userStr,pages,fontNames,fontNames,orientStr,paTop,paLft,paHgt,
    paWdt,iaHgt,iaWdt);
}


/*  PSPrintEnd
**  ----------
**  prints PostScript commands which terminate a document.
*/

void PSPrintEnd(void)
{
  printf("%%Trailer\nend\t\t%% md\n%%%%EOF\n");
}


/*  PSInclFile
**  ----------
**  appends the contents of the specified file to stdout. The prefix PSPLIB_DIR is
**  prepended to the file name. #include directives within the included file are
**  recursively substituted (<> means PSP_LIBDIR, "" means current directory).
**  #ifdef directives are evaluated considering symbol definitions made with
**  PSDefSymbol(). #define directives cause further symbol definitions. Unsupported
**  directives are: #undef, #elif.
*/

void PSInclFile(char *fileName)
{
  FILE *fp;
  char lnBuf[121],*lnPtr,*attr,ch;
  enum { eTokInclude, eTokDefine, eTokIfdef, eTokElse, eTokEndif, eTokIllegal } tok;
  static const char * const tokStrs[] = {
    "include", "define", "ifdef", "else", "endif", "illegal"
  };
  struct {
    enum { ePathIfdef, ePathElse } kind;
    bool taken;
    int  ignCnt;
  } stk[IFSTK_SIZE];
  int idx = -1;
  bool taken,prtIt = TRUE;

  if(!(fp = fopen(fileName,"r"))) {
    printf("%%%%pspager error: could not include \"%s\"\n",fileName);
    return;
  }
  while(fgets(lnBuf,121,fp))
    if(lnBuf[0] == '#') {
      for(lnPtr=lnBuf+1; isalpha(*lnPtr); lnPtr++);
      *lnPtr = '\0', lnPtr++;
      for(tok=eTokInclude; tok<eTokIllegal; tok++)
        if(strcmp(lnBuf+1,tokStrs[tok]) == 0)
          break;
      while(*lnPtr==' ')
        lnPtr++; 
      if(tok==eTokDefine || tok==eTokIfdef) {
        attr = lnPtr;
        while(isalnum(*lnPtr) || *lnPtr=='_')
          lnPtr++;
        *lnPtr = '\0';
      }
      switch(tok) {
        case eTokInclude:
          if(prtIt) {
            ch = *lnPtr=='<' ? '>' : '"', lnPtr++;
            attr = lnPtr;
            if(!(lnPtr = strchr(lnPtr,ch))) {
              puts("%%pspager error: invalid syntax");
              continue;
            }
            *lnPtr = '\0';
            if(ch == '>') 
              attr = TackOn(PSP_LIBDIR,attr);
            PSInclFile(attr);
          }
          break;
        case eTokDefine:
          if(prtIt)
            PSDefSymbol(attr);
          break;
        case eTokIfdef:
          if(idx < 0 || stk[idx].taken) {
            idx++;
            if(idx == IFSTK_SIZE)
              FatalExit("#ifdef stack overflow");
            stk[idx].kind = ePathIfdef;
            stk[idx].taken = PSSymDefined(attr);
            stk[idx].ignCnt = 0;
            prtIt = stk[idx].taken;
          }
          else if(!stk[idx].taken)
            stk[idx].ignCnt++;
          break;
        case eTokElse:
          if(idx < 0) {
            puts("%%pspager error: else without ifdef");
            continue;
          }
          if(stk[idx].ignCnt > 0)
            continue;
          if(stk[idx].kind == ePathElse) {
            puts("%%pspager error: unbalanced else");
            continue;
          }
          stk[idx].kind = ePathElse;
          taken = stk[idx].taken;
          prtIt = stk[idx].taken = !taken;
          break;
        case eTokEndif:
          if(idx < 0) {
            puts("%%pspager error: unbalanced endif");
            continue;
          }
          if(stk[idx].ignCnt > 0)
            stk[idx].ignCnt--;
          else {
            idx--;
            if(idx < 0 || stk[idx].taken)
              prtIt = TRUE;
          }
          break;
        default:
          puts("%%pspager error: unrecognized token");
      }
    }
    else
      if(prtIt)
        printf("%s",lnBuf); /*puts() appends a NL*/
  fclose(fp);
}


/*  PSDefSymbol
**  -----------
**  defines a preprocessor symbol for evaluation by PSInclFile().
**  Multiple definitions are ignored.
*/

void PSDefSymbol(char *symName)
{
  char *cp;
  int h = 0,d = 1;

  if(!symBuf) {
    symBuf = NEW(char,SYMBUF_SIZE);
    symPtr = symBuf;
    symEnd = symBuf+SYMBUF_SIZE;
    symTab = NEW(char *,SYMTAB_SIZE);
    memset(symTab,0,SYMTAB_SIZE*sizeof(char *));
  }

  for(cp=symName; *cp!='\0'; cp++)
    h = (256*h + *cp) % SYMTAB_SIZE;
  FOREVER
    if(symTab[h])
      if(strcmp(symTab[h],symName) == 0)
        return; /* already defined */
      else {
        h += d; d += 2; /* collision */
        if(h >= SYMTAB_SIZE)
          h -= SYMTAB_SIZE;
        if(d == SYMTAB_SIZE)
          FatalExit("symbol table overflow");
      }
    else
      break; /* empty slot found */

  if(symPtr+strlen(symName) >= symEnd)
    FatalExit("symbol buffer overflow");
  symTab[h] = symPtr;
  strcpy(symPtr,symName);
  symPtr += strlen(symName)+1;
}


/*  PSUndefAll
**  ----------
**  frees all buffers allocated by PSDefSymbol()
*/

void PSUndefAll(void)
{
  DELETE(symBuf);
  DELETE(symTab);
}


/*  PSReadFile
**  ----------
**  reads the contents of the specified file into internal buffers and returns the
**  resulting number of pages.
*/

int PSReadFile(FILE *inFile)
{
  char lnBuf[121],*lnPtr,*wrEnd,*fndPtr;
  int lnCnt = 0,pgCnt = 1;

  if(!(inBuf = NEW(char,INBUF_SIZE)))
    FatalExit("no memory for input buffer");
  wrEnd = inBuf+INBUF_SIZE;
  wrPtr = rdPtr = inBuf;

  while(fgets(lnBuf,121,inFile)) {
    if(lnBuf[0] == '\f') {
      pgCnt++, lnCnt=0; /* form feed -> new page */
      continue;
    }
    lnPtr = lnBuf;
    while((fndPtr = strpbrk(lnPtr,"()\\\n"))) {
      CpyStr(lnPtr,fndPtr);
      if(*fndPtr == '\n') {
        CpyChar('\0');
        break;
      }
      CpyChar('\\');
      CpyChar(*fndPtr);
      lnPtr = fndPtr+1;
    }
    lnCnt++;
    if(lnCnt == PAGE_LIMIT)
      pgCnt++, lnCnt=0;
  }
  return pgCnt;
}


/*  PSFreeFile
**  ----------
**  frees the internal buffers allocated by PSReadFile().
*/

void PSFreeFile(void)
{
  DELETE(inBuf);
}


/*  PSNextLine
**  ----------
**  returns a pointer to the next line of the file read by PSReadFile(). The '\n' has
**  been stripped and to all (, ) and \ characters a \ has been preprended. If there
**  are no more lines, NULL is returned.
*/

char *PSNextLine(void)
{
  char *p;

  if(rdPtr < wrPtr) {
    p = rdPtr;
    rdPtr += strlen(p)+1;
    return p;
  }
  else
    return NULL;
}


/*  TackOn
**  ------
**  concatenates a path name and a file name to a full path name. The returned string
**  is owned by the caller (so it has to be freed by him/her).
*/

char *TackOn(const char *pn, const char *fn)
{
  int n = strlen(pn);
  char *fpn;

  if (n == 0) {
    fpn = NEW(char, strlen(fn)+1);
    strcpy(fpn, fn);
  }
  else if(pn[n-1] == '/' || pn[n-1] == ':') {
    fpn = NEW(char, n+strlen(fn)+1);
    sprintf(fpn,"%s%s",pn,fn);
  }
  else {
    fpn = NEW(char, n+strlen(fn)+2);
    sprintf(fpn,"%s/%s",pn,fn);
  }
  return fpn;
}
