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

#if !defined( SWIGIMPORTED )
#include <iostream>
#include <fstream>
#include <string>
#include <typeinfo>
#include <cxxabi.h>
#include <memory>

#include <boost/serialization/access.hpp>
#include <boost/serialization/serialization.hpp>

#include "framecpp/archive/DebugArchive/DebugArchive.hh"
#include "framecpp/archive/JsonArchive/JsonArchive.hh"
#include "framecpp/Common/FrameCPPServices.hh"

namespace FrameCPP
{
    namespace Archive
    {
        class WalkingDebugArchive; // Forward declaration for walking functionality
    }
    
    namespace Debug
    {
        /// \brief Output format for Frame structure printing
        enum class OutputFormat
        {
            DEBUG,  ///< Human-readable debug text format  
            JSON    ///< Structured JSON format
        };
            /// \brief Generic debug printer for Frame structures with boost serialization
            ///
            /// This class provides a flexible, configurable interface for debugging
            /// any Frame structure that uses boost serialization. It leverages the
            /// existing SerialIO save() methods to automatically format debug output
            /// with proper field names, type information, and version-aware formatting.
            ///
            /// The class supports multiple alignment modes, configurable formatting
            /// options, and works with all Frame types (FrTOCData, FrameH, FrVect,
            /// FrTable, etc.) through a generic template interface.
            ///
            /// Usage Examples:
            /// ```cpp
            /// // Simple usage with defaults
            /// FrTOCData tocData;
            /// FrameDebugPrinter printer;
            /// printer(tocData);  // Uses version=9, SEPARATOR alignment, std::cout
            ///
            /// // Configure specific options with method chaining
            /// printer.setAlignment(AlignmentMode::COLUMNS)
            ///        .setVersion(7)
            ///        .showTypeInfo(false);
            /// printer(frameHeader);
            ///
            /// // Direct attribute modification
            /// printer.version = 6;
            /// printer.alignment = AlignmentMode::NONE;
            /// printer(vector);
            /// ```
            class FramePrinter
            {
            public:
                /// \brief Constructor with output stream
                ///
                /// \param[in,out] os Output stream for debug information (default: std::cout)
                explicit FramePrinter( std::ostream& os = std::cout );
                
                /// \brief Constructor with output stream and services context
                ///
                /// \param[in,out] os Output stream for debug information
                /// \param[in] services Services context for logging functionality
                FramePrinter( std::ostream& os, const FrameCPP::Common::FrameCPPServices& services );

                //=============================================================
                // Fluent Interface Methods (Method Chaining)
                //=============================================================

                /// \brief Set frame specification version
                ///
                /// \param[in] ver Frame specification version (3-9)
                /// \return Reference to this printer for method chaining
                FramePrinter& setVersion( unsigned int ver );

                /// \brief Set alignment mode
                ///
                /// \param[in] mode Alignment mode for output formatting
                /// \return Reference to this printer for method chaining
                FramePrinter& setAlignment( AlignmentMode mode );

                /// \brief Set field name/value separator string
                ///
                /// \param[in] sep Separator string (default: " : ")
                /// \return Reference to this printer for method chaining
                FramePrinter& setSeparator( const std::string& sep );

                /// \brief Set indentation level
                ///
                /// \param[in] spaces Number of spaces for indentation (default: 2)
                /// \return Reference to this printer for method chaining
                FramePrinter& setIndentation( int spaces );

                /// \brief Enable/disable type information display
                ///
                /// \param[in] show Whether to show type information (default: true)
                /// \return Reference to this printer for method chaining
                FramePrinter& showTypeInfo( bool show );

                /// \brief Enable/disable version-specific annotations
                ///
                /// \param[in] show Whether to show version annotations (default: true)
                /// \return Reference to this printer for method chaining
                FramePrinter& showVersionInfo( bool show );

                /// \brief Set maximum field name width for alignment
                ///
                /// \param[in] width Maximum field name width (0 = auto-calculate)
                /// \return Reference to this printer for method chaining
                FramePrinter& setMaxFieldWidth( size_t width );

                /// \brief Set maximum value width for column alignment
                ///
                /// \param[in] width Maximum value width (0 = auto-calculate)
                /// \return Reference to this printer for method chaining
                FramePrinter& setMaxValueWidth( size_t width );

