// SPDX-License-Identifier: LGPL-2.1-or-later
/** @file
 * TODO: insert short description here
 *//*
 * Authors:
 * see git history
 * Bob Jamison
 *
 * Copyright (C) 2018 Authors
 * Released under GNU LGPL v2.1+, read the file 'COPYING' for more information.
 */
/*
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
/*
 * This is intended to be a standalone, reduced capability
 * implementation of Gzip and Zip functionality.  Its
 * targeted use case is for archiving and retrieving single files
 * which use these encoding types.  Being memory based and
 * non-optimized, it is not useful in cases where very large
 * archives are needed or where high performance is desired.
 * However, it should hopefully work very well for smaller,
 * one-at-a-time tasks.  What you get in return is the ability
 * to drop these files into your project and remove the dependencies
 * on ZLib and Info-Zip.  Enjoy.
 */


#include <cstdio>
#include <cstdarg>
#include <ctime>

#include <string>
#include <utility>

#include "ziptool.h"






//########################################################################
//#  A D L E R  3 2
//########################################################################

/**
 * Constructor
 */
Adler32::Adler32()
{
    reset();
}

/**
 * Destructor
 */
Adler32::~Adler32()
= default;

/**
 * Reset Adler-32 checksum to initial value.
 */
void Adler32::reset()
{
    value = 1;
}

// ADLER32_BASE is the largest prime number smaller than 65536
#define ADLER32_BASE 65521

void Adler32::update(unsigned char b)
{
    unsigned long s1 = value & 0xffff;
    unsigned long s2 = (value >> 16) & 0xffff;
    s1 += b & 0xff;
    s2 += s1;
    value = ((s2 % ADLER32_BASE) << 16) | (s1 % ADLER32_BASE);
}

void Adler32::update(char *str)
{
    if (str)
        while (*str)
            update((unsigned char)*str++);
}


/**
 * Returns current checksum value.
 */
unsigned long Adler32::getValue()
{
    return value & 0xffffffffL;
}



//########################################################################
//#  C R C  3 2
//########################################################################

/**
 * Constructor
 */
Crc32::Crc32()
{
    reset();
}

/**
 * Destructor
 */
Crc32::~Crc32()
= default;

static bool crc_table_ready = false;
static unsigned long crc_table[256];

/**
 * make the table for a fast CRC.
 */
static void makeCrcTable()
{
    if (crc_table_ready)
        return;
    for (int n = 0; n < 256; n++)
        {
        unsigned long c = n;
        for (int k = 8;  --k >= 0; )
            {
            if ((c & 1) != 0)
                c = 0xedb88320 ^ (c >> 1);
            else
                c >>= 1;
            }
        crc_table[n] = c;
        }
    crc_table_ready = true;
}


/**
 * Reset CRC-32 checksum to initial value.
 */
void Crc32::reset()
{
    value = 0;
    makeCrcTable();
}

void Crc32::update(unsigned char b)
{
    unsigned long c = ~value;

    c &= 0xffffffff;
    c = crc_table[(c ^ b) & 0xff] ^ (c >> 8);
    value = ~c;
}


void Crc32::update(char *str)
{
    if (str)
        while (*str)
            update((unsigned char)*str++);
}

void Crc32::update(const std::vector<unsigned char> &buf)
{
    std::vector<unsigned char>::const_iterator iter;
    for (iter=buf.begin() ; iter!=buf.end() ; ++iter)
        {
        unsigned char ch = *iter;
        update(ch);
        }
}


/**
 * Returns current checksum value.
 */
unsigned long Crc32::getValue()
{
    return value & 0xffffffffL;
}

//########################################################################
//#  I N F L A T E R
//########################################################################


/**
 *
 */
struct Huffman
{
    int *count;  // number of symbols of each length
    int *symbol; // canonically ordered symbols
};

/**
 *
 */
class Inflater
{
public:

    Inflater();

    virtual ~Inflater();

    static const int MAXBITS   =  15; // max bits in a code
    static const int MAXLCODES = 286; // max number of literal/length codes
    static const int MAXDCODES =  30; // max number of distance codes
    static const int MAXCODES  = 316; // max codes lengths to read
    static const int FIXLCODES = 288; // number of fixed literal/length codes

    /**
     *
     */
    bool inflate(std::vector<unsigned char> &destination,
                 std::vector<unsigned char> &source);

private:

    /**
     *
     */
    void error(char const *fmt, ...)
    #ifdef G_GNUC_PRINTF
    G_GNUC_PRINTF(2, 3)
    #endif
    ;

    /**
     *
     */
    void trace(char const *fmt, ...)
    #ifdef G_GNUC_PRINTF
    G_GNUC_PRINTF(2, 3)
    #endif
    ;

    /**
     *
     */
    void dump();

    /**
     *
     */
    int buildHuffman(Huffman *h, int *length, int n);

    /**
     *
     */
    bool getBits(int need, int *oval);

    /**
     *
     */
    int doDecode(Huffman *h);

    /**
     *
     */
    bool doCodes(Huffman *lencode, Huffman *distcode);

    /**
     *
     */
    bool doStored();

    /**
     *
     */
    bool doFixed();

    /**
     *
     */
    bool doDynamic();


    std::vector<unsigned char>dest;

    std::vector<unsigned char>src;
    unsigned long srcPos;  //current read position
    int bitBuf;
    int bitCnt;

};


/**
 *
 */
Inflater::Inflater() :
    dest(),
    src(),
    srcPos(0),
    bitBuf(0),
    bitCnt(0)
{
}

/**
 *
 */
Inflater::~Inflater()
= default;

/**
 *
 */
void Inflater::error(char const *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    fprintf(stdout, "Inflater error:");
    vfprintf(stdout, fmt, args);
    fprintf(stdout, "\n");
    va_end(args);
}

/**
 *
 */
void Inflater::trace(char const *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    fprintf(stdout, "Inflater:");
    vfprintf(stdout, fmt, args);
    fprintf(stdout, "\n");
    va_end(args);
}


/**
 *
 */
void Inflater::dump()
{
    for (unsigned char i : dest)
        {
        fputc(i, stdout);
        }
}

/**
 *
 */
int Inflater::buildHuffman(Huffman *h, int *length, int n)
{
    // count number of codes of each length
    for (int len = 0; len <= MAXBITS; len++)
        h->count[len] = 0;
    for (int symbol = 0; symbol < n; symbol++)
        (h->count[length[symbol]])++;   // assumes lengths are within bounds
    if (h->count[0] == n)               // no codes!
        {
        error("huffman tree will result in failed decode");
        return -1;
        }

    // check for an over-subscribed or incomplete set of lengths
    int left = 1;                // number of possible codes left of current length
    for (int len = 1; len <= MAXBITS; len++)
        {
        left <<= 1;                     // one more bit, double codes left
        left -= h->count[len];          // deduct count from possible codes
        if (left < 0)
            {
            error("huffman over subscribed");
            return -1;
            }
        }

    // generate offsets into symbol table for each length for sorting
    int offs[MAXBITS+1]; //offsets in symbol table for each length
    offs[1] = 0;
    for (int len = 1; len < MAXBITS; len++)
        offs[len + 1] = offs[len] + h->count[len];

    /*
     * put symbols in table sorted by length, by symbol order within each
     * length
     */
    for (int symbol = 0; symbol < n; symbol++)
        if (length[symbol] != 0)
            h->symbol[offs[length[symbol]]++] = symbol;

    // return zero for complete set, positive for incomplete set
    return left;
}


/**
 *
 */
bool Inflater::getBits(int requiredBits, int *oval)
{
    long val = bitBuf;

    //add more bytes if needed
    while (bitCnt < requiredBits)
        {
        if (srcPos >= src.size())
            {
            error("premature end of input");
            return false;
            }
        val |= ((long)(src[srcPos++])) << bitCnt;
        bitCnt += 8;
        }

    //update the buffer and return the data
    bitBuf =  (int)(val >> requiredBits);
    bitCnt -= requiredBits;
    *oval = (int)(val & ((1L << requiredBits) - 1));

    return true;
}


