//
// 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 "ModernTOCPrinter.hh"
#include <iomanip>

using FrameCPP::Universal::FrTOC;

namespace FrameCPP
{
    namespace Test
    {
        ModernTOCPrinter::ModernTOCPrinter( std::ostream& os )
            : m_os( os ), m_legacy_printer( os )
        {
        }

        void
        ModernTOCPrinter::print( const FrTOC* toc, unsigned int version )
        {
            // Most sections can reuse legacy printer logic
            // Only Event sections need special v8+ handling

            // Use legacy printer for all non-event sections
            // (printFrTOCData, printFrTOCAdcData, etc. are identical)

            // We'll manually call the sections in order, using legacy printer
            // where appropriate, and overriding for events

            // FrTOCData section - same for all versions
            m_os << "nFrame: " << toc->GetNFrame() << std::endl;

            // dataQuality (v5+, so always present in v8+)
            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) - always present in v8+
            m_os << "nSH: " << toc->GetSHid().size() << std::endl;
            if ( !toc->GetSHid().empty() )
            {
                printArray( "SHid", toc->GetSHid() );
                printArray( "SHname", toc->GetSHname() );
            }

            // Detector information - always present in v8+
            m_os << "nDetector: " << toc->GetNameDetector().size()
                 << std::endl;
            if ( !toc->GetNameDetector().empty() )
            {
                printArray( "nameDetector", toc->GetNameDetector() );
                printArray( "positionDetector", toc->GetPositionDetector() );
            }

            // FrStatData - only for v8 (deprecated in v9) - appears BEFORE ADC
            if ( version < 9 )
            {
                // 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 << "nStatType: " << statData.size() << std::endl;

                if ( !statData.empty() )
                {
                    for ( auto& [name, stat] : statData )
                    {
                        m_os << "nameStatData: " << name << std::endl;
                        m_os << "detectorStatData: " << stat.detector
                             << std::endl;
                        m_os << "nStatInstance: " << stat.stat_instances.size()
                             << std::endl;

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

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

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

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

            // ADC, Proc, Sim, Ser, Summary - same as legacy
            auto& adc = toc->GetADC();
            m_os << "nADC: " << adc.size() << std::endl;
            if ( !adc.empty() )
            {
                m_os << "name:";
                for ( auto& [name, info] : adc )
                    m_os << " " << name;
                m_os << std::endl;

                // channelID array (v8 only, removed in v9)
                if ( version >= 4 && version < 9 )
                {
                    m_os << "channelID:";
                    for ( auto& [name, info] : adc )
                        m_os << " " << ( info.m_channelID.empty() ? 0 : info.m_channelID[0] );
                    m_os << std::endl;
                }

                // groupID array (v8 only, removed in v9)
                if ( version >= 4 && version < 9 )
                {
                    m_os << "groupID:";
                    for ( auto& [name, info] : adc )
                        m_os << " " << ( info.m_groupID.empty() ? 0 : info.m_groupID[0] );
                    m_os << std::endl;
                }

                m_os << "positionADC:";
                for ( auto& [name, info] : adc )
                    for ( auto pos : info.m_positionADC )
                        m_os << " " << pos;
                m_os << std::endl;
            }

            auto& proc = toc->GetProc();
            m_os << "nProc: " << proc.size() << std::endl;
            if ( !proc.empty() )
            {
                m_os << "nameProc:";
                for ( auto& [name, info] : proc )
                    m_os << " " << name;
                m_os << std::endl;

                m_os << "positionProc:";
                for ( auto& [name, info] : proc )
                    for ( auto pos : info )
                        m_os << " " << pos;
                m_os << std::endl;
            }

            auto& sim = toc->GetSim();
            m_os << "nSim: " << sim.size() << std::endl;
            if ( !sim.empty() )
            {
                m_os << "nameSim:";
                for ( auto& [name, info] : sim )
                    m_os << " " << name;
                m_os << std::endl;

                m_os << "positionSim:";
                for ( auto& [name, info] : sim )
                    for ( auto pos : info )
                        m_os << " " << pos;
                m_os << std::endl;
            }

            auto& ser = toc->GetSer();
            m_os << "nSer: " << ser.size() << std::endl;
            if ( !ser.empty() )
            {
                m_os << "nameSer:";
                for ( auto& [name, info] : ser )
                    m_os << " " << name;
                m_os << std::endl;

                m_os << "positionSer:";
                for ( auto& [name, info] : ser )
                    for ( auto pos : info )
                        m_os << " " << pos;
                m_os << std::endl;
            }

            auto& summary = toc->GetSummary();
            m_os << "nSummary: " << summary.size() << std::endl;
            if ( !summary.empty() )
            {
                m_os << "nameSum:";
                for ( auto& [name, info] : summary )
                    m_os << " " << name;
                m_os << std::endl;

                m_os << "positionSum:";
                for ( auto& [name, info] : summary )
                    for ( auto pos : info )
                        m_os << " " << pos;
                m_os << std::endl;
            }

            // Events - v8+ specific (includes nTotalEvent)
            printFrTOCEvent( toc, version );

            // Sim Events - v8+ specific (includes nTotalSEvent)
            printFrTOCSimEvent( toc, version );
        }

        //=====================================================================
        // FrTOCEvent - v8+ adds nTotalEvent field
        //=====================================================================
        void
        ModernTOCPrinter::printFrTOCEvent( const FrTOC* toc,
                                           unsigned int  version )
        {
            auto& events = toc->GetEvent();

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

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

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

            // Number of events per type
            m_os << "nEvent:";
            for ( auto& [name, evts] : events )
            {
                m_os << " " << evts.size();
            }
            m_os << std::endl;

            // v8+ specific: total event count
            size_t total = 0;
            for ( auto& [name, evts] : events )
                total += evts.size();
            m_os << "nTotalEvent: " << total << std::endl;

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

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

            // Amplitude
            m_os << "amplitudeEvent:";
            for ( auto& [name, evts] : events )
            {
                for ( auto& evt : evts )
                {
                    m_os << " " << std::fixed << std::setprecision( 6 )
                         << evt.amplitudeEvent;
                }
            }
            m_os << std::endl;

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

        //=====================================================================
        // FrTOCSimEvent - v8+ adds nTotalSEvent field
        //=====================================================================
        void
        ModernTOCPrinter::printFrTOCSimEvent( const FrTOC* toc,
                                              unsigned int  version )
        {
            auto& events = toc->GetSimEvent();

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

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

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

            // Number of events per type
            m_os << "nSimEvent:";
            for ( auto& [name, evts] : events )
            {
                m_os << " " << evts.size();
            }
            m_os << std::endl;

            // v8+ specific: total event count
            size_t total = 0;
            for ( auto& [name, evts] : events )
                total += evts.size();
            m_os << "nTotalSEvent: " << total << std::endl;

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

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

            // Amplitude
            m_os << "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 << "positionSimEvent:";
            for ( auto& [name, evts] : events )
            {
                for ( auto& evt : evts )
                {
                    m_os << " " << evt.positionSimEvent;
                }
            }
            m_os << std::endl;
        }

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

    } // namespace Test
} // namespace FrameCPP
