//
// LDASTools frameCPP - A library implementing the LIGO/Virgo frame
// specification
//
// Copyright (C) 2018 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
//

#include <framecpp_config.h>

#include <assert.h>

#include <stdexcept>
#include <iostream>
#include <typeinfo>
#include <cxxabi.h>
#include <cstdlib>
#include <map>

#include "ldastoolsal/AtExit.hh"
#include "ldastoolsal/gpstime.hh"
#include "ldastoolsal/mutexlock.hh"
#include "ldastoolsal/Logger.hh"
#include "ldastoolsal/SimpleLogger.hh"

#include "framecpp/Common/Description.hh"
#include "framecpp/Common/FrameSpec.hh"

#include "framecpp/Common/IOStream.hh"

#include "framecpp/Common/FrDetector.hh"
#include "framecpp/Common/FrSH.hh"
#include "framecpp/Common/FrStatData.hh"

#include "framecpp/Common/FrameSpec.tcc"

using LDASTools::AL::GPSTime;
using LDASTools::AL::MutexLock;

namespace FrameCPP
{
    namespace Common
    {
        // Runtime debugging utility functions
        namespace
        {
            // Portable C++ name demangling utility
            std::string
            demangle_type_name( const char* mangled_name )
            {
                if ( !mangled_name )
                    return "null";

#ifdef __GNUG__ // GCC or Clang
                int   status = 0;
                char* demangled = abi::__cxa_demangle(
                    mangled_name, nullptr, nullptr, &status );
                if ( status == 0 && demangled )
                {
                    std::string result( demangled );
                    free( demangled );
                    return result;
                }
#endif
                // Fallback: return mangled name if demangling fails or not
                // supported
                return std::string( mangled_name );
            }
        } // namespace

        //-------------------------------------------------------------------
        /// \brief Collection of frame specifications
        ///
        /// This is a lookup table on the implementation details for
        /// of defined frame specifications
        //-------------------------------------------------------------------
        class FrameSpec::frame_spec_container
            : public LDASTools::AL::unordered_map< version_type, Info* >
        {
        public:
            //-----------------------------------------------------------------
            /// \brief Default constructor
            //-----------------------------------------------------------------
            frame_spec_container( );
            //-----------------------------------------------------------------
            /// \brief Destructor
            //-----------------------------------------------------------------
            virtual ~frame_spec_container( );
        };

        //-------------------------------------------------------------------
        /// Initialize the resources needed to manage the collection of
        /// frame specification details.
        //-------------------------------------------------------------------
        FrameSpec::frame_spec_container::frame_spec_container( )
        {
        }

        //-------------------------------------------------------------------
        /// Release the resources associated with managing the collection of
        /// frame specification details.
        //-------------------------------------------------------------------
        FrameSpec::frame_spec_container::~frame_spec_container( )
        {
            for ( const_iterator cur = begin( ), last = end( ); cur != last;
                  ++cur )
            {
                delete cur->second;
            }
            erase( begin( ), end( ) );
        }

        //-------------------------------------------------------------------
        /// Release the resources back to the system.
        /// By being virtual, all the resources associated with any derived
        /// class are given a chance for proper cleanup.
        //-------------------------------------------------------------------
        FrameSpec::ObjectInterface::~ObjectInterface( )
        {
        }

