//
// 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>

#include <iostream>
#include <stdexcept>

#include <boost/shared_array.hpp>

#include "ldastoolsal/types.hh"
#include "ldastoolsal/reverse.hh"

#include "framecpp/Common/CompressionZeroSuppress.hh"

static const INT_2S MIN_INT_2S( std::numeric_limits< INT_2S >::min( ) );
static const INT_4S MIN_INT_4S( std::numeric_limits< INT_4S >::min( ) );
static const INT_8S MIN_INT_8S( std::numeric_limits< INT_8S >::min( ) );

typedef boost::shared_array< CHAR_U > data_type;
typedef data_type::element_type       data_element_type;

typedef FrameCPP::Compression::ZeroSuppress::data_type_type data_type_type;
typedef FrameCPP::Compression::ZeroSuppress::nbytes_type    nbytes_type;
typedef FrameCPP::Compression::ZeroSuppress::ndata_type     ndata_type;

// To initially generate the log table algorithmically:
static const char* init_nBitTable256( );
template < typename T >
INT_2U calcNBits( T Number );

static const char* nBitTable256( init_nBitTable256( ) );

template <>
inline INT_2U
calcNBits( INT_2U Number )
{
    INT_2U t; // temporaries

    return ( ( t = ( Number >> 8 ) ) ) ? 8 + nBitTable256[ t ]
                                       : nBitTable256[ Number ];
}

template <>
inline INT_2U
calcNBits( INT_2S Number )
{
    return calcNBits( (INT_2U)Number );
}

template <>
INT_2U
calcNBits( INT_4U Number )
{
    INT_2U r; // r will be lg(v)
    INT_4U t, tt; // temporaries

    if ( ( tt = ( Number >> 16 ) ) )
    {
        r = ( ( t = ( tt >> 8 ) ) ) ? 24 + nBitTable256[ t ]
                                    : 16 + nBitTable256[ tt ];
    }
    else
    {
        r = ( ( t = ( Number >> 8 ) ) ) ? 8 + nBitTable256[ t ]
                                        : nBitTable256[ Number ];
    }
    return r;
}

template <>
INT_2U
calcNBits( INT_8U Number )
{
    INT_2U r; // r will be lg(v)
    INT_8U t, tt, ttt; // temporaries

    if ( ( ttt = ( Number >> 32 ) ) )
    {
        if ( ( tt = ( ttt >> 16 ) ) )
        {
            r = ( ( t = ( tt >> 8 ) ) ) ? 56 + nBitTable256[ t ]
                                        : 48 + nBitTable256[ tt ];
        }
        else
        {
            r = ( ( t = ( ttt >> 8 ) ) ) ? 40 + nBitTable256[ t ]
                                         : 32 + nBitTable256[ ttt ];
        }
    }
    else
    {
        if ( ( tt = ( Number >> 16 ) ) )
        {
            r = ( ( t = ( tt >> 8 ) ) ) ? 24 + nBitTable256[ t ]
                                        : 16 + nBitTable256[ tt ];
        }
        else
        {
            r = ( ( t = ( Number >> 8 ) ) ) ? 8 + nBitTable256[ t ]
                                            : nBitTable256[ Number ];
        }
    }
    return r;
}

template <>
inline INT_2U
calcNBits( INT_8S Number )
{
    return calcNBits( (INT_8U)Number );
}

template <>
inline INT_2U
calcNBits( INT_4S Number )
{
    return calcNBits( (INT_4U)Number );
}

namespace
{
    static INT_4S ZERO_COMPRESS_INT_2_BSIZE = 12;
    static INT_4S ZERO_COMPRESS_INT_4_BSIZE = 8;
    static INT_4S ZERO_COMPRESS_INT_8_BSIZE = 8;

    //---------------------------------------------------------------------
    // Group of templates to facilitate generalization of the compression
    // algroithm.
    //---------------------------------------------------------------------
    template < int T >
    INT_4S BSize( );
    template < typename T >
    T LowerBound( );
    template < typename T >
    INT_2U SignificantBits( );
    template < typename T >
    T SignificantBitsMask( );

    //---------------------------------------------------------------------
    // Specialization of the above routines for 2 byte integers
    //---------------------------------------------------------------------

    template <>
    inline INT_4S
    BSize< 2 >( )
    {
        return ZERO_COMPRESS_INT_2_BSIZE;
    }

    template <>
    inline INT_2S
    LowerBound( )
    {
        return MIN_INT_2S;
    }

