/*
Copyright 2011 Google Inc. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Author: lode.vandevenne@gmail.com (Lode Vandevenne)
Author: jyrki.alakuijala@gmail.com (Jyrki Alakuijala)
*/

/*
Zopfli compressor program. It can output gzip-, zlib- or deflate-compatible
data. By default it creates a .gz file. This tool can only compress, not
decompress. Decompression can be done by any standard gzip, zlib or deflate
decompressor.
*/

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

#include "deflate.h"
#include "gzip_container.h"
#include "zlib_container.h"

/*
Loads a file into a memory array.
*/
static void LoadFile(const char* filename,
                     unsigned char** out, size_t* outsize) {
  FILE* file;

  *out = 0;
  *outsize = 0;
  file = fopen(filename, "rb");
  if (!file) return;

  fseek(file , 0 , SEEK_END);
  *outsize = ftell(file);
  rewind(file);

  *out = (unsigned char*)malloc(*outsize);

  if (*outsize && (*out)) {
    size_t testsize = fread(*out, 1, *outsize, file);
    if (testsize != *outsize) {
      /* It could be a directory */
      free(*out);
      *out = 0;
      *outsize = 0;
    }
  }

  assert(!(*outsize) || out);  /* If size is not zero, out must be allocated. */
  fclose(file);
}

/*
Saves a file from a memory array, overwriting the file if it existed.
*/
static void SaveFile(const char* filename,
                     const unsigned char* in, size_t insize) {
  FILE* file = fopen(filename, "wb" );
  assert(file);
  fwrite((char*)in, 1, insize, file);
  fclose(file);
}

typedef enum {
  OUTPUT_GZIP,
  OUTPUT_ZLIB,
  OUTPUT_DEFLATE
} OutputType;

/*
outfilename: filename to write output to, or 0 to write to stdout instead
*/
void CompressFile(const Options* options,
                  OutputType output_type,
                  const char* infilename,
                  const char* outfilename) {
  unsigned char* in;
  size_t insize;
  unsigned char* out = 0;
  size_t outsize = 0;
  LoadFile(infilename, &in, &insize);
  if (insize == 0) {
    fprintf(stderr, "Invalid filename: %s\n", infilename);
    return;
  }
  if (output_type == OUTPUT_GZIP) {
    GzipCompress(options, in, insize, &out, &outsize);
  } else if (output_type == OUTPUT_ZLIB) {
    ZlibCompress(options, in, insize, &out, &outsize);
  } else if (output_type == OUTPUT_DEFLATE) {
    unsigned char bp = 0;
    Deflate(options, 2 /* Dynamic block */, 1, in, insize, &bp, &out, &outsize);
  } else {
    assert(0);
  }
  if (outfilename) {
    SaveFile(outfilename, out, outsize);
  } else {
    size_t i;
    for (i = 0; i < outsize; i++) {
      /* Works only if terminal does not convert newlines. */
      printf("%c", out[i]);
    }
  }

  free(out);
  free(in);
}

/*
Add two strings together. Size does not matter. Result must be freed.
*/
static char* AddStrings(const char* str1, const char* str2) {
  size_t len = strlen(str1) + strlen(str2);
  char* result = (char*)malloc(len + 1);
  if (!result) exit(-1); /* Allocation failed. */
  strcpy(result, str1);
  strcat(result, str2);
  return result;
}

static char StringsEqual(const char* str1, const char* str2) {
  return strcmp(str1, str2) == 0;
}

int main(int argc, char* argv[]) {
  Options options;
  const char* filename = 0;
  int output_to_stdout = 0;
  int i;
  OutputType output_type = OUTPUT_GZIP;

  InitOptions(&options);

  for (i = 1; i < argc; i++) {
    if (StringsEqual(argv[i], "-v")) options.verbose = 1;
    else if (StringsEqual(argv[i], "-c")) output_to_stdout = 1;
    else if (StringsEqual(argv[i], "--deflate")) output_type = OUTPUT_DEFLATE;
    else if (StringsEqual(argv[i], "--zlib")) output_type = OUTPUT_ZLIB;
    else if (StringsEqual(argv[i], "--gzip")) output_type = OUTPUT_GZIP;
    else if (StringsEqual(argv[i], "--i5")) options.numiterations = 5;
    else if (StringsEqual(argv[i], "--i10")) options.numiterations = 10;
    else if (StringsEqual(argv[i], "--i15")) options.numiterations = 15;
    else if (StringsEqual(argv[i], "--i25")) options.numiterations = 25;
    else if (StringsEqual(argv[i], "--i50")) options.numiterations = 50;
    else if (StringsEqual(argv[i], "--i100")) options.numiterations = 100;
    else if (StringsEqual(argv[i], "--i250")) options.numiterations = 250;
    else if (StringsEqual(argv[i], "--i500")) options.numiterations = 500;
    else if (StringsEqual(argv[i], "--i1000")) options.numiterations = 1000;
    else if (StringsEqual(argv[i], "-h")) {
      fprintf(stderr, "Usage: zopfli [OPTION]... FILE\n"
          "  -h    gives this help\n"
          "  -c    write the result on standard output, instead of disk"
          " filename + '.gz'\n"
          "  -v    verbose mode\n"
          "  --gzip  output to gzip format (default)\n"
          "  --deflate  output to deflate format instead of gzip\n"
          "  --zlib  output to zlib format instead of gzip\n");
      fprintf(stderr, "  --i5  less compression, but faster\n"
          "  --i10  less compression, but faster\n"
          "  --i15  default compression, 15 iterations\n"
          "  --i25  more compression, but slower\n"
          "  --i50  more compression, but slower\n"
          "  --i100  more compression, but slower\n"
          "  --i250  more compression, but slower\n"
          "  --i500  more compression, but slower\n"
          "  --i1000  more compression, but slower\n");
      return 0;
    }
  }

  for (i = 1; i < argc; i++) {
    if (argv[i][0] != '-') {
      char* outfilename;
      filename = argv[i];
      if (output_to_stdout) {
        outfilename = 0;
      } else if (output_type == OUTPUT_GZIP) {
        outfilename = AddStrings(filename, ".gz");
      } else if (output_type == OUTPUT_ZLIB) {
        outfilename = AddStrings(filename, ".zlib");
      } else if (output_type == OUTPUT_DEFLATE) {
        outfilename = AddStrings(filename, ".deflate");
      } else {
        assert(0);
      }
      if (options.verbose && outfilename) {
        fprintf(stderr, "Saving to: %s\n", outfilename);
      }
      CompressFile(&options, output_type, filename, outfilename);
      free(outfilename);
    }
  }

  if (!filename) {
    fprintf(stderr,
            "Please provide filename\nFor help, type: %s -h\n", argv[0]);
  }

  return 0;
}
