//
// LDASTools frameCPP - A library implementing the LIGO/Virgo frame
// specification
//
// Copyright (C) 2018 California Institute of Technology
//
// LDASTools frameCPP is free software; you may redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 (GPLv2) of the
// License or at your discretion, any later version.
//
// LDASTools frameCPP is distributed in the hope that it will be useful, but
// without any warranty or even the implied warranty of merchantability
// or fitness for a particular purpose. See the GNU General Public
// License (GPLv2) for more details.
//
// Neither the names of the California Institute of Technology (Caltech),
// The Massachusetts Institute of Technology (M.I.T), The Laser
// Interferometer Gravitational-Wave Observatory (LIGO), nor the names
// of its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// You should have received a copy of the licensing terms for this
// software included in the file LICENSE located in the top-level
// directory of this package. If you did not, you can view a copy at
// http://dcc.ligo.org/M1500244/LICENSE
//

#include <framecpp_config.h>

extern "C" {
#include <zlib.h>
} // extern "C"

#include <iostream>

#include <stdexcept>
#include <sstream>

#include "ldastoolsal/types.hh"

#include "framecpp/Common/CompressionGZip.hh"

namespace FrameCPP
{
    namespace Compression
    {
        //-------------------------------------------------------------------
        /// The routines defined in this namespace are specific to the
        /// GZip compression algorithm.
        /// Routines are defined for both compression and decompression
        /// of data buffers.
        //-------------------------------------------------------------------
        namespace GZip
        {
            //-----------------------------------------------------------------
            /// The data must be in an uncompressed state before this is
            /// called.
            //-----------------------------------------------------------------
            void
            Compress( const CHAR_U*                  DataIn,
                      INT_8U                         NBytesIn,
                      size_t                         Level,
                      boost::shared_array< CHAR_U >& DataOut,
                      INT_8U&                        NBytesOut )
            {
                if ( Level == 0 )
                {
                    return;
                }
                if ( Level > 9 )
                {
                    Level = 9;
                }

                uLongf bufferSize = uLongf( NBytesIn * 1.001 + 13 );

                boost::shared_array< CHAR_U > buffer(
                    new CHAR_U[ bufferSize ] );
#if HAVE_LIBZ_COMPRESS2
                ::compress2(
                    buffer.get( ), &bufferSize, DataIn, NBytesIn, Level );
#else
                ::compress( buffer.get( ), &bufferSize, DataIn, NBytesIn );
#endif

                NBytesOut = bufferSize;
                DataOut.reset( new CHAR_U[ NBytesOut ] );
                std::copy(
                    &buffer[ 0 ], &( buffer[ NBytesOut ] ), &( DataOut[ 0 ] ) );
            } // func - Compress

            //-----------------------------------------------------------------
            /// This will uncompress the data using the appropriate GZip
            /// call.
            //-----------------------------------------------------------------
            void
            Expand( const CHAR_U*                  DataIn,
                    INT_8U                         NBytesIn,
                    boost::shared_array< CHAR_U >& DataOut,
                    INT_8U&                        NBytesOut )
            {
                //---------------------------------------------------------------
                // This may throw std::bad_alloc
                //---------------------------------------------------------------
#ifdef FRAMECPP_DEBUG
                std::cout << "Compression::GZip::Expand: nBytesIn: " << NBytesIn
                          << std::endl;
                std::cerr << "DEBUG: GZIP: Expand:"
                          << " NBytesOut: " << NBytesOut
                          << std::endl;
#endif /* FRAMECPP_DEBUG */
                DataOut.reset( new CHAR_U[ NBytesOut ] );
                uLongf uncompressedLength( NBytesOut );

                int error = uncompress(
                    DataOut.get( ), &uncompressedLength, DataIn, NBytesIn );

                //---------------------------------------------------------------
                // Check the return status from the uncompress
                //---------------------------------------------------------------
                if ( error != Z_OK )
                {
                    DataOut.reset( (CHAR_U*)NULL );
                    switch ( error )
                    {
                    case Z_MEM_ERROR:
                        throw std::bad_alloc( );
                        break;

                    case Z_BUF_ERROR:
                    case Z_DATA_ERROR:
                        throw std::runtime_error(
                            "Compressed data is corrupt." );
                        break;

                    default:
                        throw std::runtime_error(
                            "An unknown error occurred during "
                            "uncompression." );
                    }
                }

                //---------------------------------------------------------------
                // Make sure we uncompressed the correct number of bytes.
                //---------------------------------------------------------------
                if ( uncompressedLength != NBytesOut )
                {
                    std::ostringstream msg;

                    msg << "Compressed data is corrupt ("
                        << "Buffer expands to " << uncompressedLength
                        << " bytes in length instead of " << NBytesOut
                        << " bytes)";
                    throw std::range_error( msg.str( ) );
                }

                //---------------------------------------------------------------
                // Successfully expanded the buffer, so return the results
                //---------------------------------------------------------------
            } // func - Exapnd

        } // namespace GZip

    } // namespace Compression

} // namespace FrameCPP
