#include "xentax.h"

bool CompressZLIB(ifstream& ifile, ofstream& ofile)
{
 return CompressZLIB(ifile, ofile, 0);
}

bool CompressZLIB(ifstream& ifile, ofstream& ofile, int level)
{
/*
 // allocate deflate state
 z_stream strm;
 ZeroMemory(&strm, sizeof(strm));
 strm.zalloc = Z_NULL;
 strm.zfree = Z_NULL;
 strm.opaque = Z_NULL;
 int ret = deflateInit(&strm, level);
 if(ret != Z_OK) return ret;

 int flush;

 const uint32 CHUNKSIZE = 16384;
 unsigned char out[CHUNKSIZE];

 // compress until end of file
 do
 {
     unsigned char in[CHUNKSIZE];
     strm.avail_in = fread(in, 1, CHUNKSIZE, source);
     if(ferror(source)) {
        deflateEnd(&strm);
        return Z_ERRNO;
       }

     flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;
     strm.next_in = in;

     // run deflate() on input until output buffer not full, finish compression if all of source has been read in
     do {

         strm.avail_out = CHUNKSIZE;
         strm.next_out = out;
         ret = deflate(&strm, flush);    // no bad return value
         assert(ret != Z_STREAM_ERROR);  // state not clobbered
         unsigned int have = CHUNKSIZE - strm.avail_out;
         if(fwrite(out, 1, have, dest) != have || ferror(dest)) {
            deflateEnd(&strm);
            return Z_ERRNO;
           }

     } while(strm.avail_out == 0);
     assert(strm.avail_in == 0); // all input will be used

 } while(flush != Z_FINISH);  // done when last data in file processed
 assert(ret == Z_STREAM_END); // stream will be complete

 // clean up and return
 deflateEnd(&strm);
 return Z_OK;
*/

 return true;
}

bool DecompressZLIB(ifstream& ifile, ofstream& ofile)
{
 return DecompressZLIB(ifile, ofile, 0);
}

bool DecompressZLIB(ifstream& ifile, ofstream& ofile, int windowBits)
{
 // validate
 if(!ifile.is_open()) return error("ifstream not open.");
 if(!ofile.is_open()) return error("ofstream not open.");

 // save current position
 ifstream::pos_type position = ifile.tellg();
 if(ifile.fail()) return error("Failed to retrieve ifstream position.");

 // allocate inflate buffers
 const uint32 MAXCHUNKSIZE = 65536;
 boost::shared_array<char> idata(new char[MAXCHUNKSIZE]);
 boost::shared_array<char> odata(new char[MAXCHUNKSIZE]);

 // while we have consecutive streams
 ifstream::pos_type bytes_read = 0;
 for(;;)
    {
     // allocate inflate state
     z_stream strm;
     ZeroMemory(&strm, sizeof(strm));
     int ret = inflateInit2(&strm, windowBits);
     if(ret != Z_OK) return error("ZLIB initialization failed.");

     // inflate until stream ends or end of file
     for(;;)
        {
         // get remaining filesize
         ifile.seekg(0, ios::end);
         ifstream::pos_type endpos = ifile.tellg();
         ifstream::pos_type distance = endpos - position;
         ifile.seekg(position);

         // end of file
         if(!distance) {
            inflateEnd(&strm);
            return true;
           }

         // read a chunk of data
         uint32 CHUNKSIZE = ((uint32)distance < MAXCHUNKSIZE ? (uint32)distance : MAXCHUNKSIZE);
         ifile.read(idata.get(), CHUNKSIZE);
         if(ifile.fail()) {
            inflateEnd(&strm);
            return error("ZLIB read error.");
           }

         // check if any data was read
         strm.avail_in = (uInt)ifile.gcount();
         if(strm.avail_in == 0) break;

         // set next input pointer (to buffer)
         strm.next_in = reinterpret_cast<Bytef*>(idata.get());

         // run inflate() on input until output buffer not full
         for(;;)
            {
             strm.avail_out = CHUNKSIZE;
             strm.next_out = reinterpret_cast<Bytef*>(odata.get());

             // decompress data
             ret = inflate(&strm, Z_NO_FLUSH);
             if(ret == Z_NEED_DICT) {                
                inflateEnd(&strm);
                return error("ZLIB need dictionary error.");;
               }
             else if(ret == Z_DATA_ERROR) {
                uLong total_out = strm.total_out;
                inflateEnd(&strm);
                if(!bytes_read || total_out) return error("ZLIB data error."); // error
                else return true; // no more consecutive zlib streams to read
               }
             else if(ret == Z_MEM_ERROR) {
                ret = Z_DATA_ERROR;
                inflateEnd(&strm);
                return error("ZLIB memory error.");
               }
             else if(ret == Z_STREAM_ERROR) {
                ret = Z_DATA_ERROR;
                inflateEnd(&strm);
                return error("ZLIB stream error.");
               }

             // save decompressed data
             unsigned int have = CHUNKSIZE - strm.avail_out;
             ofile.write(odata.get(), have);
             if(ofile.fail()) {
                inflateEnd(&strm);
                return error("ZLIB error writing data.");
               }
             bytes_read += have;
    
             // stop when buffer is not full
             if(strm.avail_out != 0) break;
            }

         // done when inflate() says it's done
         if(ret == Z_STREAM_END) break;
        }

     // clean up and move to end of stream
     position += strm.total_in;
     inflateEnd(&strm);
     ifile.seekg(position);
     if(ifile.fail()) break;
    }

 return true;
}

