//
// 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__FrameHSerialIO_HH
#define FrameCPP_UNIVERSAL__IMPL__FrameHSerialIO_HH

#if !defined( SWIGIMPORTED )
#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"
#endif /* !defined( SWIGIMPORTED ) */

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

#if !defined( SWIGIMPORTED )
namespace FrameCPP
{
    namespace Universal
    {
        class FrameH;
        namespace FrameHImpl
        {
            /// \brief Serialization I/O implementation for FrameH data structures
            ///
            /// This class provides versioned serialization support for FrameH
            /// objects using Boost.Serialization. It handles the evolution of
            /// the FrameH data structure across different frame specification
            /// versions (v3 through v9).
            ///
            /// \tparam FRAME_H_T The FrameH type being serialized (typically FrameH)
            template < typename FRAME_H_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/deserialize FrameH data
                ///
                /// This unified method handles both serialization and deserialization
                /// of FrameH data, managing the complex version-specific field evolution
                /// and container handling across frame specification versions v3-v9.
                ///
                /// \tparam Archive The Boost.Serialization archive type
                ///
                /// \param[in,out] ar The archive to read from or write to
                /// \param[in] version The serialization version (3-9)
                ///
                /// \throws boost::archive::archive_exception on serialization
                /// errors
                ///
                /// Version-specific handling:
                /// - v3: Basic header fields with ULeapS and localTime
                /// - v4: Adds dataQuality, simEvent, auxTable containers
                /// - v5: Removes localTime, renames trigData to event
                /// - v9: Removes ULeapS field
                ///
                /// \note SearchContainers are converted to/from vectors for serialization
                /// \note Field evolution and default values handled automatically
                template < class Archive >
                void
                serialize( Archive& ar, const unsigned int version )
                {
                    using boost::serialization::make_nvp;

                    if ( version >= 3 )
                    {
                        // Basic frame header fields
                        ar& make_nvp( "name", name ) &
                            make_nvp( "run", run ) &
                            make_nvp( "frame", frame ) &
                            make_nvp( "GTime", GTime ) &
                            make_nvp( "dt", dt );

                        // Data quality field (v4+)
                        if ( version >= 4 )
                        {
                            ar& make_nvp( "dataQuality", dataQuality );
                        }
                        else if ( typename Archive::is_loading() )
                        {
                            dataQuality = 0;
                        }

                        // ULeapS field (v3-v8 only, removed in v9)
                        if ( version <= 8 )
                        {
                            if ( typename Archive::is_saving() )
                            {
                                // For saving (backward compatibility), use a default value
                                v3::ULeapS_type ULeapS_compat = 0;
                                ar& make_nvp( "ULeapS", ULeapS_compat );
                            }
                            else
                            {
                                // For loading, read but discard (no longer stored in v9)
                                v3::ULeapS_type ULeapS_compat;
                                ar& make_nvp( "ULeapS", ULeapS_compat );
                            }
                        }

                        // Local time field (v3-v4 only, removed in v5+)
                        if ( version >= 3 && version <= 4 )
                        {
                            if ( typename Archive::is_saving() )
                            {
                                // For saving (backward compatibility), use a default value
                                v3::localTime_type localTime_compat = 0;
                                ar& make_nvp( "localTime", localTime_compat );
                            }
                            else
                            {
                                // For loading, read but discard (no longer stored in v5+)
                                v3::localTime_type localTime_compat;
                                ar& make_nvp( "localTime", localTime_compat );
                            }
                        }

                        // Raw data pointer
                        ar& make_nvp( "rawData", rawData );

                        // Handle SearchContainers - save/load using different methods
                        if ( typename Archive::is_saving() )
                        {
                            // Convert SearchContainers to vectors for serialization
                            convertContainerToVector( ar, "type", type );
                            convertContainerToVector( ar, "user", user );
                            convertContainerToVector( ar, "detectSim", detectSim );
                            convertContainerToVector( ar, "detectProc", detectProc );
                            convertContainerToVector( ar, "history", history );
                            convertContainerToVector( ar, "procData", procData );
                            convertContainerToVector( ar, "simData", simData );
                        }
                        else
                        {
                            // Reconstruct SearchContainers from vectors
                            reconstructContainerFromVector( ar, "type", type );
                            reconstructContainerFromVector( ar, "user", user );
                            reconstructContainerFromVector( ar, "detectSim", detectSim );
                            reconstructContainerFromVector( ar, "detectProc", detectProc );
                            reconstructContainerFromVector( ar, "history", history );
                            reconstructContainerFromVector( ar, "procData", procData );
                            reconstructContainerFromVector( ar, "simData", simData );
                        }

                        // Event containers with version-specific naming
                        if ( version >= 5 )
                        {
                            if ( typename Archive::is_saving() )
                            {
                                convertContainerToVector( ar, "event", event );
                            }
                            else
                            {
                                reconstructContainerFromVector( ar, "event", event );
                            }
                        }
                        else if ( version >= 3 )
                        {
                            // In v3-v4, event was called trigData
                            if ( typename Archive::is_saving() )
                            {
                                convertContainerToVector( ar, "trigData", event );
                            }
                            else
                            {
                                reconstructContainerFromVector( ar, "trigData", event );
                            }
                        }

                        // SimEvent container (v4+)
                        if ( version >= 4 )
                        {
                            if ( typename Archive::is_saving() )
                            {
                                convertContainerToVector( ar, "simEvent", simEvent );
                            }
                            else
                            {
                                reconstructContainerFromVector( ar, "simEvent", simEvent );
                            }
                        }
                        else if ( typename Archive::is_loading() )
                        {
                            simEvent.clear( );
                        }

                        // Summary and auxiliary data
                        if ( typename Archive::is_saving() )
                        {
                            convertContainerToVector( ar, "summaryData", summaryData );
                            convertContainerToVector( ar, "auxData", auxData );
                        }
                        else
                        {
                            reconstructContainerFromVector( ar, "summaryData", summaryData );
                            reconstructContainerFromVector( ar, "auxData", auxData );
                        }

                        // AuxTable container (v4+)
                        if ( version >= 4 )
                        {
                            if ( typename Archive::is_saving() )
                            {
                                convertContainerToVector( ar, "auxTable", auxTable );
                            }
                            else
                            {
                                reconstructContainerFromVector( ar, "auxTable", auxTable );
                            }
                        }
                        else if ( typename Archive::is_loading() )
                        {
                            auxTable.clear( );
                        }
                    }
                    else if ( typename Archive::is_loading() )
                    {
                        // Pre-v3: Only relevant for loading - initialize with default values
                        name.clear( );
                        run = 0;
                        frame = 0;
                        dataQuality = 0;
                        GTime = current_version::GTime_type( );
                        dt = 0.0;
                        rawData.reset( );
                        
                        type.clear( );
                        user.clear( );
                        detectSim.clear( );
                        detectProc.clear( );
                        history.clear( );
                        procData.clear( );
                        simData.clear( );
                        event.clear( );
                        simEvent.clear( );
                        summaryData.clear( );
                        auxData.clear( );
                        auxTable.clear( );
                    }
                }