    template <>
    inline INT_2U
    SignificantBits< INT_2U >( )
    {
        return 4;
    }

    template <>
    inline INT_2U
    SignificantBits< INT_2S >( )
    {
        return SignificantBits< INT_2U >( );
    }

    template <>
    inline INT_2U
    SignificantBitsMask( )
    {
        return 0xF;
    }

    //---------------------------------------------------------------------
    // Specialization of the above routines for 4 byte integers
    //---------------------------------------------------------------------

    template <>
    inline INT_4S
    BSize< 4 >( )
    {
        return ZERO_COMPRESS_INT_4_BSIZE;
    }

    template <>
    inline INT_4S
    LowerBound( )
    {
        return MIN_INT_4S;
    }

    template <>
    inline INT_2U
    SignificantBits< INT_4U >( )
    {
        return 5;
    }

    template <>
    inline INT_2U
    SignificantBits< INT_4S >( )
    {
        return SignificantBits< INT_4U >( );
    }

    template <>
    inline INT_4U
    SignificantBitsMask( )
    {
        return 0x1F;
    }

    //---------------------------------------------------------------------
    // Specialization of the above routines for 8 byte integers
    //---------------------------------------------------------------------

    template <>
    inline INT_4S
    BSize< 8 >( )
    {
        return ZERO_COMPRESS_INT_8_BSIZE;
    }

    template <>
    inline INT_8S
    LowerBound( )
    {
        return MIN_INT_8S;
    }

    template <>
    inline INT_2U
    SignificantBits< INT_8U >( )
    {
        return 6;
    }

    template <>
    inline INT_2U
    SignificantBits< INT_8S >( )
    {
        return SignificantBits< INT_8U >( );
    }

    template <>
    inline INT_8U
    SignificantBitsMask( )
    {
        return 0x3F;
    }

    //---------------------------------------------------------------------
    //
    //---------------------------------------------------------------------

    template < typename T >
    inline INT_2U
    StorageBits( )
    {
        return sizeof( T ) << 3; // Multiply by 8
    }

    template < typename T >
    inline INT_2U
    SpaceBits( )
    {
        return StorageBits< T >( ) - SignificantBits< T >( );
    }

    //---------------------------------------------------------------------
    //
    //---------------------------------------------------------------------

    template < typename TS, typename TU >
    int zcomp( TU*        Out,
               INT_8U*    compL,
               const TS*  data,
               ndata_type nData,
               INT_8U     bSize = BSize< sizeof( TS ) >( ) );

    template < typename OutType, typename InType >
    void zexpand( OutType*     DataOut,
                  const InType* DataIn,
                  ndata_type    NDataOut,
                  ndata_type    NDataIn );

    template < typename InType, typename OutType >
    void
    compress( const data_element_type*                  Source,
              INT_8U                                    SourceLength,
              boost::shared_array< data_element_type >& Dest,
              INT_8U&                                   DestByteLength )
    {
        using namespace FrameCPP;

        try
        {
            typedef OutType out_type;
            typedef InType  in_type;

            DestByteLength = 0;
            Dest.reset( (data_element_type*)NULL );

            boost::shared_array< out_type > comp_buffer(
                new out_type[ SourceLength ] );
            INT_8U comp_buffer_len( SourceLength * sizeof( out_type ) );

            zcomp( comp_buffer.get( ),
                   &comp_buffer_len,
                   reinterpret_cast< const in_type* >( Source ),
                   SourceLength );

            INT_4U ncdata( comp_buffer_len / sizeof( out_type ) );
            Dest.reset( new data_element_type[ comp_buffer_len ] );
            std::copy( &comp_buffer[ 0 ],
                       &comp_buffer[ ncdata ],
                       reinterpret_cast< out_type* >( Dest.get( ) ) );
            DestByteLength = comp_buffer_len;
        }
        catch ( const FrUncompressable& Error )
        {
            // Return uncompressed buffer
        }
    }

#if 0
  template< typename InType, typename OutType > void
  compress( const data_element_type* Source,
	    INT_8U SourceLength,
	    boost::shared_array< data_element_type >& Dest,
	    INT_8U& DestByteLength )
  {
    using namespace FrameCPP;

    try
    {
      typedef OutType out_type;
      typedef InType in_type;

      DestByteLength = 0;
      Dest.reset( (data_element_type*)NULL );

      boost::shared_array< out_type >	comp_buffer( new out_type[ SourceLength ] );
      INT_8U			comp_buffer_len( SourceLength
						 * sizeof( out_type ) );

      zcomp( comp_buffer.get( ),
	     &comp_buffer_len,
	     reinterpret_cast< const in_type* >( Source ),
	     SourceLength );

      INT_4U ncdata( comp_buffer_len / sizeof( out_type ) );
      Dest.reset( new data_element_type[ comp_buffer_len ] );
      std::copy( &comp_buffer[ 0 ], &comp_buffer[ ncdata ],
		 reinterpret_cast< out_type* >( Dest.get( ) ) );
      DestByteLength = comp_buffer_len;
    }
    catch( const FrUncompressable& Error )
    {
      // Return uncompressed buffer
    }
  }
#endif /* 0 */

} // namespace

