//
// 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__FrEndOfFileClassicIO_HH
#define FrameCPP_UNIVERSAL__IMPL__FrEndOfFileClassicIO_HH

#ifndef SWIGIMPORTED
#include <string>
#include <vector>
#endif /* SWIGIMPORTED */

#if !defined( SWIGIMPORTED )
#include "ldastoolsal/types.hh"

#include "framecpp/Common/FrameStream.hh"

#include "framecpp/Universal/impl/FrameSpec.hh"
#include "framecpp/Universal/impl/FrEndOfFileData.hh"

#endif /* ! defined(SWIGIMPORTED) */

#if !defined( SWIGIMPORTED )
#include "framecpp/Common/Description.hh"

#include "framecpp/Common/FrEndOfFile.hh"

#endif /* !defined( SWIGIMPORTED ) */

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

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

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

        namespace FrEndOfFileImpl
        {
            template < typename T >
            class ClassicIO : public virtual Data, public Common::FrEndOfFile
            {
            public:
                // Type definitions required by FrameSpec templates
                typedef T               self_type;
                typedef Common::IStream istream_type;
                typedef Common::OStream ostream_type;

                // Bring base class GetDescription methods into scope
                using FrameCPP::Common::FrameSpec::Object::GetDescription;

                //-------------------------------------------------------
                //
                //-------------------------------------------------------
                ClassicIO( ) : Common::FrEndOfFile( nullptr, 0 )
                {
                }

                virtual const char*
                ObjectStructName( ) const
                {
                    return ( FrameSpecName( ) );
                }

                //-----------------------------------------------------------------
                /// \brief Number of bytes needed to write this structure
                ///
                /// \param[in] Stream
                ///     The stream from which to the object is being read or
                ///     written.
                ///
                /// \return
                ///     The number of bytes need to read or write this object.
                //-----------------------------------------------------------------
                virtual Common::FrameSpec::size_type
                Bytes( const Common::StreamBase& Stream ) const
                {
                    auto frame_stream_version = StreamVersion( Stream );
                    logger_log_debug(logger(), 10, "FrEndOfFile::Bytes() entry, stream version ", frame_stream_version);

                    Common::FrameSpec::size_type nframes_bytes = sizeof( nFrames );
                    logger_log_debug(logger(), 20, "FrEndOfFile::Bytes() nFrames field: ", nframes_bytes, " bytes");

                    Common::FrameSpec::size_type nbytes_bytes = ( frame_stream_version >= 5 ) 
                        ? sizeof( nBytes ) 
                        : sizeof( v3::nBytes_type );
                    logger_log_debug(logger(), 20, "FrEndOfFile::Bytes() nBytes field: ", nbytes_bytes, " bytes (v", frame_stream_version, " format)");

                    Common::FrameSpec::size_type toc_bytes = ( frame_stream_version >= 4 ) 
                        ? ( ( frame_stream_version >= 5 ) ? sizeof( seekTOC ) : sizeof( v4::seekTOC_type ) ) 
                        : 0;
                    logger_log_debug(logger(), 20, "FrEndOfFile::Bytes() TOC field: ", toc_bytes, " bytes");

                    Common::FrameSpec::size_type toc_checksum_bytes = ( frame_stream_version >= 9 ) 
                        ? sizeof( chkSumTOC ) 
                        : 0;
                    logger_log_debug(logger(), 20, "FrEndOfFile::Bytes() TOC checksum field: ", toc_checksum_bytes, " bytes");

                    Common::FrameSpec::size_type header_checksum_bytes = ( frame_stream_version >= 8 ) 
                        ? sizeof( chkSumFrHeader ) 
                        : 0;
                    logger_log_debug(logger(), 20, "FrEndOfFile::Bytes() FrHeader checksum field: ", header_checksum_bytes, " bytes");

                    Common::FrameSpec::size_type struct_checksum_bytes = ( frame_stream_version >= 8 ) 
                        ? sizeof( chkSum ) 
                        : 0;
                    logger_log_debug(logger(), 20, "FrEndOfFile::Bytes() structure checksum field: ", struct_checksum_bytes, " bytes");

                    Common::FrameSpec::size_type legacy_flag_bytes = ( ( frame_stream_version >= 3 ) && ( frame_stream_version < 5 ) ) 
                        ? sizeof( v3::chkFlag_type ) 
                        : 0;
                    logger_log_debug(logger(), 20, "FrEndOfFile::Bytes() legacy chkFlag field: ", legacy_flag_bytes, " bytes");

                    Common::FrameSpec::size_type legacy_type_bytes = ( ( frame_stream_version >= 5 ) && ( frame_stream_version < 8 ) ) 
                        ? sizeof( v5::chkType_type ) 
                        : 0;
                    logger_log_debug(logger(), 20, "FrEndOfFile::Bytes() legacy chkType field: ", legacy_type_bytes, " bytes");

                    Common::FrameSpec::size_type file_checksum_bytes = sizeof( chkSumFile );
                    logger_log_debug(logger(), 20, "FrEndOfFile::Bytes() file checksum field: ", file_checksum_bytes, " bytes");

                    Common::FrameSpec::size_type retval = nframes_bytes + nbytes_bytes + toc_bytes + toc_checksum_bytes + 
                                                          header_checksum_bytes + struct_checksum_bytes + 
                                                          legacy_flag_bytes + legacy_type_bytes + file_checksum_bytes;

                    logger_log_debug(logger(), 30, "FrEndOfFile::Bytes() total: ", retval, " bytes for stream version ", frame_stream_version);
                    return retval;
                }

                //-----------------------------------------------------------------
                /// @brief The description of structure
                ///
                /// @param[in,out] Stream
                ///   The output stream on which to write the description
                ///
                /// @return
                ///     A Description object which describes this structure as
                ///     specified by the frame specification.
                //-----------------------------------------------------------------
                virtual const Common::Description*
                GetDescription( unsigned int version ) const;

                //-----------------------------------------------------------------
                /// \brief Write the structure to the stream
                ///
                /// \param[in] Stream
                ///     The output stream where the object is to be written.
                //-----------------------------------------------------------------
                virtual void
                Write( ostream_type& Stream ) const
                {
                    auto frame_stream_version = StreamVersion( Stream );
                    logger_log_debug(logger(), 10, "FrEndOfFile::Write() entry, stream version ", frame_stream_version);

                    //---------------------------------------------------------------------
                    // Get local information
                    //---------------------------------------------------------------------

                    const nFrames_type nFrames( Stream.GetNumberOfFrames( ) );
                    logger_log_debug(logger(), 20, "FrEndOfFile::Write() nFrames from stream: ", nFrames);

                    const nBytes_type  nBytes(
                        Stream.tellp( ) + std::streampos( Bytes( Stream ) ) );
                    logger_log_debug(logger(), 20, "FrEndOfFile::Write() nBytes calculated: ", nBytes, " (position + structure size)");

                    const seekTOC_type seekTOC( ( Stream.GetTOCOffset( ) > 0 )
                                                    ? nBytes -
                                                        Stream.GetTOCOffset( )
                                                    : 0 );
                    logger_log_debug(logger(), 20, "FrEndOfFile::Write() seekTOC calculated: ", seekTOC, " (TOC offset: ", Stream.GetTOCOffset(), ")");

                    chkSumTOC = Stream.GetTOCChecksum( );
                    logger_log_debug(logger(), 20, "FrEndOfFile::Write() TOC checksum: ", pretty_print_checksum(chkSumTOC));

                    chkSumFrHeader_type chkSumFrHeader =
                        getFrHeaderChecksum( Stream );
                    logger_log_debug(logger(), 20, "FrEndOfFile::Write() FrHeader checksum: ", pretty_print_checksum(chkSumFrHeader));

                    chkSum_type     chkSum = 0;
                    chkSumFile_type chkSumFile = 0;

                    //---------------------------------------------------------------------
                    // Write out to the stream
                    //---------------------------------------------------------------------
                    auto pos_before_write = Stream.tellp();
                    logger_log_debug(logger(), 30, "FrEndOfFile::Write() writing fields to stream at position: ", pos_before_write);

                    Stream << nFrames;
                    logger_log_debug(logger(), 40, "FrEndOfFile::Write() wrote nFrames: ", nFrames, " (pos: ", Stream.tellp(), ")");

                    if ( frame_stream_version >= 5 )
                    {
                        Stream << nBytes;
                        logger_log_debug(logger(), 40, "FrEndOfFile::Write() wrote nBytes (v5+): ", nBytes, " (pos: ", Stream.tellp(), ")");
                    }
                    else if ( frame_stream_version >= 3 )
                    {
                        v3::nBytes_type nBytes_v3 = v3::nBytes_type( nBytes );
                        Stream << nBytes_v3;
                        logger_log_debug(logger(), 40, "FrEndOfFile::Write() wrote nBytes (v3 format): ", nBytes_v3, " (pos: ", Stream.tellp(), ")");
                    }

                    if ( frame_stream_version >= 8 )
                    {
                        Stream << seekTOC;
                        logger_log_debug(logger(), 40, "FrEndOfFile::Write() wrote seekTOC: ", seekTOC, " (pos: ", Stream.tellp(), ")");

                        if ( frame_stream_version >= 9 )
                        {
                            Stream << chkSumTOC;
                            logger_log_debug(logger(), 40, "FrEndOfFile::Write() wrote chkSumTOC: ", pretty_print_checksum(chkSumTOC), " (pos: ", Stream.tellp(), ")");
                        }

                        Stream << chkSumFrHeader;
                        logger_log_debug(logger(), 40, "FrEndOfFile::Write() wrote chkSumFrHeader: ", pretty_print_checksum(chkSumFrHeader), " (pos: ", Stream.tellp(), ")");

                        if ( Stream.GetCheckSumObject( ) &&
                             Stream.GetCheckSumObject( )->GetChecksum( ) )
                        {
                            chkSum = Stream.GetCheckSumObject( )->Value( );
                        }
                        Stream << chkSum;
                        logger_log_debug(logger(), 40, "FrEndOfFile::Write() wrote structure chkSum: ", pretty_print_checksum(chkSum), " (pos: ", Stream.tellp(), ")");

                        if ( Stream.GetCheckSumFile( ) &&
                             Stream.GetCheckSumFile( )->GetChecksum( ) )
                        {
                            chkSumFile = Stream.GetCheckSumFile( )->Value( );
                        }
                        Stream << chkSumFile;
                        auto pos_after_write = Stream.tellp();
                        logger_log_debug(logger(), 40, "FrEndOfFile::Write() wrote file chkSumFile: ", pretty_print_checksum(chkSumFile), " (pos: ", pos_after_write, ")");
                        logger_log_debug(logger(), 30, "FrEndOfFile::Write() total bytes written: ", (pos_after_write - pos_before_write));
                    }
                    else if ( frame_stream_version >= 3 )
                    {
                        logger_log_debug(logger(), 30, "FrEndOfFile::Write() using legacy format (v", frame_stream_version, ")");

                        v3::chkFlag_type chkType = CheckSum::NONE;
                        chkSumFile = 0;

                        CheckSumFilter* crc = Stream.GetCheckSumFile( );
                        if ( crc )
                        {
                            chkType = crc->Type( );
                            logger_log_debug(logger(), 40, "FrEndOfFile::Write() checksum filter type: ", chkType);

                            if ( frame_stream_version >= 5 )
                            {
                                crc->Filter( &chkType, sizeof( chkType ) );
                                crc->Filter( &chkSumFile, sizeof( chkSumFile ) );
                                crc->Filter( &seekTOC, sizeof( seekTOC ) );
                                logger_log_debug(logger(), 40, "FrEndOfFile::Write() applied v5+ checksum filters");
                            }
                            else if ( frame_stream_version >= 3 )
                            {
                                if ( frame_stream_version >= 4 )
                                {
                                    auto seekTOC_v4 = v4::seekTOC_type( seekTOC );
                                    crc->Filter( &seekTOC_v4, sizeof( seekTOC_v4 ) );
                                    logger_log_debug(logger(), 40, "FrEndOfFile::Write() applied v4 seekTOC filter");
                                }
                            }
                            chkSumFile = crc->Value( );
                        }

                        Stream << chkType << chkSumFile;
                        logger_log_debug(logger(), 40, "FrEndOfFile::Write() wrote legacy chkType: ", chkType, ", chkSum: ", pretty_print_checksum(chkSumFile));

                        if ( frame_stream_version >= 5 )
                        {
                            Stream << seekTOC;
                            logger_log_debug(logger(), 40, "FrEndOfFile::Write() wrote seekTOC (v5): ", seekTOC);
                        }
                        else if ( frame_stream_version >= 4 )
                        {
                            v4::seekTOC_type seekTOC_v4 = v4::seekTOC_type( seekTOC );
                            Stream << seekTOC_v4;
                            logger_log_debug(logger(), 40, "FrEndOfFile::Write() wrote seekTOC (v4): ", seekTOC_v4);
                        }
                    }

                    logger_log_debug(logger(), 30, "FrEndOfFile::Write() completed successfully");
                }

            protected:
                //-----------------------------------------------------------------
                /// @brief Assign values from stream content;
                //-----------------------------------------------------------------
                virtual void assign( INT_4U              FrameSpecVersion,
                                     assign_stream_type& Stream );

                //-----------------------------------------------------------------
                /// \brief Instantiate from a stream
                ///
                /// \param[in] Stream
                ///     The input stream from where the object is being read.
                //-----------------------------------------------------------------
                template < typename STREAM_TYPE >
                void readFromStream( STREAM_TYPE& Stream );

            private:
                //-----------------------------------------------------------------
                /// \brief Unified field reading with policy-based validation
                ///
                /// This template method provides a single source of truth for
                /// reading FrEndOfFile fields across all frame specification
                /// versions (v3-v9). The ValidationPolicy template parameter
                /// controls whether checksum validation is performed.
                ///
                /// \tparam ValidationPolicy Policy controlling checksum behavior
                ///         (NoValidationPolicy or ChecksumValidationPolicy)
                /// \tparam StreamType Input stream type (IStringStream or
                ///         IFrameStream)
                /// \param version Frame specification version
                /// \param stream Input stream to read from
                //-----------------------------------------------------------------
                template<typename ValidationPolicy, typename StreamType>
                void readFieldsWithPolicy(INT_4U version, StreamType& stream);

                //-----------------------------------------------------------------
                /// \brief Read modern format fields (v8-v9)
                ///
                /// Reads fields in the v8-v9 layout:
                /// seekTOC → chkSumTOC → chkSumFrHeader → chkSum → chkSumFile
                ///
                /// \tparam ValidationPolicy Policy controlling validation
                /// \tparam StreamType Input stream type
                /// \param version Frame specification version
                /// \param stream Input stream to read from
                //-----------------------------------------------------------------
                template<typename ValidationPolicy, typename StreamType>
                void readModernFormat(INT_4U version, StreamType& stream);

                //-----------------------------------------------------------------
                /// \brief Read legacy format fields (v3-v7)
                ///
                /// Reads fields in the v3-v7 layout with complex filter
                /// manipulation for proper checksum calculation:
                /// chkType/chkFlag → chkSum → seekTOC
                ///
                /// \tparam ValidationPolicy Policy controlling filter manipulation
                /// \tparam StreamType Input stream type
                /// \param version Frame specification version
                /// \param stream Input stream to read from
                //-----------------------------------------------------------------
                template<typename ValidationPolicy, typename StreamType>
                void readLegacyFormat(INT_4U version, StreamType& stream);

            protected:
                //-----------------------------------------------------------------
                /// \brief Demotes object to previous version of the frame spec
                ///
                /// \param[in] Target
                ///     The version of the frame specification to demote too.
                /// \param[in] Obj
                ///     The version of the object to demote.
                /// \param[in] Stream
                ///     The input stream from which the original object was
                ///     read.
                ///
                /// \return
                ///     An object of the previous generation.
                //-----------------------------------------------------------------
                virtual demote_ret_type
                demote( INT_2U              Target,
                        demote_arg_type     Obj,
                        demote_stream_type* Stream ) const
                {
                    throw Unimplemented(
                        "Object* FrEndOfFile::demote( Object* Obj ) const",
                        DATA_FORMAT_VERSION,
                        __FILE__,
                        __LINE__ );
                    return ( nullptr );
                }

                virtual chkSumFrHeader_type
                getFrHeaderChecksum( ostream_type& Stream ) const = 0;

                //-----------------------------------------------------------------
                /// \brief Promotes object to another version of the frame spec
                ///
                /// \param[in] Target
                ///     The version of the promoted frame specification.
                /// \param[in] Obj
                ///     The object to be promoted.
                /// \param[in] Stream
                ///     The input stream from which the original object was
                ///     read.
                ///
                /// \return
                ///     An object promoted to the next generation.
                //-----------------------------------------------------------------
                virtual promote_ret_type
                promote( INT_2U               Target,
                         promote_arg_type     Obj,
                         promote_stream_type* Stream ) const
                {
                    throw Unimplemented(
                        "Object* FrEndOfFile::promote( Object* Obj ) const",
                        DATA_FORMAT_VERSION,
                        __FILE__,
                        __LINE__ );
                    return ( nullptr );
                }
            };

        } // namespace FrEndOfFileImpl
    } // namespace Universal
} // namespace FrameCPP
#endif /* ! defined(SWIGIMPORTED) */

#endif /* FrameCPP_UNIVERSAL__IMPL__FrEndOfFileClassicIO_HH */
