/*
 * copy - test program for my getopt() re-implementation
 *
 * This program is in the public domain.
 */

#define VERSION \
"0.3"

#define COPYRIGHT \
"This program is in the public domain."

/* for isprint(), printf(), fopen(), perror(), getenv(), strcmp(), etc. */
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* for my getopt() re-implementation */
#include "getopt.h"

/* the default verbosity level is 0 (no verbose reporting) */
static unsigned verbose = 0;

/* print version and copyright information */
static void
version(char *progname)
{
  printf("%s version %s\n"
         "%s\n",
         progname,
         VERSION,
         COPYRIGHT);
}

/* print a help summary */
static void
help(char *progname)
{
  printf("Usage: %s [options] [FILE]...\n"
         "Options:\n"
         "-h or -help             show this message and exit\n"
         "-append                 append to the output file\n"
         "-o FILE or\n"
         "-output FILE            send output to FILE (default is stdout)\n"
         "-r or --rotate          rotate letters 13 positions (rot13)\n"
         "-rNUM or\n"
         "--rotate=NUM            rotate letters NUM positions\n"
         "-truncate               truncate the output file "
                                 "(this is the default)\n"
         "-v or -verbose          increase the level of verbosity by 1"
                                 "(the default is 0)\n"
         "-vNUM or\n"
         "-verbose=NUM            set the level of verbosity to NUM\n"
         "-V or -version          print program version and exit\n"
         "\n"
         "This program reads the specified FILEs "
         "(or stdin if none are given)\n"
         "and writes their bytes to the specified output FILE "
         "(or stdout if none is\n"
         "given.) It can optionally rotate letters.\n",
         progname);
}

/* print usage information to stderr */
static void
usage(char *progname)
{
  fprintf(stderr,
          "Summary: %s [-help] [-version] [options] [FILE]...\n",
          progname);
}

/* input file handler -- returns nonzero or exit()s on failure */
static int
handle(char *progname,
       FILE *infile,  char *infilename,
       FILE *outfile, char *outfilename,
       int rotate)
{
  int c;
  unsigned long bytes_copied = 0;

  if (verbose > 2)
    {
      fprintf(stderr,
              "%s: copying from `%s' to `%s'\n",
              progname,
              infilename,
              outfilename);
    }
  while ((c = getc(infile)) != EOF)
    {
      if (rotate && isalpha(c))
        {
          const char *letters = "abcdefghijklmnopqrstuvwxyz";
          char *match;
          if ((match = strchr(letters, tolower(c))))
            {
              char rc = letters[(match - letters + rotate) % 26];
              if (isupper(c))
                rc = toupper(rc);
              c = rc;
            }
        }
      if (putc(c, outfile) == EOF)
        {
          perror(outfilename);
          exit(1);
        }
      bytes_copied ++;
    }
  if (! feof(infile))
    {
      perror(infilename);
      return 1;
    }
  if (verbose > 2)
    {
      fprintf(stderr,
              "%s: %lu bytes copied from `%s' to `%s'\n",
              progname,
              bytes_copied,
              infilename,
              outfilename);
    }
  return 0;
}