namespace FrameCPP
{
    namespace Compression
    {
        //-------------------------------------------------------------------
        /// The routines defined in this namespace are specific to the
        /// ZeroSuppress compression algorithm.
        /// Routines are defined for both compression and decompression
        /// of data buffers.
        //-------------------------------------------------------------------
        namespace ZeroSuppress
        {
            template < typename InType, typename OutType >
            void
            diff_compress( const data_element_type* Source,
                           INT_8U                   SourceLength,
                           INT_4U                   DataType,
                           boost::shared_array< data_element_type >& Dest,
                           INT_8U& DestByteLength )
            {
                try
                {
                    typedef OutType out_type;
                    typedef InType  in_type;

                    DestByteLength = 0;
                    Dest.reset( (data_element_type*)NULL );

                    boost::shared_array< data_element_type > diff_buffer;
                    boost::shared_array< out_type >          comp_buffer(
                        new out_type[ SourceLength + 4 ] );
                    INT_8U comp_buffer_len( ( SourceLength + 4 ) *
                                            sizeof( out_type ) );

                    Differential::Encode(
                        Source, SourceLength, DataType, diff_buffer );
                    int ret = zcomp(
                        comp_buffer.get( ),
                        &comp_buffer_len,
                        reinterpret_cast< in_type* >( diff_buffer.get( ) ),
                        SourceLength );

                    if ( ret == 0 )
                    {
                        INT_4U ncdata( comp_buffer_len / sizeof( out_type ) );
                        Dest.reset( new data_element_type[ comp_buffer_len ] );
                        std::copy(
                            comp_buffer.get( ),
                            comp_buffer.get( ) + ncdata,
                            reinterpret_cast< out_type* >( Dest.get( ) ) );
                        DestByteLength = comp_buffer_len;
                    }
                }
                catch ( const FrUncompressable& Error )
                {
                    // Return uncompressed buffer
                }
            }

#if 0
      template< typename InType, typename OutType > void
      diff_compress( const data_element_type* Source,
		     INT_8U SourceLength,
		     INT_4U DataType,
		     boost::shared_array< data_element_type >& Dest,
		     INT_8U& DestByteLength )
      {
	try
	{
	  typedef OutType out_type;
	  typedef InType in_type;

	  DestByteLength = 0;
	  Dest.reset( (data_element_type*)NULL );

	  boost::shared_array< data_element_type >	diff_buffer;
	  boost::shared_array< out_type >		comp_buffer( new out_type[ SourceLength + 4 ] );
	  INT_8U				comp_buffer_len( ( SourceLength + 4 )
								 * sizeof( out_type ) );

	  Differential::Encode( Source, SourceLength, DataType, diff_buffer );
	  int ret = zcomp( comp_buffer.get( ), &comp_buffer_len,
			   reinterpret_cast< in_type* >( diff_buffer.get( ) ),
			   SourceLength );

	  if ( ret == 0 )
	  {
	    INT_4U ncdata( comp_buffer_len / sizeof( out_type ) );
	    Dest.reset( new data_element_type[ comp_buffer_len ] );
	    std::copy( comp_buffer.get( ), comp_buffer.get( ) + ncdata,
		       reinterpret_cast< out_type* >( Dest.get( ) ) );
	    DestByteLength = comp_buffer_len;
	  }
	}
	catch( const FrUncompressable& Error )
	{
	  // Return uncompressed buffer
	}
      }
#endif /* 0 */