                /// \brief Set field filter for selective output
                ///
                /// Sets a field filter to selectively include or exclude fields
                /// from the debug output. Pass nullptr to disable filtering.
                ///
                /// \param[in] filter Pointer to field filter (nullptr = show all fields)
                /// \return Reference to this printer for method chaining
                FramePrinter& setFieldFilter( const FieldFilter* filter );

                /// \brief Set value transformer for custom value formatting
                ///
                /// Sets a value transformer to format field values before display.
                /// Pass nullptr to disable value transformation.
                ///
                /// \param[in] transformer Pointer to value transformer (nullptr = no transformation)
                /// \return Reference to this printer for method chaining
                FramePrinter& setValueTransformer( const ValueTransformer* transformer );

                /// \brief Set output format (DEBUG or JSON)
                ///
                /// Controls whether output is human-readable debug text or structured JSON.
                ///
                /// \param[in] format Output format to use
                /// \return Reference to this printer for method chaining
                FramePrinter& setOutputFormat( OutputFormat format );

                /// \brief Enable/disable object walking into containers
                ///
                /// When enabled, the printer will recursively walk into PTR_STRUCT
                /// containers and print the contents of individual objects.
                ///
                /// \param[in] walk Whether to walk into container objects
                /// \return Reference to this printer for method chaining
                FramePrinter& setWalkObjects( bool walk );

                /// \brief Set maximum recursion depth for object walking
                ///
                /// Controls how deep the printer will recursively walk into
                /// nested object structures. 0 = no walking, higher values allow
                /// deeper traversal. Default: 0 (no walking).
                ///
                /// \param[in] depth Maximum recursion depth (0-10 recommended)
                /// \return Reference to this printer for method chaining
                FramePrinter& setWalkDepth( int depth );

                /// \brief Enable/disable container content walking
                ///
                /// When enabled, PTR_STRUCT containers will be iterated and their
                /// individual elements will be printed. When disabled, only container
                /// sizes and types are shown.
                ///
                /// \param[in] walk_containers Whether to walk container contents
                /// \return Reference to this printer for method chaining
                FramePrinter& setWalkContainers( bool walk_containers );

                //=============================================================
                // Function Call Operators (Primary Interface)
                //=============================================================

                /// \brief Print Frame structure using class configuration
                ///
                /// This is the primary interface for printing Frame structures.
                /// Uses the version and formatting options configured in the class.
                ///
                /// \tparam FrameType Type of Frame structure (must have boost serialization)
                /// \param[in] frame_object Frame structure to debug print
                template < typename FrameType >
                void operator()( const FrameType& frame_object );

                /// \brief Print Frame structure with version override
                ///
                /// Prints using the specified version, overriding the class's
                /// version setting for this call only.
                ///
                /// \tparam FrameType Type of Frame structure (must have boost serialization)
                /// \param[in] frame_object Frame structure to debug print
                /// \param[in] ver Frame specification version to use for this call
                template < typename FrameType >
                void operator()( const FrameType& frame_object, unsigned int ver );

                //=============================================================
                // Public Configuration Attributes
                //=============================================================

                unsigned int version = 9;                          ///< Frame specification version
                AlignmentMode alignment = AlignmentMode::SEPARATOR; ///< Output alignment mode
                std::string separator = " : ";                     ///< Field name/value separator
                int indentation = 2;                               ///< Indentation spaces
                bool show_type_info = true;                        ///< Show type information
                bool show_version_info = true;                     ///< Show version annotations
                size_t max_field_width = 0;                        ///< Max field width (0=auto)
                size_t max_value_width = 0;                        ///< Max value width (0=auto)
                OutputFormat output_format = OutputFormat::DEBUG;  ///< Output format (DEBUG or JSON)
                bool walk_objects = false;                         ///< Walk into container objects
                bool walk_containers = false;                      ///< Walk PTR_STRUCT containers
                int walk_depth = 0;                                ///< Maximum recursion depth for walking

            private:
                std::ostream& m_os;                ///< Output stream reference
                const FieldFilter* m_field_filter; ///< Optional field filter (nullptr = show all)
                const ValueTransformer* m_value_transformer; ///< Optional value transformer (nullptr = no transformation)
                const FrameCPP::Common::FrameCPPServices* m_services; ///< Optional services context for logging (nullptr = no logging)

