//
// LDASTools frameCPP - A library implementing the LIGO/Virgo frame
// specification
//
// Copyright (C) 2025 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
//

#ifndef FrameCPP_UNIVERSAL__IMPL__FrVectSerialIO_HH
#define FrameCPP_UNIVERSAL__IMPL__FrVectSerialIO_HH

#if !defined( SWIGIMPORTED )
#include <boost/serialization/binary_object.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/version.hpp>

#include "framecpp/Common/FrameSpec.hh"

#include "framecpp/Universal/impl/HelperSerialIO.hh"
#include "framecpp/Universal/impl/DimensionSerialIO.hh"
#include "framecpp/Universal/impl/FrVectCompressionMapping.hh"

#endif // !defined( SWIGIMPORTED )

#include "framecpp/Universal/impl/FrVectData.hh"

#if !defined( SWIGIMPORTED )
namespace FrameCPP
{
    namespace Universal
    {
        class FrVect;
        namespace FrVectImpl
        {
            /// \brief Serialization I/O implementation for FrVect data
            /// structures
            ///
            /// This class provides versioned serialization support for FrVect
            /// objects using Boost.Serialization. It handles the evolution of
            /// the FrVect data structure across different frame specification
            /// versions (v3 through v9).
            ///
            /// \tparam FR_VECT_T The FrVect type being serialized (typically
            /// FrVect)
            template < typename FR_VECT_T >
            class SerialIO : public virtual Data
            {
            public:
                /// \brief Default constructor
                ///
                /// Constructs an empty SerialIO object ready for serialization
                /// operations. The object inherits data members from the Data
                /// base class.
                SerialIO( )
                {
                }

                SerialIO( const SerialIO& ) = default;
                SerialIO( SerialIO&& ) = default;
                SerialIO& operator=( const SerialIO& ) = delete;
                SerialIO& operator=( SerialIO&& ) = delete;

            private:
                friend class boost::serialization::access;

                /// \brief Convert compression schemes between different frame
                /// versions
                ///
                /// This method handles the mapping of compression scheme enums
                /// between different versions of the frame specification using
                /// data-driven mapping tables. It properly handles the evolution
                /// of compression schemes across versions, including cases where
                /// certain schemes were added, removed, or renamed.
                ///
                /// \tparam SourceVersion The version type to convert from
                /// (e.g., v3, v4, v8) \tparam TargetVersion The version type to
                /// convert to (e.g., v9)
                ///
                /// \param[in] source The compression scheme value from the
                /// source version \param[out] target The converted compression
                /// scheme value for the target version \param[in] data_type The
                /// FrVect data type (FR_VECT_2S, FR_VECT_4R, etc.) for
                /// data-type-specific mappings
                ///
                /// \note The endianness bit is preserved during conversion
                /// \note If a compression scheme doesn't exist in the target
                /// version,
                ///       it is converted to RAW
                /// \note For ZERO_SUPPRESS modes, the mapping depends on the
                /// data type
                ///
                /// Version-specific mappings:
                /// - v3: Supports RAW, GZIP, DIFF, DIFF_GZIP
                /// - v4-v7: Adds ZERO_SUPPRESS_SHORT (2-byte types only)
                /// - v8: Data-type-specific ZERO_SUPPRESS_WORD_2/4/8
                /// - v9: Unified ZERO_SUPPRESS for all types
                template < typename SourceVersion, typename TargetVersion >
                static void
                convertCompressionScheme(
                    typename SourceVersion::compress_type  source,
                    typename TargetVersion::compress_type& target,
                    Data::data_types_type                  data_type )
                {
                    using namespace FrVectImpl::CompressionMapping;

                    // Use VersionConverter to handle both direct (adjacent) and
                    // chained (non-adjacent) version conversions automatically
                    INT_2U source_mode = static_cast< INT_2U >( source );
                    INT_2U target_mode = VersionConverter< SourceVersion, TargetVersion >::convert(
                        source_mode, data_type );

                    // Assign to target with proper type casting
                    target = static_cast< typename TargetVersion::
                                              compress_type >( target_mode );
                }