/**
 *
 */
int Inflater::doDecode(Huffman *h)
{
    int bitTmp  = bitBuf;
    int left    = bitCnt;
    int code    = 0;
    int first   = 0;
    int index   = 0;
    int len     = 1;
    int *next = h->count + 1;
    while (true)
        {
        while (left--)
            {
            code   |=  bitTmp & 1;
            bitTmp >>= 1;
            int count  =   *next++;
            if (code < first + count)
                { /* if length len, return symbol */
                bitBuf = bitTmp;
                bitCnt = (bitCnt - len) & 7;
                return h->symbol[index + (code - first)];
                }
            index +=  count;
            first +=  count;
            first <<= 1;
            code  <<= 1;
            len++;
            }
        left = (MAXBITS+1) - len;
        if (left == 0)
            break;
        if (srcPos >= src.size())
            {
            error("premature end of input");
            dump();
            return -1;
            }
        bitTmp = src[srcPos++];
        if (left > 8)
            left = 8;
        }

    error("no end of block found");
    return -1;
}

/**
 *
 */
bool Inflater::doCodes(Huffman *lencode, Huffman *distcode)
{
    static const int lens[29] = { // Size base for length codes 257..285
        3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
        35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258};
    static const int lext[29] = { // Extra bits for length codes 257..285
        0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
        3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0};
    static const int dists[30] = { // Offset base for distance codes 0..29
        1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
        257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
        8193, 12289, 16385, 24577};
    static const int dext[30] = { // Extra bits for distance codes 0..29
        0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
        7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
        12, 12, 13, 13};

    //decode literals and length/distance pairs
    while (true)
        {
        int symbol = doDecode(lencode);
        if (symbol == 256)
            break;
        if (symbol < 0)
            {
            return false;
            }
        if (symbol < 256) //literal
            {
            dest.push_back(symbol);
            }
        else if (symbol > 256)//length
            {
            symbol -= 257;
            if (symbol >= 29)
                {
                error("invalid fixed code");
                return false;
                }
            int ret;
            if (!getBits(lext[symbol], &ret))
                return false;
            int len = lens[symbol] + ret;

            symbol = doDecode(distcode);//distance
            if (symbol < 0)
                {
                return false;
                }

            if (!getBits(dext[symbol], &ret))
                return false;
            unsigned int dist = dists[symbol] + ret;
            if (dist > dest.size())
                {
                error("distance too far back %d/%d", dist, dest.size());
                dump();
                //printf("pos:%d\n", srcPos);
                return false;
                }

            // copy length bytes from distance bytes back
            //dest.push_back('{');
            while (len--)
                {
                dest.push_back(dest[dest.size() - dist]);
                }
            //dest.push_back('}');

            }
        }

    return true;
}

/**
 */
bool Inflater::doStored()
{
    //trace("### stored ###");

    // clear bits from current byte
    bitBuf = 0;
    bitCnt = 0;

    // length
    if (srcPos + 4 > src.size())
        {
        error("not enough input");
        return false;
        }

    int len = src[srcPos++];
    len |= src[srcPos++] << 8;
    //trace("### len:%d", len);
    // check complement
    if (src[srcPos++] != (~len & 0xff) ||
        src[srcPos++] != ((~len >> 8) & 0xff))
        {
        error("twos complement for storage size do not match");
        return false;
        }

    // copy data
    if (srcPos + len > src.size())
        {
        error("Not enough input for stored block");
        return false;
        }
    while (len--)
        dest.push_back(src[srcPos++]);

    return true;
}

/**
 */
bool Inflater::doFixed()
{
    //trace("### fixed ###");

    static bool firstTime = true;
    static int lencnt[MAXBITS+1], lensym[FIXLCODES];
    static int distcnt[MAXBITS+1], distsym[MAXDCODES];
    static Huffman lencode = {lencnt, lensym};
    static Huffman distcode = {distcnt, distsym};

    if (firstTime)
        {
        firstTime = false;

        int lengths[FIXLCODES];

        // literal/length table
        int symbol = 0;
        for ( ; symbol < 144; symbol++)
            lengths[symbol] = 8;
        for ( ; symbol < 256; symbol++)
            lengths[symbol] = 9;
        for ( ; symbol < 280; symbol++)
            lengths[symbol] = 7;
        for ( ; symbol < FIXLCODES; symbol++)
            lengths[symbol] = 8;
        buildHuffman(&lencode, lengths, FIXLCODES);

        // distance table
        for (int symbol = 0; symbol < MAXDCODES; symbol++)
            lengths[symbol] = 5;
        buildHuffman(&distcode, lengths, MAXDCODES);
        }

    // decode data until end-of-block code
    bool ret = doCodes(&lencode, &distcode);
    return ret;
}

/**
 */
bool Inflater::doDynamic()
{
    //trace("### dynamic ###");
    int lengths[MAXCODES];                      // descriptor code lengths
    int lencnt[MAXBITS+1], lensym[MAXLCODES];   // lencode memory
    int distcnt[MAXBITS+1], distsym[MAXDCODES]; // distcode memory
    Huffman lencode  = {lencnt, lensym};          // length code
    Huffman distcode = {distcnt, distsym};        // distance code
    static const int order[19] =                // permutation of code length codes
        {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};

    // get number of lengths in each table, check lengths
    int ret;
    if (!getBits(5, &ret))
        return false;
    int nlen  = ret + 257;
    if (!getBits(5, &ret))
        return false;
    int ndist = ret + 1;
    if (!getBits(4, &ret))
        return false;
    int ncode = ret + 4;
    if (nlen > MAXLCODES || ndist > MAXDCODES)
        {
        error("Bad codes");
        return false;
        }

    // get code length code lengths
    int index = 0;
    for ( ; index < ncode; index++)
        {
        if (!getBits(3, &ret))
            return false;
        lengths[order[index]] = ret;
        }
    for ( ; index < 19; index++)
        lengths[order[index]] = 0;

    // build huffman table for code lengths codes
    if (buildHuffman(&lencode, lengths, 19) != 0)
        return false;

    // read length/literal and distance code length tables
    index = 0;
    while (index < nlen + ndist)
        {
        int symbol = doDecode(&lencode);
        if (symbol < 16)                // length in 0..15
            lengths[index++] = symbol;
        else
            {                          // repeat instruction
            int len = 0;               // assume repeating zeros
            if (symbol == 16)
                {         // repeat last length 3..6 times
                if (index == 0)
                    {
                    error("no last length");
                    return false;
                    }
                len = lengths[index - 1];// last length
                if (!getBits(2, &ret))
                    return false;
                symbol = 3 + ret;
                }
            else if (symbol == 17)      // repeat zero 3..10 times
                {
                if (!getBits(3, &ret))
                    return false;
                symbol = 3 + ret;
                }
            else                        // == 18, repeat zero 11..138 times
                {
                if (!getBits(7, &ret))
                    return false;
                symbol = 11 + ret;
                }
            if (index + symbol > nlen + ndist)
                {
                error("too many lengths");
                return false;
                }
            while (symbol--)            // repeat last or zero symbol times
                lengths[index++] = len;
            }
        }

    // build huffman table for literal/length codes
    int err = buildHuffman(&lencode, lengths, nlen);
    if (err < 0 || (err > 0 && nlen - lencode.count[0] != 1))
        {
        error("incomplete length codes");
        //return false;
        }
    // build huffman table for distance codes
    err = buildHuffman(&distcode, lengths + nlen, ndist);
    if (err < 0 || (err > 0 && nlen - lencode.count[0] != 1))
        {
        error("incomplete dist codes");
        return false;
        }

    // decode data until end-of-block code
    bool retn = doCodes(&lencode, &distcode);
    return retn;
}

/**
 */
