//
// 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__FrProcDataSerialIO_HH
#define FrameCPP_UNIVERSAL__IMPL__FrProcDataSerialIO_HH

#if !defined( SWIGIMPORTED ) && defined( __cplusplus )
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/split_member.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/version.hpp>
#include <boost/serialization/shared_ptr.hpp>

#include "framecpp/Common/FrameSpec.hh"
#include "framecpp/Universal/impl/HelperSerialIO.hh"
#include "framecpp/Universal/impl/GPSTime.hh"
#endif /* !defined( SWIGIMPORTED ) */

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

#if !defined( SWIGIMPORTED ) && defined( __cplusplus )
namespace FrameCPP
{
    namespace Universal
    {
        class FrProcData;
        namespace FrProcDataImpl
        {
            /// \brief Serialization I/O implementation for FrProcData data
            /// structures
            ///
            /// This class provides versioned serialization support for
            /// FrProcData objects using Boost.Serialization. It handles the
            /// complex evolution of the FrProcData data structure across
            /// different frame specification versions (v3 through v9).
            ///
            /// \tparam FR_PROC_DATA_T The FrProcData type being serialized
            /// (typically FrProcData)
            template < typename FR_PROC_DATA_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 Serialize FrProcData data to an archive
                ///
                /// This method saves the FrProcData data to a serialization
                /// archive, handling version-specific data formats and
                /// significant field evolution. FrProcData has major changes
                /// across versions with new type systems and container
                /// additions.
                ///
                /// \tparam Archive The Boost.Serialization archive type
                ///
                /// \param[in,out] ar The archive to write data to
                /// \param[in] version The target serialization version (3-9)
                ///
                /// \throws boost::archive::archive_exception on serialization
                /// errors
                ///
                /// Version-specific handling:
                /// - v3: Basic fields (name, comment, sampleRate, time offset,
                /// fShift)
                /// - v4: Adds table container
                /// - v5: Major addition of type/subType fields, timeOffset as
                /// REAL_8,
                ///       adds tRange, phase, fRange, BW, auxParam, history
                ///       container
                /// - v9: Adds MIME type support
                ///
                /// \note Complex container handling for data, aux, table,
                /// history \note Type system evolution from v5 onwards
                template < class Archive >
                void
                save( Archive& ar, const unsigned int version ) const
                {
                    using boost::serialization::make_nvp;

                    if ( version >= 3 )
                    {
                        // Basic fields available in all versions
                        ar& make_nvp( "name", name ) &
                            make_nvp( "comment", comment );

                        // v3/v4 used separate time offset fields
                        if ( version >= 5 )
                        {
                            auto output_type{ type };
                            auto output_subType{ subType };

                            if ( ( version < 9 ) && ( type == MIME ) )
                            {
                                output_type = UNKNOWN_TYPE;
                                output_subType = 0;
                            }
                            // v5+ added type classification
                            ar& make_nvp( "type", output_type ) &
                                make_nvp( "subType", output_subType ) &
                                make_nvp( "timeOffset", timeOffset ) &
                                make_nvp( "tRange", tRange ) &
                                make_nvp( "fShift", fShift ) &
                                make_nvp( "phase", phase ) &
                                make_nvp( "fRange", fRange ) &
                                make_nvp( "BW", BW );

                            // Auxiliary parameters (v5+)
                            ar& make_nvp( "auxParam", auxParam );
                        }
                        else
                        {
                            v3::sampleRate_type sampleRate{ 0 };
                            // v3/v4: Basic fields with separate time offset
                            ar& make_nvp( "sampleRate", sampleRate );

                            // Convert REAL_8 timeOffset to separate S/N fields
                            // for v3/v4

                            INT_4U timeOffsetS;
                            INT_4U timeOffsetN;

                            ConvertToSecAndNsec(
                                timeOffset, timeOffsetS, timeOffsetN );

                            ar& make_nvp( "timeOffsetS", timeOffsetS ) &
                                make_nvp( "timeOffsetN", timeOffsetN ) &
                                make_nvp( "fShift", fShift );
                        }

#if WORKING
                        // v9+ MIME support
                        if ( version >= 9 )
                        {
                            ar& make_nvp( "mimeType", mimeType );

                            // Convert MIME data to vector for serialization
                            std::vector< CHAR_U > mimeData_vector(
                                mimeData.begin( ), mimeData.end( ) );
                            ar& make_nvp( "mimeData", mimeData_vector );
                        }
#endif /* WORKING */

                        // Convert containers to vectors for serialization
                        std::vector< FrVect > data_vector;
                        data_vector.reserve( data.size( ) );
                        for ( const auto& item : data )
                        {
                            if ( item )
                            {
                                data_vector.push_back( *item );
                            }
                        }
                        ar& make_nvp( "data", data_vector );

                        std::vector< FrVect > aux_vector;
                        aux_vector.reserve( aux.size( ) );
                        for ( const auto& item : aux )
                        {
                            if ( item )
                            {
                                aux_vector.push_back( *item );
                            }
                        }
                        ar& make_nvp( "aux", aux_vector );

                        // Table container (v4+)
                        if ( version >= 4 )
                        {
                            std::vector< FrTable > table_vector;
                            table_vector.reserve( table.size( ) );
                            for ( const auto& item : table )
                            {
                                if ( item )
                                {
                                    table_vector.push_back( *item );
                                }
                            }
                            ar& make_nvp( "table", table_vector );
                        }

                        // History container (v5+)
                        if ( version >= 5 )
                        {
                            std::vector< FrHistory > history_vector;
                            history_vector.reserve( history.size( ) );
                            for ( const auto& item : history )
                            {
                                if ( item )
                                {
                                    history_vector.push_back( *item );
                                }
                            }
                            ar& make_nvp( "history", history_vector );
                        }
                    }
                }