                /// \brief Unified serialize method for FrVect data
                ///
                /// This method handles both serialization (saving) and
                /// deserialization (loading) of FrVect data using compile-time
                /// template specialization. It provides version-aware handling
                /// of frame specification evolution from v3 through v9.
                ///
                /// The method uses `if constexpr` to generate optimized code
                /// paths at compile time, ensuring that only the necessary
                /// operations are included for each archive type (input vs
                /// output).
                ///
                /// \tparam Archive The Boost.Serialization archive type
                ///
                /// \param[in,out] ar The archive for reading or writing data
                /// \param[in] version The frame specification version (3-9)
                ///
                /// \throws boost::archive::archive_exception on serialization
                /// errors \throws std::bad_alloc if memory allocation fails
                /// during loading
                ///
                /// **Version-specific handling:**
                /// - **v3-4**: Uses INT_4U for size fields (nData, nBytes)
                /// - **v5+**: Uses INT_8U for size fields
                /// - **v9**: Adds dataValid fields for data quality information
                /// - **Pre-v3**: Provides default values during loading
                ///
                /// **Memory management:**
                /// - **Saving**: Uses existing memory from m_data members
                /// - **Loading**: Allocates memory dynamically for data arrays
                ///
                /// **Type conversions:**
                /// - Compression schemes converted between frame versions
                /// - Size field types converted based on version requirements
                /// - Endianness handling preserved during conversions
                ///
                /// \note Binary data serialized using
                /// boost::serialization::make_binary_object \note Dimension
                /// serialization delegated to DimensionSerialIO \note Template
                /// specialization eliminates runtime overhead
                ///
                /// \see serializeCompression() for compression type handling
                /// \see serializeDataBlob() for memory management details
                /// \see convertCompressionScheme() for version-specific
                /// conversions
                template < class Archive >
                void
                serialize( Archive& ar, const unsigned int version )
                {
                    using boost::serialization::make_binary_object;
                    using boost::serialization::make_nvp;

                    // Always serialize name as string
                    ar& make_nvp( "name", m_data.name );

                    // Handle compression type based on version
                    serializeCompression< Archive >( ar, version );

                    // Type field
                    serializeType< Archive >( ar, version );

                    // Handle size fields based on version
                    serializeSizeFields< Archive >( ar, version );

                    // Data blob
                    serializeDataBlob< Archive >( ar );

                    // Dimensions
                    serializeDimensions< Archive >( ar, version );

                    // Unit Y
                    serializeUnitY< Archive >( ar, version );

                    // v9 specific fields - dataValid
                    serializeDataValid< Archive >( ar, version );
                }

            private:
                /// \brief Serialize compression field with version-specific
                /// handling
                ///
                /// Handles the serialization of compression type fields across
                /// different frame specification versions. Uses template
                /// specialization to handle version-specific compression scheme
                /// conversions at compile time.
                ///
                /// \tparam Archive The Boost.Serialization archive type
                /// \param[in,out] ar The archive for reading/writing
                /// compression data \param[in] version The frame specification
                /// version (3-9)
                ///
                /// **Version handling:**
                /// - **v9+**: Direct serialization of current compression type
                /// - **v8**: Converts via serializeCompressionVersion<Archive,
                /// v8>
                /// - **v4-v7**: Converts via
                /// serializeCompressionVersion<Archive, v4>
                /// - **v3**: Converts via serializeCompressionVersion<Archive,
                /// v3>
                /// - **Pre-v3**: Sets RAW compression for loading operations
                ///
                /// \note Uses compile-time branching with `if constexpr` for
                /// optimization \see serializeCompressionVersion() for
                /// version-specific conversions \see convertCompressionScheme()
                /// for the conversion algorithm
                template < class Archive >
                void
                serializeCompression( Archive& ar, const unsigned int version )
                {
                    using boost::serialization::make_nvp;

                    if ( version >= 9 )
                    {
                        ar& make_nvp( "compress", m_data.compress );
                    }
                    else if ( version >= 8 )
                    {
                        serializeCompressionVersion< Archive, v8 >( ar );
                    }
                    else if ( version >= 4 )
                    {
                        serializeCompressionVersion< Archive, v4 >( ar );
                    }
                    else if ( version >= 3 )
                    {
                        serializeCompressionVersion< Archive, v3 >( ar );
                    }
                    else
                    {
                        // Pre-v3 handling
                        if
                            constexpr( Archive::is_loading::value )
                            {
                                m_data.compress = current_version::
                                    compression_scheme_type::RAW;
                            }
                    }
                }

