//
// 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__FrEndOfFileData_HH
#define FrameCPP_UNIVERSAL__IMPL__FrEndOfFileData_HH

#include "ldastoolsal/types.hh"
#include "ldastoolsal/Logger.hh"

#include <sstream>  // For std::ostringstream in policy validation

// Forward declaration to avoid circular dependencies
namespace FrameCPP {
namespace Common {
    struct FrameCPPServices;
}
}

#if !defined( SWIGIMPORTED )

#include "framecpp/Common/CheckSumFilter.hh"

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

using FrameCPP::Common::CheckSum;
using FrameCPP::Common::CheckSumFilter;
using FrameCPP::Common::ChecksumState;

namespace FrameCPP
{
    namespace Universal
    {
        // Forward declaration of helper function defined in FrEndOfFile.cc
        // Returns INT_4U (chkSumFrHeader_type)
        template<typename StreamType>
        inline INT_4U
        calculateFrHeaderChecksumForValidation(const StreamType& stream, unsigned int version);

        namespace FrEndOfFileImpl
        {
          class Data: public Impl::FrObjectMetaData< Data >
            {
            public:
              struct v3 {
                using nFrames_type = INT_4U;
                using nBytes_type = INT_4U;
                using chkFlag_type = INT_4U;
                using chkSum_type = INT_4U;
              };

              struct v4 {
                using nFrames_type = v3::nFrames_type;
                using nBytes_type = v3::nBytes_type;
                using chkFlag_type = v3::chkFlag_type;
                using chkSum_type = v3::chkSum_type;
                using seekTOC_type = INT_4U;
              };

              struct v5 {
                using nFrames_type = v4::nFrames_type;
                using nBytes_type = INT_8U;
                using chkType_type = v4::chkFlag_type;
                using chkSum_type = v4::chkSum_type;
                using seekTOC_type =  INT_8U;
              };

              using v6 = v5;
              using v7 = v6;

              struct v8 {
                using nFrames_type = v7::nFrames_type;
                using nBytes_type = v7::nBytes_type;
                using seekTOC_type = v7::seekTOC_type;
                using chkSumFrHeader_type = INT_4U;
                using chkSum_type = INT_4U;
                using chkSumFile_type = v7::chkSum_type;
              };

              struct v9 {
                using nFrames_type = v8::nFrames_type;
                using nBytes_type = v8::nBytes_type;
                using seekTOC_type = v8::seekTOC_type;
                using chkSumTOC_type = INT_4U;
                using chkSumFrHeader_type = v8::chkSumFrHeader_type;
                using chkSum_type = v8::chkSum_type;
                using chkSumFile_type = v8::chkSumFile_type;
              };

              //-------------------------------------------------------

              using current_version = v9;