/* argument parser and dispatcher */
int
main(int argc, char * argv[])
{
  /* the program name */
  char *progname = argv[0];
  /* during argument parsing, opt contains the return value from getopt() */
  int opt;
  /* the output filename is initially 0 (a.k.a. stdout) */
  char *outfilename = 0;
  /* the default return value is initially 0 (success) */
  int retval = 0;
  /* initially we truncate */
  int append = 0;
  /* initially we don't rotate letters */
  int rotate = 0;

  /* short options string */
  char *shortopts = "Vho:r::v::";
  /* long options list */
  struct option longopts[] =
  {
    /* name,        has_arg,           flag, val */ /* longind */
    { "append",     no_argument,       0,     0  }, /*       0 */
    { "truncate",   no_argument,       0,     0  }, /*       1 */
    { "version",    no_argument,       0,    'V' }, /*       3 */
    { "help",       no_argument,       0,    'h' }, /*       4 */
    { "output",     required_argument, 0,    'o' }, /*       5 */
    { "rotate",     optional_argument, 0,    'r' }, /*       6 */
    { "verbose",    optional_argument, 0,    'v' }, /*       7 */
    /* end-of-list marker */
    { 0, 0, 0, 0 }
  };
  /* long option list index */
  int longind = 0;

  /* 
   * print a warning when the POSIXLY_CORRECT environment variable will
   * interfere with argument placement
   */
  if (getenv("POSIXLY_CORRECT"))
    {
      fprintf(stderr,
              "%s: "
              "Warning: implicit argument reordering disallowed by "
              "POSIXLY_CORRECT\n",
              progname);
    }

  /* parse all options from the command line */
  while ((opt =
          getopt_long_only(argc, argv, shortopts, longopts, &longind)) != -1)
    switch (opt)
      {
      case 0: /* a long option without an equivalent short option */
        switch (longind)
          {
          case 0: /* -append */
            append = 1;
            break;
          case 1: /* -truncate */
            append = 0;
            break;
          default: /* something unexpected has happened */
            fprintf(stderr,
                    "%s: "
                    "getopt_long_only unexpectedly returned %d for `--%s'\n",
                    progname,
                    opt,
                    longopts[longind].name);
            return 1;
          }
        break;
      case 'V': /* -version */
        version(progname);
        return 0;
      case 'h': /* -help */
        help(progname);
        return 0;
      case 'r': /* -rotate[=NUM] */
        if (optarg)
          {
            /* we use this while trying to parse a numeric argument */
            char ignored;
            if (sscanf(optarg,
                       "%d%c",
                       &rotate,
                       &ignored) != 1)
              {
                fprintf(stderr,
                        "%s: "
                        "rotation `%s' is not a number\n",
                        progname,
                        optarg);
                usage(progname);
                return 2;
              }
            /* normalize rotation */
            while (rotate < 0)
              {
                rotate += 26;
              }
            rotate %= 26;
          }
        else
          rotate = 13;
        break;
      case 'o': /* -output=FILE */
        outfilename = optarg;
        /* we allow "-" as a synonym for stdout here */
        if (! strcmp(optarg, "-"))
          {
            outfilename = 0;
          }
        break;
      case 'v': /* -verbose[=NUM] */
        if (optarg)
          {
            /* we use this while trying to parse a numeric argument */
            char ignored;
            if (sscanf(optarg,
                       "%u%c",
                       &verbose,
                       &ignored) != 1)
              {
                fprintf(stderr,
                        "%s: "
                        "verbosity level `%s' is not a number\n",
                        progname,
                        optarg);
                usage(progname);
                return 2;
              }
          }
        else
          verbose ++;
        break;
      case '?': /* getopt_long_only noticed an error */
        usage(progname);
        return 2;
      default: /* something unexpected has happened */
        fprintf(stderr,
                "%s: "
                "getopt_long_only returned an unexpected value (%d)\n",
                progname,
                opt);
        return 1;
      }

  /* re-open stdout to outfilename, if requested */
  if (outfilename)
    {
      if (! freopen(outfilename, (append ? "a" : "w"), stdout))
        {
          perror(outfilename);
          return 1;
        }
    }
  else
    {
      /* make a human-readable version of the output filename "-" */
      outfilename = "stdout";
      /* you can't truncate stdout */
      append = 1;
    }

  if (verbose)
    {
      fprintf(stderr,
              "%s: verbosity level is %u; %s `%s'; rotation %d\n",
              progname,
              verbose,
              (append ? "appending to" : "truncating"),
              outfilename,
              rotate);
    }

  if (verbose > 1)
    {
      fprintf(stderr,
              "%s: %d input file(s) were given\n",
              progname,
              ((argc > optind) ? (argc - optind) : 0));
    }

  if (verbose > 3)
  {
      fprintf(stderr,
              "\topterr: %d\n\toptind: %d\n\toptopt: %d (%c)\n\toptarg: %s\n",
              opterr,
              optind,
              optopt, optopt,
              optarg ? optarg : "(null)");
  }

  /* handle each of the input files (or stdin, if no files were given) */
  if (optind < argc)
    {
      int argindex;
      
      for (argindex = optind; argindex < argc; argindex ++)
        {
          char *infilename = argv[argindex];
          FILE *infile;
          
          /* we allow "-" as a synonym for stdin here */
          if (! strcmp(infilename, "-"))
            {
              infile = stdin;
              infilename = "stdin";
            }
          else if (! (infile = fopen(infilename, "r")))
            {
              perror(infilename);
              retval = 1;
              continue;
            }
          if (handle(progname,
                     infile, argv[optind],
                     stdout, outfilename,
                     rotate))
            {
              retval = 1;
              fclose(infile);
              continue;
            }
          if ((infile != stdin) && fclose(infile))
            {
              perror(infilename);
              retval = 1;
            }
        }
    }
  else
    {
      retval =
        handle(progname,
               stdin, "stdin",
               stdout, outfilename,
               rotate);
    }

  /* close stdout */
  if (fclose(stdout))
    {
      perror(outfilename);
      return 1;
    }

  if (verbose > 3)
    {
      fprintf(stderr,
              "%s: normal return, exit code is %d\n",
              progname,
              retval);
    }
  return retval;
}