                /// \brief Serialize compression for specific frame version
                ///
                /// Template method that handles compression type conversion
                /// between the current frame version and a specific
                /// target/source version. Uses compile-time template
                /// specialization to generate optimal code for each version
                /// pair.
                ///
                /// \tparam Archive The Boost.Serialization archive type
                /// \tparam VersionType The target frame version type (v3, v4,
                /// v8, etc.) \param[in,out] ar The archive for reading/writing
                /// compression data
                ///
                /// **Operation modes:**
                /// - **Saving**: Converts from current version compression
                /// scheme to VersionType
                /// - **Loading**: Converts from VersionType compression scheme
                /// to current version
                ///
                /// **Conversion process:**
                /// 1. Create temporary variable of VersionType::compress_type
                /// 2. For saving: Convert current → VersionType using
                /// convertCompressionScheme
                /// 3. Serialize the version-specific compression type
                /// 4. For loading: Convert VersionType → current using
                /// convertCompressionScheme
                ///
                /// \note Template specialization ensures only relevant code is
                /// compiled \note Endianness bits are preserved during all
                /// conversions \see convertCompressionScheme() for the actual
                /// conversion logic
                template < class Archive, typename VersionType >
                void
                serializeCompressionVersion( Archive& ar )
                {
                    using boost::serialization::make_nvp;

                    typename VersionType::compress_type compress_ver;

                    if
                        constexpr( Archive::is_saving::value )
                        {
                            // Saving: convert from current to target version
                            current_version::compress_type compress_temp =
                                static_cast< current_version::compress_type >(
                                    m_data.compress );
                            convertCompressionScheme< current_version,
                                                      VersionType >(
                                compress_temp, compress_ver,
                                static_cast< Data::data_types_type >(
                                    m_data.type ) );
                        }

                    ar& make_nvp( "compress", compress_ver );

                    if
                        constexpr( Archive::is_loading::value )
                        {
                            // Loading: convert from source version to current
                            current_version::compress_type compress_temp;
                            convertCompressionScheme< VersionType,
                                                      current_version >(
                                compress_ver, compress_temp,
                                static_cast< Data::data_types_type >(
                                    m_data.type ) );
                            m_data.compress = static_cast<
                                current_version::compression_scheme_type >(
                                compress_temp );
                        }
                }

                /// \brief Serialize FrVect data type field
                ///
                /// Handles serialization of the FrVect data type field, which
                /// specifies the element type of the vector data (e.g.,
                /// FR_VECT_8R, FR_VECT_4R, etc.).
                ///
                /// \tparam Archive The Boost.Serialization archive type
                /// \param[in,out] ar The archive for reading/writing type data
                /// \param[in] version The frame specification version (3-9)
                ///
                /// **Version handling:**
                /// - **v3+**: Serializes the actual type field from m_data.type
                /// - **Pre-v3**: Sets default type FR_VECT_8R during loading
                /// operations
                ///
                /// \note Uses `if constexpr` to eliminate loading code for
                /// output archives
                template < class Archive >
                void
                serializeType( Archive& ar, const unsigned int version )
                {
                    using boost::serialization::make_nvp;

                    if ( version >= 3 )
                    {
                        ar& make_nvp( "type", m_data.type );
                    }
                    else if
                        constexpr( Archive::is_loading::value )
                        {
                            m_data.type =
                                current_version::data_types_type::FR_VECT_8R;
                        }
                }