bool Inflater::inflate(std::vector<unsigned char> &destination,
                       std::vector<unsigned char> &source)
{
    dest.clear();
    src = source;
    srcPos = 0;
    bitBuf = 0;
    bitCnt = 0;

    while (true)
        {
        int last; // one if last block
        if (!getBits(1, &last))
            return false;
        int type; // block type 0..3
        if (!getBits(2, &type))
            return false;
        switch (type)
            {
            case 0:
                if (!doStored())
                    return false;
                break;
            case 1:
                if (!doFixed())
                    return false;
                break;
            case 2:
                if (!doDynamic())
                    return false;
                break;
            default:
                error("Unknown block type %d", type);
                return false;
            }
        if (last)
            break;
        }

    destination = dest;

    return true;
}






//########################################################################
//#  D E F L A T E R
//########################################################################


#define DEFLATER_BUF_SIZE 32768
class Deflater
{
public:

    /**
     *
     */
    Deflater();

    /**
     *
     */
    virtual ~Deflater();

    /**
     *
     */
    virtual void reset();

    /**
     *
     */
    virtual bool update(int ch);

    /**
     *
     */
    virtual bool finish();

    /**
     *
     */
    virtual std::vector<unsigned char> &getCompressed();

    /**
     *
     */
    bool deflate(std::vector<unsigned char> &dest,
                 const std::vector<unsigned char> &src);

    void encodeDistStatic(unsigned int len, unsigned int dist);

private:

    //debug messages
    void error(char const *fmt, ...)
    #ifdef G_GNUC_PRINTF
    G_GNUC_PRINTF(2, 3)
    #endif
    ;

    void trace(char const *fmt, ...)
    #ifdef G_GNUC_PRINTF
    G_GNUC_PRINTF(2, 3)
    #endif
    ;

    bool compressWindow();

    bool compress();

    std::vector<unsigned char> compressed;

    std::vector<unsigned char> uncompressed;

    std::vector<unsigned char> window;

    unsigned int windowPos;

    //#### Output
    unsigned int outputBitBuf;
    unsigned int outputNrBits;

    void put(int ch);

    void putWord(int ch);

    void putFlush();

    void putBits(unsigned int ch, unsigned int bitsWanted);

    void putBitsR(unsigned int ch, unsigned int bitsWanted);

    //#### Huffman Encode
    void encodeLiteralStatic(unsigned int ch);

    unsigned char windowBuf[DEFLATER_BUF_SIZE];
    //assume 32-bit ints
    unsigned int windowHashBuf[DEFLATER_BUF_SIZE];
};


//########################################################################
//# A P I
//########################################################################


/**
 *
 */
Deflater::Deflater()
{
    reset();
}

/**
 *
 */
Deflater::~Deflater()
= default;

/**
 *
 */
void Deflater::reset()
{
    compressed.clear();
    uncompressed.clear();
    window.clear();
	windowPos = 0;
    outputBitBuf = 0;
    outputNrBits = 0;
    for (int k=0; k<DEFLATER_BUF_SIZE; k++)
    {
        windowBuf[k]=0;
        windowHashBuf[k]=0;
    }
}

/**
 *
 */
bool Deflater::update(int ch)
{
    uncompressed.push_back((unsigned char)(ch & 0xff));
    return true;
}

/**
 *
 */
bool Deflater::finish()
{
    return compress();
}

/**
 *
 */
std::vector<unsigned char> &Deflater::getCompressed()
{
    return compressed;
}


/**
 *
 */
bool Deflater::deflate(std::vector<unsigned char> &dest,
                       const std::vector<unsigned char> &src)
{
    reset();
    uncompressed = src;
    if (!compress())
        return false;
    dest = compressed;
    return true;
}







//########################################################################
//# W O R K I N G    C O D E
//########################################################################


//#############################
//#  M E S S A G E S
//#############################

/**
 *  Print error messages
 */
void Deflater::error(char const *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    fprintf(stdout, "Deflater error:");
    vfprintf(stdout, fmt, args);
    fprintf(stdout, "\n");
    va_end(args);
}

/**
 *  Print trace messages
 */
void Deflater::trace(char const *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    fprintf(stdout, "Deflater:");
    vfprintf(stdout, fmt, args);
    fprintf(stdout, "\n");
    va_end(args);
}




//#############################
//#  O U T P U T
//#############################

/**
 *
 */
void Deflater::put(int ch)
{
    compressed.push_back(ch);
    outputBitBuf = 0;
    outputNrBits = 0;
}

/**
 *
 */
void Deflater::putWord(int ch)
{
    int lo = (ch   ) & 0xff;
    int hi = (ch>>8) & 0xff;
    put(lo);
    put(hi);
}

/**
 *
 */
void Deflater::putFlush()
{
    if (outputNrBits > 0)
        {
        put(outputBitBuf & 0xff);
        }
    outputBitBuf = 0;
    outputNrBits = 0;
}

/**
 *
 */
void Deflater::putBits(unsigned int ch, unsigned int bitsWanted)
{
    //trace("n:%4u, %d\n", ch, bitsWanted);

    while (bitsWanted--)
        {
        //add bits to position 7.  shift right
        outputBitBuf = (outputBitBuf>>1) + (ch<<7 & 0x80);
        ch >>= 1;
        outputNrBits++;
        if (outputNrBits >= 8)
            {
            unsigned char b = outputBitBuf & 0xff;
            //printf("b:%02x\n", b);
            put(b);
            }
        }
}

static unsigned int bitReverse(unsigned int code, unsigned int nrBits)
{
    unsigned int outb = 0;
    while (nrBits--)
        {
        outb = (outb << 1) | (code & 0x01);
        code >>= 1;
        }
    return outb;
}


/**
 *
 */
void Deflater::putBitsR(unsigned int ch, unsigned int bitsWanted)
{
    //trace("r:%4u, %d", ch, bitsWanted);

    unsigned int rcode = bitReverse(ch, bitsWanted);

    putBits(rcode, bitsWanted);

}


//#############################
//#  E N C O D E
//#############################



void Deflater::encodeLiteralStatic(unsigned int ch)
{
    //trace("c: %d", ch);

    if (ch < 144)
        {
        putBitsR(ch + 0x0030 , 8); // 00110000
        }
    else if (ch < 256)
        {
        putBitsR(ch - 144 + 0x0190 , 9); // 110010000
        }
    else if (ch < 280)
        {
        putBitsR(ch - 256 + 0x0000 , 7); // 0000000
        }
    else if (ch < 288)
        {
        putBitsR(ch - 280 + 0x00c0 , 8); // 11000000
        }
    else //out of range
        {
        error("Literal out of range: %d", ch);
        }

}


struct LenBase
{
    unsigned int base;
    unsigned int range;
    unsigned int bits;
};

LenBase lenBases[] =
{
    // clang-format off
    {   3,  1, 0 },
    {   4,  1, 0 },
    {   5,  1, 0 },
    {   6,  1, 0 },
    {   7,  1, 0 },
    {   8,  1, 0 },
    {   9,  1, 0 },
    {  10,  1, 0 },
    {  11,  2, 1 },
    {  13,  2, 1 },
    {  15,  2, 1 },
    {  17,  2, 1 },
    {  19,  4, 2 },
    {  23,  4, 2 },
    {  27,  4, 2 },
    {  31,  4, 2 },
    {  35,  8, 3 },
    {  43,  8, 3 },
    {  51,  8, 3 },
    {  59,  8, 3 },
    {  67, 16, 4 },
    {  83, 16, 4 },
    {  99, 16, 4 },
    { 115, 16, 4 },
    { 131, 32, 5 },
    { 163, 32, 5 },
    { 195, 32, 5 },
    { 227, 32, 5 },
    { 258,  1, 0 }
    // clang-format on
};

struct DistBase
{
    unsigned int base;
    unsigned int range;
    unsigned int bits;
};

