//
// 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 <strings.h>

} // extern "C"

#include <algorithm>
#include <iostream>
#include <iterator>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <valarray>

#include <boost/shared_array.hpp>

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

#include "framecpp/Common/Compression.hh"
#include "framecpp/Common/CompressionDifferential.hh"
#include "framecpp/Common/CompressionGZip.hh"
#include "framecpp/Common/CompressionZeroSuppress.hh"
#include "framecpp/Common/CompressionZstd.hh"

using namespace FrameCPP;

#define LM_INFO 0

#if LM_DEBUG
#define AT( ) std::cerr << "INFO: " << __FILE__ << " " << __LINE__ << std::endl;
#else
#define AT( )
#endif
namespace
{
    typedef LDASTools::AL::unordered_map< INT_4U, INT_4U >
        data_type_size_mapping;

    using namespace FrameCPP::Compression;

    data_type_size_mapping&
    size_map_init( )
    {
        static data_type_size_mapping m;

#define MAP( A, B ) m[ A ] = sizeof( B )

        MAP( FR_VECT_C, CHAR );
        MAP( FR_VECT_2S, INT_2S );
        MAP( FR_VECT_8R, REAL_8 );
        MAP( FR_VECT_4R, REAL_4 );
        MAP( FR_VECT_4S, INT_4S );
        MAP( FR_VECT_8S, INT_8S );
        MAP( FR_VECT_8C, COMPLEX_8 );
        MAP( FR_VECT_16C, COMPLEX_16 );
        MAP( FR_VECT_2U, INT_2U );
        MAP( FR_VECT_4U, INT_4U );
        MAP( FR_VECT_8U, INT_8U );
        MAP( FR_VECT_1U, CHAR_U );

#undef MAP

        return m;
    }

    static const data_type_size_mapping& size_map = size_map_init( );

    template < typename _Map >
    inline typename _Map::key_type
    reverse_lookup( typename _Map::mapped_type Key, const _Map& Map )
    {
        for ( typename _Map::const_iterator cur = Map.begin( ),
                                            last = Map.end( );
              cur != last;
              ++cur )
        {
            if ( cur->second == Key )
            {
                return cur->first;
            }
        }

        std::ostringstream msg;

        msg << "Cannot reverse map: " << Key << "(0x" << std::hex << Key << ")";
        throw std::range_error( msg.str( ) );
    }

    template < typename _Map >
    inline typename _Map::mapped_type
    reverse_lookup( typename _Map::key_type Key, const _Map& Map )
    {

        typename _Map::const_iterator cm = Map.find( Key );

        if ( cm != Map.end( ) )
        {
            return cm->second;
        }
        std::ostringstream msg;

        msg << "Cannot reverse map: " << Key << "(0x" << std::hex << Key << ")";
        throw std::range_error( msg.str( ) );
    }

    template < typename T >
    INT_4U data_type_id( );

    template <>
    inline INT_4U
    data_type_id< INT_4S >( )
    {
        return FR_VECT_4S;
    }

    template <>
    inline INT_4U
    data_type_id< INT_4U >( )
    {
        return FR_VECT_4U;
    }

    template <>
    inline INT_4U
    data_type_id< INT_8S >( )
    {
        return FR_VECT_8S;
    }

    template <>
    inline INT_4U
    data_type_id< INT_8U >( )
    {
        return FR_VECT_8U;
    }

    INT_2U
    data_size( INT_4U Type )
    {
        data_type_size_mapping::const_iterator cur = size_map.find( Type );

        if ( cur == size_map.end( ) )
        {
            std::ostringstream msg;

            msg << "unknown size for data type: " << Type;

            throw std::range_error( msg.str( ) );
        }
        return cur->second;
    }

    void swap_data( INT_8U                                 NData,
                    FrameCPP::Compression::data_types_type Type,
                    CHAR_U*                                Buffer );

    INT_4U
    DiffGZip( const CHAR_U*                  Source,
              const INT_8U                   SourceLength,
              const INT_8U                   SourceByteLength,
              INT_4U                         DataType,
              INT_4U                         Level,
              const compress_type_mapping&   CompressionMapping,
              boost::shared_array< CHAR_U >& Dest,
              INT_8U&                        DestByteLength )
    {
        compress_type t( MODE_RAW );

        boost::shared_array< CHAR_U > diff_buffer;

        switch ( DataType )
        {
        case FR_VECT_C:
        case FR_VECT_1U:
        case FR_VECT_2S:
        case FR_VECT_2U:
        case FR_VECT_4S:
        case FR_VECT_4U:
            Differential::Encode( Source, SourceLength, DataType, diff_buffer );
            GZip::Compress( diff_buffer.get( ),
                            SourceByteLength,
                            Level,
                            Dest,
                            DestByteLength );
            // reset_data_pointer( Data, DataByteLength );
            t = MODE_DIFF_GZIP;
            break;
        default:
            GZip::Compress(
                Source, SourceByteLength, Level, Dest, DestByteLength );
            t = MODE_GZIP;
            break;
        }
        return (INT_4U)reverse_lookup( t, CompressionMapping );
    }

