/*                                                               -*- C -*-
 *  MAIN.C
 *
 *  (c)Copyright 1991-93 by Tobias Ferber,  All Rights Reserved
 */

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

#include "cvt.h"
#include "filecopy.h"


/*** / GLOBALS / ***/

int global_numchars= 256;   /* #of different chars in the local charset.
                             * This number is also used to decide whether
                             * an octal character code in a string runs
                             * out of range */

int global_maxhexdigits= 2; /* max. #of hexadecimal digits needed to express
                             * 'global_numchars' */
int global_maxoctdigits= 3; /* max. #of octal digits needed to express
                             * 'global_numchars */

int global_numerrors= 0;    /* global error counter */
int global_maxerrors= 5;    /* #of errors until we abort */
int global_checkexists= 0;  /* flag for read_flist() */
char *whoami;               /* copy of argv[0] */

/* input, output and error stream */
FILE *fin, *fout, *ferr;

#ifdef DEBUG
int debuglevel;
#endif

static void display_version_information(void)
{
  puts("CVT Version " VERSION " (compiled " __DATE__ ", " __TIME__ ")\n"
       "(c)Copyright 1991-93 by Tobias Ferber,  All Rights Reserved.\n");
}


/*** / MAIN / ***/

void main(int argc, char *argv[])
{
  char *rulefile, *outfmt, *errfile, *tmpfile;

  int badopt, err, dryrun, silent;
  fnode_t *fn;

#ifdef _DCC /* Dice */
  expand_args(argc,argv, &argc,&argv);
#endif /* _DCC */

  /* initialize globals */

  rulefile= outfmt= errfile= tmpfile= (char *)0L;
  badopt= err= dryrun= silent= 0;

#ifdef __MSDOS__

  /* filenames on MS-DOG systems look very ugly: all uppercase and
   * backslashes.  Perform some cosmetics. */

  whoami= "cvt";

#else /* !__MSDOS__ */
  whoami= argv[0];

#endif /* __MSDOS__ */

  fin  = stdin;
  fout = stdout;
  ferr = stderr;

  while(--argc>0 && !badopt)
  {
    char *arg= *++argv;

    if(*arg=='-')
    {
      if(arg[1]=='-')
        arg= convert_args(*argv);

      switch(*++arg)
      {

/*-c*/  case 'c':
          if(arg[1]) ++arg;
          else arg= (--argc > 0) ? *(++argv) : (char *)0L;

          if(arg && *arg)
          { int r= atoi(arg);
            if(0<r) global_numchars= r;
            else echo("iIllegal charset size: %d"
                       " (using `-c%d')",r,global_numchars);

            global_maxhexdigits= numdigits(global_numchars, 0x10);
            global_maxoctdigits= numdigits(global_numchars, 010);
          }
          else
          { echo("missing charset size after `%s' option",*argv);
            ++badopt;
          }
          break;

/*-d*/  case 'd':

          if(arg[1]) ++arg;
          else arg= (--argc > 0) ? *(++argv) : (char *)0L;

#ifdef DEBUG
          if(arg && *arg)
          { debuglevel= atoi(arg);
            if(debuglevel < 0 || debuglevel > 4)
            { echo("debug level %d is not in [0..4].  (using `-d1')",
                debuglevel,*argv);
              debuglevel= 1;
            }
          }
          else
          { echo("missing debug level after `%s' option.",*argv);
            ++badopt;
          }
#else
          echo("not compiled w/ -DDEBUG.  No debug information available -- Sorry.");
          /* no error */

#endif /* DEBUG */

          break;

/*-E*/  case 'E':
          if(arg[1]) ++arg;
          else arg= (--argc > 0) ? *(++argv) : (char *)0L;

          if(arg && *arg)
          { if(errfile)
            { echo("option `%s' has already been seen!",*argv);
              echo("more than one error filename in your command line");
              ++badopt;
            }
            else errfile= arg;
          }
          else
          { echo("missing filename after `%s' option",*argv);
            ++badopt;
          }
          break;

/*-e*/  case 'e':
          if(arg[1]) ++arg;
          else arg= (--argc > 0) ? *(++argv) : (char *)0L;

          if(arg && *arg)
          { int e= atoi(arg);
            if(0<e) global_maxerrors= e;
            else echo("illegal maximum #of errors: %d"
                       " (using `-e%d')",e,global_maxerrors);
          }
          else
          { echo("missing maximum #of errors after `%s' option",*argv);
            ++badopt;
          }
          break;

/*-f*/  case 'f':
          if(arg[1]) ++arg;
          else arg= (--argc > 0) ? *(++argv) : (char *)0L;

          if(arg && *arg)
          { if(rulefile)
            { echo("option `%s' has already been seen!",*argv);
              echo("more than one script filename in your command line");
              ++badopt;
            }
            else rulefile= arg;
          }
          else
          { echo("missing filename after `%s' option",*argv);
            ++badopt;
          }
          break;

/*-?*/  case '?':
/*-h*/  case 'h':
          fprintf(stderr,
            "usage: %s [options] [-f rulefile] [-o outfile] [infiles]\n\n",
            whoami);

          display_args();
          badopt= 1; /* hack: means exit. */
          break;

/*-l*/  case 'l':
          if(arg[1]) ++arg;
          else arg= (--argc > 0) ? *(++argv) : (char *)0L;

          if(arg && *arg)
          {
            if( read_flist(arg) != 0)
              ++badopt;
          }
          else
          { echo("missing filename after `%s' option",*argv);
            ++badopt;
          }
          break;

/*-n*/  case 'n':
          dryrun= 1;
          break;

/*-o*/  case 'o':
          if(arg[1]) ++arg;
          else arg= (--argc > 0) ? *(++argv) : (char *)0L;

          if(arg && *arg)
          {
            if(outfmt)
            { echo("option `%s' has already been seen!",*argv);
              echo("more than one output filename in your command line");
              ++badopt;
            }
            else outfmt= arg;
          }
          else
          { echo("missing output filename after `%s' option",*argv);
            ++badopt;
          }
          break;

/*-s*/  case 's':
          silent= 1;
          break;

/*-t*/  case 't':
          if(arg[1]) ++arg;
          else arg= (--argc > 0) ? *(++argv) : (char *)0L;

          if(arg && *arg)
          { if(tmpfile)
            { echo("option `%s' has already been seen!",*argv);
              echo("more than one temporary output filename in your command line");
              ++badopt;
            }
            else
            { if( tmpfile= (char *)malloc((strlen(arg)+1) * sizeof(char)) )
                strcpy(tmpfile,arg);
              else
              { echo("out of memory... aaaiiiiiieeeeeeeee!");
                badopt= 103;
              }
            }
          }
          else
          { echo("missing filename after `%s' option",*argv);
            ++badopt;
          }
          break;

/*-v*/  case 'v':
          display_version_information();
          break;

/*-x*/  case 'x':
          global_checkexists= 1;
          break;

/*??*/  default:
          echo("unrecognized option `%s'",*argv);
          badopt++;
          break;
      }
    }
    else if(*arg=='@')
    {
      if(arg[1]) ++arg;
      else arg= (--argc > 0) ? *(++argv) : (char *)0L;

      if(arg && *arg)
      {
        if( read_flist(arg) != 0)
          ++badopt;
      }
      else
      { echo("missing filename after `%s'",*argv);
        ++badopt;
      }
    }
    else
    {
      if(global_checkexists)
      {
        FILE *fp= fopen(arg,"r");

        if(fp)
          fclose(fp);

        else
        { perror(arg);
          ++badopt;
        }
      }

      if(!badopt)
      {
        if( chain_fname(0L,arg) )
        {
          echo("out of memory... bzzzzzzzz!");
          ++badopt;
        }
      }
    }
  }

  if(!badopt)
  {
    FILE *fp;

#ifdef DEBUG
    if(debuglevel >= 1)
      print_flist();
#endif

    /*
     * open error stream
     */

    if(errfile)
    {
      if( !(ferr= fopen(errfile,"w")) )
      { perror(errfile);
        echo("can't direct error output to `%s' -- will use stderr",
          errfile);
        ferr= stderr;
      }
    }

    err= init_rules();

    if(!err)
    {
      /*
       * In --dryrun mode the explicit use of the --file keyword
       * is not needed.  Instead we treat all input files as script
       * files and try to parse them respectively.
       * If however there was a --file <rulefile> in the command-line
       * we will ignore futher input files (if any).
       */

      if(dryrun)
      {
        if(rulefile)
        {

#ifdef DEBUG
          if(flist) /* global_numfiles is >= 1 */
          { echo("debug-warning: %d input files ignored.\n"
                   "(They make no sense w/ options --dryrun --file \"%s\")",
              global_numfiles, rulefile );
          }
#endif /* DEBUG */

          if( fp= cvtopen(rulefile) )
          {
            err= parsefile(fp);
            fclose(fp);
          }
          else perror(rulefile);
        }
        else /* !rulefile */
        {
          if(flist) /* global_numfiles is >= 1 */
          {
            fnode_t *fn;

            for(fn= flist; fn && !err; fn= fn->next)
            {
              if( fp= cvtopen(rulefile= fn->filename) )
              {
                err= parsefile(fp);
                fclose(fp);

                if(!err && fn->next)
                {
                  fprintf(ferr, "%s: %d rules, no errors.\n",
                    rulefile, global_numrules);

                  exit_rules();
                  err= init_rules();
                }
              }
              else err++;
            }
          }
          else /* !flist */
          { rulefile= "stdin";  /* filename for messages */
            err= parsefile(stdin);
          }
        }
      }
      else /* !dryrun */
      {
        if(rulefile)
          fp= cvtopen(rulefile);
        else
        { echo("warning: no conversion file... copying...");
          fp= (FILE *)0L;
        }

        if(fp)
        {
          err= parsefile(fp);

          if(fp!=stdin)
            fclose(fp);

          if(!err)
          {
#ifdef DEBUG
            if(debuglevel >= 3)
              dump_crules();
#endif
            if(global_numrules==0)
            { echo("warning: file '%s' contains no rules... copying...",
                rulefile);
            }
          }
        }
        else if(rulefile)
        { echo("no conversion file similar or equal to `%s' -- Sorry.",
            rulefile);
          err= 1;
        }
      }
    }
    else
    { echo("not enough memory for a %d character charset!", global_numchars);
      err= 1;
    }
  }
  else /* badopt != 0 */
    err= 1;

  /*
   *  convert
   */

  fn= flist;

  if(!dryrun && !err) do
  {
    char *infile, *outfile= (char *)0L;

    if(fn)
    {
      infile= fn->filename;

      if( !(fin= fopen(infile,"rb")) )
      { perror(infile);
        echo("can't access your input file `%s'",infile);
        err= 1;
      }
    }
    else
    { infile= "stdin";
      fin= stdin;
    }

    if(!err)
    {
      if(outfmt)
      {
        outfile= (char *)malloc(strlen(infile)+strlen(outfmt));

        if(!tmpfile)
          tmpfile= tfname(TEMPFILE_FORMAT);

        if(!outfile || !tmpfile)
        {
          echo("out of memory... aaaaarrrgggghhhh!");
          err= 1;
        }
        else
        {
          sprintf(outfile,outfmt,infile);

          if(!silent)
            printf("  %s\n",outfile);

#ifdef DEBUG
          if(debuglevel >= 1)
            printf("%d chars for output filename `%s' allocated,\n"
                   "writing temporary data on `%s'.\n",
              strlen(infile)+strlen(outfmt), outfile, tmpfile);
#endif /* DEBUG */
        }
      }
      else outfile= (char *)0L; /* use stdout */

      /* Wenn wir nach stdout konvertieren sollen, dann brauchen wir
       * kein tempor"ares File, denn wenn wir an die console schicken
       * machen wir eh' nix kaputt und wenn 'stdin' redirected ist,
       * hat die Shell das File auch schon zerschossen...
       * Wir tun dem Benutzer aber den Gefallen und puffern die Ausgabe
       * trotzdem, wenn er es explizit (mittels `-t' Option) w"unscht. */

      if(!err && tmpfile)
      {
        if( !(fout= fopen(tmpfile, "wb")) )
        { perror(tmpfile);
          echo("can't access temporary output file `%s'",tmpfile);
          err= 1;
        }
      }
    }

    if(!err)
      dothehardpart(); /* let's roll! */

    if(fin && fin!=stdin)
      fclose(fin);

    if(fout && fout!=stdout)
      fclose(fout);

    if(!err)
    {
      long n;

      /* Wenn wir in ein tempor"ares File geschrieben haben, (was immer
       * der Fall ist, wenn wir nicht nach stdout geschrieben haben)
       * dann m"ussen wir jetzt noch kopieren.  Wir k"onnen das tmpfile
       * nicht einfach renamen, weil es nicht im aktuellen Verzeichnis
       * stehen mu\3. */

      if(outfile)
      {
        if(fin= fopen(tmpfile,"rb"))
        {
          if(fout= fopen(outfile,"wb"))
          {
            if( (n= filecopy(fin,fout,0L)) < 0L )
            {
              if(n==-1)
              { perror(tmpfile);
                echo("error reading temporary data from `%s'",tmpfile);
              }
              else
              { perror(outfile);
                echo("error writing output to `%s'",outfile);
                echo("temporary output written on `%s'",tmpfile);
              }
              err= 1;
            }
#ifdef DEBUG
            else if(debuglevel >= 1)
              printf("%ld byte(s) of output\n",n);
#endif
            fclose(fout);
          }
          else
          { perror(outfile);
            echo("can't write to `%s'",outfile);
            echo("temporary output written on `%s'",tmpfile);
            err= 1;
          }
          fclose(fin);
        }
        else
        { perror(tmpfile);
          echo("couldn't re-open temporary file `%s'",tmpfile);
          err= 1;
        }
      }
      else if(tmpfile) /* but !outfile */
      {
        FILE *fp;

        /* Wir haben in ein tempor"ares File geschrieben und m"ussen die
         * Ausgabe auf die Console umleiten.  Wir machen das Zeichen f"ur
         * Zeichen... */

        if( fp= fopen(tmpfile, "rb") )
        { for(n=0; !feof(fp); n++)
          { char c= fgetc(fp);
            if(!feof(fp))
              fputc(c,stdout);
          }
          fclose(fp);
        }
        else
        { perror(tmpfile);
          echo("couldn't re-open temporary file");
          err= 1;
        }
      }
      /* otherwise we wrote to <stdout> directly */
    }

    if(tmpfile)
    { if(!err)
        remove(tmpfile);
      free(tmpfile);
      tmpfile= (char *)0L;
    }

    if(fn)
      fn= fn->next;

    if(outfile)
    { free(outfile);
      outfile= (char *)0L;
    }

  } while(fn && !err);

  if(err)
  { echo("*** [%s] exit code %d.",
      (rulefile && *rulefile) ? rulefile : "no rules", err);
  }
  else if(dryrun && rulefile)
    fprintf(ferr, "%s: %d rules, no errors.\n",rulefile, global_numrules);

  if(flist)
    purge_flist();

  if(crules)
    exit_rules();

  if(ferr && ferr!=stderr)
    fclose(ferr);

  exit(err ? 1:0);
}
