//
// 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__DimensionSerialIO_HH
#define FrameCPP_UNIVERSAL__IMPL__DimensionSerialIO_HH

#include <boost/serialization/nvp.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/split_free.hpp>
#include <boost/serialization/version.hpp>

#include "framecpp/Common/FrameSpec.hh"
#include "framecpp/Universal/impl/DimensionData.hh"

namespace FrameCPP
{
    namespace Universal
    {
        class Dimension;

        namespace DimensionImpl
        {
            template < typename DIMENSION_T >
            class SerialIO : public virtual Data
            {
            public:
                SerialIO( )
                {
                }
                
                SerialIO( const SerialIO& ) = default;
                SerialIO( SerialIO&& ) = default;
                SerialIO& operator=( const SerialIO& ) = delete;
                SerialIO& operator=( SerialIO&& ) = delete;

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

                template < class Archive >
                void
                serialize( Archive& archiver, const unsigned int version )
                {
                    archiver& boost::serialization::base_object< Data >( *this );
                }
            };
        } // namespace DimensionImpl
    } // namespace Universal
} // namespace FrameCPP

namespace boost
{
    namespace serialization
    {
        /// \brief Serialize Dimension data to an archive
        ///
        /// This function saves Dimension data to a serialization archive,
        /// handling version-specific data formats and type conversions. It
        /// ensures backward compatibility by converting current version data
        /// to the appropriate format for the target version.
        ///
        /// \tparam Archive The Boost.Serialization archive type
        ///
        /// \param[in,out] archiver The archive to write data to
        /// \param[in] dimension The Dimension data object to serialize
        /// \param[in] version The target serialization version (3-9)
        ///
        /// \throws boost::archive::archive_exception on serialization errors
        ///
        /// Version-specific handling:
        /// - v3: Uses INT_4U for nx field, no startX field
        /// - v4: Adds startX field, still uses INT_4U for nx
        /// - v5+: Uses INT_8U for nx field, includes startX field
        ///
        /// \note All versions include dx and unitX fields
        /// \note startX defaults to 0.0 for versions prior to v4
        template < class Archive >
        void
        save( Archive&                                        archiver,
              const FrameCPP::Universal::DimensionImpl::Data& dimension,
              const unsigned int                              version )
        {
            using boost::serialization::make_nvp;
            using FrameCPP::Universal::DimensionImpl::Data;
            using FrameCPP::Common::FrameSpec;

            // Handle nx field based on target version
            if ( version >= 5 )
            {
                // v5+ uses INT_8U (current version)
                auto nx{ dimension.GetNx( ) };
                archiver& make_nvp( "nx", nx );
            }
            else if ( version >= 3 )
            {
                // v3/v4 use INT_4U
                Data::v3::nx_type nx_v3 =
                    static_cast< Data::v3::nx_type >( dimension.GetNx( ) );
                archiver& make_nvp( "nx", nx_v3 );
            }

            // dx field (same for all versions)
            auto dx{ dimension.GetDx( ) };
            archiver& make_nvp( "dx", dx );

            // startX field (v4+ only)
            if ( version >= 4 )
            {
                auto startX{ dimension.GetStartX( ) };
                archiver& make_nvp( "startX", startX );
            }

            // unitX field (same for all versions)
            auto unitX{ static_cast< const std::string& >(
                dimension.GetUnitX( ) ) };
            archiver& make_nvp( "unitX", unitX );
        }

        /// \brief Deserialize Dimension data from an archive
        ///
        /// This function loads Dimension data from a serialization archive,
        /// handling version-specific data formats and type conversions. It
        /// ensures forward compatibility by converting older version data
        /// to the current version format.
        ///
        /// \tparam Archive The Boost.Serialization archive type
        ///
        /// \param[in,out] archiver The archive to read data from
        /// \param[out] dimension The Dimension data object to populate
        /// \param[in] version The source serialization version (3-9)
        ///
        /// \throws boost::archive::archive_exception on deserialization errors
        ///
        /// Version-specific handling:
        /// - Pre-v3: Initializes with default values
        /// - v3: Loads INT_4U nx field, sets startX to 0.0
        /// - v4: Loads INT_4U nx and startX fields
        /// - v5+: Loads INT_8U nx field and startX field
        ///
        /// \note For versions < v4, startX is initialized to 0.0
        /// \note Type conversions are performed automatically for nx field
        ///
        /// \warning The caller must ensure the archive contains valid
        /// Dimension data
        template < class Archive >
        void
        load( Archive&                                  archiver,
              FrameCPP::Universal::DimensionImpl::Data& dimension,
              const unsigned int                        version )
        {
            using boost::serialization::make_nvp;
            using FrameCPP::Universal::DimensionImpl::Data;

            // Handle nx field based on source version
            if ( version >= 5 )
            {
                // v5+ uses INT_8U (current version)
                Data::nx_type nx;
                archiver&     make_nvp( "nx", nx );
                dimension.SetNx( nx );
            }
            else if ( version >= 3 )
            {
                // v3/v4 use INT_4U
                Data::v3::nx_type nx_v3;
                archiver&         make_nvp( "nx", nx_v3 );
                dimension.SetNx(
                    static_cast< Data::nx_type >( nx_v3 ) );
            }
            else
            {
                // Pre-v3, default value
                dimension.SetNx( 0 );
            }

            // dx field (same for all versions)
            if ( version >= 3 )
            {
                Data::dx_type dx;
                archiver&     make_nvp( "dx", dx );
                dimension.SetDx( dx );
            }
            else
            {
                // Pre-v3, default value
                dimension.SetDx( 1.0 );
            }

            // startX field (v4+ only)
            if ( version >= 4 )
            {
                Data::startX_type startX;
                archiver&         make_nvp( "startX", startX );
                dimension.SetStartX( startX );
            }
            else
            {
                // v3 and earlier, default value
                dimension.SetStartX( 0.0 );
            }

            // unitX field (same for all versions)
            if ( version >= 3 )
            {
                std::string unitX;
                archiver&   make_nvp( "unitX", unitX );
                dimension.SetUnitX( unitX );
            }
            else
            {
                // Pre-v3, default value
                dimension.SetUnitX( "" );
            }
        }

    } // namespace serialization
} // namespace boost

/// \brief Split serialization for Dimension data
///
/// This macro tells Boost.Serialization to use separate save() and load()
/// functions for the DimensionImpl::Data class, allowing for different logic
/// during serialization and deserialization. This is necessary for handling
/// version differences and type conversions.
BOOST_SERIALIZATION_SPLIT_FREE( FrameCPP::Universal::DimensionImpl::Data )

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

#endif /* FrameCPP_UNIVERSAL__IMPL__DimensionSerialIO_HH */
