/*                                                               -*- C -*-
 *  FLIST.C
 *
 *  (c)Copyright 1994 by Tobias Ferber,  All Rights Reserved
 *
 *  This file is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published
 *  by the Free Software Foundation; either version 1 of the License,
 *  or (at your option) any later version.
 *
 *  This file is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* $VER: $Id: flist.c,v 1.9 1995/03/20 18:14:59 tf Exp $ */

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

#include "debug.h"
#include "flist.h"

/* My favorite macro */
#define NIL(type) (type)0

#ifdef __GNUC__
/* suggested parentheses around assignment used as truth value */
#define if(assignment)    if( (assignment) )
#endif /* __GNUC__ */

#ifndef MAXPATHLEN
#define MAXPATHLEN 1024
#endif /* !MAXPATHLEN */

/****** flist/--background-- *************************************************
*
*	FLIST ist eine auf Dateinamen spezialisierte FIFO Implementierung.
*	Der Haupt-Anwendungsbereich d"urfte wohl im Bereich des Command-Line
*	Parsings liegen, dem Durchwandern der Argumente (Optionen) eines
*	Programms.
*
*	Meist liegen im argv[] Array die Optionen und die Dateinamen in
*	gemischter Form vor.  Da ein Umsortieren von argv[] kritisch ist
*	empfiehlt es sich die Liste der Argumente von links nach rechts zu
*	durchwandern und die Dateinamen in einer separaten Liste zu sammeln.
*	Genau hier setzt FLIST an.
*
******************************************************************************
*
*/

typedef struct fnode {  /* filename node */
  struct fnode *next;   /* next node (command line from left to right) */
  char *filename;       /* the input filename */
  char *hostname;       /* where `filename' appeared, NIL == command-line */
  long hostline;        /* line number in which `filename' appeared */
} fnode_t;

static fnode_t *flist= NIL(fnode_t *),  /* very first node of the list */
               *fcurr= NIL(fnode_t *),  /* head of the remaining sub-list */
               *ftail= NIL(fnode_t *);  /* very last node of the list */

/*
 *  >>> PRIVATE <<<
 */

/****** flist/fnode_new ******************************************************
*
*   NAME
*	fnode_new -- fnode constructor (static)
*
*   SYNOPSIS
*	fn= fnode_new()
*
*	static fnode_t *fnode_new();
*
*   FUNCTION
*
*   SEE ALSO
*	fnode_dispose()
*
******************************************************************************
*
*  Ich habe aus den ****i* xxx/yyy **** Kommentaren der `static functions'
*  normale Kommentare gemacht, damit ich nicht zwei docfiles erstellen mu\3...
*/

static fnode_t *fnode_new(void)
{
  fnode_t *fn;

  DD(bug_enter("fnode_new"));

  fn= (fnode_t *)malloc( sizeof(struct fnode) );

  if(fn)
  {
    fn->next     = NIL(fnode_t *);
    fn->filename =
    fn->hostname = NIL(char *);
    fn->hostline = 0;

  }

  DD(bug_leave("fnode_new (%s)", fn ? "successful":"failed"));

  return fn;
}

/****** flist/fnode_dispose **************************************************
*
*   NAME
*	fnode_dispose -- fnode destructor (static)
*
*   SYNOPSIS
*	nil= fnode_dispose(fn)
*
*	static fnode_t *fnode_dispose(fnode_t *);
*
*   FUNCTION
*
*   SEE ALSO
*	fnode_new()
*
******************************************************************************
*
*/

static fnode_t *fnode_dispose(fnode_t *fn)
{
  DD(bug_enter("fnode_dispose"));

  if(fn)
  {
    if(fn->filename)
    {
      free(fn->filename);
      fn->filename= NIL(char *);
    }

    fn->hostname= NIL(char *);
    free(fn);
  }

  DD(bug_leave("fnode_dispose"));

  return NIL(fnode_t *);
}