    template < typename T >
    INT_4U compressZeroSuppressComplexType( );

    template < typename T >
    void
    compressZeroSuppressComplex( const CHAR_U* Source,
                                 const INT_8U  SourceLength,
                                 const INT_8U  SourceByteLength,
                                 INT_4U        DataType,
                                 boost::shared_array< CHAR_U >& DataOut,
                                 INT_8U&                        DataOutLen )
    {
        //-------------------------------------------------------------------
        // Allocate a buffer of the appropriate size
        //-------------------------------------------------------------------
        typedef T src_type;

        const size_t src_len = ( SourceLength * 2 );

        boost::shared_array< CHAR_U > src( new CHAR_U[ SourceByteLength ] );

        const src_type* s = reinterpret_cast< const src_type* >( Source );
        src_type*       rdst = reinterpret_cast< src_type* >( src.get( ) );
        src_type*       idst = &rdst[ SourceLength ];

        size_t c = SourceLength;
        //-------------------------------------------------------------------
        // Re-Order the bytes so they are actually have complex pairing.
        //-------------------------------------------------------------------
        while ( c-- )
        {
            //-----------------------------------------------------------------
            // Copy real
            //-----------------------------------------------------------------
            *rdst = *s;
            s++;
            //-----------------------------------------------------------------
            // Copy imaginary
            //-----------------------------------------------------------------
            *idst = *s;
            s++;
            //-----------------------------------------------------------------
            // Advance
            //-----------------------------------------------------------------
            rdst++;
            idst++;
        }
        ZeroSuppress::DiffCompress< sizeof( T ) >(
            reinterpret_cast< const CHAR_U* >( &( src[ 0 ] ) ),
            src_len,
            data_type_id< src_type >( ),
            DataOut,
            DataOutLen );
    }

    template < typename T >
    void
    expandZeroSuppressComplex( const CHAR_U*                  Source,
                               const INT_8U                   SourceLength,
                               const INT_8U                   SourceByteLength,
                               bool                           NativeOrder,
                               INT_4U                         DataType,
                               boost::shared_array< CHAR_U >& DataOut,
                               INT_8U&                        DataOutLen )
    {
        //-------------------------------------------------------------------
        // Allocate a buffer of the appropriate size
        //-------------------------------------------------------------------
        typedef T    src_type;
        const size_t src_len = ( SourceLength * 2 );

        //-------------------------------------------------------------------
        // Expand the buffer
        //-------------------------------------------------------------------
        ZeroSuppress::DiffExpand< sizeof( src_type ) >(
            Source,
            SourceByteLength,
            !NativeOrder,
            data_type_id< src_type >( ),
            DataOut,
            src_len,
            DataOutLen );
        if ( DataOutLen )
        {
            //-----------------------------------------------------------------
            // Create a temporary buffer
            //-----------------------------------------------------------------
            boost::shared_array< CHAR_U > tout( new CHAR_U[ DataOutLen ] );
            src_type* rsrc = reinterpret_cast< src_type* >( DataOut.get( ) );
            src_type* isrc = &( rsrc[ SourceLength ] );
            src_type* dst = reinterpret_cast< src_type* >( tout.get( ) );
            size_t    c = SourceLength;

            //-----------------------------------------------------------------
            // Re-Order the bytes so they are actually have complex pairing.
            //-----------------------------------------------------------------
            while ( c-- )
            {
                //---------------------------------------------------------------
                // Copy real
                //---------------------------------------------------------------
                *dst = *rsrc;
                dst++;
                //---------------------------------------------------------------
                // Copy imaginary
                //---------------------------------------------------------------
                *dst = *isrc;
                dst++;
                //---------------------------------------------------------------
                // Advance
                //---------------------------------------------------------------
                rsrc++;
                isrc++;
            }

            //-----------------------------------------------------------------
            // Reset DataOut
            //-----------------------------------------------------------------
            DataOut.swap( tout );
        } // if ( DataOutLen )
    } // expandZero
} // namespace

