/*
 *     Copyright (c) 2017, NVIDIA CORPORATION.  All rights reserved.
 *
 * NVIDIA CORPORATION and its licensors retain all intellectual property
 * and proprietary rights in and to this software, related documentation
 * and any modifications thereto.  Any use, reproduction, disclosure or
 * distribution of this software and related documentation without an express
 * license agreement from NVIDIA CORPORATION is strictly prohibited.
 *
 */

/*
 * This tool is uses the zlib general purpose compression library
 * written by Jean-loup Gailly and Mark Adler.  This tool is based
 * on the excellent and well-documented zpipe.c example Version 1.2
 * (November 2004) written by Mark Adler.  See www.zlib.net for
 * more information on zlib.
 * zlib is Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler
 */
/*
 * pgunzip.c - decompress, using zlib, a file
 */

#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "zlib.h"

static int debug = 0;
static int debug2 = 0;

#define BUFSIZE 16384

/*
 * decompress infile to outfile.
 * return 0 on success, or a zlib error if the library returns an error.
 */
static int
zipdecompress(FILE *infile, FILE *outfile)
{
  int zret;
  unsigned int bytesout, b;
  z_stream ztream;
  unsigned char inbuf[BUFSIZE];
  unsigned char outbuf[BUFSIZE];

  /* initialize */
  ztream.zalloc = Z_NULL;
  ztream.zfree = Z_NULL;
  ztream.opaque = Z_NULL;
  ztream.avail_in = 0;
  ztream.next_in = Z_NULL;
  zret = inflateInit(&ztream);
  if (zret != Z_OK)
    return zret;

  /* read a chunk at a time */
  do {
    ztream.avail_in = fread(inbuf, 1, BUFSIZE, infile);
    if (ferror(infile)) {
      inflateEnd(&ztream);
      return Z_ERRNO;
    }
    if (ztream.avail_in == 0) /* end of file */
      break;
    ztream.next_in = inbuf;

    /* decompress until the last output buffer chunk */
    do {
      do {
        ztream.avail_out = BUFSIZE;
        ztream.next_out = outbuf;
        zret = inflate(&ztream, Z_NO_FLUSH);
        if (zret == Z_STREAM_ERROR) {
          inflateEnd(&ztream);
          return zret;
        }
        switch (zret) {
        case Z_NEED_DICT:
        case Z_DATA_ERROR:
        case Z_MEM_ERROR:
          inflateEnd(&ztream);
          return zret;
        }
        bytesout = BUFSIZE - ztream.avail_out;
        if (bytesout) {
          b = fwrite(outbuf, sizeof(char), bytesout, outfile);
          if (b != bytesout || ferror(outfile)) {
            inflateEnd(&ztream);
            return Z_ERRNO;
          }
        }
        /* this loop handles the common case where
         * a block of input generates more than one block of
         * output */
      } while (ztream.avail_out == 0);
      /* this loop handles the case where we've appended multiple
       * compressed files into one section.  inflate() will
       * return at the end of the first compressed file,
       * with avail_in and next_in pointing to the start of the next
       * compressed file */
      if (zret == Z_STREAM_END) {
        if (debug2)
          fprintf(outfile, "<<streamend>>");
        zret = inflateReset(&ztream);
        if (zret != Z_OK) {
          inflateEnd(&ztream);
          return zret;
        }
      }
    } while (ztream.avail_in != 0);

    /* continue until EOF on infile */
  } while (!feof(infile));

  inflateEnd(&ztream);
  if (zret != Z_OK)
    return zret;
  return 0;
} /* zipdecompress */

/*
 * report error from zlib
 */
static void
puterr(int err, FILE *infile, FILE *outfile, char *infilename,
       char *outfilename)
{
  switch (err) {
  case Z_ERRNO:
    if (ferror(infile)) {
      fprintf(stderr, "pgunzip: error reading input file: %s\n", infilename);
      err = 0;
    }
    if (ferror(outfile)) {
      fprintf(stderr, "pgunzip: error reading output file: %s\n", outfilename);
      err = 0;
    }
    if (err) {
      fprintf(stderr, "pgunzip: unknown file error\n");
    }
    break;
  case Z_STREAM_ERROR:
    fprintf(stderr, "pgunzip: invalid compression level\n");
    break;
  case Z_NEED_DICT:
  case Z_DATA_ERROR:
    fprintf(stderr, "pgunzip: invalid or incomplete data\n");
    break;
  case Z_MEM_ERROR:
    fprintf(stderr, "pgunzip: out of memory\n");
    break;
  case Z_VERSION_ERROR:
    fprintf(stderr, "pgunzip: zlib version mismatch\n");
    break;
  }
} /* puterr */

/*
 * decompress a file
 */
int
main(int argc, char *argv[])
{
  int a, dofile = 0;
  char *infilename = NULL, *outfilename = NULL;
  FILE *infile = NULL, *outfile = NULL;
  int err = 0;
  for (a = 1; a < argc; ++a) {
    if (!dofile && argv[a][0] == '-') {
      if (strcmp(argv[a], "-d") == 0) {
        debug = 1;
      } else if (strcmp(argv[a], "-g") == 0) {
        debug2 = 1;
      } else if (strcmp(argv[a], "--") == 0) {
        dofile = 1;
      } else {
        if (!err)
          fprintf(stderr, "pgunzip infile output\n");
        fprintf(stderr, "   unexpected argument: %s\n", argv[a]);
        err = 1;
      }
    } else {
      if (infilename == NULL) {
        infilename = argv[a];
        if (!dofile && strcmp(infilename, "-") == 0) {
          infile = stdin;
        } else {
          infile = fopen(infilename, "rb");
          if (infile == NULL) {
            if (!err)
              fprintf(stderr, "pgunzip infile output\n");
            fprintf(stderr, "  could not open input file: %s\n", argv[a]);
            err = 1;
          }
        }
      } else if (outfilename == NULL) {
        outfilename = argv[a];
        if (!dofile && strcmp(outfilename, "-") == 0) {
          outfile = stdout;
        } else {
          outfile = fopen(outfilename, "w");
          if (outfile == NULL) {
            if (!err)
              fprintf(stderr, "pgunzip infile output\n");
            fprintf(stderr, "  could not open output file: %s\n", argv[a]);
            err = 1;
          }
        }
      } else {
        if (!err)
          fprintf(stderr, "pgunzip infile output\n");
        fprintf(stderr, "   unexpected argument: %s\n", argv[a]);
        err = 1;
      }
    }
  }
  if (err)
    return err;

  if (infile == NULL)
    infile = stdin;
  if (outfile == NULL)
    outfile = stdout;

  err = zipdecompress(infile, outfile);
  if (err)
    puterr(err, infile, outfile, infilename, outfilename);
  return err;
} /* main */