/****** flist/flist_addnode **************************************************
*
*   NAME
*	flist_addnode -- F"ugt eine fnode in die flist ein (static)
*
*   SYNOPSIS
*	flist_addnode(fn)
*
*	static void flist_addnode(fnode_t *);
*
*   FUNCTION
*	Diese Funktion h"angt die gegebene fnode `fn' an die Liste der fnode
*	Strukturen an und aktualisiert `flist', `fcurr' und `ftail'.
*
*
*   SEE ALSO
*	flist_addfile(), flist_from_file()
*
******************************************************************************
*
*/

static void flist_addnode(fnode_t *fn)
{
  DD(bug_enter("flist_addnode"));

  if(fn)
  {
    if(!flist)  flist= fn;
    if(!fcurr)  fcurr= fn;

    if(ftail)
      ftail->next= fn;

    ftail= fn;
  }

  DD(bug_leave("flist_addnode"));
}


/****** flist/fnode_from_file ************************************************
*
*   NAME
*	fnode_from_file -- Erzeugt eine fnode aus dem n"achsten Dateinamen
*
*   SYNOPSIS
*	fn= fnode_from_file(fp);
*
*	fnode_t *fnode_from_file(FILE *);
*
*   FUNCTION
*	Diese Funktion legt eine neue fnode Struktur an und liest deren
*	Auspr"agung aus dem Stream `fp' ein.  Dabei werden die Felder
*	`filename' und `hostline' gesetzt, der hostname jedoch nicht.
*	Ein Aufruf dieser Funktion mit NIL(FILE *) als Argument f"uhrt dazu,
*	da\3 der line Counter auf 1 zur"uckgesetzt wird.
*
*   INPUTS
*	fp	      - Der Stream aus dem der n"achste Dateiname gelesen
*			werden soll
*
*   RESULT
*	fn	      - Ein Zeiger auf die erstellte fnode Struktur
*
*   SEE ALSO
*	flist_from_file()
*
******************************************************************************
*
*/

static fnode_t *fnode_from_file(FILE *fp)
{
  static long line= 1;
  fnode_t *fn= NIL(fnode_t *);

  typedef enum { outer_mode,
                 word_mode,
                 string_mode,
                 return_mode,
                 error_mode
               } smode_t;

  smode_t smode= outer_mode;

  DD(bug_enter("fnode_from_file"));

  if(fp) /* -> neue fnode anlegen und Auspr"agung einlesen */
  {
    if( fn= fnode_new() )
    {
      char *fname= (char *)malloc( MAXPATHLEN * sizeof(char) );

      if( fname )
      {
        int c, n=0;
        fn->filename= fname;

        while( smode != return_mode && smode != error_mode && !feof(fp) )
        {
          c= fgetc(fp);

          if(feof(fp) && c!=EOF)
            c= EOF;

          switch(c)
          {
            case ' ': case '\t':
              switch(smode)
              {
                case word_mode:
                  fname[n++]= '\0';
                  smode= return_mode;
                  break;

                case string_mode:
                  fname[n++]= c;
                  break;

                case outer_mode:
                case return_mode:
                case error_mode:
                  break;
              }
              break;

            case '\n': case '\r':
              switch(smode)
              {
                case word_mode:
                  fname[n++]= '\0';
                  smode= return_mode;
                  break;

                case string_mode:
                  sprintf(fname,"%ld: unterminated string at EOL; missing quotes `\"'",line);
                  smode= error_mode;
                  break;

                case outer_mode:
                case return_mode:
                case error_mode:
                  break;
              }
              line++;

              { int d= fgetc(fp);
                if(!( (c=='\n' && d=='\r') || (c=='\r' && d=='\n') ))
                  ungetc(d,fp);
              }
              break;

            case '\"':
              switch(smode)
              {
                case outer_mode:
                  smode= string_mode;
                  fn->hostline= line;
                  break;

                case string_mode:
                  fname[n++]= '\0';
                  smode= return_mode;
                  break;

                case word_mode:
                  fname[n++]= '\0';
                  ungetc(c,fp);
                  smode= return_mode;
                  break;

                case return_mode:
                case error_mode:
                  break;
              }
              break;

            case EOF:
              switch(smode)
              {
                case word_mode:
                  if( feof(fp) )
                  {
                    fname[n++]= '\0';
                    smode= return_mode;
                  }
                  else fname[n++]= c;
                  break;

                case string_mode:
                  if( feof(fp) )
                  {
                    sprintf(fname,"%ld: unterminated string at EOF; missing quotes `\"'",line);
                    smode= error_mode;
                  }
                  else fname[n++]= c;
                  break;

                case outer_mode:
                case return_mode:
                case error_mode:
                  break;
              }
              break;

            default:
              switch(smode)
              {
                case outer_mode:
                  smode= word_mode;
                  fn->hostline= line;
                  /* fall through */

                case word_mode:
                case string_mode:
                  fname[n++]= c;
                  break;

                case return_mode:
                case error_mode:
                  break;
              }
              break;
          }

          if(n >= MAXPATHLEN)
          {
            sprintf(fname,"%ld: line too long",line);
            smode= error_mode;
          }

        } /* wend */

        if(smode != error_mode)
          fname[n]= '\0';
        else
          fn->hostline= 0;
      }
      else /* !fname */
        fn= fnode_dispose(fn);
    }
  }
  else /* !fp || !fname */
    line= 1;

  DD(bug_leave("fnode_from_file"));

  return fn;
}