                /// \brief Serialize size fields with version-specific type
                /// handling
                ///
                /// Handles serialization of nData and nBytes fields, which
                /// specify the number of data elements and total byte size
                /// respectively. Different frame versions use different integer
                /// types for these fields.
                ///
                /// \tparam Archive The Boost.Serialization archive type
                /// \param[in,out] ar The archive for reading/writing size data
                /// \param[in] version The frame specification version (3-9)
                ///
                /// **Version-specific type handling:**
                /// - **v5+**: Uses INT_8U (64-bit) for both nData and nBytes
                /// - **v3-v4**: Uses INT_4U (32-bit) with conversion to/from
                /// INT_8U
                /// - **Pre-v3**: Sets default values (0) during loading
                /// operations
                ///
                /// **Type conversion process (v3-v4):**
                /// - **Saving**: Convert INT_8U → INT_4U using static_cast
                /// - **Loading**: Convert INT_4U → INT_8U using static_cast
                ///
                /// \warning No overflow checking is performed during type
                /// conversions \note Uses `if constexpr` for compile-time
                /// optimization
                template < class Archive >
                void
                serializeSizeFields( Archive& ar, const unsigned int version )
                {
                    using boost::serialization::make_nvp;

                    if ( version >= 5 )
                    {
                        // v5+ uses INT_8U directly
                        ar& make_nvp( "nData", m_data.nData );
                        ar& make_nvp( "nBytes", m_data.nBytes );
                    }
                    else if ( version >= 3 )
                    {
                        // v3/v4 use INT_4U - need conversion
                        if
                            constexpr( Archive::is_saving::value )
                            {
                                v3::nData_type nData_v3 =
                                    static_cast< v3::nData_type >(
                                        m_data.nData );
                                v3::nBytes_type nBytes_v3 =
                                    static_cast< v3::nBytes_type >(
                                        m_data.nBytes );
                                ar& make_nvp( "nData", nData_v3 );
                                ar& make_nvp( "nBytes", nBytes_v3 );
                            }
                        else
                        {
                            v3::nData_type  nData_v3;
                            v3::nBytes_type nBytes_v3;
                            ar&             make_nvp( "nData", nData_v3 );
                            ar&             make_nvp( "nBytes", nBytes_v3 );
                            m_data.nData =
                                static_cast< current_version::nData_type >(
                                    nData_v3 );
                            m_data.nBytes =
                                static_cast< current_version::nBytes_type >(
                                    nBytes_v3 );
                        }
                    }
                    else if
                        constexpr( Archive::is_loading::value )
                        {
                            // Pre-v3: default values
                            m_data.nData = 0;
                            m_data.nBytes = 0;
                        }
                }