            template < typename InType, typename OutType >
            void
            expand( const data_element_type*                  DataIn,
                    INT_8U                                    DataInBytes,
                    bool                                      ByteSwap,
                    boost::shared_array< data_element_type >& DataOut,
                    INT_8U                                    NDataOut,
                    INT_8U&                                   NBytesOut )
            {
                typedef OutType out_type;
                typedef InType  in_type;

                const INT_8U data_out_byte_len =
                    ( NDataOut * sizeof( out_type ) );
                boost::shared_array< data_element_type > data_in_swap;

                const data_element_type* di( DataIn );

                if ( ByteSwap )
                {
                    //-------------------------------------------------------------
                    // Need a copy of the input buffer to put the bytes in the
                    // correct order
                    //-------------------------------------------------------------
                    data_in_swap.reset( new data_element_type[ DataInBytes ] );
                    std::copy(
                        DataIn, DataIn + DataInBytes, data_in_swap.get( ) );
                    reverse< sizeof( in_type ) >(
                        data_in_swap.get( ), DataInBytes / sizeof( in_type ) );
                    di = data_in_swap.get( );
                }
                boost::shared_array< data_element_type > data_out(
                    new data_element_type[ data_out_byte_len ] );

                // Issue #381: pass the compressed source length so
                // zexpand can substitute zero for out-of-range reads
                // instead of walking off the buffer and crashing on an
                // unmapped page.
                zexpand( reinterpret_cast< out_type* >( data_out.get( ) ),
                         reinterpret_cast< const in_type* >( di ),
                         NDataOut,
                         DataInBytes / sizeof( in_type ) );

                DataOut.swap( data_out );
                NBytesOut = data_out_byte_len;
            }

#if 0
      template< typename InType, typename OutType > void
      expand( const data_element_type* DataIn, INT_8U DataInBytes,
	      bool ByteSwap,
	      boost::shared_array< data_element_type >& DataOut, ndata_type NDataOut,
	      INT_8U& NBytesOut )
      {
	typedef OutType out_type;
	typedef InType in_type;
	
	const INT_8U		data_out_byte_len=
	  ( NDataOut * sizeof( out_type ) );
	boost::shared_array< data_element_type >	data_in_swap;

	const data_element_type*		di( DataIn );

	if ( ByteSwap )
	{
	  //-------------------------------------------------------------
	  // Need a copy of the input buffer to put the bytes in the
	  // correct order
	  //-------------------------------------------------------------
	  data_in_swap.reset( new data_element_type[ DataInBytes ] );
	  std::copy( DataIn, DataIn + DataInBytes,
		     data_in_swap.get( ) );
	  reverse< sizeof( in_type ) >( data_in_swap.get( ),
					DataInBytes / sizeof( in_type ) );
	  di = data_in_swap.get( );
	}
	boost::shared_array< data_element_type >	data_out( new data_element_type[ data_out_byte_len ] );

	zexpand( reinterpret_cast< out_type* >( data_out.get( ) ),
		reinterpret_cast< const in_type* >( di ),
		NDataOut );

	DataOut = data_out;
	NBytesOut = data_out_byte_len;
      }
#endif /* 0 */

            template <>
            void
            Compress< 2 >( const data_element_type* Source,
                           INT_8U                   SourceLength,
                           boost::shared_array< data_element_type >& Dest,
                           INT_8U& DestByteLength )
            {
                typedef INT_2U out_type;
                typedef INT_2S in_type;

                compress< in_type, out_type >(
                    Source, SourceLength, Dest, DestByteLength );
            }

#if 0
      template<> void
      Compress< 2 >( const data_element_type* Source,
		     INT_8U SourceLength,
		     boost::shared_array< data_element_type >& Dest,
		     INT_8U& DestByteLength )
      {
	typedef INT_2U out_type;
	typedef INT_2S in_type;

	compress< in_type, out_type>( Source, SourceLength,
				      Dest, DestByteLength );
      }
#endif /* 0 */

            template <>
            void
            Compress< 4 >( const data_element_type* Source,
                           INT_8U                   SourceLength,
                           boost::shared_array< data_element_type >& Dest,
                           INT_8U& DestByteLength )
            {
                typedef INT_4U out_type;
                typedef INT_4S in_type;

                compress< in_type, out_type >(
                    Source, SourceLength, Dest, DestByteLength );
            }

#if 0
      template<> void
      Compress< 4 >( const data_element_type* Source,
		     INT_8U SourceLength,
		     boost::shared_array< data_element_type >& Dest,
		     INT_8U& DestByteLength )
      {
	typedef INT_4U out_type;
	typedef INT_4S in_type;

	compress< in_type, out_type>( Source, SourceLength,
				      Dest, DestByteLength );
      }
#endif /* 0 */