/*
 *  >>> PUBLIC <<<
 */


/****** flist/flist_dispose **************************************************
*
*   NAME
*	flist_dispose -- Gibt die Liste der Dateinamen frei
*
*   SYNOPSIS
*	flist_dispose()
*
*	void flist_dispose(void);
*
*   FUNCTION
*
*   SEE ALSO
*	flist_addfile(), flist_from_file()
*
******************************************************************************
*
*/

void flist_dispose(void)
{
  D(bug_enter("flist_dispose"));

  while(flist)
  {
    fnode_t *t= flist;
    flist= flist->next;

    (void)fnode_dispose(t);
  }

  fcurr=
  ftail= NIL(fnode_t *);

  D(bug_leave("flist_dispose"));
}


/****** flist/flist_addfile **************************************************
*
*   NAME
*	flist_addfile -- F"ugt einen Dateinamen in die Fileliste ein
*
*   SYNOPSIS
*	error = flist_addfile(filename)
*
*	int flist_addfile(char *);
*
*   FUNCTION
*	Diese Funktion h"angt einen Dateinamen (wie er z.B. von der command
*	line kommt) an das Ende der Liste der Dateinamen an.
*
*   INPUTS
*	filename      - Der Name der Datei
*
*   RESULT
*	error	      - 0, wenn alles geklappt hat
*			1, wenn nicht genug Speicher verf"ugbar war
*
*   SEE ALSO
*	flist_dispose()
*
******************************************************************************
*
*/

int flist_addfile(char *fname)
{
  fnode_t *fn= NIL(fnode_t *);

  D(bug_enter("flist_addfile \"%s\"",fname));

  if(fname)
  {
    if( fn= fnode_new() )
    {
      fn->hostname = NIL(char *);
      fn->hostline = 0;

      fn->filename = strdup(fname);

      if(fn->filename)
        flist_addnode(fn);

      else fn= fnode_dispose(fn);
    }
  }

  D(bug_leave("flist_addfile (%s)", fn ? "successful":"failed"));

  return fn ? 0:1;
}


