// quad.cpp, version 1.12, author: ilia muraviev
#include <cstdio>
#include <cstdlib>
#include <cstring>
namespace std {}
using namespace std;

static FILE *infile; // input file stream
static FILE *outfile; // output file stream

class TEncoder {
private:
  long long low; // 64-bit extended integer
  unsigned int range;
  unsigned int buffer;
  unsigned int help;

  inline void ShiftLow() {
    if ((low ^ 0xff000000) >= (1 << 24)) {
      unsigned int c = static_cast<unsigned int>(low >> 32);
      fputc((buffer + c), outfile);
      c += 255;
      for (; help > 0; help--)
        fputc(c, outfile);
      buffer = static_cast<unsigned int>(low) >> 24;
    }
    else
      help++;
    low = static_cast<unsigned int>(low) << 8;
  }

public:
  TEncoder(): low(0), range(0xffffffff), buffer(0),
      help(0) {}

  void Encode(unsigned int cum, unsigned int cnt,
      unsigned int total) {
    low += (cum * (range /= total));
    range *= cnt;
    while (range < (1 << 24)) {
      range <<= 8;
      ShiftLow();
    }
  }

  void Flush() {
    for (int i = 0; i < 5; i++)
      ShiftLow();
  }
};

class TDecoder {
private:
  unsigned int range;
  unsigned int buffer;

public:
  TDecoder(): range(0xffffffff) {}

  void Init() {
    for (int i = 0; i < 5; i++)
      buffer = (buffer << 8) + fgetc(infile);
  }

  unsigned int GetCount(unsigned int total) {
    unsigned int count = buffer / (range /= total);
    if (count >= total)
      fprintf(stderr, "data error\n"), exit(1);
    return (count);
  }

  void Update(unsigned int cum, unsigned int cnt) {
    buffer -= (cum * range);
    range *= cnt;
    while (range < (1 << 24)) {
      range <<= 8;
      buffer = (buffer << 8) + fgetc(infile);
    }
  }
};

#define NUM 511 // number of symbols
#define TABSIZE 64

class TCounter {
private:
  inline void Rescale() {
    total = 0;
    for (int i = 0; i < NUM; i++)
      total += (cnt[i] -= (cnt[i] >> 1));
  }

public:
  unsigned short total; // 2 + (NUM * 2) = 1k
  unsigned short cnt[NUM];

  TCounter(): total(0) {
    memset(&cnt, 0, sizeof(cnt));
  }

  void Init(int n = NUM - 1) {
    for (int i = 0; i < n; i++)
      cnt[i] = 1;
    total = n;
  }

  void Add(int s) {
    total += 4;
    if ((cnt[s] += 4) >= 256)
      Rescale();
  }

  void AddX(int m) {
    cnt[m] += 8;
    if ((total += 8) >= (1 << 15))
      Rescale();
  }
};

class TPPM {
private:
  enum {
    ESC = 510 // escape symbol
  };
  TCounter counter2[1024];
  TCounter counter0;
  TCounter counterE;
  TCounter counterX;

  void Encode(TCounter &counter, int s) {
    unsigned int cum = 0;
    for (int i = 0; i < s; i++)
      cum += counter.cnt[i];

    encoder.Encode(cum, counter.cnt[s], counter.total);
  }

  int Decode(TCounter &counter) {
    unsigned int count = decoder.GetCount(counter.total);
    unsigned int cum = 0;

    int s = 0;
    while ((cum += counter.cnt[s]) <= count)
      s++;

    decoder.Update((cum - counter.cnt[s]), counter.cnt[s]);

    return (s);
  }

  void Set(TCounter &counter) {
    counterE.total = 0;
    for (int i = 0; i < NUM - 1; i++) {
      if (!counter.cnt[i])
        counterE.total += (counterE.cnt[i] = counter0.cnt[i]);
      else
        counterE.cnt[i] = 0;
    }
    if (!counterE.total)
      fprintf(stderr, "stream error\n"), exit(1);
  }

public:
  TEncoder encoder;
  TDecoder decoder;

  TPPM() {
    counter0.Init();
    counterX.Init(TABSIZE);
  }

  void Encode(int s, int x) {
    if (!counter2[x].cnt[s]) {
      if (counter2[x].total) {
        Encode(counter2[x], ESC);

        Set(counter2[x]);
        Encode(counterE, s);
      }
      else
        Encode(counter0, s);

      counter0.Add(s);
      counter2[x].Add(ESC);
    }
    else
      Encode(counter2[x], s);
    counter2[x].Add(s);
  }

  int Decode(int x) {
    int s;
    if (counter2[x].total) {
      if ((s = Decode(counter2[x])) == ESC) {
        Set(counter2[x]);
        counter0.Add(s = Decode(counterE));
        counter2[x].Add(ESC);
      }
    }
    else {
      counter0.Add(s = Decode(counter0));
      counter2[x].Add(ESC);
    }
    counter2[x].Add(s);

    return (s);
  }