bool DecompressZLIB(ifstream& ifile, size_t n, ofstream& ofile, int windowBits)
{
 // validate
 if(n < 2) return error("Unexpected number of ZLIB bytes to read.");
 if(!ifile.is_open()) return error("ifstream not open.");
 if(!ofile.is_open()) return error("ofstream not open.");

 // save current position
 ifstream::pos_type position = ifile.tellg();
 if(ifile.fail()) return error("Failed to retrieve ifstream position.");

 // read data
 boost::shared_array<char> idata(new char[n]);
 ifile.read(idata.get(), n);
 if(ifile.fail()) return error("Read failure.");

 // allocate inflate buffers
 const uint32 CHUNKSIZE = 65536;
 boost::shared_array<char> odata(new char[CHUNKSIZE]);

 // allocate inflate state
 z_stream strm;
 ZeroMemory(&strm, sizeof(strm));
 int ret = inflateInit2(&strm, windowBits);
 if(ret != Z_OK) return error("ZLIB initialization failed.");

 // initialize inflate state
 strm.avail_in = n;
 strm.next_in = reinterpret_cast<Bytef*>(idata.get());

 // run inflate() on input until output buffer not full
 ifstream::pos_type bytes_read = 0;
 for(;;)
    {
     // initialize inflate state
     strm.avail_out = CHUNKSIZE;
     strm.next_out = reinterpret_cast<Bytef*>(odata.get());

     // decompress data
     ret = inflate(&strm, Z_NO_FLUSH);
     if(ret == Z_NEED_DICT) {                
        inflateEnd(&strm);
        return error("ZLIB need dictionary error.");;
       }
     else if(ret == Z_DATA_ERROR) {
        inflateEnd(&strm);
        return error("ZLIB data error.");
       }
     else if(ret == Z_MEM_ERROR) {
        ret = Z_DATA_ERROR;
        inflateEnd(&strm);
        return error("ZLIB memory error.");
       }
     else if(ret == Z_STREAM_ERROR) {
        ret = Z_DATA_ERROR;
        inflateEnd(&strm);
        return error("ZLIB stream error.");
       }

     // save decompressed data
     unsigned int have = CHUNKSIZE - strm.avail_out;
     ofile.write(odata.get(), have);
     if(ofile.fail()) {
        inflateEnd(&strm);
        return error("ZLIB error writing data.");
       }
     bytes_read += have;
    
     // stop when buffer is not full
     if(strm.avail_out != 0) break;
    }

 // done when inflate() says it's done
 return (ret == Z_STREAM_END);
}