            template <>
            void
            Expand< 2 >( const data_element_type*                  DataIn,
                         INT_8U                                    DataInBytes,
                         bool                                      ByteSwap,
                         boost::shared_array< data_element_type >& DataOut,
                         ndata_type                                NDataOut,
                         INT_8U&                                   NBytesOut )
            {
                typedef INT_2S out_type;
                typedef INT_2U in_type;

                expand< in_type, out_type >( DataIn,
                                             DataInBytes,
                                             ByteSwap,
                                             DataOut,
                                             NDataOut,
                                             NBytesOut );
            } // function template void Expand<2>()

#if 0
      template< > void
      Expand< 2 >( const data_element_type* DataIn, INT_8U DataInBytes,
		   bool ByteSwap,
		   boost::shared_array< data_element_type >& DataOut, ndata_type NDataOut,
		   INT_8U& NBytesOut )
      {
	typedef INT_2S out_type;
	typedef INT_2U in_type;
	
	expand< in_type, out_type >( DataIn, DataInBytes, ByteSwap,
				     DataOut, NDataOut, NBytesOut );
      } // function template void Expand<2>()
#endif /* 0 */

            template <>
            void
            Expand< 4 >( const data_element_type*                  DataIn,
                         INT_8U                                    DataInBytes,
                         bool                                      ByteSwap,
                         boost::shared_array< data_element_type >& DataOut,
                         ndata_type                                NDataOut,
                         INT_8U&                                   NBytesOut )
            {
                typedef INT_4S out_type;
                typedef INT_4U in_type;

                expand< in_type, out_type >( DataIn,
                                             DataInBytes,
                                             ByteSwap,
                                             DataOut,
                                             NDataOut,
                                             NBytesOut );
            } // function template void Expand<4>()

#if 0
      template< > void
      Expand< 4 >( const data_element_type* DataIn, INT_8U DataInBytes,
		   bool ByteSwap,
		   boost::shared_array< data_element_type >& DataOut, ndata_type NDataOut,
		   INT_8U& NBytesOut )
      {
	typedef INT_4S out_type;
	typedef INT_4U in_type;
	
	expand< in_type, out_type >( DataIn, DataInBytes, ByteSwap,
				     DataOut, NDataOut, NBytesOut );
      } // function template void Expand<4>()
#endif /* 0 */

            template <>
            void
            Expand< 8 >( const data_element_type*                  DataIn,
                         INT_8U                                    DataInBytes,
                         bool                                      ByteSwap,
                         boost::shared_array< data_element_type >& DataOut,
                         ndata_type                                NDataOut,
                         INT_8U&                                   NBytesOut )
            {
                typedef INT_8S out_type;
                typedef INT_8U in_type;

                expand< in_type, out_type >( DataIn,
                                             DataInBytes,
                                             ByteSwap,
                                             DataOut,
                                             NDataOut,
                                             NBytesOut );
            } // function template void Expand<8>()

#if 0
      template< > void
      Expand< 8 >( const data_element_type* DataIn, INT_8U DataInBytes,
		   bool ByteSwap,
		   boost::shared_array< data_element_type >& DataOut, ndata_type NDataOut,
		   INT_8U& NBytesOut )
      {
	typedef INT_8S out_type;
	typedef INT_8U in_type;
	
	expand< in_type, out_type >( DataIn, DataInBytes, ByteSwap,
				     DataOut, NDataOut, NBytesOut );
      } // function template void Expand<8>()
#endif /* 0 */

            template <>
            void
            DiffCompress< 2 >( const data_element_type* Source,
                               INT_8U                   SourceLength,
                               INT_4U                   DataType,
                               boost::shared_array< data_element_type >& Dest,
                               INT_8U& DestByteLength )
            {
                typedef INT_2U out_type;
                typedef INT_2S in_type;

                diff_compress< in_type, out_type >(
                    Source, SourceLength, DataType, Dest, DestByteLength );
            }

            template <>
            void
            DiffCompress< 4 >( const data_element_type* Source,
                               INT_8U                   SourceLength,
                               INT_4U                   DataType,
                               boost::shared_array< data_element_type >& Dest,
                               INT_8U& DestByteLength )
            {
                typedef INT_4U out_type;
                typedef INT_4S in_type;

                diff_compress< in_type, out_type >(
                    Source, SourceLength, DataType, Dest, DestByteLength );
            }

