//
// 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
//

#include "LegacyTOCPrinter.hh"
#include <iomanip>

using FrameCPP::Universal::FrTOC;

namespace FrameCPP
{
    namespace Test
    {
        LegacyTOCPrinter::LegacyTOCPrinter( std::ostream& os, unsigned int indent )
            : m_os( os ), m_indent( indent ), m_indent_str( indent, ' ' )
        {
        }

        void
        LegacyTOCPrinter::print( const FrTOC* toc, unsigned int version )
        {
            // Print all TOC sections in order
            printFrTOCData( toc, version );
            printFrTOCStatData( toc, version ); // v<9 only - between Detector and ADC
            printFrTOCAdcData( toc, version );
            printFrTOCProcData( toc, version );
            printFrTOCSimData( toc, version );
            printFrTOCSerData( toc, version );
            printFrTOCSummary( toc, version );
            printFrTOCEvent( toc, version );
            printFrTOCSimEvent( toc, version );
        }

        //=====================================================================
        // FrTOCData - Basic frame metadata
        //=====================================================================
        void
        LegacyTOCPrinter::printFrTOCData( const FrTOC* toc,
                                          unsigned int  version )
        {
            // nFrame (all versions)
            m_os << m_indent_str << "nFrame: " << toc->GetNFrame() << std::endl;

            // dataQuality (v5+)
            if ( version >= 5 )
            {
                printArray( "dataQuality", toc->GetDataQuality() );
            }

            // GPS time arrays
            printArray( "GTimeS", toc->GetGTimeS() );
            printArray( "GTimeN", toc->GetGTimeN() );

            // Frame duration
            printArray( "dt", toc->GetDt() );

            // Frame header positions
            printArray( "positionH", toc->GetPositionH() );

            // SH data (shared history)
            if ( version >= 4 )
            {
                m_os << m_indent_str << "nSH: " << toc->GetSHid().size() << std::endl;
                if ( !toc->GetSHid().empty() )
                {
                    printArray( "SHid", toc->GetSHid() );
                    printArray( "SHname", toc->GetSHname() );
                }
            }

            // Detector information (v5+)
            if ( version >= 5 )
            {
                m_os << m_indent_str << "nDetector: " << toc->GetNameDetector().size()
                     << std::endl;
                if ( !toc->GetNameDetector().empty() )
                {
                    printArray( "nameDetector", toc->GetNameDetector() );
                    printArray( "positionDetector",
                                toc->GetPositionDetector() );
                }
            }
        }

        //=====================================================================
        // FrTOCAdcData - ADC channel index
        //=====================================================================
        void
        LegacyTOCPrinter::printFrTOCAdcData( const FrTOC* toc,
                                             unsigned int  version )
        {
            auto& adc = toc->GetADC();

            m_os << m_indent_str << "nADC: " << adc.size() << std::endl;

            if ( adc.empty() )
                return;

            // Channel names
            m_os << m_indent_str << "name:";
            for ( auto& [name, info] : adc )
            {
                m_os << " " << name;
            }
            m_os << std::endl;

            // channelID array (v4-v7)
            if ( version >= 4 && version < 8 )
            {
                m_os << m_indent_str << "channelID:";
                for ( auto& [name, info] : adc )
                {
                    // Use first frame's channelID (should be same for all frames)
                    m_os << " " << ( info.m_channelID.empty() ? 0 : info.m_channelID[0] );
                }
                m_os << std::endl;
            }

            // groupID array (v4-v7)
            if ( version >= 4 && version < 8 )
            {
                m_os << m_indent_str << "groupID:";
                for ( auto& [name, info] : adc )
                {
                    // Use first frame's groupID (should be same for all frames)
                    m_os << " " << ( info.m_groupID.empty() ? 0 : info.m_groupID[0] );
                }
                m_os << std::endl;
            }

            // Channel positions (flattened array)
            m_os << m_indent_str << "positionADC:";
            for ( auto& [name, info] : adc )
            {
                for ( auto pos : info.m_positionADC )
                {
                    m_os << " " << pos;
                }
            }
            m_os << std::endl;
        }

        //=====================================================================
        // FrTOCProcData - Processed data channel index
        //=====================================================================
        void
        LegacyTOCPrinter::printFrTOCProcData( const FrTOC* toc,
                                              unsigned int  version )
        {
            auto& proc = toc->GetProc();

            m_os << m_indent_str << "nProc: " << proc.size() << std::endl;

            if ( proc.empty() )
                return;

            // Channel names
            m_os << m_indent_str << "nameProc:";
            for ( auto& [name, info] : proc )
            {
                m_os << " " << name;
            }
            m_os << std::endl;

            // Channel positions (flattened array)
            m_os << m_indent_str << "positionProc:";
            for ( auto& [name, info] : proc )
            {
                for ( auto pos : info )
                {
                    m_os << " " << pos;
                }
            }
            m_os << std::endl;
        }