                /// \brief Internal implementation for printing Frame structures
                ///
                /// \tparam FrameType Type of Frame structure
                /// \param[in] frame_object Frame structure to print
                /// \param[in] ver Frame specification version to use
                template < typename FrameType >
                void printImpl( const FrameType& frame_object, unsigned int ver );

                /// \brief Get human-readable type name for Frame structures
                ///
                /// \tparam FrameType Type of Frame structure
                /// \return Human-readable type name
                template < typename FrameType >
                const char* getTypeName( );
            };

            //=================================================================
            // Convenience Factory Functions
            //=================================================================

            /// \brief Create printer configured for compact output
            ///
            /// \param[in,out] os Output stream (default: std::cout)
            /// \return FrameDebugPrinter configured for compact formatting
            FramePrinter compactPrinter( std::ostream& os = std::cout );

            /// \brief Create printer configured for separator-aligned output
            ///
            /// \param[in,out] os Output stream (default: std::cout)
            /// \return FrameDebugPrinter configured for separator alignment
            FramePrinter alignedPrinter( std::ostream& os = std::cout );

            /// \brief Create printer configured for columnar output
            ///
            /// \param[in,out] os Output stream (default: std::cout)
            /// \return FrameDebugPrinter configured for column alignment
            FramePrinter columnarPrinter( std::ostream& os = std::cout );

            /// \brief Create printer configured for JSON output
            ///
            /// \param[in,out] os Output stream (default: std::cout)
            /// \return FrameDebugPrinter configured for JSON format
            FramePrinter jsonPrinter( std::ostream& os = std::cout );

            /// \brief Create printer configured for object walking
            ///
            /// Creates a printer that will recursively walk into containers
            /// and display the contents of individual objects within them.
            ///
            /// \param[in,out] os Output stream (default: std::cout)
            /// \param[in] depth Maximum recursion depth (default: 3)
            /// \return FrameDebugPrinter configured for object walking
            FramePrinter walkingPrinter( std::ostream& os = std::cout, int depth = 3 );

            /// \brief Create printer configured for full recursive walking
            ///
            /// Creates a printer with maximum depth walking for complete
            /// frame structure inspection.
            ///
            /// \param[in,out] os Output stream (default: std::cout)
            /// \return FrameDebugPrinter configured for full walking
            FramePrinter fullWalkPrinter( std::ostream& os = std::cout );

            /// \brief Create printer configured for shallow container inspection
            ///
            /// Shows container sizes and types but doesn't walk into individual objects.
            /// This is the default behavior for existing code.
            ///
            /// \param[in,out] os Output stream (default: std::cout)
            /// \return FrameDebugPrinter configured for shallow inspection
            FramePrinter shallowPrinter( std::ostream& os = std::cout );

            //=================================================================
            // Template Method Implementations
            //=================================================================

            template < typename FrameType >
            void FramePrinter::operator()( const FrameType& frame_object )
            {
                printImpl( frame_object, version );
            }

            template < typename FrameType >
            void FramePrinter::operator()( const FrameType& frame_object, unsigned int ver )
            {
                printImpl( frame_object, ver );
            }