            template <>
            void
            DiffCompress< 8 >( const data_element_type* Source,
                               INT_8U                   SourceLength,
                               INT_4U                   DataType,
                               boost::shared_array< data_element_type >& Dest,
                               INT_8U& DestByteLength )
            {
                typedef INT_8U out_type;
                typedef INT_8S in_type;

                diff_compress< in_type, out_type >(
                    Source, SourceLength, DataType, Dest, DestByteLength );
            }

#if 0
      template<> void
      DiffCompress< 2 >( const data_element_type* Source,
			 INT_8U SourceLength,
			 INT_4U DataType,
			 boost::shared_array< data_element_type >& Dest,
			 INT_8U& DestByteLength )
      {
	typedef INT_2U out_type;
	typedef INT_2S in_type;

	diff_compress< in_type, out_type >( Source, SourceLength, DataType,
					    Dest, DestByteLength );
      }

      template<> void
      DiffCompress< 4 >( const data_element_type* Source,
			 INT_8U SourceLength,
			 INT_4U DataType,
			 boost::shared_array< data_element_type >& Dest,
			 INT_8U& DestByteLength )
      {
	typedef INT_4U out_type;
	typedef INT_4S in_type;

	diff_compress< in_type, out_type >( Source, SourceLength, DataType,
					    Dest, DestByteLength );
      }

      template<> void
      DiffCompress< 8 >( const data_element_type* Source,
			 INT_8U SourceLength,
			 INT_4U DataType,
			 boost::shared_array< data_element_type >& Dest,
			 INT_8U& DestByteLength )
      {
	typedef INT_8U out_type;
	typedef INT_8S in_type;

	diff_compress< in_type, out_type >( Source, SourceLength, DataType,
					    Dest, DestByteLength );
      }
#endif /* 0 */

#if 0
      template < int Size > void
      DiffExpand( const data_element_type* DataIn,
		  nbytes_type DataInBytes,
		  bool ByteSwap,
		  data_type_type DataType,
		  boost::shared_array< data_element_type >& DataOut,
		  ndata_type NDataOut,
		  nbytes_type& NBytesOut )
      {
	Expand< Size >( DataIn, DataInBytes, ByteSwap,
			DataOut, NDataOut, NBytesOut );

	if ( NBytesOut )
	{
	  Differential::Decode( DataOut.get( ), NDataOut,
				(data_types_type)DataType );
	}
      }
#endif /* 0 */

            template < int Size >
            void
            DiffExpand( const data_element_type* DataIn,
                        nbytes_type              DataInBytes,
                        bool                     ByteSwap,
                        data_type_type           DataType,
                        data_type&               DataOut,
                        ndata_type               NDataOut,
                        nbytes_type&             NBytesOut )
            {
                Expand< Size >( DataIn,
                                DataInBytes,
                                ByteSwap,
                                DataOut,
                                NDataOut,
                                NBytesOut );

                if ( NBytesOut )
                {
                    //---------------------------------------------------------------
                    // Complex types (FR_VECT_8C, FR_VECT_16C) need 2× buffer space
                    // because Differential::Decode multiplies NData by 2
                    //---------------------------------------------------------------
                    if ( DataType == FR_VECT_8C || DataType == FR_VECT_16C )
                    {
                        const nbytes_type new_byte_len = NBytesOut * 2;
                        boost::shared_array< data_element_type > new_buffer(
                            new data_element_type[ new_byte_len ] );
                        std::copy( DataOut.get( ),
                                   DataOut.get( ) + NBytesOut,
                                   new_buffer.get( ) );
                        DataOut.swap( new_buffer );
                        NBytesOut = new_byte_len;
                    }

                    Differential::Decode(
                        DataOut.get( ), NDataOut, (data_types_type)DataType );
                }
            }

#define INSTANTIATE( LM_SIZE )                                                 \
    template void DiffExpand< LM_SIZE >(                                       \
        const data_element_type*                  DataIn,                      \
        nbytes_type                               DataInBytes,                 \
        bool                                      ByteSwap,                    \
        data_type_type                            DataType,                    \
        boost::shared_array< data_element_type >& DataOut,                     \
        ndata_type                                NDataOut,                    \
        nbytes_type&                              NBytesOut )

            INSTANTIATE( 2 );
            INSTANTIATE( 4 );
            INSTANTIATE( 8 );

#undef INSTANTIATE

        } // namespace ZeroSuppress

    } // namespace Compression

} // namespace FrameCPP

namespace
{
    using FrameCPP::FrUncompressable;
    using FrameCPP::FrZCompIRangeError;
    using FrameCPP::FrZCompRangeError;