DistBase distBases[] =
{
    // clang-format off
    {     1,    1,  0 },
    {     2,    1,  0 },
    {     3,    1,  0 },
    {     4,    1,  0 },
    {     5,    2,  1 },
    {     7,    2,  1 },
    {     9,    4,  2 },
    {    13,    4,  2 },
    {    17,    8,  3 },
    {    25,    8,  3 },
    {    33,   16,  4 },
    {    49,   16,  4 },
    {    65,   32,  5 },
    {    97,   32,  5 },
    {   129,   64,  6 },
    {   193,   64,  6 },
    {   257,  128,  7 },
    {   385,  128,  7 },
    {   513,  256,  8 },
    {   769,  256,  8 },
    {  1025,  512,  9 },
    {  1537,  512,  9 },
    {  2049, 1024, 10 },
    {  3073, 1024, 10 },
    {  4097, 2048, 11 },
    {  6145, 2048, 11 },
    {  8193, 4096, 12 },
    { 12289, 4096, 12 },
    { 16385, 8192, 13 },
    { 24577, 8192, 13 }
    // clang-format on
};

void Deflater::encodeDistStatic(unsigned int len, unsigned int dist)
{

    //## Output length

    if (len < 3 || len > 258)
        {
        error("Length out of range:%d", len);
        return;
        }

    bool found = false;
    for (int i=0 ; i<29 ; i++)
        {
        unsigned int base  = lenBases[i].base;
        unsigned int range = lenBases[i].range;
        if (base + range > len)
            {
            unsigned int lenCode = 257 + i;
            unsigned int length  = len - base;
            //trace("--- %d %d %d %d", len, base, range, length);
            encodeLiteralStatic(lenCode);
            putBits(length, lenBases[i].bits);
            found = true;
            break;
            }
        }
    if (!found)
        {
        error("Length not found in table:%d", len);
        return;
        }

    //## Output distance

    if (dist < 4 || dist > 32768)
        {
        error("Distance out of range:%d", dist);
        return;
        }

    found = false;
    for (int i=0 ; i<30 ; i++)
        {
        unsigned int base  = distBases[i].base;
        unsigned int range = distBases[i].range;
        if (base + range > dist)
            {
            unsigned int distCode = i;
            unsigned int distance = dist - base;
            //error("--- %d %d %d %d", dist, base, range, distance);
            putBitsR(distCode, 5);
            putBits(distance, distBases[i].bits);
            found = true;
            break;
            }
        }
    if (!found)
        {
        error("Distance not found in table:%d", dist);
        return;
        }
}


//#############################
//#  C O M P R E S S
//#############################


/**
 * This method does the dirty work of dictionary
 * compression.  Basically it looks for redundant
 * strings and has the current duplicate refer back
 * to the previous one.
 */
bool Deflater::compressWindow()
{
    windowPos = 0;
    unsigned int windowSize = window.size();
    //### Compress as much of the window as possible

    unsigned int hash = 0;
    //Have each value be a long with the byte at this position,
    //plus the 3 bytes after it in the window
    for (int i=windowSize-1 ; i>=0 ; i--)
        {
        unsigned char ch = window[i];
        windowBuf[i] = ch;
        hash = ((hash<<8) & 0xffffff00) | ch;
        windowHashBuf[i] = hash;
        }

    while (windowPos < windowSize - 3)
        {
        //### Find best match, if any
        unsigned int bestMatchLen  = 0;
        unsigned int bestMatchDist = 0;
        if (windowPos >= 4)
            {
            for (unsigned int lookBack=0 ; lookBack<windowPos-4 ; lookBack++)
                {
                //Check 4-char hashes first, before continuing with string
                if (windowHashBuf[lookBack] == windowHashBuf[windowPos])
                    {
                    unsigned int lookAhead=4;
                    unsigned int lookAheadMax = windowSize - 4 - windowPos;
                    if (lookBack + lookAheadMax >= windowPos -4 )
                        lookAheadMax = windowPos - 4 - lookBack;
                    if (lookAheadMax > 258)
                        lookAheadMax = 258;
                    unsigned char *wp = &(windowBuf[windowPos+4]);
                    unsigned char *lb = &(windowBuf[lookBack+4]);
                    while (lookAhead<lookAheadMax)
                        {
                        if (*lb++ != *wp++)
                            break;
                        lookAhead++;
                        }
                    if (lookAhead > bestMatchLen)
                        {
                        bestMatchLen  = lookAhead;
                        bestMatchDist = windowPos - lookBack;
                        }
                    }
                }
            }
        if (bestMatchLen > 3)
            {
            //Distance encode
            //trace("### distance");
            /*
            printf("### 1 '");
            for (int i=0 ; i < bestMatchLen ; i++)
                fputc(window[windowPos+i], stdout);
            printf("'\n### 2 '");
            for (int i=0 ; i < bestMatchLen ; i++)
                fputc(window[windowPos-bestMatchDist+i], stdout);
            printf("'\n");
            */
            encodeDistStatic(bestMatchLen, bestMatchDist);
            windowPos += bestMatchLen;
            }
        else
            {
            //Literal encode
            //trace("### literal");
            encodeLiteralStatic(windowBuf[windowPos]);
            windowPos++;
            }
        }

    while (windowPos < windowSize)
        encodeLiteralStatic(windowBuf[windowPos++]);

    encodeLiteralStatic(256);
    return true;
}


/**
 *
 */
bool Deflater::compress()
{
    //trace("compress");
    unsigned long total = 0L;
    windowPos = 0;
    std::vector<unsigned char>::iterator iter;
    for (iter = uncompressed.begin(); iter != uncompressed.end() ; )
        {
        total += windowPos;
        trace("total:%ld", total);
        if (windowPos > window.size())
            windowPos = window.size();
        window.erase(window.begin() , window.begin()+windowPos);
        while (window.size() < 32768 && iter != uncompressed.end())
            {
            window.push_back(*iter);
            ++iter;
            }
        if (window.size() >= 32768)
            putBits(0x00, 1); //0  -- more blocks
        else
            putBits(0x01, 1); //1  -- last block
        putBits(0x01, 2); //01 -- static trees
        if (!compressWindow())
            return false;
        }
    putFlush();
    return true;
}





//########################################################################
//#  G Z I P    F I L E
//########################################################################

/**
 * Constructor
 */
GzipFile::GzipFile() :
    data(),
    fileName(),
    fileBuf(),
    fileBufPos(0),
    compressionMethod(0)
{
}

/**
 * Destructor
 */
GzipFile::~GzipFile()
= default;

/**
 *  Print error messages
 */
void GzipFile::error(char const *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    fprintf(stdout, "GzipFile error:");
    vfprintf(stdout, fmt, args);
    fprintf(stdout, "\n");
    va_end(args);
}

/**
 *  Print trace messages
 */
void GzipFile::trace(char const *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    fprintf(stdout, "GzipFile:");
    vfprintf(stdout, fmt, args);
    fprintf(stdout, "\n");
    va_end(args);
}

/**
 *
 */
void GzipFile::put(unsigned char ch)
{
    data.push_back(ch);
}

/**
 *
 */
void GzipFile::setData(const std::vector<unsigned char> &str)
{
    data = str;
}

/**
 *
 */
void GzipFile::clearData()
{
    data.clear();
}

/**
 *
 */
std::vector<unsigned char> &GzipFile::getData()
{
    return data;
}

/**
 *
 */
std::string &GzipFile::getFileName()
{
    return fileName;
}

/**
 *
 */
void GzipFile::setFileName(const std::string &val)
{
    fileName = val;
}



//#####################################
//# U T I L I T Y
//#####################################

/**
 *  Loads a new file into an existing GzipFile
 */
bool GzipFile::loadFile(const std::string &fName)
{
    FILE *f = fopen(fName.c_str() , "rb");
    if (!f)
        {
        error("Cannot open file %s", fName.c_str());
        return false;
        }
    while (true)
        {
        int ch = fgetc(f);
        if (ch < 0)
            break;
        data.push_back(ch);
        }
    fclose(f);
    setFileName(fName);
    return true;
}



//#####################################
//# W R I T E
//#####################################

/**
 *
 */