                /// \brief Serialize data blob with automatic memory management
                ///
                /// Handles serialization of the actual vector data payload
                /// using binary object serialization. Automatically manages
                /// memory allocation during loading operations.
                ///
                /// \tparam Archive The Boost.Serialization archive type
                /// \param[in,out] ar The archive for reading/writing binary
                /// data
                ///
                /// **Memory management:**
                /// - **Saving**: Uses existing memory pointed to by m_data.data
                /// - **Loading**: Allocates new memory array of size
                /// m_data.nBytes
                ///
                /// **Binary serialization:**
                /// - Uses boost::serialization::make_binary_object for
                /// efficient transfer
                /// - Preserves exact byte layout without type conversion
                /// - Only processes data if m_data.nBytes > 0
                ///
                /// **Memory allocation details:**
                /// - Creates new data_type::element_type array during loading
                /// - Uses smart pointer (m_data.data.reset()) for automatic
                /// cleanup
                /// - Memory size determined by previously deserialized
                /// m_data.nBytes
                ///
                /// \note Binary data is platform-dependent (endianness,
                /// alignment) \note Uses `if constexpr` to eliminate allocation
                /// code for output archives \warning Caller must ensure
                /// m_data.nBytes is set before calling during load
                template < class Archive >
                void
                serializeDataBlob( Archive& ar )
                {
                    using boost::serialization::make_binary_object;
                    using boost::serialization::make_nvp;

                    if
                        constexpr( Archive::is_loading::value )
                        {
                            if ( m_data.nBytes > 0 )
                            {
                                m_data.data.reset( new data_type::element_type
                                                       [ m_data.nBytes ] );
                            }
                        }

                    if ( m_data.nBytes > 0 )
                    {
                        auto serialized = make_binary_object(
                            m_data.data.get( ), m_data.nBytes );
                        ar& make_nvp( "data", serialized );
                    }
                }

                /// \brief Serialize vector dimension information
                ///
                /// Handles serialization of the dimension vector, which
                /// contains metadata about the vector's dimensional structure
                /// (start values, step sizes, units, etc.).
                ///
                /// \tparam Archive The Boost.Serialization archive type
                /// \param[in,out] ar The archive for reading/writing dimension
                /// data \param[in] version The frame specification version
                /// (3-9)
                ///
                /// **Version handling:**
                /// - **v3+**: Serializes the full dimension vector using
                /// DimensionSerialIO
                /// - **Pre-v3**: Clears dimension vector during loading
                /// operations
                ///
                /// **Dimension vector contents:**
                /// - Each element contains start, deltaX, name, and unit
                /// information
                /// - Supports multi-dimensional vector data structures
                /// - Serialization delegated to DimensionSerialIO
                /// implementation
                ///
                /// \note Dimension serialization is handled by boost's STL
                /// container support \see DimensionSerialIO for individual
                /// dimension element serialization
                template < class Archive >
                void
                serializeDimensions( Archive& ar, const unsigned int version )
                {
                    using boost::serialization::make_nvp;

                    if ( version >= 3 )
                    {
                        ar& make_nvp( "dimension", m_data.dimension );
                    }
                    else if
                        constexpr( Archive::is_loading::value )
                        {
                            m_data.dimension.clear( );
                        }
                }

                /// \brief Serialize unitY field with version-aware handling
                ///
                /// Handles serialization of the unitY field, which contains the
                /// physical unit information for the Y-axis of the vector data.
                /// This field provides dimensional analysis support for vector
                /// quantities.
                ///
                /// \tparam Archive The Boost.Serialization archive type
                /// \param[in,out] ar The archive for reading/writing unitY data
                /// \param[in] version The frame specification version (3-9)
                ///
                /// **Version handling:**
                /// - **v3+**: Serializes the unitY string field from
                /// m_data.unitY
                /// - **Pre-v3**: Clears unitY field during loading operations
                ///
                /// **Field contents:**
                /// - Physical unit string (e.g., "m", "Hz", "strain", etc.)
                /// - Empty string for dimensionless quantities
                /// - Used for dimensional analysis and unit conversion
                ///
                /// \note Uses `if constexpr` to eliminate clearing code for
                /// output archives
                template < class Archive >
                void
                serializeUnitY( Archive& ar, const unsigned int version )
                {
                    using boost::serialization::make_nvp;

                    if ( version >= 3 )
                    {
                        ar& make_nvp( "unitY", m_data.unitY );
                    }
                    else if
                        constexpr( Archive::is_loading::value )
                        {
                            m_data.unitY.clear( );
                        }
                }