  void EncodeIndex(int m) {
    Encode(counterX, m);
    counterX.AddX(m);
  }

  int DecodeIndex() {
    int m = Decode(counterX);
    counterX.AddX(m);

    return (m);
  }
} ppm;

#define N (1 << 24) // buffer size: 16m
#define X (1 << 16)
#define MINMATCH 3
#define MAXMATCH 256

static unsigned char buf[N + 4];
static unsigned int tab[X][TABSIZE];

static void e8e9transform(int t, int n) {
  for (int i = 0; i < (n - 4); ) {
    switch (buf[i++]) {
    case 0xe8: // call
    case 0xe9: // jmp
      int addr = *(reinterpret_cast<int *>(&buf[i]));

      if (t) {
        if ((addr >= -i) && (addr < (n - i)))
          addr += i;
        else if ((addr > 0) && (addr < n))
          addr -= n;
      }
      else {
        if (addr < 0) {
          if ((addr + i) >= 0)
            addr += n;
        }
        else if (addr < n)
          addr -= i;
      }
      *(reinterpret_cast<int *>(&buf[i])) = addr;

      i += 4;
    }
  }
}

inline unsigned char gethash(int i) {
  return (((*(reinterpret_cast<unsigned int *>(&buf[i]))
      & 0xffffff) * 123456791) >> 24);
}

inline int getmatch(int i, int n) {
  int x = *(reinterpret_cast<unsigned short *>(&buf[i - 2]));

  int maxlen = MINMATCH - 1; // length of best match
  int index; // match index

  int end = i + MAXMATCH;
  if (end > n)
    end = n;

  unsigned int hash = gethash(i);

  for (int m = 0; (m < TABSIZE) && (tab[x][m]); m++) {
    if ((tab[x][m] >> 24) != hash)
      continue;

    int p = tab[x][m] & 0xffffff;

    if (buf[p + maxlen] != buf[i + maxlen])
      continue;

    int j = i;
    while ((buf[p++] == buf[j]) && (++j < end));

    if ((j -= i) > maxlen) {
      index = m;

      if ((i + (maxlen = j)) >= end)
        break;
    }
  }
  if (maxlen < MINMATCH)
    return (1);

  return (maxlen - (index >= 4));
}

inline int checkmatch(int i, int len) {
  int x = *(reinterpret_cast<unsigned short *>(&buf[i - 2]));

  int end = (i + len) + 1;

  unsigned int hash = gethash(i);

  for (int m = 0; (m < TABSIZE) && (tab[x][m]); m++) {
    if ((tab[x][m] >> 24) != hash)
      continue;

    int p = tab[x][m] & 0xffffff;

    if (buf[p + len] != buf[i + len])
      continue;

    int j = i;
    while ((buf[p++] == buf[j]) && (++j < end));

    if (j == end)
      return (1); // a longer match found
  }
  return (0);
}

static void encode(int max) {
  int size; // uncompressed size
  if ((fseek(infile, 0, SEEK_END)) || ((size = ftell(infile)) < 0)) {
    fprintf(stderr, "file too large\n"); // over 2g
    exit(1);
  }
  rewind(infile);
  fwrite(&size, 1, sizeof(size), outfile);

  int n;
  while ((n = fread(&buf, 1, N, infile)) > 0) { // read the buffer
    e8e9transform(1, n);

    memset(&tab, 0, sizeof(tab)); // reset the table

    int i = 0;
    for (; (i < 2) && (i < n); i++) // output two literals
      ppm.Encode(buf[i], 0);

    while (i < n) {
      int x = *(reinterpret_cast<unsigned short *>(&buf[i - 2]));

      int maxlen = MINMATCH - 1; // length of best match
      int index; // match index

      int end = i + MAXMATCH;
      if (end > n)
        end = n;

      unsigned int hash = gethash(i);

      for (int m = 0; (m < TABSIZE) && (tab[x][m]); m++) {
        if ((tab[x][m] >> 24) != hash)
          continue;

        int p = tab[x][m] & 0xffffff;

        if (buf[p + maxlen] != buf[i + maxlen])
          continue;

        int j = i;
        while ((buf[p++] == buf[j]) && (++j < end));

        if ((j -= i) > maxlen) {
          index = m;

          if ((i + (maxlen = j)) >= end)
            break;
        }
      }
      if ((maxlen >= MINMATCH) && (i < (n - maxlen))) {
        if (max) { // max mode
          int len = maxlen;
          int maxvalue = (len - ((len == MINMATCH)
              && (index >= 4))) + getmatch((i + len), n);

          if (maxvalue < ((MAXMATCH * 2) - 1)) {
            for (int j = 1; j < len; j++) {
              int temp = (j + (j == 1)) + getmatch((i + j), n);

              if (temp > maxvalue) {
                maxvalue = temp;
                maxlen = j; // optimize match length
              }
            }
            if ((maxlen < len) && (maxlen >= MINMATCH)) {
              len = maxlen - 1;
              end = i + maxlen;

              for (int m = 0; m < index; m++) {
                if ((tab[x][m] >> 24) != hash)
                  continue;

                int p = tab[x][m] & 0xffffff;

                if (buf[p + len] != buf[i + len])
                  continue;

                int j = i;
                while ((buf[p++] == buf[j]) && (++j < end));

                if (j == end) {
                  index = m; // replace match index
                  break;
                }
              }
            }
          }
        }
        else { // normal mode
          if ((maxlen < MAXMATCH)
              && (checkmatch((i + 1), maxlen))) {
            maxlen = 0; // discard current match
          }
        }
      }
      for (int k = TABSIZE - 1; k > 0; k--) // update the table
        tab[x][k] = tab[x][k - 1];
      tab[x][0] = (hash << 24) + i;

      if (maxlen >= MINMATCH) {
        ppm.Encode(((256 - MINMATCH) + maxlen),
            (x >> 6)); // output match length
        ppm.EncodeIndex(index); // output match index
        i += maxlen;
      }
      else // not long enough match
        ppm.Encode(buf[i++], (x >> 6)); // output a literal
    }
  }
  ppm.encoder.Flush(); // [!]

  if (ftell(infile) != size)
    fprintf(stderr, "size mismatch\n"), exit(1);
}