bool GzipFile::putByte(unsigned char ch)
{
    fileBuf.push_back(ch);
    return true;
}



/**
 *
 */
bool GzipFile::putLong(unsigned long val)
{
    fileBuf.push_back( (unsigned char)((val    ) & 0xff));
    fileBuf.push_back( (unsigned char)((val>> 8) & 0xff));
    fileBuf.push_back( (unsigned char)((val>>16) & 0xff));
    fileBuf.push_back( (unsigned char)((val>>24) & 0xff));
    return true;
}



/**
 *
 */
bool GzipFile::write()
{
    fileBuf.clear();

    putByte(0x1f); //magic
    putByte(0x8b); //magic
    putByte(   8); //compression method
    putByte(0x08); //flags.  say we have a crc and file name

    unsigned long ltime = (unsigned long) time(nullptr);
    putLong(ltime);

    //xfl
    putByte(0);
    //OS
    putByte(0);

    //file name
    for (char i : fileName)
        putByte(i);
    putByte(0);


    //compress
    std::vector<unsigned char> compBuf;
    Deflater deflater;
    if (!deflater.deflate(compBuf, data))
        {
        return false;
        }

    std::vector<unsigned char>::iterator iter;
    for (iter=compBuf.begin() ; iter!=compBuf.end() ; ++iter)
        {
        unsigned char ch = *iter;
        putByte(ch);
        }

    Crc32 crcEngine;
    crcEngine.update(data);
    unsigned long crc = crcEngine.getValue();
    putLong(crc);

    putLong(data.size());

    return true;
}


/**
 *
 */
bool GzipFile::writeBuffer(std::vector<unsigned char> &outBuf)
{
    if (!write())
        return false;
    outBuf.clear();
    outBuf = fileBuf;
    return true;
}


/**
 *
 */
bool GzipFile::writeFile(const std::string &fileName)
{
    if (!write())
        return false;
    FILE *f = fopen(fileName.c_str(), "wb");
    if (!f)
        return false;
    std::vector<unsigned char>::iterator iter;
    for (iter=fileBuf.begin() ; iter!=fileBuf.end() ; ++iter)
        {
        unsigned char ch = *iter;
        fputc(ch, f);
        }
    fclose(f);
    return true;
}


//#####################################
//# R E A D
//#####################################

bool GzipFile::getByte(unsigned char *ch)
{
    if (fileBufPos >= fileBuf.size())
        {
        error("unexpected end of data");
        return false;
        }
    *ch = fileBuf[fileBufPos++];
    return true;
}

/**
 *
 */
bool GzipFile::getLong(unsigned long *val)
{
    if (fileBuf.size() - fileBufPos < 4)
        return false;
    int ch1 = fileBuf[fileBufPos++];
    int ch2 = fileBuf[fileBufPos++];
    int ch3 = fileBuf[fileBufPos++];
    int ch4 = fileBuf[fileBufPos++];
    *val = ((ch4<<24) & 0xff000000L) |
           ((ch3<<16) & 0x00ff0000L) |
           ((ch2<< 8) & 0x0000ff00L) |
           ((ch1    ) & 0x000000ffL);
    return true;
}

bool GzipFile::read()
{
    fileBufPos = 0;

    unsigned char ch;

    //magic cookie
    if (!getByte(&ch))
        return false;
    if (ch != 0x1f)
        {
        error("bad gzip header");
        return false;
        }
    if (!getByte(&ch))
        return false;
    if (ch != 0x8b)
        {
        error("bad gzip header");
        return false;
        }

    //## compression method
    if (!getByte(&ch))
        return false;
    compressionMethod = ch & 0xff;

    //## flags
    if (!getByte(&ch))
        return false;
    //bool ftext    = ch & 0x01;
    bool fhcrc    = ch & 0x02;
    bool fextra   = ch & 0x04;
    bool fname    = ch & 0x08;
    bool fcomment = ch & 0x10;

    //trace("cm:%d ftext:%d fhcrc:%d fextra:%d fname:%d fcomment:%d",
    //         cm, ftext, fhcrc, fextra, fname, fcomment);

    //## file time
    unsigned long ltime;
    if (!getLong(&ltime))
        return false;
    //time_t mtime = (time_t)ltime;

    //## XFL
    if (!getByte(&ch))
        return false;
    //int xfl = ch;

    //## OS
    if (!getByte(&ch))
        return false;
    //int os = ch;

    //std::string timestr = ctime(&mtime);
    //trace("xfl:%d os:%d mtime:%s", xfl, os, timestr.c_str());

    if (fextra)
        {
        if (!getByte(&ch))
            return false;
        long xlen = ch;
        if (!getByte(&ch))
            return false;
        xlen = (xlen << 8) + ch;
        for (long l=0 ; l<xlen ; l++)
            {
            if (!getByte(&ch))
                return false;
            }
        }

    if (fname)
        {
        fileName = "";
	while (true)
	    {
            if (!getByte(&ch))
                return false;
            if (ch==0)
                break;
            fileName.push_back(ch);
            }
        }

    if (fcomment)
        {
	while (true)
	    {
            if (!getByte(&ch))
                return false;
            if (ch==0)
                break;
            }
        }

    if (fhcrc)
        {
        if (!getByte(&ch))
            return false;
        if (!getByte(&ch))
            return false;
        }

    //read remainder of stream
    //compressed data runs up until 8 bytes before end of buffer
    std::vector<unsigned char> compBuf;
    while (fileBufPos < fileBuf.size() - 8)
        {
        if (!getByte(&ch))
            return false;
        compBuf.push_back(ch);
        }
    //uncompress
    data.clear();
    Inflater inflater;
    if (!inflater.inflate(data, compBuf))
        {
        return false;
        }

    //Get the CRC and compare
    Crc32 crcEngine;
    crcEngine.update(data);
    unsigned long calcCrc = crcEngine.getValue();
    unsigned long givenCrc;
    if (!getLong(&givenCrc))
        return false;
    if (givenCrc != calcCrc)
        {
        error("Specified crc, %ud, not what received: %ud",
                givenCrc, calcCrc);
        return false;
        }

    //Get the file size and compare
    unsigned long givenFileSize;
    if (!getLong(&givenFileSize))
        return false;
    if (givenFileSize != data.size())
        {
        error("Specified data size, %ld, not what received: %ld",
                givenFileSize, data.size());
        return false;
        }

    return true;
}



/**
 *
 */
bool GzipFile::readBuffer(const std::vector<unsigned char> &inbuf)
{
    fileBuf = inbuf;
    if (!read())
        return false;
    return true;
}


/**
 *
 */
bool GzipFile::readFile(const std::string &fileName)
{
    fileBuf.clear();
    FILE *f = fopen(fileName.c_str(), "rb");
    if (!f)
        return false;
    while (true)
        {
        int ch = fgetc(f);
        if (ch < 0)
            break;
        fileBuf.push_back(ch);
        }
    fclose(f);
    if (!read())
        return false;
    return true;
}








//########################################################################
//#  Z I P    F I L E
//########################################################################

/**
 * Constructor
 */
ZipEntry::ZipEntry() :
    crc (0L),
    fileName (),
    comment (),
    compressionMethod (8),
    compressedData (),
    uncompressedData (),
    position (0)
{
}

/**
 *
 */
ZipEntry::ZipEntry(std::string fileNameArg,
                   std::string commentArg) :
    crc (0L),
    fileName (std::move(fileNameArg)),
    comment (std::move(commentArg)),
    compressionMethod (8),
    compressedData (),
    uncompressedData (),
    position (0)
{
}

/**
 * Destructor
 */
ZipEntry::~ZipEntry()
= default;


/**
 *
 */
std::string ZipEntry::getFileName()
{
    return fileName;
}

/**
 *
 */
void ZipEntry::setFileName(const std::string &val)
{
    fileName = val;
}

/**
 *
 */
std::string ZipEntry::getComment()
{
    return comment;
}

/**
 *
 */
void ZipEntry::setComment(const std::string &val)
{
    comment = val;
}

