//
// LDASTools frameCPP - A library implementing the LIGO/Virgo frame
// specification
//
// Copyright (C) 2019 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__FrEventSerialIO_HH
#define FrameCPP_UNIVERSAL__IMPL__FrEventSerialIO_HH

#if defined( __cplusplus )

#if !defined( SWIGIMPORTED )
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/split_free.hpp>
#include <boost/serialization/version.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/utility.hpp>  // For std::pair serialization
#include <boost/serialization/shared_ptr.hpp>
#endif /* !defined( SWIGIMPORTED ) */

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

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

#if !defined( SWIGIMPORTED )
namespace FrameCPP
{
    namespace Universal
    {
        class FrEvent;

        namespace FrEventImpl
        {
            /// \brief Serialization I/O implementation for FrEvent data
            /// structures
            ///
            /// This class provides versioned serialization support for FrEvent
            /// objects using Boost.Serialization. It handles the complex evolution
            /// of the FrEvent data structure across different frame specification
            /// versions (v3 through v9).
            ///
            /// \tparam FR_EVENT_T The FrEvent type being serialized (typically
            /// FrEvent)
            template < typename FR_EVENT_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 FrEvent data to an archive
                ///
                /// This method saves the FrEvent data to a serialization archive,
                /// handling version-specific data formats and field evolution.
                /// FrEvent has significant changes across versions with new fields
                /// and type modifications.
                ///
                /// \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, inputs, GTime, etc.)
                /// - v4: Adds timeBefore, timeAfter, triggerStatus fields
                /// - v5: Renames triggerStatus to eventStatus, adds parameters
                /// - v7: Changes paramValue_type from REAL_4 to REAL_8
                /// - v8-9: Stable structure
                ///
                /// \note Complex containers (data, table) require special handling
                /// \note Parameter arrays added in v5+
                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 ) &
                            make_nvp( "inputs", inputs ) &
                            make_nvp( "GTime", GTime );

                        // Fields added in v4+
                        if ( version >= 4 )
                        {
                            ar& make_nvp( "timeBefore", timeBefore ) &
                                make_nvp( "timeAfter", timeAfter );

                            // triggerStatus in v4, eventStatus in v5+
                            if ( version >= 5 )
                            {
                                ar& make_nvp( "eventStatus", eventStatus );
                            }
                            else
                            {
                                ar& make_nvp( "triggerStatus", eventStatus );
                            }

                            ar& make_nvp( "amplitude", amplitude ) &
                                make_nvp( "probability", probability ) &
                                make_nvp( "statistics", statistics );

                            // Parameters added in v5+
                            if ( version >= 5 )
                            {
                                // Handle parameter type changes (v7: REAL_4 -> REAL_8)
                                if ( version >= 7 )
                                {
                                    ar& make_nvp( "Params", Params );
                                }
                                else
                                {
                                    // Convert REAL_8 to REAL_4 for older versions
                                    std::vector< std::pair< std::string, REAL_4 > > params_v5;
                                    for ( const auto& param : Params )
                                    {
                                        params_v5.emplace_back( param.first,
                                                                static_cast< REAL_4 >( param.second ) );
                                    }
                                    ar& make_nvp( "Params", params_v5 );
                                }
                            }

                            // Data and table containers (v4+)
                            // Convert SearchContainer to vector 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< 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 );
                        }
                    }
                }

                /// \brief Deserialize FrEvent data from an archive
                ///
                /// This method loads FrEvent data from a serialization archive,
                /// handling version-specific data formats and field evolution.
                /// It properly initializes newer fields for older 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: Load basic fields, initialize v4+ fields with defaults
                /// - v4: Load timing fields, initialize v5+ fields
                /// - v5+: Load parameter arrays and status fields
                /// - v7+: Handle REAL_8 parameter values
                ///
                /// \note SearchContainers are reconstructed from vectors
                /// \note Type conversions handle parameter value evolution
                ///
                /// \warning The caller must ensure the archive contains valid
                /// FrEvent 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 ) &
                            make_nvp( "inputs", inputs ) &
                            make_nvp( "GTime", GTime );

                        // Fields added in v4+
                        if ( version >= 4 )
                        {
                            ar& make_nvp( "timeBefore", timeBefore ) &
                                make_nvp( "timeAfter", timeAfter );

                            // triggerStatus in v4, eventStatus in v5+
                            if ( version >= 5 )
                            {
                                ar& make_nvp( "eventStatus", eventStatus );
                            }
                            else
                            {
                                ar& make_nvp( "triggerStatus", eventStatus );
                            }

                            ar& make_nvp( "amplitude", amplitude ) &
                                make_nvp( "probability", probability ) &
                                make_nvp( "statistics", statistics );

                            // Parameters added in v5+
                            if ( version >= 5 )
                            {
                                // Handle parameter type changes (v7: REAL_4 -> REAL_8)
                                if ( version >= 7 )
                                {
                                    ar& make_nvp( "Params", Params );
                                }
                                else
                                {
                                    // Convert REAL_4 to REAL_8 for current version
                                    std::vector< std::pair< std::string, REAL_4 > > params_v5;
                                    ar& make_nvp( "Params", params_v5 );
                                    Params.clear( );
                                    for ( const auto& param : params_v5 )
                                    {
                                        Params.emplace_back( param.first,
                                                             static_cast< REAL_8 >( param.second ) );
                                    }
                                }
                            }
                            else
                            {
                                // v3/v4: Initialize parameters with empty list
                                Params.clear( );
                            }

                            // Data and table containers (v4+)
                            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< 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
                        {
                            // v3: Initialize v4+ fields with defaults
                            timeBefore = 0.0;
                            timeAfter = 0.0;
                            eventStatus = 0;
                            amplitude = 0.0;
                            probability = -1.0; // Default "unknown" probability
                            statistics.clear( );
                            Params.clear( );
                            data.clear( );
                            table.clear( );
                        }
                    }
                    else
                    {
                        // Pre-v3: Initialize all fields with defaults
                        name.clear( );
                        comment.clear( );
                        inputs.clear( );
                        GTime = GTime_type( );
                        timeBefore = 0.0;
                        timeAfter = 0.0;
                        eventStatus = 0;
                        amplitude = 0.0;
                        probability = -1.0;
                        statistics.clear( );
                        Params.clear( );
                        data.clear( );
                        table.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 version evolution of FrEvent.
                BOOST_SERIALIZATION_SPLIT_MEMBER( )
            };

        } // namespace FrEventImpl
    } // namespace Universal
} // namespace FrameCPP

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

#endif /* !defined( SWIGIMPORTED ) */

#endif /* defined( __cplusplus ) */

#endif /* FrameCPP_UNIVERSAL__IMPL__FrEventSerialIO_HH */
