/* ======================================================================== *\
!
! *
! * This file is part of MARS, the MAGIC Analysis and Reconstruction
! * Software. It is distributed to you in the hope that it can be a useful
! * and timesaving tool in analysing Data of imaging Cerenkov telescopes.
! * It is distributed WITHOUT ANY WARRANTY.
! *
! * Permission to use, copy, modify and distribute this software and its
! * documentation for any purpose is hereby granted without fee,
! * provided that the above copyright notice appear in all copies and
! * that both that copyright notice and this permission notice appear
! * in supporting documentation. It is provided "as is" without express
! * or implied warranty.
! *
!
!
!   Author(s): Thomas Bretz, 12/2005 <mailto:tbretz@astro.uni-wuerzburg.de>
!
!   Copyright: MAGIC Software Development, 2000-2005
!
!
\* ======================================================================== */


//////////////////////////////////////////////////////////////////////////////
//
// MZlib
//
// This is a C++ wrapper for zlib.
//
// WARNING: - There might not be support for all features.
//          - seek calls might be rather slow
//
//////////////////////////////////////////////////////////////////////////////
#include "MZlib.h"

#include <string.h> // memcpy (UBuntu 8.10)

ClassImp(MZlib);

using namespace std;

// --------------------------------------------------------------------------
//
// Open a file by name. Test if it is open like for an ifstream
// It doesn't matter whether the file is gzip compressed or not.
//
void MZlib::open(const char* name)
{
    if (is_open())
    {
        clear(rdstate()|ios::badbit);
        return;
    }

    fFile = gzopen(name, "rb");
    if (fFile == 0)
    {
        clear(rdstate()|ios::badbit);
        return;
    }
}

// --------------------------------------------------------------------------
//
// Close an open file.
//
void MZlib::close()
{
    if (!is_open())
        return;

    if (gzclose(fFile) != Z_OK)
        clear(rdstate()|ios::badbit);

    fFile = 0;
}

// --------------------------------------------------------------------------
//
// Fill the buffer starting at the current file position and reset buffer
// pointers by calling setg
//
int MZlib::fill_buffer(int iputback)
{
    const int num = gzread(fFile, fBuffer+4, fgBufferSize-4);
    if (num <= 0) // ERROR or EOF
        return EOF;

    // reset buffer pointers
    setg(fBuffer+(4-iputback), fBuffer+4, fBuffer+4+num);

    return num;
}

// --------------------------------------------------------------------------
//
// Handle a buffer underflow (buffer went empty)
//
int MZlib::underflow()
{
    if (gptr() && gptr()<egptr())
        return * reinterpret_cast<unsigned char *>(gptr());

    if (!is_open())
        return EOF;

    // gptr()-eback(): if more than four bytes are already flushed
    const int iputback = gptr()-eback()>4 ? 4 : gptr()-eback();

    // Copy the last four bytes flushed into the putback area
    memcpy(fBuffer+(4-iputback), gptr()-iputback, iputback);

    if (fill_buffer(iputback)==EOF)
        return EOF;

    // return next character
    return *reinterpret_cast<unsigned char *>(gptr());
}

// --------------------------------------------------------------------------
//
# if (__GNUC__>2)
streambuf::pos_type MZlib::seekoff(streambuf::off_type offset, ios_base::seekdir dir,
                                   ios_base::openmode)
# else
streampos MZlib::seekoff(streamoff offset, int dir, int)
# endif
{
    // Using a switch instead results in:
    //  In member function `virtual std::streampos MZlib::seekoff(long int, std::_Ios_Seekdir, std::_Ios_Openmode)':
    //  warning: enumeration value `_M_ios_seekdir_end' not handled in switch
    //  warning: case value `0' not in enumerated type `_Ios_Seekdir'
    //  warning: case value `1' not in enumerated type `_Ios_Seekdir'
    //  warning: case value `2' not in enumerated type `_Ios_Seekdir'

    if (dir==ios::cur)
    {
        // Calculate future position in streambuffer
        const char *ptr = gptr()+offset;

        // Position in z-stream
        const z_off_t zpos = gztell(fFile)+gptr()-egptr(); //gzseek(fFile, 0, SEEK_CUR);

        // Check if the new position will still be in the buffer
        // In this case the target data was already decompressed.
        if (ptr<eback() || ptr>=egptr())
            return seekpos(zpos+ptr-egptr());

        gbump(offset);
        return zpos+offset;

        // zpos-blen: Position in z-stream coresponding to buffer position
        // return seekpos(gztell(fFile)+gptr()-egptr()+offset);
    }

    if (dir==ios::beg)
        return seekpos(offset);

    /*
      // SEEK_END not supported by zlib
      if (dir==ios::end)
      {
          // Position in z-stream
          const z_off_t zpos = gzseek(fFile, offset, SEEK_END);
          if (zpos<0)
              return EOF;
  
          return fill_buffer()==EOF ? EOF : zpos;
      }
      */
    return EOF;

}

// --------------------------------------------------------------------------
//
# if (__GNUC__>2)
streambuf::pos_type MZlib::seekpos(streambuf::pos_type pos, ios_base::openmode)
# else
streampos MZlib::seekpos(streampos pos, int)
# endif
{
    // Seek the z-stream to the given position
    if (gzseek(fFile, pos, SEEK_SET)<0)
        return EOF;

    // Fill buffer
    if (fill_buffer()==EOF)
        return EOF;

    return pos;
}