/**
 *
 */
unsigned long ZipEntry::getCompressedSize()
{
    return (unsigned long)compressedData.size();
}

/**
 *
 */
int ZipEntry::getCompressionMethod()
{
    return compressionMethod;
}

/**
 *
 */
void ZipEntry::setCompressionMethod(int val)
{
    compressionMethod = val;
}

/**
 *
 */
std::vector<unsigned char> &ZipEntry::getCompressedData()
{
    return compressedData;
}

/**
 *
 */
void ZipEntry::setCompressedData(const std::vector<unsigned char> &val)
{
    compressedData = val;
}

/**
 *
 */
unsigned long ZipEntry::getUncompressedSize()
{
    return (unsigned long)uncompressedData.size();
}

/**
 *
 */
std::vector<unsigned char> &ZipEntry::getUncompressedData()
{
    return uncompressedData;
}

/**
 *
 */
void ZipEntry::setUncompressedData(const std::vector<unsigned char> &val)
{
    uncompressedData = val;
}

void ZipEntry::setUncompressedData(const std::string &s)
{
    uncompressedData.clear();
    uncompressedData.reserve(s.size());
    uncompressedData.insert(uncompressedData.begin(), s.begin(), s.end());
}

/**
 *
 */
unsigned long ZipEntry::getCrc()
{
    return crc;
}

/**
 *
 */
void ZipEntry::setCrc(unsigned long val)
{
    crc = val;
}

/**
 *
 */
void ZipEntry::write(unsigned char ch)
{
    uncompressedData.push_back(ch);
}

/**
 *
 */
void ZipEntry::finish()
{
    Crc32 c32;
    std::vector<unsigned char>::iterator iter;
    for (iter = uncompressedData.begin() ;
           iter!= uncompressedData.end() ; ++iter)
        {
        unsigned char ch = *iter;
        c32.update(ch);
        }
    crc = c32.getValue();
    switch (compressionMethod)
        {
        case 0: //none
            {
            for (iter = uncompressedData.begin() ;
               iter!= uncompressedData.end() ; ++iter)
                {
                unsigned char ch = *iter;
                compressedData.push_back(ch);
                }
            break;
            }
        case 8: //deflate
            {
            Deflater deflater;
            if (!deflater.deflate(compressedData, uncompressedData))
                {
                //some error
                }
            break;
            }
        default:
            {
            printf("error: unknown compression method %d\n",
                    compressionMethod);
            }
        }
}




/**
 *
 */
bool ZipEntry::readFile(const std::string &fileNameArg,
                        const std::string &commentArg)
{
    crc = 0L;
    uncompressedData.clear();
    fileName = fileNameArg;
    comment  = commentArg;
    FILE *f = fopen(fileName.c_str(), "rb");
    if (!f)
        {
        return false;
        }
    while (true)
        {
        int ch = fgetc(f);
        if (ch < 0)
            break;
        uncompressedData.push_back((unsigned char)ch);
        }
    fclose(f);
    finish();
    return true;
}


/**
 *
 */
void ZipEntry::setPosition(unsigned long val)
{
    position = val;
}

/**
 *
 */
unsigned long ZipEntry::getPosition()
{
    return position;
}







/**
 * Constructor
 */
ZipFile::ZipFile() :
    entries(),
    fileBuf(),
    fileBufPos(0),
    comment()
{
}

/**
 * Destructor
 */
ZipFile::~ZipFile()
{
    std::vector<ZipEntry *>::iterator iter;
    for (iter=entries.begin() ; iter!=entries.end() ; ++iter)
        {
        ZipEntry *entry = *iter;
        delete entry;
        }
    entries.clear();
}

/**
 *
 */
void ZipFile::setComment(const std::string &val)
{
    comment = val;
}

/**
 *
 */
std::string ZipFile::getComment()
{
    return comment;
}


/**
 *
 */
std::vector<ZipEntry *> &ZipFile::getEntries()
{
    return entries;
}



//#####################################
//# M E S S A G E S
//#####################################

void ZipFile::error(char const *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    fprintf(stdout, "ZipFile error:");
    vfprintf(stdout, fmt, args);
    fprintf(stdout, "\n");
    va_end(args);
}

void ZipFile::trace(char const *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    fprintf(stdout, "ZipFile:");
    vfprintf(stdout, fmt, args);
    fprintf(stdout, "\n");
    va_end(args);
}

//#####################################
//# U T I L I T Y
//#####################################

/**
 *
 */
ZipEntry *ZipFile::addFile(const std::string &fileName,
                      const std::string &comment)
{
    ZipEntry *ze = new ZipEntry();
    if (!ze->readFile(fileName, comment))
        {
        delete ze;
        return nullptr;
        }
    entries.push_back(ze);
    return ze;
}


/**
 *
 */
ZipEntry *ZipFile::newEntry(const std::string &fileName,
                            const std::string &comment)
{
    ZipEntry *ze = new ZipEntry(fileName, comment);
    entries.push_back(ze);
    return ze;
}


//#####################################
//# W R I T E
//#####################################

/**
 *
 */
bool ZipFile::putLong(unsigned long val)
{
    fileBuf.push_back( ((int)(val    )) & 0xff);
    fileBuf.push_back( ((int)(val>> 8)) & 0xff);
    fileBuf.push_back( ((int)(val>>16)) & 0xff);
    fileBuf.push_back( ((int)(val>>24)) & 0xff);
    return true;
}


/**
 *
 */
bool ZipFile::putInt(unsigned int val)
{
    fileBuf.push_back( (val    ) & 0xff);
    fileBuf.push_back( (val>> 8) & 0xff);
    return true;
}

/**
 *
 */
bool ZipFile::putByte(unsigned char val)
{
    fileBuf.push_back(val);
    return true;
}

/**
 *
 */
bool ZipFile::writeFileData()
{
    std::vector<ZipEntry *>::iterator iter;
    for (iter = entries.begin() ; iter != entries.end() ; ++iter)
        {
        ZipEntry *entry = *iter;
        entry->setPosition(fileBuf.size());
        //##### HEADER
        std::string fname = entry->getFileName();
        putLong(0x04034b50L);
        putInt(20); //versionNeeded
        putInt(0); //gpBitFlag
        //putInt(0); //compression method
        putInt(entry->getCompressionMethod()); //compression method
        putInt(0); //mod time
        putInt(0); //mod date
        putLong(entry->getCrc()); //crc32
        putLong(entry->getCompressedSize());
        putLong(entry->getUncompressedSize());
        putInt(fname.size());//fileName length
        putInt(8);//extra field length
        //file name
        for (char i : fname)
            putByte((unsigned char)i);
        //extra field
        putInt(0x7855);
        putInt(4);
        putInt(100);
        putInt(100);

        //##### DATA
        std::vector<unsigned char> &buf = entry->getCompressedData();
        std::vector<unsigned char>::iterator iter;
        for (iter = buf.begin() ; iter != buf.end() ; ++iter)
            {
            unsigned char ch = (unsigned char) *iter;
            putByte(ch);
            }
        }
    return true;
}

/**
 *
 */