/****** flist/flist_from_file ************************************************
*
*   NAME
*	flist_from_file -- Baut eine Liste aus den Dateinamen in einem File
*
*   SYNOPSIS
*	error= flist_from_file(filename);
*
*	int flist_from_file(char *);
*
*   FUNCTION
*	Diese Funktion h"angt alle in der angegebenen Datei aufgef"uhrten
*	Dateinamen an die Fileliste an.
*
*   INPUTS
*	filename      - Der Name der Datei mit den Filenamen
*
*   RESULT
*	error	      - 0, wenn alles geklappt hat
*			1, im Fehlerfall
*
*   SEE ALSO
*	flist_dispose()
*
******************************************************************************
*
*/

int flist_from_file(char *host)
{
  int rc= 0;
  FILE *fp;

  D(bug_enter("flist_from_file \"%s\"",host));

  if( (fp= fopen(host,"r")) )
  {
    fnode_t *fn;

    /* reset the line counter */
    fnode_from_file( NIL(FILE *) );

    while( !rc && !feof(fp) && !ferror(fp) )
    {
      if( fn= fnode_from_file(fp) )
      {
        if(fn->hostline > 0)
        {
          if(fn->filename && *fn->filename)
          {
            fn->hostname= host;
            flist_addnode(fn);
          }
        }
        else if(fn->filename && *fn->filename)
        {
          /* fn->filename contains an error message */
          fprintf(stderr,"%s: %s\n",host,fn->filename);
          rc= 1;
        }
        else fn= fnode_dispose(fn); /* EOF */
      }
      else /* !fn */
      {
        fprintf(stderr,"%s: %ld: ran out of memory!\n", host, fn->hostline);
        rc= 2;
      }
    }
  }
  else perror(host);

  D(bug_leave("flist_from_file (%s)", rc ? "failed":"successful"));

  return rc;
}

/**/

char *flist_getname(void)
{
  D(bug("flist_getname()"));
  return fcurr ? fcurr->filename : NIL(char *);
}


char *flist_gethost(void)
{
  D(bug("flist_gethost()"));
  return fcurr ? fcurr->hostname : NIL(char *);
}


long flist_gethostline(void)
{
  D(bug("flist_gethostline()"));
  return fcurr ? fcurr->hostline : 0L;
}

int flist_nextfile(void)
{
  D(bug("flist_nextfile()"));

  if(fcurr)
    fcurr= fcurr->next;

  return fcurr ? 1:0;
}


#if defined(DEBUG) || defined(TEST)

void flist_print(FILE *fp)
{
  fnode_t *fn= flist;

  D(bug_enter("flist_print"));

  if(fn)
  {
    int n;
    fprintf(fp,"list of input files:\n");

    for(n=0; fn; n++, fn= fn->next)
      fprintf(fp,"\t%s:%ld: \"%s\"\n",
        (fn->hostname ? fn->hostname
                      : "command-line"), fn->hostline, fn->filename);
  }
  else fprintf(fp,"list of input files is empty\n");

  D(bug_leave("flist_print"));
}
#endif /* DEBUG || TEST */


#ifdef TEST


int main(int argc, char **argv)
{
  int rc= 0;
  char *whoami= argv[0];

  while(--argc > 0 && !rc)
  {
    char *arg= *++argv;
    if(*arg == '@')
    {
      if(arg[1]) ++arg;
      else arg= (--argc > 0) ? *(++argv) : (char *)0L;

      if(arg && *arg)
      {
        if( rc= flist_from_file(arg) )
          fprintf(stderr,"%s: reading filenames from `%s' failed\n",whoami,arg);
      }
      else
      {
        fprintf(stderr,"%s: missing filename after `%s'\n",whoami,*argv);
        rc= 1;
      }
    }
    else /* !@... */
    {
      if( rc= flist_addfile(arg) )
        fprintf(stderr,"%s: out of memory... aaaiiiiiieeeeeeeee!\n",whoami);
    }
  }

  if(!rc)
    flist_print(stdout);

  flist_dispose();

  if(rc) fprintf(stderr,"%s: [%s] *** Error %d\n"
                        "%s terminated abnormally (error %d)\n", whoami, *argv, rc, whoami, rc);

  return rc;
}

#endif /* TEST */