        //-------------------------------------------------------------------
        /// Allocation of the object's resources.
        //-------------------------------------------------------------------
        FrameSpec::Object::Object( class_type              Class,
                                   const Description*      Description,
                                   frame_spec_version_type FrameSpec )
            : m_class( Class ), m_desc( Description ),
              frame_spec_version( FrameSpec )
        {
            // Debug type name will be set by derived class constructors via
            // updateDebugTypeName() This avoids RTTI timing issues where
            // typeid(*this) only sees base class during construction
            updateDebugTypeName( );
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        FrameSpec::Object::Object( const Object& Source )
            : ObjectInterface( Source ), m_class( Source.m_class ),
              m_desc( Source.m_desc ),
              frame_spec_version( Source.frame_spec_version )
        {
            // Debug type name will be set by derived class constructors via
            // updateDebugTypeName() This avoids RTTI timing issues where
            // typeid(*this) only sees base class during construction
            updateDebugTypeName( );
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        FrameSpec::Object::~Object( )
        {
            // FIXED: Avoid static logger in destructor to prevent segfaults from 
            // static destructor ordering issues during program shutdown.
            // Only log destructor debug output if explicitly enabled.
#ifdef FRAMECPP_DEBUG
            // Enhanced with dual type information to identify which specific
            // frame object types are being destroyed Note: Cannot call virtual
            // functions (ObjectStructName, GetClass) in destructor as object is
            // partially destroyed
            std::string current_rtti =
                demangle_type_name( typeid( *this ).name( ) );

            if ( !m_debug_type_name.empty( ) )
            {
                // Display both original (cached at construction) and current
                // (RTTI during destruction) types
                std::cerr << "FrameSpec::Object::~Object() destructor called, original_type="
                          << m_debug_type_name << ", current_rtti=" << current_rtti
                          << ", this=" << static_cast< const void* >( this ) << std::endl;
            }
            else
            {
                // Fallback to RTTI only (debug caching was not enabled)
                std::cerr << "FrameSpec::Object::~Object() destructor called, current_rtti="
                          << current_rtti << ", this=" << static_cast< const void* >( this )
                          << std::endl;
            }
#endif /* FRAMECPP_DEBUG */
        }
        
        // Note: For future implementation, destructor logging should be moved to
        // use dependency injection when a comprehensive refactoring is possible.

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        void
        FrameSpec::Object::updateDebugTypeName( ) const
        {
            // Always cache the type name since it's a lightweight operation and
            // we need it available when the destructor runs with debug level 50
            // The actual debug level check happens in the destructor
            m_debug_type_name = demangle_type_name( typeid( *this ).name( ) );
        }

        /*-------------------------------------------------------------------
          -------------------------------------------------------------------*/
        // Removed GetDescription(ostream_type&) override - use base class
        // implementation that properly calls
        // GetDescription(StreamVersion(Stream))

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        ostream_type&
        FrameSpec::Object::WriteNext( ostream_type& Stream ) const
        {
            Stream.NextPtrStruct( this ).Write( Stream );
            return Stream;
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        FrameSpec::class_type
        FrameSpec::Object::getClass( ) const
        {
            return GetClass( );
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        FrameSpec::Object*
        FrameSpec::ObjectInterface::Create( ) const
        {
            std::ostringstream msg;
            const Description* d( GetDescription( ) );

            msg << "Cannot create object of class: " << getClass( );
            if ( d )
            {
                msg << " (" << d->GetName( ) << ")";
            }

            throw std::runtime_error( msg.str( ) );
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        FrameSpec::Object*
        FrameSpec::ObjectInterface::Create( std::istream& Buffer ) const
        {
            std::ostringstream msg;
            const Description* d( GetDescription( ) );

            msg << "Cannot create object of class: " << getClass( );
            if ( d )
            {
                msg << " (" << d->GetName( ) << ")";
            }
            msg << " from std::istream";

            throw std::runtime_error( msg.str( ) );
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        FrameSpec::Object*
        FrameSpec::ObjectInterface::Clone( ) const
        {
            const Description* d( GetDescription( ) );
            std::ostringstream msg;

            msg << "Cannot clone object of class: " << getClass( );
            if ( d )
            {
                msg << " (" << d->GetName( ) << ")";
            }

            throw std::runtime_error( msg.str( ) );
        }

        //-------------------------------------------------------------------
        // NOTE: DemoteObject() and PromoteObject() implementations removed.
        // Universal objects are version-agnostic - all version adaptation
        // happens at the I/O layer via pCreate/pWrite/pRead.
        //-------------------------------------------------------------------

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        void
        FrameSpec::ObjectInterface::VerifyObject( Verify&       Verifier,
                                                  IFrameStream& Stream ) const
        {
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        FrameSpec::Info::Info( version_type           MajorVersion,
                               version_type           MinorVersion,
                               verification_func_type VerificationFunc )
            : m_version( MajorVersion ), m_version_minor( MinorVersion ),
              m_verification_func( VerificationFunc )
        {
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        FrameSpec::Info::~Info( )
        {
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        const FrameSpec::Object*
        FrameSpec::Info::FrameObject( frame_object_types ObjectId ) const
        {
            object_container::const_iterator retval =
                m_definitions.find( ObjectId );
            if ( retval == m_definitions.end( ) )
            {
                std::ostringstream msg;

                msg << "Unable to locate Object: " << ObjectId
                    << " for Version: " << Version( )
                    << " of the frame specification";
                throw std::range_error( msg.str( ) );
            }
            return retval->second.get( );
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        const FrameSpec::Object*
        FrameSpec::Info::FrameObject( frame_object_types ObjectId, const std::string& structureName ) const
        {
            object_container::const_iterator retval =
                m_definitions.find( ObjectId );
            if ( retval == m_definitions.end( ) )
            {
                std::ostringstream msg;

                msg << "Unable to locate Object: " << ObjectId;
                if ( !structureName.empty( ) )
                {
                    msg << " (" << structureName << ")";
                }
                msg << " for Version: " << Version( )
                    << " of the frame specification";
                throw std::range_error( msg.str( ) );
            }
            return retval->second.get( );
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        void
        FrameSpec::Info::FrameObject( object_type ObjectTemplate )
        {
            m_definitions[ ObjectTemplate->GetClass( ) ] = ObjectTemplate;
        }

        //-------------------------------------------------------------------
        // Bidirectional FSI mapping implementation (Option 4 - DRY principle)
        //-------------------------------------------------------------------
        
        // Single source of truth for FSI mappings
        const FrameSpec::Info::FSIMapping FrameSpec::Info::fsi_mappings[] = {
            // Core frame objects
            {FSI_FRAME_H, "FrameH"},
            {FSI_FR_ADC_DATA, "FrAdcData"},
            {FSI_FR_DETECTOR, "FrDetector"},
            {FSI_FR_END_OF_FILE, "FrEndOfFile"},
            {FSI_FR_END_OF_FRAME, "FrEndOfFrame"},
            {FSI_FR_EVENT, "FrEvent"},
            {FSI_FR_EVENT, "FrTrigData"},  // Backwards compatibility alias
            {FSI_FR_HISTORY, "FrHistory"},
            {FSI_FR_MSG, "FrMsg"},
            {FSI_FR_PROC_DATA, "FrProcData"},
            {FSI_FR_RAW_DATA, "FrRawData"},
            {FSI_FR_SER_DATA, "FrSerData"},
            {FSI_FR_SIM_DATA, "FrSimData"},
            {FSI_FR_SIM_EVENT, "FrSimEvent"},
            {FSI_FR_STAT_DATA, "FrStatData"},
            {FSI_FR_SUMMARY, "FrSummary"},
            {FSI_FR_TABLE, "FrTable"},
            {FSI_FR_TOC, "FrTOC"},
            {FSI_FR_VECT, "FrVect"},
            
            // Meta objects
            {FSI_FR_SH, "FrSH"},
            {FSI_FR_SE, "FrSE"},
            
            // Sentinel
            {FSI_FR_NULL, nullptr}
        };
        
        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        FrameSpec::Info::frame_object_types
        FrameSpec::Info::GetDirectFSI(const std::string& structureName)
        {
            // Build static map on first call (lazy initialization)
            static const std::map<std::string, frame_object_types> name_to_fsi = []() {
                std::map<std::string, frame_object_types> map;
                for (const FSIMapping* p = fsi_mappings; p->name != nullptr; ++p) {
                    map[p->name] = p->fsi;
                }
                return map;
            }();

            auto it = name_to_fsi.find(structureName);
            return (it != name_to_fsi.end()) ? it->second : FSI_FR_NULL;
        }
        
        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        const char*
        FrameSpec::Info::GetCanonicalName(frame_object_types fsi)
        {
            // Find first (canonical) name for this FSI
            for (const FSIMapping* p = fsi_mappings; p->name != nullptr; ++p) {
                if (p->fsi == fsi) {
                    return p->name;
                }
            }
            return nullptr;
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        inline FrameSpec::frame_spec_container&
        FrameSpec::frame_specs( )
        {
            static frame_spec_container m_frame_specs;

            return m_frame_specs;
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        FrameSpec::Info*
        FrameSpec::SpecInfo( version_type Version )
        {
            frame_spec_container::const_iterator retval =
                frame_specs( ).find( Version );

            if ( retval == frame_specs( ).end( ) )
            {
                std::ostringstream msg;

                msg << "No frame definition found for version " << Version
                    << " of the frame specification" << std::endl;
#ifdef FRAMECPP_DEBUG
                std::cerr << msg.str( );
                assert( 0 );
#endif /* FRAMECPP_DEBUG */
                throw std::range_error( msg.str( ) );
            }
            return retval->second;
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        void
        FrameSpec::SpecInfo( version_type Version, FrameSpec::Info* I )
        {
            if ( I )
            {
                static MutexLock::baton_type key;

                MutexLock lock( key, __FILE__, __LINE__ );

                frame_specs( )[ Version ] = I;
            }
        }

        //-------------------------------------------------------------------
        /// \cond IGNORE_TEMPLATE_SPECIALIZATION
        // Returns the number of bytes required to read or write a GPS
        // time structure.
        //-------------------------------------------------------------------
        template <>
        FrameSpec::size_type
        Bytes< GPSTime >( const GPSTime& Source )
        {
            return sizeof( INT_4U ) + sizeof( INT_4U );
        }
        /* \endcond */ /* IGNORE_TEMPLATE_SPECIALIZATION */

        //-------------------------------------------------------------------
        ///
        //-------------------------------------------------------------------
        FrameSpec::FrameSpec( )
        {
        }

        template FrameSpec::ObjectWithChecksum< FrameSpec::Object, INT_4U >::
            ObjectWithChecksum( class_type,
                                const Description*,
                                frame_spec_version_type FrameSpec );
        template const char*
        FrameSpec::ObjectWithChecksum< FrameSpec::Object,
                                       INT_4U >::CheckSumDataClass( );
        template const char*
        FrameSpec::ObjectWithChecksum< FrameSpec::Object,
                                       INT_4U >::CheckSumDataComment( );

#define INSTANTIATE( LM_TYPE, LM_CRC_TYPE )                                    \
    template FrameSpec::ObjectWithChecksum< LM_TYPE, LM_CRC_TYPE >::           \
        ObjectWithChecksum(                                                    \
            const FrameSpec::ObjectWithChecksum< LM_TYPE, LM_CRC_TYPE >& );    \
    template FrameSpec::ObjectWithChecksum< LM_TYPE, LM_CRC_TYPE >::           \
        ~ObjectWithChecksum( );                                                \
    template streamsize_type                                                   \
    FrameSpec::ObjectWithChecksum< LM_TYPE, LM_CRC_TYPE >::Bytes(              \
        const StreamBase& ) const;                                             \
    template FrameSpec::ObjectWithChecksum< LM_TYPE, LM_CRC_TYPE >::self_type* \
    FrameSpec::ObjectWithChecksum< LM_TYPE, LM_CRC_TYPE >::Create(             \
        istream_type& ) const;                                                 \
    template void                                                              \
    FrameSpec::ObjectWithChecksum< LM_TYPE, LM_CRC_TYPE >::Write(              \
        ostream_type& ) const

        // clang-format off
        //---------------------------------------------------------------
        // Instantiation of the methods give them global visibility
        //---------------------------------------------------------------
        INSTANTIATE( FrameSpec::Object, INT_4U );
        INSTANTIATE( FrDetector, INT_4U );
        INSTANTIATE( FrSH, INT_4U );
        INSTANTIATE( FrStatData, INT_4U );
        // clang-format on

#undef INSTANTIATE

    } // namespace Common

    //---------------------------------------------------------------------
    /// \return
    ///     current version of frame spec.
    //---------------------------------------------------------------------
    INT_2U
    GetDataFormatVersion( )
    {
        return FRAME_SPEC_CURRENT;
    }

    //---------------------------------------------------------------------
    /// \return
    ///     The string representation of the version of frameCPP
    //---------------------------------------------------------------------
    std::string
    GetVersion( )
    {
        std::ostringstream oss;

        oss << "frameCPP-" << VERSION;

        return oss.str( );
    }

} // namespace FrameCPP