bool ZipFile::writeCentralDirectory()
{
    unsigned long cdPosition = fileBuf.size();
    std::vector<ZipEntry *>::iterator iter;
    for (iter = entries.begin() ; iter != entries.end() ; ++iter)
        {
        ZipEntry *entry = *iter;
        std::string fname   = entry->getFileName();
        std::string ecomment = entry->getComment();
        putLong(0x02014b50L);  //magic cookie
        putInt(2386); //versionMadeBy
        putInt(20); //versionNeeded
        putInt(0); //gpBitFlag
        putInt(entry->getCompressionMethod()); //compression method
        putInt(0); //mod time
        putInt(0); //mod date
        putLong(entry->getCrc()); //crc32
        putLong(entry->getCompressedSize());
        putLong(entry->getUncompressedSize());
        putInt(fname.size());//fileName length
        putInt(4);//extra field length
        putInt(ecomment.size());//comment length
        putInt(0); //disk number start
        putInt(0); //internal attributes
        putLong(0); //external attributes
        putLong(entry->getPosition());

        //file name
        for (char i : fname)
            putByte((unsigned char)i);
        //extra field
        putInt(0x7855);
        putInt(0);
        //comment
        for (char i : ecomment)
            putByte((unsigned char)i);
        }
    unsigned long cdSize = fileBuf.size() - cdPosition;

    putLong(0x06054b50L);
    putInt(0);//number of this disk
    putInt(0);//nr of disk with central dir
    putInt(entries.size()); //number of entries on this disk
    putInt(entries.size()); //number of entries total
    putLong(cdSize);  //size of central dir
    putLong(cdPosition); //position of central dir
    putInt(comment.size());//comment size
    for (char i : comment)
        putByte(i);
    return true;
}



/**
 *
 */
bool ZipFile::write()
{
    fileBuf.clear();
    if (!writeFileData())
        return false;
    if (!writeCentralDirectory())
        return false;
    return true;
}


/**
 *
 */
bool ZipFile::writeBuffer(std::vector<unsigned char> &outBuf)
{
    if (!write())
        return false;
    outBuf.clear();
    outBuf = fileBuf;
    return true;
}


/**
 *
 */
bool ZipFile::writeFile(const std::string &fileName)
{
    if (!write())
        return false;
    FILE *f = fopen(fileName.c_str(), "wb");
    if (!f)
        return false;
    std::vector<unsigned char>::iterator iter;
    for (iter=fileBuf.begin() ; iter!=fileBuf.end() ; ++iter)
        {
        unsigned char ch = *iter;
        fputc(ch, f);
        }
    fclose(f);
    return true;
}

//#####################################
//# R E A D
//#####################################

/**
 *
 */
bool ZipFile::getLong(unsigned long *val)
{
    if (fileBuf.size() - fileBufPos < 4)
        return false;
    int ch1 = fileBuf[fileBufPos++];
    int ch2 = fileBuf[fileBufPos++];
    int ch3 = fileBuf[fileBufPos++];
    int ch4 = fileBuf[fileBufPos++];
    *val = ((ch4<<24) & 0xff000000L) |
           ((ch3<<16) & 0x00ff0000L) |
           ((ch2<< 8) & 0x0000ff00L) |
           ((ch1    ) & 0x000000ffL);
    return true;
}

/**
 *
 */
bool ZipFile::getInt(unsigned int *val)
{
    if (fileBuf.size() - fileBufPos < 2)
        return false;
    int ch1 = fileBuf[fileBufPos++];
    int ch2 = fileBuf[fileBufPos++];
    *val = ((ch2<< 8) & 0xff00) |
           ((ch1    ) & 0x00ff);
    return true;
}


/**
 *
 */
bool ZipFile::getByte(unsigned char *val)
{
    if (fileBuf.size() <= fileBufPos)
        return false;
    *val = fileBuf[fileBufPos++];
    return true;
}


/**
 *
 */
bool ZipFile::readFileData()
{
    //printf("#################################################\n");
    //printf("###D A T A\n");
    //printf("#################################################\n");
    while (true)
        {
        unsigned long magicCookie;
        if (!getLong(&magicCookie))
            {
            error("magic cookie not found");
            break;
            }
        trace("###Cookie:%lx", magicCookie);
        if (magicCookie == 0x02014b50L) //central directory
            break;
        if (magicCookie != 0x04034b50L)
            {
            error("file header not found");
            return false;
            }
        unsigned int versionNeeded;
        if (!getInt(&versionNeeded))
            {
            error("bad version needed found");
            return false;
            }
        unsigned int gpBitFlag;
        if (!getInt(&gpBitFlag))
            {
            error("bad bit flag found");
            return false;
            }
        unsigned int compressionMethod;
        if (!getInt(&compressionMethod))
            {
            error("bad compressionMethod found");
            return false;
            }
        unsigned int modTime;
        if (!getInt(&modTime))
            {
            error("bad modTime found");
            return false;
            }
        unsigned int modDate;
        if (!getInt(&modDate))
            {
            error("bad modDate found");
            return false;
            }
        unsigned long crc32;
        if (!getLong(&crc32))
            {
            error("bad crc32 found");
            return false;
            }
        unsigned long compressedSize;
        if (!getLong(&compressedSize))
            {
            error("bad compressedSize found");
            return false;
            }
        unsigned long uncompressedSize;
        if (!getLong(&uncompressedSize))
            {
            error("bad uncompressedSize found");
            return false;
            }
        unsigned int fileNameLength;
        if (!getInt(&fileNameLength))
            {
            error("bad fileNameLength found");
            return false;
            }
        unsigned int extraFieldLength;
        if (!getInt(&extraFieldLength))
            {
            error("bad extraFieldLength found");
            return false;
            }
        std::string fileName;
        for (unsigned int i=0 ; i<fileNameLength ; i++)
            {
            unsigned char ch;
            if (!getByte(&ch))
                break;
            fileName.push_back(ch);
            }
        std::string extraField;
        for (unsigned int i=0 ; i<extraFieldLength ; i++)
            {
            unsigned char ch;
            if (!getByte(&ch))
                break;
            extraField.push_back(ch);
            }
        trace("#########################  DATA");
        trace("FileName           :%d:%s" , fileName.size(), fileName.c_str());
        trace("Extra field        :%d:%s" , extraField.size(), extraField.c_str());
        trace("Version needed     :%d" , versionNeeded);
        trace("Bitflag            :%d" , gpBitFlag);
        trace("Compression Method :%d" , compressionMethod);
        trace("Mod time           :%d" , modTime);
        trace("Mod date           :%d" , modDate);
        trace("CRC                :%lx", crc32);
        trace("Compressed size    :%ld", compressedSize);
        trace("Uncompressed size  :%ld", uncompressedSize);

        //#### Uncompress the data
        std::vector<unsigned char> compBuf;
        if (gpBitFlag & 0x8)//bit 3 was set.  means we don't know compressed size
            {
            unsigned char c1, c2, c3, c4;
            c2 = c3 = c4 = 0;
            while (true)
                {
                unsigned char ch;
                if (!getByte(&ch))
                    {
                    error("premature end of data");
                    break;
                    }
                compBuf.push_back(ch);
                c1 = c2; c2 = c3; c3 = c4; c4 = ch;
                if (c1 == 0x50 && c2 == 0x4b && c3 == 0x07 && c4 == 0x08)
                    {
                    trace("found end of compressed data");
                    //remove the cookie
                    compBuf.erase(compBuf.end() -4, compBuf.end());
                    break;
                    }
                }
            }
        else
            {
            for (unsigned long bnr = 0 ; bnr < compressedSize ; bnr++)
                {
                unsigned char ch;
                if (!getByte(&ch))
                    {
                    error("premature end of data");
                    break;
                    }
                compBuf.push_back(ch);
                }
            }

        printf("### data: ");
        for (int i=0 ; i<10 ; i++)
            printf("%02x ", compBuf[i] & 0xff);
        printf("\n");

        if (gpBitFlag & 0x8)//only if bit 3 set
            {
            /* this cookie was read in the loop above
            unsigned long dataDescriptorSignature ;
            if (!getLong(&dataDescriptorSignature))
                break;
            if (dataDescriptorSignature != 0x08074b50L)
                {
                error("bad dataDescriptorSignature found");
                return false;
                }
            */
            unsigned long crc32;
            if (!getLong(&crc32))
                {
                error("bad crc32 found");
                return false;
                }
            unsigned long compressedSize;
            if (!getLong(&compressedSize))
                {
                error("bad compressedSize found");
                return false;
                }
            unsigned long uncompressedSize;
            if (!getLong(&uncompressedSize))
                {
                error("bad uncompressedSize found");
                return false;
                }
            }//bit 3 was set
        //break;

        std::vector<unsigned char> uncompBuf;
        switch (compressionMethod)
            {
            case 8: //deflate
                {
                Inflater inflater;
                if (!inflater.inflate(uncompBuf, compBuf))
                    {
                    return false;
                    }
                break;
                }
            default:
                {
                error("Unimplemented compression method %d", compressionMethod);
                return false;
                }
            }

        if (uncompressedSize != uncompBuf.size())
            {
            error("Size mismatch.  Expected %ld, received %ld",
                uncompressedSize, uncompBuf.size());
            return false;
            }

        Crc32 crcEngine;
        crcEngine.update(uncompBuf);
        unsigned long crc = crcEngine.getValue();
        if (crc != crc32)
            {
            error("Crc mismatch.  Calculated %08ux, received %08ux", crc, crc32);
            return false;
            }

        ZipEntry *ze = new ZipEntry(fileName, comment);
        ze->setCompressionMethod(compressionMethod);
        ze->setCompressedData(compBuf);
        ze->setUncompressedData(uncompBuf);
        ze->setCrc(crc);
        entries.push_back(ze);


        }
    return true;
}