                /// \brief Deserialize FrProcData data from an archive
                ///
                /// This method loads FrProcData data from a serialization
                /// archive, handling version-specific data formats and the
                /// complex evolution of FrProcData across frame versions.
                ///
                /// \tparam Archive The Boost.Serialization archive type
                ///
                /// \param[in,out] ar The archive to read data from
                /// \param[in] version The source serialization version (3-9)
                ///
                /// \throws boost::archive::archive_exception on deserialization
                /// errors
                ///
                /// Version-specific handling:
                /// - Pre-v3: Initialize with default values
                /// - v3/v4: Load basic fields, convert time offset
                /// - v5+: Load type system and additional fields
                /// - v9+: Load MIME type support
                ///
                /// \note Containers are reconstructed from vectors
                /// \note Default values provided for fields not in older
                /// versions
                ///
                /// \warning The caller must ensure the archive contains valid
                /// FrProcData data
                template < class Archive >
                void
                load( Archive& ar, const unsigned int version )
                {
                    using boost::serialization::make_nvp;

                    if ( version >= 3 )
                    {
                        // Basic fields available in all versions
                        ar& make_nvp( "name", name ) &
                            make_nvp( "comment", comment );

                        if ( version >= 5 )
                        {
                            // v5+ added type classification
                            ar& make_nvp( "type", type ) &
                                make_nvp( "subType", subType ) &
                                make_nvp( "timeOffset", timeOffset ) &
                                make_nvp( "tRange", tRange ) &
                                make_nvp( "fShift", fShift ) &
                                make_nvp( "phase", phase ) &
                                make_nvp( "fRange", fRange ) &
                                make_nvp( "BW", BW );

                            // Auxiliary parameters (v5+)
                            ar& make_nvp( "auxParam", auxParam );
                        }
                        else
                        {
                            v3::sampleRate_type sampleRate{ 0 };

                            // v3/v4: Basic fields with separate time offset
                            ar& make_nvp( "sampleRate", sampleRate );

                            INT_4U timeOffsetS, timeOffsetN;
                            ar&    make_nvp( "timeOffsetS", timeOffsetS ) &
                                make_nvp( "timeOffsetN", timeOffsetN ) &
                                make_nvp( "fShift", fShift );

                            // Convert to v5+ format
                            timeOffset = ConvertToRealSeconds( timeOffsetS,
                                                               timeOffsetN );

                            // Initialize v5+ fields with defaults
                            type = 0; // UNKNOWN_TYPE
                            subType = 0; // UNKNOWN_SUB_TYPE
                            tRange = 0.0;
                            phase = 0.0;
                            fRange = 0.0;
                            BW = 0.0;
                            auxParam.clear( );
                        }

#if WORKING
                        // v9+ MIME support
                        if ( version >= 9 )
                        {
                            ar& make_nvp( "mimeType", mimeType );

                            std::vector< CHAR_U > mimeData_vector;
                            ar& make_nvp( "mimeData", mimeData_vector );
                            mimeData.assign( mimeData_vector.begin( ),
                                             mimeData_vector.end( ) );
                        }
                        else
                        {
                            mimeType.clear( );
                            mimeData.clear( );
                        }
#endif /* WORKING */

                        // Reconstruct containers from vectors
                        std::vector< FrVect > data_vector;
                        ar&                   make_nvp( "data", data_vector );
                        data.clear( );
                        for ( const auto& item : data_vector )
                        {
                            auto new_item = boost::shared_ptr< FrVect >(
                                new FrVect( item ) );
                            data.push_back( new_item );
                        }

                        std::vector< FrVect > aux_vector;
                        ar&                   make_nvp( "aux", aux_vector );
                        aux.clear( );
                        for ( const auto& item : aux_vector )
                        {
                            auto new_item = boost::shared_ptr< FrVect >(
                                new FrVect( item ) );
                            aux.push_back( new_item );
                        }

                        // Table container (v4+)
                        if ( version >= 4 )
                        {
                            std::vector< FrTable > table_vector;
                            ar& make_nvp( "table", table_vector );
                            table.clear( );
                            for ( const auto& item : table_vector )
                            {
                                auto new_item = boost::shared_ptr< FrTable >(
                                    new FrTable( item ) );
                                table.push_back( new_item );
                            }
                        }
                        else
                        {
                            table.clear( );
                        }

                        // History container (v5+)
                        if ( version >= 5 )
                        {
                            std::vector< FrHistory > history_vector;
                            ar& make_nvp( "history", history_vector );
                            history.clear( );
                            for ( const auto& item : history_vector )
                            {
                                auto new_item = boost::shared_ptr< FrHistory >(
                                    new FrHistory( item ) );
                                history.push_back( new_item );
                            }
                        }
                        else
                        {
                            history.clear( );
                        }
                    }
                    else
                    {
                        // Pre-v3: Initialize with default values
                        name.clear( );
                        comment.clear( );
                        type = 0;
                        subType = 0;
                        timeOffset = 0.0;
                        tRange = 0.0;
                        fShift = 0.0;
                        phase = 0.0;
                        fRange = 0.0;
                        BW = 0.0;
                        auxParam.clear( );
                        data.clear( );
                        aux.clear( );
                        table.clear( );
                        history.clear( );
                    }
                }

                /// \brief Boost.Serialization split member macro
                ///
                /// This macro splits the serialize() method into separate
                /// save() and load() methods, allowing for different logic
                /// during serialization and deserialization. This is essential
                /// for handling the complex evolution of FrProcData.
                BOOST_SERIALIZATION_SPLIT_MEMBER( )
            };

        } // namespace FrProcDataImpl
    } // namespace Universal
} // namespace FrameCPP

/// \brief Specify the class version for Boost.Serialization
///
/// This macro tells Boost.Serialization that FrProcDataImpl::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::FrProcDataImpl::Data, 9 )
BOOST_CLASS_VERSION( FrameCPP::Universal::FrProcData, 9 )

#endif /* !defined( SWIGIMPORTED ) */

#endif /* FrameCPP_UNIVERSAL__IMPL__FrProcDataSerialIO_HH */