                /// \brief Helper method to convert SearchContainer to vector for serialization
                template < class Archive, typename ContainerType >
                void convertContainerToVector( Archive& ar, const char* name, const ContainerType& container ) const
                {
                    using boost::serialization::make_nvp;
                    using value_type = typename ContainerType::value_type::element_type;
                    
                    std::vector< value_type > vector_data;
                    vector_data.reserve( container.size( ) );
                    for ( const auto& item : container )
                    {
                        if ( item )
                        {
                            vector_data.push_back( *item );
                        }
                    }
                    ar& make_nvp( name, vector_data );
                }

                /// \brief Helper method to reconstruct SearchContainer from vector
                template < class Archive, typename ContainerType >
                void reconstructContainerFromVector( Archive& ar, const char* name, ContainerType& container )
                {
                    using boost::serialization::make_nvp;
                    using value_type = typename ContainerType::value_type::element_type;
                    
                    std::vector< value_type > vector_data;
                    ar& make_nvp( name, vector_data );
                    container.clear( );
                    for ( const auto& item : vector_data )
                    {
                        auto new_item = boost::shared_ptr< value_type >( new value_type( item ) );
                        container.push_back( new_item );
                    }
                }

                // No split member macro needed - using unified serialize() method
            };

        } // namespace FrameHImpl
    } // namespace Universal
} // namespace FrameCPP

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

#endif /* !defined( SWIGIMPORTED ) */

#endif /* FrameCPP_UNIVERSAL__IMPL__FrameHSerialIO_HH */