/**
 *
 */
bool ZipFile::readCentralDirectory()
{
    //printf("#################################################\n");
    //printf("###D I R E C T O R Y\n");
    //printf("#################################################\n");
    while (true)
        {
        //We start with a central directory cookie already
        //Check at the bottom of the loop.
        unsigned int version;
        if (!getInt(&version))
            {
            error("bad version found");
            return false;
            }
        unsigned int versionNeeded;
        if (!getInt(&versionNeeded))
            {
            error("bad version found");
            return false;
            }
        unsigned int gpBitFlag;
        if (!getInt(&gpBitFlag))
            {
            error("bad bit flag found");
            return false;
            }
        unsigned int compressionMethod;
        if (!getInt(&compressionMethod))
            {
            error("bad compressionMethod found");
            return false;
            }
        unsigned int modTime;
        if (!getInt(&modTime))
            {
            error("bad modTime found");
            return false;
            }
        unsigned int modDate;
        if (!getInt(&modDate))
            {
            error("bad modDate found");
            return false;
            }
        unsigned long crc32;
        if (!getLong(&crc32))
            {
            error("bad crc32 found");
            return false;
            }
        unsigned long compressedSize;
        if (!getLong(&compressedSize))
            {
            error("bad compressedSize found");
            return false;
            }
        unsigned long uncompressedSize;
        if (!getLong(&uncompressedSize))
            {
            error("bad uncompressedSize found");
            return false;
            }
        unsigned int fileNameLength;
        if (!getInt(&fileNameLength))
            {
            error("bad fileNameLength found");
            return false;
            }
        unsigned int extraFieldLength;
        if (!getInt(&extraFieldLength))
            {
            error("bad extraFieldLength found");
            return false;
            }
        unsigned int fileCommentLength;
        if (!getInt(&fileCommentLength))
            {
            error("bad fileCommentLength found");
            return false;
            }
        unsigned int diskNumberStart;
        if (!getInt(&diskNumberStart))
            {
            error("bad diskNumberStart found");
            return false;
            }
        unsigned int internalFileAttributes;
        if (!getInt(&internalFileAttributes))
            {
            error("bad internalFileAttributes found");
            return false;
            }
        unsigned long externalFileAttributes;
        if (!getLong(&externalFileAttributes))
            {
            error("bad externalFileAttributes found");
            return false;
            }
        unsigned long localHeaderOffset;
        if (!getLong(&localHeaderOffset))
            {
            error("bad localHeaderOffset found");
            return false;
            }
        std::string fileName;
        for (unsigned int i=0 ; i<fileNameLength ; i++)
            {
            unsigned char ch;
            if (!getByte(&ch))
                break;
            fileName.push_back(ch);
            }
        std::string extraField;
        for (unsigned int i=0 ; i<extraFieldLength ; i++)
            {
            unsigned char ch;
            if (!getByte(&ch))
                break;
            extraField.push_back(ch);
            }
        std::string fileComment;
        for (unsigned int i=0 ; i<fileCommentLength ; i++)
            {
            unsigned char ch;
            if (!getByte(&ch))
                break;
            fileComment.push_back(ch);
            }
        trace("######################### ENTRY");
        trace("FileName           :%s" , fileName.c_str());
        trace("Extra field        :%s" , extraField.c_str());
        trace("File comment       :%s" , fileComment.c_str());
        trace("Version            :%d" , version);
        trace("Version needed     :%d" , versionNeeded);
        trace("Bitflag            :%d" , gpBitFlag);
        trace("Compression Method :%d" , compressionMethod);
        trace("Mod time           :%d" , modTime);
        trace("Mod date           :%d" , modDate);
        trace("CRC                :%lx", crc32);
        trace("Compressed size    :%ld", compressedSize);
        trace("Uncompressed size  :%ld", uncompressedSize);
        trace("Disk nr start      :%ld", diskNumberStart);
        trace("Header offset      :%ld", localHeaderOffset);


        unsigned long magicCookie;
        if (!getLong(&magicCookie))
            {
            error("magic cookie not found");
            return false;
            }
        trace("###Cookie:%lx", magicCookie);
        if (magicCookie  == 0x06054b50L) //end of central directory
            break;
        else if (magicCookie == 0x05054b50L) //signature
            {
            //## Digital Signature
            unsigned int signatureSize;
            if (!getInt(&signatureSize))
                {
                error("bad signatureSize found");
                return false;
                }
            std::string digitalSignature;
            for (unsigned int i=0 ; i<signatureSize ; i++)
                {
                unsigned char ch;
                if (!getByte(&ch))
                    break;
                digitalSignature.push_back(ch);
                }
            trace("######## SIGNATURE :'%s'" , digitalSignature.c_str());
            }
        else if (magicCookie != 0x02014b50L) //central directory
            {
            error("directory file header not found");
            return false;
            }
        }

    unsigned int diskNr;
    if (!getInt(&diskNr))
        {
        error("bad diskNr found");
        return false;
        }
    unsigned int diskWithCd;
    if (!getInt(&diskWithCd))
        {
        error("bad diskWithCd found");
        return false;
        }
    unsigned int nrEntriesDisk;
    if (!getInt(&nrEntriesDisk))
        {
        error("bad nrEntriesDisk found");
        return false;
        }
    unsigned int nrEntriesTotal;
    if (!getInt(&nrEntriesTotal))
        {
        error("bad nrEntriesTotal found");
        return false;
        }
    unsigned long cdSize;
    if (!getLong(&cdSize))
        {
        error("bad cdSize found");
        return false;
        }
    unsigned long cdPos;
    if (!getLong(&cdPos))
        {
        error("bad cdPos found");
        return false;
        }
    unsigned int commentSize;
    if (!getInt(&commentSize))
        {
        error("bad commentSize found");
        return false;
        }
    comment = "";
    for (unsigned int i=0 ; i<commentSize ; i++)
        {
        unsigned char ch;
        if (!getByte(&ch))
            break;
        comment.push_back(ch);
        }
    trace("######## Zip Comment :'%s'" , comment.c_str());

    return true;
}


/**
 *
 */
bool ZipFile::read()
{
    fileBufPos = 0;
    if (!readFileData())
        {
        return false;
        }
    if (!readCentralDirectory())
        {
        return false;
        }
    return true;
}

/**
 *
 */
bool ZipFile::readBuffer(const std::vector<unsigned char> &inbuf)
{
    fileBuf = inbuf;
    if (!read())
        return false;
    return true;
}


/**
 *
 */
bool ZipFile::readFile(const std::string &fileName)
{
    fileBuf.clear();
    FILE *f = fopen(fileName.c_str(), "rb");
    if (!f)
        return false;
    while (true)
        {
        int ch = fgetc(f);
        if (ch < 0)
            break;
        fileBuf.push_back(ch);
        }
    fclose(f);
    if (!read())
        return false;
    return true;
}









//########################################################################
//#  E N D    O F    F I L E
//########################################################################