namespace FrameCPP
{
    namespace Compression
    {
        //-------------------------------------------------------------------
        /// This will take on input buffer and generate a compressed version
        /// of the input buffer.
        //-------------------------------------------------------------------
        void
        Compress(
            INT_4U&                              CompressionMode,
            INT_4U                               Level,
            const compress_type_mapping&         CompressionMapping,
            const compress_type_reverse_mapping& CompressionReverseMapping,
            INT_4U                               DataType,
            const data_type_mapping&             DataTypeMapping,
            const CHAR_U*                        Source,
            const INT_8U                         SourceLength,
            const INT_8U                         SourceByteLength,
            boost::shared_array< CHAR_U >&       Dest,
            INT_8U&                              DestByteLength )
        {
            //-----------------------------------------------------------------
            // Check for the very basic case of no compression
            //-----------------------------------------------------------------
            if ( CompressionMode == MODE_RAW )
            {
                return;
            }
            //-----------------------------------------------------------------
            // Setup for failure.
            //-----------------------------------------------------------------
            Dest.reset( (CHAR_U*)NULL );
            DestByteLength = 0;
            boost::shared_array< CHAR_U > tmp;
            boost::shared_array< CHAR_U > data_out;
            INT_8U                        data_out_len = 0; // bytes

            //-----------------------------------------------------------------
            //-----------------------------------------------------------------
            INT_4U compression_mode_return = CompressionMode;
            {
                //---------------------------------------------------------------
                // Translate the compression mode
                //---------------------------------------------------------------
                compress_type_mapping::const_iterator c =
                    CompressionMapping.find( CompressionMode );

                if ( c == CompressionMapping.end( ) )
                {
#ifdef FRAMECPP_DEBUG
                    for ( auto element : CompressionMapping )
                    {
                        std::cerr << "\t 0x" << std::hex << element.first
                                  << std::dec << " 0x" << std::hex
                                  << element.second << std::dec << std::endl;
                    }
#endif /* FRAMECPP_DEBUG */

                    std::ostringstream msg;

                    msg << "compression: unknown compression mode: "
                        << CompressionMode << "(0x" << std::hex
                        << CompressionMode << std::dec << ")"
                        << " (DEBUG: " << __LINE__
                        << " size: " << CompressionMapping.size( ) << ")";
                    throw std::range_error( msg.str( ) );
                }
                CompressionMode = c->second;
            }
            {
                //---------------------------------------------------------------
                // Translate the data type
                //---------------------------------------------------------------
                data_type_mapping::const_iterator d =
                    DataTypeMapping.find( DataType );

                if ( d == DataTypeMapping.end( ) )
                {
                    std::ostringstream msg;

                    msg << "compression: unknown data type: " << DataType;
                    throw std::range_error( msg.str( ) );
                }
                DataType = d->second;
            }

            //-----------------------------------------------------------------
            // Do the compression
            //-----------------------------------------------------------------
            AT( );
            bool gzip_others( false );

            switch ( CompressionMode )
            {
            case MODE_GZIP:
                //---------------------------------------------------------------
                // Compress using GZip algorithm
                //---------------------------------------------------------------
                AT( );
                GZip::Compress(
                    Source, SourceByteLength, Level, data_out, data_out_len );
                break;
            case MODE_DIFF:
                Differential::Encode(
                    Source, SourceLength, DataType, data_out );
                data_out_len = SourceByteLength;
                break;
            case MODE_DIFF_GZIP:
                AT( );
                compression_mode_return = DiffGZip( Source,
                                                    SourceLength,
                                                    SourceByteLength,
                                                    DataType,
                                                    Level,
                                                    CompressionMapping,
                                                    data_out,
                                                    data_out_len );
                break;
            case MODE_ZSTD:
                //---------------------------------------------------------------
                // Compress using Zstd algorithm
                //---------------------------------------------------------------
                AT( );
                Zstd::Compress(
                    Source, SourceByteLength, Level, data_out, data_out_len );
                break;
            case MODE_DIFF_ZSTD:
                //---------------------------------------------------------------
                // Differential + Zstd compression
                //---------------------------------------------------------------
                AT( );
                switch ( DataType )
                {
                case FR_VECT_C:
                case FR_VECT_1U:
                case FR_VECT_2S:
                case FR_VECT_2U:
                case FR_VECT_4S:
                case FR_VECT_4U:
                {
                    boost::shared_array< CHAR_U > diff_buffer;
                    Differential::Encode( Source, SourceLength, DataType, diff_buffer );
                    Zstd::Compress( diff_buffer.get( ),
                                   SourceByteLength,
                                   Level,
                                   data_out,
                                   data_out_len );
                    break;
                }
                default:
                    Zstd::Compress(
                        Source, SourceByteLength, Level, data_out, data_out_len );
                    break;
                }
                break;
                BEGIN_WIMPLICIT_FALLTHROUGH
            /* TODO: case MODE_ZERO_SUPPRESS_OTHERWISE_GZIP: */
            case MODE_ZERO_SUPPRESS_2_OTHERWISE_GZIP:
            case MODE_ZERO_SUPPRESS_2_4_OTHERWISE_GZIP:
            case MODE_ZERO_SUPPRESS_2_4_8_OTHERWISE_GZIP:
                gzip_others = true;
                // :TRICKY: Fall through
                END_WIMPLICIT_FALLTHROUGH
            case MODE_ZERO_SUPPRESS_WORD_8:
            case MODE_ZERO_SUPPRESS_WORD_4:
            case MODE_ZERO_SUPPRESS_INT_FLOAT:
            case MODE_ZERO_SUPPRESS_SHORT:
            case MODE_ZERO_SUPPRESS:
            {
                size_t word_size = 0;
                bool   compress_complex = false;

                switch ( CompressionMode )
                {
                case MODE_ZERO_SUPPRESS_2_4_8_OTHERWISE_GZIP:
                case MODE_ZERO_SUPPRESS_WORD_8:
                    compress_complex = true;
                    word_size = 8;
                    break;
                    BEGIN_WIMPLICIT_FALLTHROUGH
                case MODE_ZERO_SUPPRESS_WORD_4:
                    compress_complex = true;
                    // :TRICKY: Fall through
                    END_WIMPLICIT_FALLTHROUGH
                case MODE_ZERO_SUPPRESS_2_4_OTHERWISE_GZIP:
                case MODE_ZERO_SUPPRESS_INT_FLOAT:
                    word_size = 4;
                    break;
                case MODE_ZERO_SUPPRESS_2_OTHERWISE_GZIP:
                case MODE_ZERO_SUPPRESS_SHORT:
                    word_size = 2;
                    break;
                case MODE_ZERO_SUPPRESS:
                    switch ( DataType )
                    {
                    case FR_VECT_2S:
                    case FR_VECT_2U:
                        word_size = 2;
                        break;
                        BEGIN_WIMPLICIT_FALLTHROUGH
                    case FR_VECT_8C:
                        compress_complex = true;
                        // :TRICKY: Fall through
                        END_WIMPLICIT_FALLTHROUGH
                    case FR_VECT_4S:
                    case FR_VECT_4U:
                    case FR_VECT_4R:
                        word_size = 4;
                        break;
                        BEGIN_WIMPLICIT_FALLTHROUGH
                    case FR_VECT_16C:
                        compress_complex = true;
                        // :TRICKY: Fall through
                        END_WIMPLICIT_FALLTHROUGH
                    case FR_VECT_8S:
                    case FR_VECT_8U:
                    case FR_VECT_8R:
                        word_size = 8;
                        break;
                    }
                    break;
                default:
                    break;
                }

                boost::shared_array< CHAR_U > diff_buffer;
                //-------------------------------------------------------------
                /// \todo See about doing gzip compression when an exception
                ///       is thrown for zero suppress compressions.
                //-------------------------------------------------------------
                switch ( DataType )
                {
                case FR_VECT_2S:
                case FR_VECT_2U:
                    //-------------------------------------------------------------
                    // All modes support at least this
                    //-------------------------------------------------------------
                    ZeroSuppress::DiffCompress< 2 >( Source,
                                                     SourceLength,
                                                     DataType,
                                                     data_out,
                                                     data_out_len );
                    if ( data_out_len )
                    {
                        compression_mode_return = INT_4U(
                            reverse_lookup( MODE_ZERO_SUPPRESS_SHORT,
                                            CompressionReverseMapping ) );
                        gzip_others = false;
                    }
                    break; // FR_VECT_2S; FR_VECT_2U
                case FR_VECT_8C:
                    if ( compress_complex )
                    {
                        if ( word_size >= 4 )
                        {
                            compressZeroSuppressComplex< INT_4U >(
                                Source,
                                SourceLength,
                                SourceByteLength,
                                DataType,
                                data_out,
                                data_out_len );
                            if ( data_out_len )
                            {
                                compression_mode_return =
                                    INT_4U( reverse_lookup(
                                        MODE_ZERO_SUPPRESS_WORD_4,
                                        CompressionReverseMapping ) );
                                //-------------------------------------------------------
                                // Flag buffer as compressed.
                                //-------------------------------------------------------
                                gzip_others = false;
                            }
                        }
                    }
                    break;
                case FR_VECT_4R:
                case FR_VECT_4S:
                case FR_VECT_4U:
                    if ( word_size >= 4 )
                    {
                        //-----------------------------------------------------------
                        // Compress the buffer
                        //-----------------------------------------------------------
                        ZeroSuppress::DiffCompress< 4 >( Source,
                                                         SourceLength,
                                                         DataType,
                                                         data_out,
                                                         data_out_len );
                        if ( data_out_len )
                        {
                            compression_mode_return = INT_4U(
                                reverse_lookup( MODE_ZERO_SUPPRESS_INT_FLOAT,
                                                CompressionReverseMapping ) );
                            //---------------------------------------------------------
                            // Flag buffer as compressed.
                            //---------------------------------------------------------
                            gzip_others = false;
                        }
                    }
                    break; // FR_VECT_4R; FR_VECT_4S; FR_VECT_4U
                case FR_VECT_16C:
                    if ( compress_complex )
                    {
                        if ( word_size >= 8 )
                        {
                            compressZeroSuppressComplex< INT_8U >(
                                Source,
                                SourceLength,
                                SourceByteLength,
                                DataType,
                                data_out,
                                data_out_len );
                            if ( data_out_len )
                            {
                                compression_mode_return =
                                    INT_4U( reverse_lookup(
                                        MODE_ZERO_SUPPRESS_WORD_8,
                                        CompressionReverseMapping ) );
                                //-------------------------------------------------------
                                // Flag buffer as compressed.
                                //-------------------------------------------------------
                                gzip_others = false;
                            }
                        }
                    }
                    break;
                case FR_VECT_8R:
                case FR_VECT_8S:
                case FR_VECT_8U:
                    if ( word_size >= 8 )
                    {
                        //-----------------------------------------------------------
                        // Compress the buffer
                        //-----------------------------------------------------------
                        ZeroSuppress::DiffCompress< 8 >( Source,
                                                         SourceLength,
                                                         DataType,
                                                         data_out,
                                                         data_out_len );
                        if ( data_out_len )
                        {
                            compression_mode_return = INT_4U(
                                reverse_lookup( MODE_ZERO_SUPPRESS_WORD_8,
                                                CompressionReverseMapping ) );
                            //---------------------------------------------------------
                            // Flag buffer as compressed.
                            //---------------------------------------------------------
                            gzip_others = false;
                        }
                    }
                    break; // FR_VECT_8R; FR_VECT_8S; FR_VECT_8U
                }
                //---------------------------------------------------------------
                // Check if gzip should be tried
                //---------------------------------------------------------------
                if ( gzip_others )
                {
                    GZip::Compress( Source,
                                    SourceByteLength,
                                    Level,
                                    data_out,
                                    data_out_len );
                    if ( data_out_len )
                    {
                        compression_mode_return = (INT_4U)reverse_lookup(
                            MODE_GZIP, CompressionReverseMapping );
                    }
                }
            }
            break;
            default:
                //---------------------------------------------------------------
                // Have not been able to do compression
                //---------------------------------------------------------------
                {
                    std::ostringstream msg;

                    msg << "unsupported compression mode: " << CompressionMode
                        << "(0x" << std::hex << CompressionMode << ")";

                    throw std::range_error( msg.str( ) );
                }
                break;
            }

            //-----------------------------------------------------------------
            // prepare for return
            //-----------------------------------------------------------------
            if ( ( data_out_len == 0 ) || ( data_out_len >= SourceByteLength ) )
            {
                data_out.reset( (CHAR_U*)NULL );
                compression_mode_return = MODE_RAW;
            }
            CompressionMode = compression_mode_return;
            Dest = data_out;
            DestByteLength = data_out_len;
        }