static void decode() {
  unsigned char r[X];

  int size = 0;
  fread(&size, 1, sizeof(size), infile);
  if (size < 0)
    fprintf(stderr, "size error\n"), exit(1);

  ppm.decoder.Init(); // [!]

  while (size > 0) {
    int i = 0;
    for (; (i < 2) && (--size >= 0); i++)
      buf[i] = ppm.Decode(0);

    while ((i < N) && (--size >= 0)) {
      int x = *(reinterpret_cast<unsigned short *>(&buf[i - 2]));

      int j = i;
      int s;
      if ((s = ppm.Decode(x >> 6)) >= 256) {
        size -= ((s -= (256 - MINMATCH)) - 1);

        int p = tab[x][(r[x] - ppm.DecodeIndex()) & (TABSIZE - 1)];
        while (--s >= 0)
          buf[i++] = buf[p++];
      }
      else
        buf[i++] = s;

      tab[x][++r[x] & (TABSIZE - 1)] = j;
    }
    e8e9transform(0, i);

    fwrite(&buf, 1, i, outfile);
  }
}

static float getratio() {
  float a = static_cast<float>(ftell(infile));
  float b = static_cast<float>(ftell(outfile));

  if ((a > 0.0) && (b > 0.0))
    return ((b * 100.0) / a);

  return (-1.0);
}

static void makename(int decompress, char *n) {
  if (decompress) {
    char *s = strrchr(n, '.');
    if (s) {
      if (strcmp(s, ".quad"))
        fprintf(stderr, "%s: unknown suffix\n", n), exit(1);
      *s = 0;
    }
  }
  else
    strcat(n, ".quad");
}

static int fileexists(char *n) {
  FILE *f = fopen(n, "rb");
  if (f) {
    fclose(f);
    return (1);
  }
  return (0);
}

int main(int argc, char **argv) {
  printf("quad 1.12 copyright (c) 2004-2007 ilia muraviev\n");

  int decompress = 0;
  int force = 0;
  int max = 0;

  while ((argc > 1) && (argv[1][0] == '-')) {
    for (int i = 1; argv[1][i] != 0; i++) {
      switch (argv[1][i]) {
      case 'd':
        decompress = 1;
        break;
      case 'f':
        force = 1;
        break;
      case 'x':
        max = 1;
        break;
      default:
        fprintf(stderr, "unknown option: %c\n", argv[1][i]);
        exit(1);
      }
    }
    argv++;
    argc--;
  }

  if (argc < 2) {
    fprintf(stderr, "usage: quad [options] infile [outfile]\n"
        "options:\n"
        "  -d decompress\n"
        "  -f force overwrite of output file\n"
        "  -x use max compression mode\n");
    exit(1);
  }

  char outname[FILENAME_MAX]; // output file name
  if (argc < 3) {
    strcpy(outname, argv[1]);
    makename(decompress, outname);
  }
  else
    strcpy(outname, argv[2]);

  if ((!force) && (fileexists(outname))) {
    fprintf(stderr, "%s already exists; not overwritten\n", outname);
    exit(1);
  }
  if ((infile = fopen(argv[1], "rb")) == NULL) {
    perror(argv[1]);
    exit(1);
  }
  if ((outfile = fopen(outname, "wb")) == NULL) {
    perror(outname);
    exit(1);
  }

  if (!decompress) {
    printf("compressing %s...\n", argv[1]);
    encode(max);
    printf("ratio: %.1f%%\n", getratio());
  }
  else {
    printf("decompressing %s...\n", argv[1]);
    decode();
  }

  fclose(outfile);
  fclose(infile);

  printf("finished\n");

  return (0);
}