    template < typename wMaxType >
    const wMaxType*
    wMaxTable( )
    {
        static wMaxType table[ sizeof( wMaxType ) * 8 + 1 ];
        static bool     initialized = false;

        if ( !initialized )
        {

            table[ 0 ] = 0;
            wMaxType m1( 0x0 ), m2( 0x1 ), m3( 0x3 ), m4( 0x7 );

            INT_2U x = 0;
            for ( INT_2U c = 0; c < ( sizeof( wMaxType ) * 2 ); ++c )
            {
                table[ ++x ] = m1;
                table[ ++x ] = m2;
                table[ ++x ] = m3;
                table[ ++x ] = m4;
                m1 = ( m1 << 4 ) | 0xF;
                m2 = ( m2 << 4 ) | 0xF;
                m3 = ( m3 << 4 ) | 0xF;
                m4 = ( m4 << 4 ) | 0xF;
            }
            initialized = true;
        }

        return table;
    }

    template < typename maskType >
    const maskType*
    maskTable( )
    {
        static maskType table[ sizeof( maskType ) * 8 + 1 ];
        static bool     initialized = false;

        if ( !initialized )
        {
            maskType m1( 0x0 ), m2( 0x1 ), m3( 0x3 ), m4( 0x7 );

            INT_2U x = 0;
            for ( INT_2U c = 0; c < ( sizeof( maskType ) * 2 ); ++c )
            {
                table[ x++ ] = m1;
                table[ x++ ] = m2;
                table[ x++ ] = m3;
                table[ x++ ] = m4;
                m1 = ( m1 << 4 ) | 0xF;
                m2 = ( m2 << 4 ) | 0xF;
                m3 = ( m3 << 4 ) | 0xF;
                m4 = ( m4 << 4 ) | 0xF;
            }
            m1 = ( m1 << 4 ) | 0xF;
            table[ x ] = m1;
            initialized = true;
        }

        return table;
    }

    template < typename TS, typename TU >
    int
    zcomp(
        TU* out, INT_8U* compL, const TS* data, ndata_type nData, INT_8U bSize )
    {
        typedef TU out_type;
        typedef TS in_type;

        out_type nBits, uData, limit;
        out_type pOut;
        in_type  max;

        INT_8U iIn, iOut, i, maxOut;

        static const in_type* wMax( wMaxTable< in_type >( ) );

        maxOut = *compL / sizeof( out_type );

        /*------------------------- store the bloc size -----*/
        out[ 0 ] = bSize;
        iOut = 0;
        pOut = 16;
        /*--------------------------------- store the data --*/
        iIn = 0;
        while ( iIn < nData )
        {
            /*------------- tune the size of the last bloc -----*/
            if ( iIn + bSize > nData )
            {
                bSize = nData - iIn;
            }

            /*--------- get the maximum amplitude --------------*/

            max = 0;
            for ( i = 0; i < bSize; ++i )
            {
                if ( data[ iIn + i ] == LowerBound< in_type >( ) )
                {
                    throw FrZCompRangeError( LowerBound< in_type >( ) );
                }
                if ( data[ iIn + i ] > 0 )
                {
                    max = max | data[ iIn + i ];
                }
                else
                {
                    max = max | -data[ iIn + i ];
                }
            }

            /*--- determine the number of bits needed -( (2*(max-1)  <
             * 2**nBits)) */

            nBits = calcNBits( max );

            /*---- encode the data size - we store nBits - 1 in 4 bits---*/

            if ( pOut != StorageBits< out_type >( ) )
            {
                out[ iOut ] = out[ iOut ] | ( ( nBits - 1 ) << pOut );
            }
            if ( pOut > SpaceBits< out_type >( ) )
            {
                iOut++;
                if ( iOut >= maxOut )
                {
#if LM_INFO
                    std::cerr << "INFO: uncompressable: "
                              << " iOut: " << iOut << " maxOut: " << maxOut
                              << std::endl;
#endif
                    throw FrUncompressable( "zcomp: iOut >= maxOut" );
                }
                pOut = pOut - StorageBits< in_type >( );
                out[ iOut ] = ( nBits - 1 ) >> -pOut;
            }
            pOut = pOut + SignificantBits< in_type >( );

            /*----------------------- encode the data itself ------*/
            if ( nBits > 1 )
            {
                limit = StorageBits< in_type >( ) - nBits;
                for ( i = 0; i < bSize; i++ )
                {
                    uData = out_type( data[ iIn + i ] + wMax[ nBits ] );

                    if ( pOut != StorageBits< out_type >( ) )
                    {
                        out[ iOut ] = out[ iOut ] | ( uData << pOut );
                    }
                    if ( pOut > limit )
                    {
                        iOut++;
                        if ( iOut >= maxOut )
                        {
#if LM_INFO
                            std::cerr << "INFO: uncompressable: "
                                      << " iOut: " << iOut
                                      << " maxOut: " << maxOut << std::endl;
#endif
                            throw FrUncompressable( "zcomp: iOut >= maxOut" );
                        }
                        pOut = pOut - StorageBits< in_type >( );
                        out[ iOut ] = uData >> -pOut;
                    }
                    pOut = pOut + nBits;
                }
            }
            /*----------------------------- increase pointer -------*/

            iIn = iIn + bSize;
        }

        *compL = sizeof( out_type ) * ( iOut + 1 );

        return ( 0 );
    }