        //=====================================================================
        // FrTOCSimData - Simulation data channel index
        //=====================================================================
        void
        LegacyTOCPrinter::printFrTOCSimData( const FrTOC* toc,
                                             unsigned int  version )
        {
            auto& sim = toc->GetSim();

            m_os << m_indent_str << "nSim: " << sim.size() << std::endl;

            if ( sim.empty() )
                return;

            // Channel names
            m_os << m_indent_str << "nameSim:";
            for ( auto& [name, info] : sim )
            {
                m_os << " " << name;
            }
            m_os << std::endl;

            // Channel positions (flattened array)
            m_os << m_indent_str << "positionSim:";
            for ( auto& [name, info] : sim )
            {
                for ( auto pos : info )
                {
                    m_os << " " << pos;
                }
            }
            m_os << std::endl;
        }

        //=====================================================================
        // FrTOCSerData - Serial data channel index
        //=====================================================================
        void
        LegacyTOCPrinter::printFrTOCSerData( const FrTOC* toc,
                                             unsigned int  version )
        {
            auto& ser = toc->GetSer();

            m_os << m_indent_str << "nSer: " << ser.size() << std::endl;

            if ( ser.empty() )
                return;

            // Channel names
            m_os << m_indent_str << "nameSer:";
            for ( auto& [name, info] : ser )
            {
                m_os << " " << name;
            }
            m_os << std::endl;

            // Channel positions (flattened array)
            m_os << m_indent_str << "positionSer:";
            for ( auto& [name, info] : ser )
            {
                for ( auto pos : info )
                {
                    m_os << " " << pos;
                }
            }
            m_os << std::endl;
        }

        //=====================================================================
        // FrTOCSummary - Summary/trend data index
        //=====================================================================
        void
        LegacyTOCPrinter::printFrTOCSummary( const FrTOC* toc,
                                             unsigned int  version )
        {
            auto& summary = toc->GetSummary();

            m_os << m_indent_str << "nSummary: " << summary.size() << std::endl;

            if ( summary.empty() )
                return;

            // Channel names
            m_os << m_indent_str << "nameSum:";
            for ( auto& [name, info] : summary )
            {
                m_os << " " << name;
            }
            m_os << std::endl;

            // Channel positions (flattened array)
            m_os << m_indent_str << "positionSum:";
            for ( auto& [name, info] : summary )
            {
                for ( auto pos : info )
                {
                    m_os << " " << pos;
                }
            }
            m_os << std::endl;
        }

        //=====================================================================
        // FrTOCEvent - Event/trigger data (COMPLEX - version-specific)
        //=====================================================================
        void
        LegacyTOCPrinter::printFrTOCEvent( const FrTOC* toc,
                                           unsigned int  version )
        {
            if ( version < 4 )
            {
                return; // No event data in v3
            }

            auto& events = toc->GetEvent();

            // Version-specific field names
            const char* count_field =
                ( version >= 5 ) ? "nEventType" : "nTrig";
            const char* name_field = ( version >= 5 ) ? "nameEvent" : "nameTrig";
            const char* time_s_field =
                ( version >= 5 ) ? "GTimeSEvent" : "GTimeSTrig";
            const char* time_n_field =
                ( version >= 5 ) ? "GTimeNEvent" : "GTimeNTrig";
            const char* pos_field =
                ( version >= 5 ) ? "positionEvent" : "positionTrig";

            // Count of event types
            m_os << m_indent_str << count_field << ": " << events.size() << std::endl;

            if ( events.empty() )
            {
                return;
            }

            // Event type names (single line, space-separated)
            m_os << m_indent_str << name_field << ":";
            for ( auto& [name, evts] : events )
            {
                m_os << " " << name;
            }
            m_os << std::endl;

            // Number of events per type (v5+)
            if ( version >= 5 )
            {
                m_os << m_indent_str << "nEvent:";
                for ( auto& [name, evts] : events )
                {
                    m_os << " " << evts.size();
                }
                m_os << std::endl;
            }

            // Total event count (v8+ only - but this is legacy printer for
            // v<8)
            // Intentionally omitted here - v8+ uses ModernTOCPrinter

            // Flatten events to parallel arrays - GPS seconds
            m_os << m_indent_str << time_s_field << ":";
            for ( auto& [name, evts] : events )
            {
                for ( auto& evt : evts )
                {
                    m_os << " " << evt.GTime.GetSeconds();
                }
            }
            m_os << std::endl;

            // GPS nanoseconds
            m_os << m_indent_str << time_n_field << ":";
            for ( auto& [name, evts] : events )
            {
                for ( auto& evt : evts )
                {
                    m_os << " " << evt.GTime.GetNanoseconds();
                }
            }
            m_os << std::endl;

            // Amplitude (v5+ only)
            if ( version >= 5 )
            {
                m_os << m_indent_str << "amplitudeEvent:";
                for ( auto& [name, evts] : events )
                {
                    for ( auto& evt : evts )
                    {
                        m_os << " " << std::fixed << std::setprecision( 6 )
                             << evt.amplitudeEvent;
                    }
                }
                m_os << std::endl;
            }

            // Event positions
            m_os << m_indent_str << pos_field << ":";
            for ( auto& [name, evts] : events )
            {
                for ( auto& evt : evts )
                {
                    m_os << " " << evt.positionEvent;
                }
            }
            m_os << std::endl;
        }