            template < typename FrameType >
            void FramePrinter::printImpl( const FrameType& frame_object, unsigned int ver )
            {
                if ( output_format == OutputFormat::JSON )
                {
                    // Create JsonArchive with current configuration
                    FrameCPP::Archive::JsonArchive json_ar( m_os, ver, true, true );
                    
                    // Serialize the frame object to JSON
                    json_ar << boost::serialization::make_nvp( getTypeName< FrameType >( ), frame_object );
                }
                else
                {
                    // Choose archive type based on walking configuration
                    if ( walk_objects || walk_containers )
                    {
                        if ( m_services )
                        {
                            auto* logger = m_services->getLogger("framecpp.archive.walking");
                            logger_log_debug(logger, 5, "FramePrinter::operator() entry - frame type: ", getTypeName< FrameType >());
                            logger_log_debug(logger, 10, "FramePrinter: Walking enabled - walk_objects=", walk_objects ? "true" : "false", 
                                           ", walk_containers=", walk_containers ? "true" : "false");
                            logger_log_debug(logger, 10, "FramePrinter: Creating WalkingDebugArchive with depth=", walk_depth);
                            // Use walking-enabled archive with services context
                            FrameCPP::Archive::WalkingDebugArchive walking_ar( m_os, ver, *m_services, alignment, separator, indentation,
                                                           show_type_info, show_version_info,
                                                           max_field_width, max_value_width, 
                                                           m_field_filter, m_value_transformer,
                                                           walk_objects, walk_containers, walk_depth );
                            
                            logger_log_debug(logger, 15, "WalkingDebugArchive created successfully");

                            // Print header with type name and version
                            m_os << getTypeName< FrameType >( ) << " (Version " << ver << "):" << std::endl;

                            // Two-pass processing for alignment
                            walking_ar.beginPass( );
                            walking_ar << frame_object;

                            if ( alignment != AlignmentMode::NONE )
                            {
                                walking_ar.nextPass( );
                                walking_ar << frame_object;
                            }
                        }
                        else
                        {
                            // No logger available but should still note this path
                            m_os << "<!-- WARNING: Walking requested but services not available -->" << std::endl;
                            // Fallback to standard DebugArchive when services not available
                            DebugArchive debug_ar( m_os, ver, alignment, separator, indentation,
                                                   show_type_info, show_version_info,
                                                   max_field_width, max_value_width, m_field_filter, m_value_transformer );

                            // Print header with type name and version
                            m_os << getTypeName< FrameType >( ) << " (Version " << ver << "):" << std::endl;

                            // Two-pass processing for alignment
                            debug_ar.beginPass( );
                            debug_ar << frame_object;

                            if ( alignment != AlignmentMode::NONE )
                            {
                                debug_ar.nextPass( );
                                debug_ar << frame_object;
                            }
                        }
                    }
                    else
                    {
                        // Log that we're not using walking archive
                        if ( m_services )
                        {
                            auto* logger = m_services->getLogger("framecpp.archive.walking");
                            logger_log_debug(logger, 10, "FramePrinter: Walking NOT enabled, using standard DebugArchive");
                        }
                        
                        // Use standard DebugArchive (existing behavior)
                        DebugArchive debug_ar( m_os, ver, alignment, separator, indentation,
                                               show_type_info, show_version_info,
                                               max_field_width, max_value_width, m_field_filter, m_value_transformer );

                        // Print header with type name and version
                        m_os << getTypeName< FrameType >( ) << " (Version " << ver << "):" << std::endl;

                        // Two-pass processing for alignment
                        debug_ar.beginPass( );
                        debug_ar << frame_object;

                        if ( alignment != AlignmentMode::NONE )
                        {
                            debug_ar.nextPass( );
                            debug_ar << frame_object;
                        }
                    }
                }
            }

            /// \brief Helper function to demangle C++ type names
            ///
            /// \param[in] mangled_name Mangled type name from typeid().name()
            /// \return Human-readable C++ type name
            inline std::string demangleTypeName( const char* mangled_name )
            {
                int status = 0;
                std::unique_ptr<char, void(*)(void*)> demangled(
                    abi::__cxa_demangle(mangled_name, nullptr, nullptr, &status),
                    std::free
                );
                
                if (status == 0 && demangled) {
                    return std::string(demangled.get());
                } else {
                    // Fallback to mangled name if demangling fails
                    return std::string(mangled_name);
                }
            }

            template < typename FrameType >
            const char* FramePrinter::getTypeName( )
            {
                // Use demangled type name for readable output
                static std::string type_name = demangleTypeName( typeid( FrameType ).name( ) );
                return type_name.c_str( );
            }

            //=================================================================
            // Specialized Type Names for Common Frame Types  
            //=================================================================
            
            /// \brief Type name specializations for cleaner output headers
            ///
            /// These template specializations can be added for specific Frame types
            /// to provide human-readable names instead of mangled typeid names.
            /// They should be defined after the Frame type headers are included.

    } // namespace Debug
} // namespace FrameCPP

#endif /* !defined( SWIGIMPORTED ) */

#endif /* FrameCPP_OOInterface_FrameDebugPrint_HH */