        void
        Expand( const bool                     NativeOrder,
                const INT_4U                   CompressionMode,
                const compress_type_mapping&   CompressionMapping,
                const INT_4U                   DataType,
                const data_type_mapping&       DataTypeMapping,
                const CHAR_U*                  Source,
                const INT_8U                   SourceLength,
                const INT_8U                   SourceByteLength,
                boost::shared_array< CHAR_U >& Dest,
                INT_8U&                        DataByteLength )
        {
            INT_4U          cm( 0 );
            data_types_type dt( data_types_type( 0 ) );

            {
                //---------------------------------------------------------------
                // Translate the data type
                //---------------------------------------------------------------
                data_type_mapping::const_iterator d =
                    DataTypeMapping.find( DataType );

                if ( d == DataTypeMapping.end( ) )
                {
                    std::ostringstream msg;

                    msg << "unknown data type: " << DataType << "(0x"
                        << std::hex << DataType << ")"
                        << " (DEBUG: " << __LINE__ << ")";
                    throw std::range_error( msg.str( ) );
                }
                dt = d->second;
            }
            {
                //---------------------------------------------------------------
                // Translate the compression mode
                //---------------------------------------------------------------
                compress_type_mapping::const_iterator c =
                    CompressionMapping.find( CompressionMode );

                if ( c == CompressionMapping.end( ) )
                {
                    std::ostringstream msg;

                    msg << "unknown compression mode: " << CompressionMode
                        << "(0x" << std::hex << CompressionMode << ")"
                        << " (DEBUG: " << __LINE__ << ")";
                    throw std::range_error( msg.str( ) );
                }
                cm = c->second;
                if ( cm == MODE_ZERO_SUPPRESS )
                {
                    switch ( dt )
                    {
                    case FR_VECT_2S:
                    case FR_VECT_2U:
                        cm = MODE_ZERO_SUPPRESS_SHORT;
                        break;
                    case FR_VECT_4S:
                    case FR_VECT_4U:
                    case FR_VECT_4R:
                    case FR_VECT_8C:
                        cm = MODE_ZERO_SUPPRESS_WORD_4;
                        break;
                    case FR_VECT_8S:
                    case FR_VECT_8U:
                    case FR_VECT_8R:
                    case FR_VECT_16C:
                        cm = MODE_ZERO_SUPPRESS_WORD_8;
                        break;
                    case FR_VECT_1U:
                    case FR_VECT_C:
                    case FR_VECT_STRING:
                        //-----------------------------------------------
                        // NOTE:
                        //   These are imposible conditions. Doing this
                        //   to keep the compiler quiet.
                        //-----------------------------------------------
                        break;
                    }
                }
            }
            AT( );
            //----------------------------------------------------------------
            // Check if any work needs to happen
            //----------------------------------------------------------------
            if ( ( CompressionMode == MODE_RAW ) && NativeOrder )
            {
                //---------------------------------------------------------------
                // Nothing to do since the source buffer is already
                // uncompressed.
                //---------------------------------------------------------------
                return;
            }
            //-----------------------------------------------------------------
            // Prepare the output buffer since some translation needs to happen
            // to expand the source buffer.
            //-----------------------------------------------------------------

            //-----------------------------------------------------------------
            // Expand data
            //-----------------------------------------------------------------
            boost::shared_array< CHAR_U > data_out;
            INT_8U                        data_out_len = 0;

            switch ( cm )
            {
            case MODE_RAW:
                if ( !NativeOrder )
                {
                    data_out_len = SourceByteLength;
                    data_out.reset( new CHAR_U[ SourceByteLength ] );
                    std::copy( Source,
                               &( Source[ SourceByteLength ] ),
                               data_out.get( ) );
                    swap_data( SourceLength, dt, data_out.get( ) );
                }
                break;
            case MODE_GZIP:
                //---------------------------------------------------------------
                // Compress using GZip algorithm
                //---------------------------------------------------------------
                AT( );
                data_out_len = data_size( dt ) * SourceLength;
                GZip::Expand(
                    Source, SourceByteLength, data_out, data_out_len );
                if ( !NativeOrder )
                {
                    swap_data( SourceLength, dt, data_out.get( ) );
                }
                break;
            case MODE_DIFF:
            {
                INT_4U ndata( SourceLength );

                if ( NativeOrder )
                {
                    Differential::Decode( Source, ndata, dt, data_out );
                }
                else
                {
                    data_out_len = SourceByteLength;
                    data_out.reset( new CHAR_U[ SourceByteLength ] );
                    std::copy( Source,
                               &( Source[ SourceByteLength ] ),
                               data_out.get( ) );
                    swap_data( SourceLength, dt, data_out.get( ) );

                    Differential::Decode( data_out.get( ), ndata, dt );
                }
                data_out_len = SourceByteLength;
            }
            break;
            case MODE_DIFF_GZIP:
            {
                AT( );
                INT_4U ndata( SourceLength );

                //-------------------------------------------------------------
                // Do the gzip decompression
                //-------------------------------------------------------------
                data_out_len = data_size( dt ) * SourceLength;
                GZip::Expand(
                    Source, SourceByteLength, data_out, data_out_len );
                //-------------------------------------------------------------
                // Put the bytes into host order
                //-------------------------------------------------------------
                if ( !NativeOrder )
                {
                    swap_data( SourceLength, dt, data_out.get( ) );
                }
                //-------------------------------------------------------------
                // Undo the differentiation in place.
                //-------------------------------------------------------------
                Differential::Decode( data_out.get( ), ndata, dt );
            }
            break;
            case MODE_ZSTD:
                //---------------------------------------------------------------
                // Expand using Zstd algorithm
                //---------------------------------------------------------------
                AT( );
                data_out_len = data_size( dt ) * SourceLength;
                Zstd::Expand(
                    Source, SourceByteLength, data_out, data_out_len );
                if ( !NativeOrder )
                {
                    swap_data( SourceLength, dt, data_out.get( ) );
                }
                break;
            case MODE_DIFF_ZSTD:
            {
                AT( );
                INT_4U ndata( SourceLength );

                //-------------------------------------------------------------
                // Do the zstd decompression
                //-------------------------------------------------------------
                data_out_len = data_size( dt ) * SourceLength;
                Zstd::Expand(
                    Source, SourceByteLength, data_out, data_out_len );
                //-------------------------------------------------------------
                // Put the bytes into host order
                //-------------------------------------------------------------
                if ( !NativeOrder )
                {
                    swap_data( SourceLength, dt, data_out.get( ) );
                }
                //-------------------------------------------------------------
                // Undo the differentiation in place.
                //-------------------------------------------------------------
                Differential::Decode( data_out.get( ), ndata, dt );
            }
            break;
            case MODE_ZERO_SUPPRESS_SHORT:
            {
                AT( );
                ZeroSuppress::DiffExpand< 2 >( Source,
                                               SourceByteLength,
                                               !NativeOrder,
                                               dt,
                                               data_out,
                                               SourceLength,
                                               data_out_len );
            }
            break;
                BEGIN_WIMPLICIT_FALLTHROUGH
            case MODE_ZERO_SUPPRESS_WORD_4:
                if ( dt == FR_VECT_8C )
                {
                    AT( );
                    expandZeroSuppressComplex< INT_4S >( Source,
                                                         SourceLength,
                                                         SourceByteLength,
                                                         NativeOrder,
                                                         dt,
                                                         data_out,
                                                         data_out_len );
                    break;
                }
                // :TRICKY: Fall through
                END_WIMPLICIT_FALLTHROUGH
            case MODE_ZERO_SUPPRESS_INT_FLOAT:
            {
                AT( );
                ZeroSuppress::DiffExpand< 4 >( Source,
                                               SourceByteLength,
                                               !NativeOrder,
                                               dt,
                                               data_out,
                                               SourceLength,
                                               data_out_len );
            }
            break;
            case MODE_ZERO_SUPPRESS_WORD_8:
            {
                if ( dt == FR_VECT_16C )
                {
                    AT( );
                    expandZeroSuppressComplex< INT_8S >( Source,
                                                         SourceLength,
                                                         SourceByteLength,
                                                         NativeOrder,
                                                         dt,
                                                         data_out,
                                                         data_out_len );
                }
                else
                {
                    AT( );
                    ZeroSuppress::DiffExpand< 8 >( Source,
                                                   SourceByteLength,
                                                   !NativeOrder,
                                                   dt,
                                                   data_out,
                                                   SourceLength,
                                                   data_out_len );
                }
            }
            break;
            default:
            {
                std::ostringstream msg;

                msg << "FrameCPP::Compression::Expand is not implemented"
                    << " (mode: " << cm << "(0x" << std::hex << cm << ") )";
                throw std::runtime_error( msg.str( ) );
            }
            break;
            }
            //-----------------------------------------------------------------
            // Return the result to the caller
            //-----------------------------------------------------------------
            if ( data_out_len <= 0 )
            {
                data_out.reset( );
            }
            Dest = data_out;
            DataByteLength = data_out_len;
        }
#if 0
    void
    Expand( bool NativeOrder,
	    INT_4U CompressionMode,
	    const compress_type_mapping& CompressionMapping,
	    INT_4U DataType,
	    const data_type_mapping& DataTypeMapping,
	    CHAR_U*& Data,
	    INT_8U& DataLength,
	    INT_8U& DataByteLength
	    )
    {
      AT( );
      // If it's not compressed, then it might need to be byte swapped   
      if( CompressionMode == MODE_RAW )
      {
	AT( );
	swap_data( DataLength, (data_types_type)DataType, Data );
	AT( );
	return;
      }

      AT( );
      // See if the data should be z-uncompressed (zero suppress method)
      // for short data only
      //
      if( ( CompressionMode == MODE_ZERO_SUPPRESS_SHORT_GZIP_OTHER ||
	    CompressionMode == MODE_ZERO_SUPPRESS_SHORT ) &&
	  ( DataType == FR_VECT_2S || DataType == FR_VECT_2U ) )
      {
	AT( );
	if ( ! NativeOrder )
	{
	  AT( );
	  swap_data( DataByteLength/sizeof( INT_2S ),
		     (data_types_type)DataType, Data );
	  NativeOrder = true;
	}

	ZeroSuppressUncompressShort( Data, DataByteLength, DataLength );
      }
      else if ( ( CompressionMode == MODE_ZERO_SUPPRESS_INT_FLOAT ) &&
		( DataType == FR_VECT_4S
		  || DataType == FR_VECT_4U
		  || DataType == FR_VECT_4R ) )
      {
	AT( );
	if ( ! NativeOrder )
	{
	  AT( );
	  swap_data( DataByteLength/sizeof( INT_4S ),
		     (data_types_type)DataType, Data );
	  NativeOrder = true;
	}
	ZeroSuppressUncompressInt( Data, DataByteLength, DataLength );
      }
      // Determine if we need to gunzip the data.
      if ( ( CompressionMode == MODE_GZIP ) ||
	   ( CompressionMode == MODE_DIFF_GZIP ) || 
	   ( CompressionMode == MODE_ZERO_SUPPRESS_SHORT_GZIP_OTHER &&
	     ( DataType != FR_VECT_2S
	       || DataType != FR_VECT_2U ) ) )
      {
	//---------------------------------------------------------------
	// Gunzip the buffer
	//---------------------------------------------------------------
	AT( );
	const INT_8U	length = DataLength * data_size( DataType );
	GZip::Expand( Data,		/* compressed data		*/
		      DataByteLength,	/* size of compressed data	*/
		      length		/* expected data length		*/
		      );
      }
    
      if ( ! NativeOrder )
      {
	AT( );
	swap_data( DataLength, (data_types_type)DataType, Data );
	NativeOrder = true;
      }

      // Integrate the data if necessary.
      if ( ( CompressionMode == MODE_DIFF ) ||
	   ( CompressionMode == MODE_DIFF_GZIP ) ||
	   ( CompressionMode == MODE_ZERO_SUPPRESS_SHORT_GZIP_OTHER ) ||
	   ( CompressionMode == MODE_ZERO_SUPPRESS_SHORT &&
	     ( DataType == FR_VECT_2S || DataType == FR_VECT_2U ) ) ||
	   ( CompressionMode == MODE_ZERO_SUPPRESS_INT_FLOAT &&
	     ( DataType == FR_VECT_4S ||
	       DataType == FR_VECT_4U ||
	       DataType == FR_VECT_4R ) ) )
      {
	Differential::Decode( Data, DataLength, (data_types_type)DataType );
      }
    } // func - Expand
#endif /* 0 */

    } // namespace Compression
} // namespace FrameCPP

namespace
{
    void
    swap_data( INT_8U                                 NData,
               FrameCPP::Compression::data_types_type Type,
               CHAR_U*                                Buffer )
    {
        using namespace FrameCPP::Compression;

        switch ( Type )
        {
        case FR_VECT_2S:
        case FR_VECT_2U:
            reverse< 2 >( Buffer, NData );
            break;
        case FR_VECT_4R:
        case FR_VECT_4S:
        case FR_VECT_4U:
            reverse< 4 >( Buffer, NData );
            break;
        case FR_VECT_8R:
        case FR_VECT_8S:
        case FR_VECT_8U:
            reverse< 8 >( Buffer, NData );
            break;
        case FR_VECT_8C:
            reverse< 4 >( Buffer, NData * 2 );
            break;
        case FR_VECT_16C:
            reverse< 8 >( Buffer, NData * 2 );
            break;
        case FR_VECT_STRING:
        {
            CHAR_U* pos( Buffer );
            INT_2U* len( (INT_2U*)( Buffer ) );
            for ( INT_4U x = 0; x < NData; x++ )
            {
                reverse< 2 >( pos, 1 );
                pos += *len + 2;
                len = (INT_2U*)pos;
            }
        }
        break;
        default:
            break;
        }
    }
} // namespace