        //=====================================================================
        // FrTOCSimEvent - Simulation event data
        //=====================================================================
        void
        LegacyTOCPrinter::printFrTOCSimEvent( const FrTOC* toc,
                                              unsigned int  version )
        {
            if ( version < 4 )
            {
                return; // No sim event data in v3
            }

            auto& events = toc->GetSimEvent();

            // Version-specific field names (similar structure to FrEvent)
            const char* count_field =
                ( version >= 5 ) ? "nSimEventType" : "nSimEvt";

            m_os << m_indent_str << count_field << ": " << events.size() << std::endl;

            if ( events.empty() )
            {
                return;
            }

            // Event type names
            m_os << m_indent_str << "nameSimEvent:";
            for ( auto& [name, evts] : events )
            {
                m_os << " " << name;
            }
            m_os << std::endl;

            // Number of events per type (v5+)
            if ( version >= 5 )
            {
                m_os << m_indent_str << "nSimEvent:";
                for ( auto& [name, evts] : events )
                {
                    m_os << " " << evts.size();
                }
                m_os << std::endl;
            }

            // Flatten to parallel arrays - GPS seconds
            m_os << m_indent_str << "GTimeSSimEvent:";
            for ( auto& [name, evts] : events )
            {
                for ( auto& evt : evts )
                {
                    m_os << " " << evt.GTime.GetSeconds();
                }
            }
            m_os << std::endl;

            // GPS nanoseconds
            m_os << m_indent_str << "GTimeNSimEvent:";
            for ( auto& [name, evts] : events )
            {
                for ( auto& evt : evts )
                {
                    m_os << " " << evt.GTime.GetNanoseconds();
                }
            }
            m_os << std::endl;

            // Amplitude (v5+ only)
            if ( version >= 5 )
            {
                m_os << m_indent_str << "amplitudeSimEvent:";
                for ( auto& [name, evts] : events )
                {
                    for ( auto& evt : evts )
                    {
                        m_os << " " << std::fixed << std::setprecision( 6 )
                             << evt.amplitudeSimEvent;
                    }
                }
                m_os << std::endl;
            }

            // Positions
            m_os << m_indent_str << "positionSimEvent:";
            for ( auto& [name, evts] : events )
            {
                for ( auto& evt : evts )
                {
                    m_os << " " << evt.positionSimEvent;
                }
            }
            m_os << std::endl;
        }

        //=====================================================================
        // FrTOCStatData - Static data (deprecated in v9)
        //=====================================================================
        void
        LegacyTOCPrinter::printFrTOCStatData( const FrTOC* toc,
                                              unsigned int  version )
        {
            if ( version >= 9 )
            {
                return; // FrStatData deprecated in v9
            }

            // Use GetStat() instead of GetStatType() - GetStat() returns the m_info
            // that's populated by Load(), while GetStatType() returns a different member
            auto& statData = toc->GetStat();

            // Always print nStatType count, even if zero
            m_os << m_indent_str << "nStatType: " << statData.size() << std::endl;

            if ( statData.empty() )
            {
                return; // No stat data to print
            }

            // Iterate over each stat entry
            for ( auto& [name, stat] : statData )
            {
                m_os << m_indent_str << "nameStatData: " << name << std::endl;
                m_os << m_indent_str << "detectorStatData: " << stat.detector << std::endl;
                m_os << m_indent_str << "nStatInstance: " << stat.stat_instances.size() << std::endl;

                // Parallel arrays for stat instances
                if ( !stat.stat_instances.empty() )
                {
                    m_os << m_indent_str << "tStart:";
                    for ( auto& inst : stat.stat_instances )
                        m_os << " " << inst.tStart;
                    m_os << std::endl;

                    m_os << m_indent_str << "tEnd:";
                    for ( auto& inst : stat.stat_instances )
                        m_os << " " << inst.tEnd;
                    m_os << std::endl;

                    m_os << m_indent_str << "versionStatData:";
                    for ( auto& inst : stat.stat_instances )
                        m_os << " " << inst.version;
                    m_os << std::endl;

                    m_os << m_indent_str << "positionStat:";
                    for ( auto& inst : stat.stat_instances )
                        m_os << " " << inst.positionStat;
                    m_os << std::endl;
                }
            }
        }

        //=====================================================================
        // Helper template
        //=====================================================================
        template < typename Container >
        void
        LegacyTOCPrinter::printArray( const char*       label,
                                      const Container& container )
        {
            m_os << m_indent_str << label << ":";
            for ( const auto& val : container )
            {
                m_os << " " << val;
            }
            m_os << std::endl;
        }

    } // namespace Test
} // namespace FrameCPP
