/**********************************************************************
 * reverse.c -- program to reverse a file byte by byte                *
 *              H. J. Bernstein, yaya@dowling.edu                     *
 *              16 April 2002                                         *
 **********************************************************************/

/********************************************************************** 
 *  reverse [-b blocksize] [[-i] input] [[-o] output]                 * 
 *          use - for stdin/stdout                                    *
 **********************************************************************/

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char * argv[]) {
       
  FILE *in, *out;            /* The input and out file handles      */
  int errflg = 0;            /* Count of argument list errors       */
  size_t blocksize = 0;      /* The size of blocks to reverse       */
  size_t bufsize = 0;        /* Actual buffer allocation >=512      */
  char* instr;               /* The path and name of the input      */
  char* outstr;              /* The path and name of the output     */
  char **endptr;             /* Pointer to pointer to invalid digit */
  char *buf;                 /* Pointer to buffer                   */
  char c;                    /* Option character                    */
  char tmpbuf[L_tmpnam+1];   /* Temporary file name                 */
  off_t inlen, ipos;         /* Input file length, output position  */
  size_t nbytes;             /* Bytes in current transfer           */
  static struct stat fstatus;/* Input file stat data                */

  /* Extract options */

  instr = (char *)NULL;
  outstr = (char *)NULL;


  /* scan for the -i, -o, -b options                                */
  opterr = 0;                 /* handle bad options here            */

  while ((c = getopt(argc, argv, "i:o:b:")) != EOF)
  {
    switch (c) {
      case 'i':               /* -i input                           */
        if (instr) errflg++;
        else instr = optarg;
        break;
      case 'o':               /* -o output                          */
        if (outstr) errflg++;
        else outstr = optarg;
        break;
      case 'b':               /* -b blocksize                       */
        if (blocksize > 0) errflg++;
        else {
          blocksize = (size_t)strtol(optarg,endptr,10);
          if (blocksize <=0 || **endptr!='\0') errflg++;
        }
        break;
      default:                /* handle invalid options             */
        errflg++;
        break;
    }
  }

  /* scan for the positional arguments                              */
  for (; optind < argc; optind++) {
    if (!instr) {
      instr = argv[optind];
    } else {
      if (!outstr) {
        outstr = argv[optind];
      } else {
        errflg++;
      }
    }
  }

  /* Report option errors                                           */
  if (errflg) {
    fprintf(stderr,"reverse:  Usage: \n");
    fprintf(stderr,"  reverse [-b blocksize] [[-i] input] [[-o] output]\n");
    fprintf(stderr,"\n");
    exit(2);
  }

  bufsize = blocksize;
  if (blocksize < 512)  bufsize = 512;
  if (blocksize < 1) blocksize = 1;

  if(! (buf=(char *)malloc(bufsize)) ) {
    fprintf(stderr,"reverse:  Insufficient memory to allocate buffer\n\n");
    exit(1);
  } 

  /* Check if the input filename string is empty or "-".  If so,
     copy to a temporary file.  If we do the copy, we have the file
     length by adding up the sizes of the transfers.  If we don't
     do the copy, we get the length with stat.                           */

  inlen = 0;
  if (!instr || strcmp(instr?instr:"-","-") == 0) {
    instr = strdup(tmpnam(&tmpbuf[0]));
    if ( (in = fopen(instr, "w+")) == NULL) {
       fprintf(stderr,"reverse:  Can't open temporary file %s.\n", instr);
       exit(1);
    }
    while (nbytes = fread(buf, 1, bufsize, stdin)) {
      if(nbytes != fwrite(buf, 1, nbytes, in)) {
        fprintf(stderr,"reverse:  Failed to write %s.\n", instr);
        exit(1);
       }
      inlen +=nbytes;
    }
    fclose(in);
  } else {
    if ( stat(instr, &fstatus) != 0 ) {
      fprintf(stderr,"reverse:  Unable to get length of %s.\n", instr);
    }
    inlen = fstatus.st_size;
  }

  if ( (in = fopen(instr, "r")) == NULL ) {
    fprintf(stderr,"reverse:  Can't open input file %s.\n", instr);
    exit(1);
  }

  /* Check if the output filename string is empty or "-".  If so
     write to stdout.                                                 */  
  if (!outstr || strcmp(outstr?outstr:"-","-") == 0) {
    out = stdout;
  } else {
    if ( (out = fopen(outstr, "a")) == NULL ) {
      fprintf(stderr,"reverse:  Can't open output file %s.\n", outstr);
      exit(1);
    }
  }

  /* Now copy from the back of the input file to the start
     of the output file, in blocks of size blocksize,
     reversing each block as it is read                               */

  for (ipos = 0; ipos < inlen; ipos += blocksize ) {
    off_t postn;
    size_t kbytes=blocksize;
    int ii;
 
    if (ipos+blocksize > inlen) {
      kbytes += inlen-blocksize-ipos;
    }
    fseek(in, inlen-kbytes-ipos, SEEK_SET);
    if (kbytes != (nbytes = fread(buf,1,kbytes,in))) {
      fprintf(stderr,
        "reverse:  Read failed at position %ld for %ld bytes, got %ld.\n",
        (long)(inlen-kbytes-ipos), (long)kbytes, (long)nbytes);
    }
    for(ii = 0; ii < nbytes/2; ii++) {
      char t;
      t = buf[nbytes-1-ii];
      buf[nbytes-1-ii] = buf[ii];
      buf[ii] = t;
    }
    fwrite(buf,1,nbytes,out);
  }

  /* close the input file, but not the output if it is stdout
     but we will do an fflush.  Note the the final exit would
     have done the same closes                                        */
  fclose(in);
  if (out != stdout) {
    fclose(out);      
  } else {
    fflush(out);      /* usually not necessary, but we'll be cautious */
  }

  exit(0);
}