    template < typename TS, typename TU >
    void
    zexpand( TS* out, const TU* data, ndata_type nData, ndata_type nDataIn )
    {
        typedef TS out_type;
        typedef TU in_type;

        in_type nBits, pIn, uData;

        static const in_type* wMax( wMaxTable< in_type >( ) );
        static const in_type* mask( maskTable< in_type >( ) );

        INT_4S i, bSize;
        INT_4U iOut, iIn;

        /*---------------- retrieve the bloc size -----*/
        // Issue #381: reject an empty input outright — reading data[0]
        // without a buffer is unrecoverable, unlike the algorithm's
        // one-word overread pattern which is handled by safe_load below.
        if ( nDataIn < 1 )
        {
            throw FrameCPP::FrUncompressable(
                "zexpand: empty input buffer" );
        }
        bSize = data[ 0 ] & 0xffff;

        iIn = 0;
        pIn = 16;

        // Issue #381: replace every `data[iIn]` load with a saturating
        // variant that returns 0 when the algorithm's last-iteration
        // one-word overread walks past the compressed buffer. Prior to
        // this change the raw indexed read walked into unmapped memory
        // on freshly-allocated buffers whose next page happened to be a
        // guard page, producing a silent SIGSEGV (x86_64) / SIGBUS
        // (arm64 macOS) with no diagnostic. The outer do/while loop
        // discards the last uData when `iOut >= nData`, so substituting
        // zero for the never-observed word preserves the original
        // successful decompression while eliminating the crash. The
        // final safety net for genuinely corrupt / truncated streams is
        // provided by the outer Verify layer catching any exception
        // downstream of a garbage decoded value.
        auto safe_load = [ data, nDataIn ]( INT_4U idx ) -> in_type
        {
            return ( idx < nDataIn ) ? data[ idx ] : in_type( 0 );
        };

        /*------------------ retrieve the data---------*/

        iOut = 0;
        do
        {
            /*-------- extract nBits -(we check if data is in 1 or 2 words)
             * ------*/

            if ( pIn <= SpaceBits< out_type >( ) )
            {
                uData = safe_load( iIn ) >> pIn;
                pIn = pIn + SignificantBits< out_type >( );
            }
            else
            {
                uData = ( safe_load( iIn ) >> pIn ) &
                    mask[ StorageBits< out_type >( ) - pIn ];
                iIn++;
                uData += safe_load( iIn ) << ( StorageBits< out_type >( ) - pIn );
                pIn = pIn - SpaceBits< out_type >( );
            }

            nBits = 1 + ( SignificantBitsMask< in_type >( ) & uData );
            if ( nBits == 1 )
            {
                nBits = 0;
            }

            /*----------------------extract data ----------*/
            for ( i = 0; i < bSize; ++i )
            {
                if ( iOut >= nData )
                {
                    break;
                }

                if ( pIn + nBits <= StorageBits< in_type >( ) )
                {
                    uData = safe_load( iIn ) >> pIn;
                    pIn = pIn + nBits;
                }
                else
                {
                    uData = ( safe_load( iIn ) >> pIn ) &
                        mask[ StorageBits< in_type >( ) - pIn ];
                    iIn++;
                    uData += safe_load( iIn ) << ( StorageBits< in_type >( ) - pIn );
                    pIn = pIn + nBits - StorageBits< in_type >( );
                }

                out[ iOut ] = ( mask[ nBits ] & uData ) - wMax[ nBits ];
                iOut++;
            }
        } while ( iOut < nData );
    }
} // namespace

static const char*
init_nBitTable256( )
{
    static char table[ 256 ];

    table[ 0 ] = 1;
    table[ 1 ] = 2;

    for ( int i = 2; i < 256; i++ )
    {
        table[ i ] = 1 + table[ i / 2 ];
    }
    return table;
}