              using nFrames_type = current_version::nFrames_type;
              using nBytes_type = current_version::nBytes_type;
              using seekTOC_type = current_version::seekTOC_type;
              using chkSumTOC_type = current_version::chkSumTOC_type;
              using chkSumFrHeader_type = current_version::chkSumFrHeader_type;
              using chkSum_type = current_version::chkSum_type;
              using chkSumFile_type = current_version::chkSumFile_type;
                //-----------------------------------------------------------------
                /// @brief Default constructor
                //-----------------------------------------------------------------
                Data( )
                {
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the checksum.
                ///
                /// \return
                ///     The checksum.
                //-----------------------------------------------------------------
                chkSum_type
                GetChkSum( ) const
                {
                    return chkSum;
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the file checksum.
                ///
                /// \return
                ///     The file checksum.
                //-----------------------------------------------------------------
                chkSumFile_type
                GetChkSumFile( ) const
                {
                    return chkSumFile;
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the FrHeader checksum.
                ///
                /// \return
                ///     The FrHEader checksum.
                //-----------------------------------------------------------------
                chkSumFrHeader_type
                GetChkSumFrHeader( ) const
                {
                    return chkSumFrHeader;
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the total number of bytes in this file.
                ///
                /// \return
                ///     The total number of bytes in this file.
                //-----------------------------------------------------------------
                nBytes_type
                GetNBytes( ) const
                {
                    return nBytes;
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the number of frames in this file.
                ///
                /// \return
                ///     The number of frames in this file.
                //-----------------------------------------------------------------
                nFrames_type
                GetNFrames( ) const
                {
                    return nFrames;
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the location of the table of contents.
                ///
                /// \return
                ///     The location of the table of contents.
                //-----------------------------------------------------------------
                seekTOC_type
                GetSeekTOC( ) const
                {
                    return seekTOC;
                }

                //-----------------------------------------------------------------
                /// \brief Field-by-field equality on the FrEndOfFile data slice.
                //-----------------------------------------------------------------
                bool
                operator==( const Data& RHS ) const
                {
                    return ( ( nFrames == RHS.nFrames ) &&
                             ( nBytes == RHS.nBytes ) &&
                             ( seekTOC == RHS.seekTOC ) &&
                             ( chkSumTOC == RHS.chkSumTOC ) &&
                             ( chkSumFrHeader == RHS.chkSumFrHeader ) &&
                             ( chkSum == RHS.chkSum ) &&
                             ( chkSumFile == RHS.chkSumFile ) );
                }

            protected:
                // Friend declarations for policy classes to access protected members
                friend struct NoValidationPolicy;
                friend struct ChecksumValidationPolicy;

                /// @brief Stub for old static logger - always returns nullptr
                /// @deprecated This is only here to prevent compilation errors. Use logger(services) instead.
                /// @return Always returns nullptr
                static SimpleLogger* logger() {
                    return nullptr;
                }

                /// @brief Get the logger for FrEndOfFile debugging (dependency injection version)
                /// @param services Services context containing logger manager
                /// @return Logger instance from services context
                static SimpleLogger* logger(const FrameCPP::Common::FrameCPPServices& services);

                /// @brief Helper class for dual-format checksum logging (decimal and hex)
                /// Maintains lazy formatting by deferring conversion until stream insertion
                template<typename T>
                struct ChecksumFormatter {
                    const T& value;
                    explicit ChecksumFormatter(const T& val) : value(val) {}
                    
                    friend std::ostream& operator<<(std::ostream& os, const ChecksumFormatter& cf) {
                        return os << cf.value << " (0x" << std::hex << cf.value << std::dec << ")";
                    }
                };

                /// @brief Convenience function to create checksum formatter
                /// @param value Checksum value to format
                /// @return ChecksumFormatter that will format as "decimal (0xhex)"
                template<typename T>
                static ChecksumFormatter<T> pretty_print_checksum(const T& value) {
                    return ChecksumFormatter<T>(value);
                }

                //-----------------------------------------------------------------
                /// Number of frames in this file
                //-----------------------------------------------------------------
                nFrames_type nFrames = 0;
                //-----------------------------------------------------------------
                /// Total number of bytes in the file; zero if not computed.
                //-----------------------------------------------------------------
                nBytes_type nBytes = 0;
                //-----------------------------------------------------------------
                /// Bytes to back up to the Beginning of the table of contents
                /// structure.
                /// If seekTOC is zero, then there is no table of contents for
                /// this file.
                //-----------------------------------------------------------------
                seekTOC_type seekTOC = 0;
                //-----------------------------------------------------------------
                /// FrTOC uniqueness checksum
                //-----------------------------------------------------------------
                mutable chkSumTOC_type chkSumTOC = 0;
                //-----------------------------------------------------------------
                /// FrHeader checksum
                //-----------------------------------------------------------------
                chkSumFrHeader_type chkSumFrHeader = 0;
                //-----------------------------------------------------------------
                /// Structure checksum.
                ///
                /// \note
                ///     The chkSum AND chkSumFile ariables are not included in
                ///     the computation of checkSum, but chkSumFrHeader is
                ///     includeed
                //-----------------------------------------------------------------
                chkSum_type chkSum = 0;
                //-----------------------------------------------------------------
                /// File checksum.
                //-----------------------------------------------------------------
                chkSumFile_type chkSumFile = 0;
                //-----------------------------------------------------------------
                /// Checksum type for v<8 files (where type is in FrEndOfFile, not FrHeader)
                /// For v8+, this is ignored and FrHeader.checksumScheme is used instead
                //-----------------------------------------------------------------
                INT_4U legacyChecksumType = 0;
            };

            //=================================================================
            // Policy Classes for Field Reading
            //=================================================================

            //-----------------------------------------------------------------
            /// \brief Policy for reading without checksum validation
            ///
            /// Used by assign() method for buffer-based deserialization where
            /// checksums may not be available or validation is not required.
            //-----------------------------------------------------------------
            struct NoValidationPolicy
            {
                /// Called before reading checksum field in legacy formats
                template<typename StreamType>
                static inline ChecksumState beforeChecksumField(StreamType& /* stream */,
                                                                int /* version */)
                {
                    // No filter manipulation needed for assign() path
                    return ChecksumState();
                }

                /// Called after reading checksum field in legacy formats
                template<typename StreamType>
                static inline void afterChecksumField(StreamType& /* stream */,
                                                      int /* version */,
                                                      ChecksumState /* saved */)
                {
                    // No filter restoration needed for assign() path
                }

                /// Freeze structure checksum before reading chkSum field
                template<typename StreamType>
                static inline void freezeStructureChecksum(StreamType& /* stream */)
                {
                    // No checksum freezing needed for assign() path
                }

                /// Freeze file checksum before reading chkSumFile field
                template<typename StreamType>
                static inline void freezeFileChecksum(StreamType& /* stream */)
                {
                    // No checksum freezing needed for assign() path
                }

                /// Called after all fields read to validate checksums
                template<typename StreamType, typename DataType>
                static inline void validateChecksums(StreamType& /* stream */,
                                                     int /* version */,
                                                     DataType& /* eof_obj */)
                {
                    // No validation in assign() path
                }
            };

            //-----------------------------------------------------------------
            /// \brief Policy for reading with checksum validation
            ///
            /// Used by readFromStream() method for full frame stream reading
            /// with comprehensive checksum validation and error detection.
            //-----------------------------------------------------------------
            struct ChecksumValidationPolicy
            {
                /// Handle pre-checksum filter suspension for v3-v7
                template<typename StreamType>
                static inline ChecksumState beforeChecksumField(StreamType& stream,
                                                                int version)
                {
                    CheckSumFilter* filter = stream.GetCheckSumFile();

                    if (version >= 5)
                    {
                        // v5-v7: Add zero placeholder, then suspend checksum
                        // This ensures checksum calculation includes a zero
                        // placeholder where the actual checksum value is stored
                        if (filter)
                        {
                            Data::v5::chkSum_type zero_checksum = 0;
                            filter->Filter(&zero_checksum, sizeof(zero_checksum));
                        }
                    }

                    // Suspend file checksum to prevent chkSum bytes from being accumulated
                    ChecksumState saved = stream.SuspendFileChecksum();
                    return saved;
                }

                /// Handle post-checksum filter restoration for v3-v7
                template<typename StreamType>
                static inline void afterChecksumField(StreamType& stream,
                                                      int version,
                                                      ChecksumState saved)
                {
                    // Resume file checksum for remaining fields (seekTOC in v4+)
                    // For v3, there are no more fields after chkSum, so resume is a no-op
                    stream.ResumeFileChecksum(std::move(saved));
                }

                /// Freeze structure checksum before reading chkSum field (v8+)
                template<typename StreamType>
                static inline void freezeStructureChecksum(StreamType& stream)
                {
                    // Freeze the structure checksum BEFORE reading chkSum field
                    // This prevents chkSum bytes from being accumulated in the checksum
                    if (stream.GetCheckSumObject())
                    {
                        stream.GetCheckSumObject()->Value();
                    }
                }

                /// Freeze file checksum before reading chkSumFile field (v8+)
                template<typename StreamType>
                static inline void freezeFileChecksum(StreamType& stream)
                {
                    // Freeze the file checksum BEFORE reading chkSumFile field
                    // This prevents chkSumFile bytes from being accumulated in the checksum
                    CheckSumFilter* filter = stream.GetCheckSumFile();
                    if (filter)
                    {
                        filter->Value();
                    }
                }

                /// Perform full checksum validation after reading
                template<typename StreamType, typename DataType>
                static inline void validateChecksums(StreamType& stream,
                                                     int version,
                                                     DataType& eof_obj)
                {
                    if (version >= 8)
                    {
                        // v8-v9: Validate FrHeader, structure, and file checksums
                        validateModernChecksums(stream, version, eof_obj);
                    }
                    else if (version >= 3)
                    {
                        // v3-v7: Validate file checksum only
                        validateLegacyChecksums(stream, version, eof_obj);
                    }
                }

            private:
                /// Validate checksums for modern formats (v8-v9)
                template<typename StreamType, typename DataType>
                static void validateModernChecksums(StreamType& stream,
                                                    int version,
                                                    DataType& eof_obj)
                {
                    //---------------------------------------------------------------------
                    // FrHeader checksum validation
                    //---------------------------------------------------------------------
                    try
                    {
                        using FrameCPP::Common::IFrameStream;

                        // Calculate FrHeader checksum using helper function
                        // (defined in FrEndOfFile.cc where Universal::FrHeader is available)
                        typename DataType::chkSumFrHeader_type calc_chkSumFrHeader =
                            FrameCPP::Universal::calculateFrHeaderChecksumForValidation(stream, version);

                        logger_log_debug(DataType::logger(), 30,
                            "FrEndOfFile::validateModernChecksums() calculated FrHeader checksum: ",
                            DataType::pretty_print_checksum(calc_chkSumFrHeader));

                        if (eof_obj.chkSumFrHeader && calc_chkSumFrHeader &&
                            (eof_obj.chkSumFrHeader != calc_chkSumFrHeader))
                        {
                            std::ostringstream msg;
                            msg << "FrHeader (via FrEndOfFile): "
                                << CheckSum::FormatError(eof_obj.chkSumFrHeader,
                                                         calc_chkSumFrHeader);
                            logger_log_error(DataType::logger(),
                                "FrEndOfFile::validateModernChecksums() FrHeader checksum mismatch: ",
                                msg.str());
                            throw FrameCPP::Common::VerifyException(
                                FrameCPP::Common::VerifyException::CHECKSUM_ERROR,
                                msg.str());
                        }
                        else if (eof_obj.chkSumFrHeader && calc_chkSumFrHeader)
                        {
                            logger_log_debug(DataType::logger(), 30,
                                "FrEndOfFile::validateModernChecksums() FrHeader checksum verified successfully");
                        }
                    }
                    catch (const FrameCPP::Common::VerifyException&)
                    {
                        throw;  // Re-throw VerifyException
                    }
                    catch (...)
                    {
                        // Ignore other exceptions (e.g., cast failures)
                    }

                    //---------------------------------------------------------------------
                    // Structure checksum validation
                    //---------------------------------------------------------------------
                    {
                        typename DataType::chkSum_type calculated_struct_checksum = 0;

                        if (stream.GetCheckSumObject() &&
                            stream.GetCheckSumObject()->GetChecksum())
                        {
                            calculated_struct_checksum = stream.GetCheckSumObject()
                                                               ->GetChecksum()
                                                               ->value();
                        }

                        logger_log_debug(DataType::logger(), 30,
                            "FrEndOfFile::validateModernChecksums() calculated structure checksum: ",
                            DataType::pretty_print_checksum(calculated_struct_checksum));

                        if (eof_obj.chkSum && calculated_struct_checksum &&
                            (eof_obj.chkSum != calculated_struct_checksum))
                        {
                            std::ostringstream msg;
                            msg << "FrEndOfFile Object: "
                                << CheckSum::FormatError(eof_obj.chkSum,
                                                         calculated_struct_checksum);
                            logger_log_error(DataType::logger(),
                                "FrEndOfFile::validateModernChecksums() structure checksum mismatch: ",
                                msg.str());
                            throw FrameCPP::Common::VerifyException(
                                FrameCPP::Common::VerifyException::CHECKSUM_ERROR,
                                msg.str());
                        }
                        else if (eof_obj.chkSum && calculated_struct_checksum)
                        {
                            logger_log_debug(DataType::logger(), 30,
                                "FrEndOfFile::validateModernChecksums() structure checksum verified successfully");
                        }
                    }

                    //---------------------------------------------------------------------
                    // File checksum validation
                    //---------------------------------------------------------------------
                    {
                        typename DataType::chkSumFile_type calculated_file_checksum = 0;
                        CheckSumFilter* checksum_file_filter = stream.GetCheckSumFile();
                        if (checksum_file_filter && checksum_file_filter->GetChecksum())
                        {
                            // IMPORTANT: Use peek() to get checksum value without freezing
                            // the CRC calculation (we're still reading!)
                            calculated_file_checksum = checksum_file_filter->GetChecksum()->peek();
                        }

                        logger_log_debug(DataType::logger(), 30,
                            "FrEndOfFile::validateModernChecksums() calculated file checksum: ",
                            DataType::pretty_print_checksum(calculated_file_checksum));

                        logger_log_debug(DataType::logger(), 20,
                            "FrEndOfFile::validateModernChecksums() verifying file checksum - calculated: ",
                            DataType::pretty_print_checksum(calculated_file_checksum), ", stored: ",
                            DataType::pretty_print_checksum(eof_obj.chkSumFile));

                        if ((calculated_file_checksum) &&
                            (eof_obj.chkSumFile) &&
                            (calculated_file_checksum != eof_obj.chkSumFile))
                        {
                            std::ostringstream msg;
                            msg << "File: "
                                << CheckSum::FormatError(eof_obj.chkSumFile,
                                                         calculated_file_checksum);
                            logger_log_error(DataType::logger(),
                                "FrEndOfFile::validateModernChecksums() file checksum mismatch: ",
                                msg.str());
                            throw FrameCPP::Common::VerifyException(
                                FrameCPP::Common::VerifyException::CHECKSUM_ERROR,
                                msg.str());
                        }
                        else if (calculated_file_checksum && eof_obj.chkSumFile)
                        {
                            logger_log_debug(DataType::logger(), 20,
                                "FrEndOfFile::validateModernChecksums() file checksum verified successfully");
                        }
                    }
                }

                /// Validate checksums for legacy formats (v3-v7)
                template<typename StreamType, typename DataType>
                static void validateLegacyChecksums(StreamType& stream,
                                                    int /* version */,
                                                    DataType& eof_obj)
                {
                    typename DataType::chkSumFile_type calculated_file_checksum = 0;

                    // Get the file checksum filter from the stream
                    // It should have been restored by afterChecksumField()
                    CheckSumFilter* checksum_file_filter = stream.GetCheckSumFile();

                    if (checksum_file_filter)
                    {
                        //-----------------------------------------------------------------
                        // Calculate the value to end checksum calculation.
                        //-----------------------------------------------------------------
                        calculated_file_checksum = checksum_file_filter->Value();
                        logger_log_debug(DataType::logger(), 30,
                            "FrEndOfFile::validateLegacyChecksums() final calculated file checksum: ",
                            DataType::pretty_print_checksum(calculated_file_checksum));
                    }

                    //-------------------------------------------------------
                    // Verify the file checksum
                    //-------------------------------------------------------
                    logger_log_debug(DataType::logger(), 20,
                        "FrEndOfFile::validateLegacyChecksums() verifying file checksum - calculated: ",
                        DataType::pretty_print_checksum(calculated_file_checksum), ", stored: ",
                        DataType::pretty_print_checksum(eof_obj.chkSumFile));

                    if ((calculated_file_checksum) &&
                        (eof_obj.chkSumFile) &&
                        (calculated_file_checksum != eof_obj.chkSumFile))
                    {
                        std::ostringstream msg;
                        msg << "File: "
                            << CheckSum::FormatError(eof_obj.chkSumFile,
                                                     calculated_file_checksum);
                        logger_log_error(DataType::logger(),
                            "FrEndOfFile::validateLegacyChecksums() file checksum mismatch: ",
                            msg.str());
                        throw FrameCPP::Common::VerifyException(
                            FrameCPP::Common::VerifyException::CHECKSUM_ERROR,
                            msg.str());
                    }
                    else if (calculated_file_checksum && eof_obj.chkSumFile)
                    {
                        logger_log_debug(DataType::logger(), 20,
                            "FrEndOfFile::validateLegacyChecksums() file checksum verified successfully");
                    }
                }
            };

        } // namespace FrEndOfFileImpl
    } // namespace universal
} // namespace FrameCPP

FR_OBJECT_META_DATA_DECLARE_UNIVERSAL( FrEndOfFileImpl )

#endif /* ! defined(SWIGIMPORTED) */

#endif /* FrameCPP_UNIVERSAL__IMPL__FrEndOfFileData_HH */