                /// \brief Serialize v9 dataValid fields for data quality
                /// information
                ///
                /// Handles serialization of the dataValid fields introduced in
                /// frame specification v9. These fields provide data quality
                /// information by storing a validity mask that indicates which
                /// data points are reliable.
                ///
                /// \tparam Archive The Boost.Serialization archive type
                /// \param[in,out] ar The archive for reading/writing dataValid
                /// data \param[in] version The frame specification version
                /// (3-9)
                ///
                /// **Version handling:**
                /// - **v9+**: Serializes all dataValid-related fields with
                /// automatic memory management
                /// - **Pre-v9**: Initializes fields with default values during
                /// loading operations
                ///
                /// **Field serialization sequence:**
                /// 1. **nDataValid**: Number of valid data points (INT_8U)
                /// 2. **dataValidCompScheme**: Compression scheme for validity
                /// data
                /// 3. **nDataValidCompBytes**: Size of compressed validity data
                /// in bytes
                /// 4. **dataValid**: Binary validity mask data (only if
                /// nDataValidCompBytes > 0)
                ///
                /// **Memory management during loading:**
                /// - Allocates new dataValid array based on nDataValidCompBytes
                /// - Uses smart pointer (m_data.dataValid.reset()) for
                /// automatic cleanup
                /// - Only allocates memory if compressed data size > 0
                ///
                /// **Data quality semantics:**
                /// - Validity mask corresponds 1:1 with main data array
                /// elements
                /// - Compressed using scheme specified in dataValidCompScheme
                /// - Enables selective processing of reliable data points only
                ///
                /// \note Binary data serialized using
                /// boost::serialization::make_binary_object \note Uses `if
                /// constexpr` for compile-time optimization \warning Caller
                /// must ensure nDataValidCompBytes is set before dataValid
                /// allocation
                template < class Archive >
                void
                serializeDataValid( Archive& ar, const unsigned int version )
                {
                    using boost::serialization::make_binary_object;
                    using boost::serialization::make_nvp;

                    if ( version >= 9 )
                    {
                        ar& make_nvp( "nDataValid", m_data.nDataValid ) &
                            make_nvp( "dataValidCompScheme",
                                      m_data.dataValidCompScheme ) &
                            make_nvp( "nDataValidCompBytes",
                                      m_data.nDataValidCompBytes );

                        if
                            constexpr( Archive::is_loading::value )
                            {
                                if ( m_data.nDataValidCompBytes > 0 )
                                {
                                    m_data.dataValid.reset(
                                        new dataValid_type::element_type
                                            [ m_data.nDataValidCompBytes ] );
                                }
                            }

                        if ( m_data.nDataValidCompBytes > 0 )
                        {
                            auto serialized = make_binary_object(
                                m_data.dataValid.get( ),
                                m_data.nDataValidCompBytes );
                            ar& make_nvp( "dataValid", serialized );
                        }
                    }
                    else if
                        constexpr( Archive::is_loading::value )
                        {
                            // Initialize v9 fields with defaults for older
                            // versions
                            m_data.nDataValid = 0;
                            m_data.dataValidCompScheme =
                                current_version::compression_scheme_type::RAW;
                            m_data.nDataValidCompBytes = 0;
                            m_data.dataValid.reset( );
                        }
                }
            };

        } // namespace FrVectImpl
    } // namespace Universal
} // namespace FrameCPP

/// \brief Specify the class version for Boost.Serialization
///
/// This macro tells Boost.Serialization that FrVectImpl::Data is at version 9,
/// which corresponds to the v9 frame specification. The serialization framework
/// uses this version number to determine how to serialize/deserialize the data.
///
/// \note This version number is passed to the save() and load() methods
BOOST_CLASS_VERSION( FrameCPP::Universal::FrVectImpl::Data, 9 )
BOOST_CLASS_VERSION( FrameCPP::Universal::FrVect, 9 )

#endif // !defined( SWIGIMPORTED )

#endif // FrameCPP_UNIVERSAL__IMPL__FrVectSerialIO_HH
