//
// 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 "DynamicTOCPrinter.hh"
#include "framecpp/Universal/FrSE.hh"
#include <iomanip>
#include <sstream>

using FrameCPP::Universal::FrTOC;
using FrameCPP::Universal::FrSE;
using FrameCPP::Common::Description;

namespace FrameCPP
{
    namespace Test
    {
        // Helper template to print vector values with optional truncation
        template<typename T>
        static void printVector(std::ostream& os, const std::vector<T>& vec, size_t max_display)
        {
            if (vec.empty())
            {
                os << "[]";
                return;
            }

            os << "[";
            // 0 means show all
            size_t count = (max_display == 0) ? vec.size() : std::min(vec.size(), max_display);

            for (size_t i = 0; i < count; ++i)
            {
                if (i > 0) os << ", ";
                os << vec[i];
            }

            if (max_display > 0 && vec.size() > max_display)
            {
                os << ", ... (" << (vec.size() - max_display) << " more)";
            }
            os << "]";
        }

        // Helper to print string vectors with optional truncation
        static void printStringVector(std::ostream& os, const std::vector<std::string>& vec, size_t max_display)
        {
            if (vec.empty())
            {
                os << "[]";
                return;
            }

            os << "[";
            // 0 means show all
            size_t count = (max_display == 0) ? vec.size() : std::min(vec.size(), max_display);

            for (size_t i = 0; i < count; ++i)
            {
                if (i > 0) os << ", ";
                os << "\"" << vec[i] << "\"";
            }

            if (max_display > 0 && vec.size() > max_display)
            {
                os << ", ... (" << (vec.size() - max_display) << " more)";
            }
            os << "]";
        }

        DynamicTOCPrinter::DynamicTOCPrinter( std::ostream& os )
            : m_os( os ), m_max_array_display( 0 )  // Default: show all elements
        {
            initializeFieldPrinters();
        }

        void
        DynamicTOCPrinter::initializeFieldPrinters()
        {
            // Basic TOC fields
            m_field_printers["nFrame"] = [](const FrTOC* toc, std::ostream& os) {
                os << toc->GetNFrame();
            };

            // Event data fields
            m_field_printers["nameEvent"] = [this](const FrTOC* toc, std::ostream& os) {
                printStringVector(os, toc->GetNameEventArray(), m_max_array_display);
            };
            m_field_printers["nEvent"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetNEventArray(), m_max_array_display);
            };
            m_field_printers["nTotalEvent"] = [](const FrTOC* toc, std::ostream& os) {
                os << toc->GetNTotalEvent();
            };
            m_field_printers["GTimeSEvent"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetGTimeSEventArray(), m_max_array_display);
            };
            m_field_printers["GTimeNEvent"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetGTimeNEventArray(), m_max_array_display);
            };
            m_field_printers["amplitudeEvent"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetAmplitudeEventArray(), m_max_array_display);
            };
            m_field_printers["positionEvent"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetPositionEventArray(), m_max_array_display);
            };

            // SimEvent data fields
            m_field_printers["nameSimEvent"] = [this](const FrTOC* toc, std::ostream& os) {
                printStringVector(os, toc->GetNameSimEventArray(), m_max_array_display);
            };
            m_field_printers["nSimEvent"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetNSimEventArray(), m_max_array_display);
            };
            m_field_printers["nTotalSEvent"] = [](const FrTOC* toc, std::ostream& os) {
                os << toc->GetNTotalSEvent();
            };
            m_field_printers["GTimeSSim"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetGTimeSSimArray(), m_max_array_display);
            };
            m_field_printers["GTimeNSim"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetGTimeNSimArray(), m_max_array_display);
            };
            m_field_printers["amplitudeSimEvent"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetAmplitudeSimEventArray(), m_max_array_display);
            };
            m_field_printers["positionSimEvent"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetPositionSimEventArray(), m_max_array_display);
            };

            // ADC data fields
            m_field_printers["name"] = [this](const FrTOC* toc, std::ostream& os) {
              printStringVector(os, toc->GetNameADCArray(), m_max_array_display);
            };
            m_field_printers["nameAdc"] = [this](const FrTOC* toc, std::ostream& os) {
                printStringVector(os, toc->GetNameADCArray(), m_max_array_display);
            };
            m_field_printers["nADC"] = [](const FrTOC* toc, std::ostream& os) {
                os << toc->GetNameADCArray().size();
            };
            m_field_printers["positionADC"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetPositionADCFlat(), m_max_array_display);
            };

            // Proc data fields
            m_field_printers["nameProc"] = [this](const FrTOC* toc, std::ostream& os) {
                printStringVector(os, toc->GetNameProcArray(), m_max_array_display);
            };
            m_field_printers["nProc"] = [](const FrTOC* toc, std::ostream& os) {
                os << toc->GetNameProcArray().size();
            };
            m_field_printers["positionProc"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetPositionProcFlat(), m_max_array_display);
            };

            // Sim data fields
            m_field_printers["nameSim"] = [this](const FrTOC* toc, std::ostream& os) {
                printStringVector(os, toc->GetNameSimArray(), m_max_array_display);
            };
            m_field_printers["nSim"] = [](const FrTOC* toc, std::ostream& os) {
                os << toc->GetNameSimArray().size();
            };
            m_field_printers["positionSim"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetPositionSimFlat(), m_max_array_display);
            };

            // Ser data fields
            m_field_printers["nameSer"] = [this](const FrTOC* toc, std::ostream& os) {
                printStringVector(os, toc->GetNameSerArray(), m_max_array_display);
            };
            m_field_printers["nSer"] = [](const FrTOC* toc, std::ostream& os) {
                os << toc->GetNameSerArray().size();
            };
            m_field_printers["positionSer"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetPositionSerFlat(), m_max_array_display);
            };

            // Summary data fields
            m_field_printers["nameSum"] = [this](const FrTOC* toc, std::ostream& os) {
                printStringVector(os, toc->GetNameSumArray(), m_max_array_display);
            };
            m_field_printers["nSum"] = [](const FrTOC* toc, std::ostream& os) {
                os << toc->GetNameSumArray().size();
            };
            m_field_printers["positionSum"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetPositionSumFlat(), m_max_array_display);
            };

            // StatData fields (v<9 only, but Description will tell us if they exist)
            m_field_printers["nameStat"] = [this](const FrTOC* toc, std::ostream& os) {
                printStringVector(os, toc->GetNameStatArray(), m_max_array_display);
            };
            m_field_printers["detectorStat"] = [this](const FrTOC* toc, std::ostream& os) {
                printStringVector(os, toc->GetDetectorStatArray(), m_max_array_display);
            };
            m_field_printers["nStatInstance"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetNStatInstanceArray(), m_max_array_display);
            };
            m_field_printers["nTotalStat"] = [](const FrTOC* toc, std::ostream& os) {
                os << toc->GetNTotalStat();
            };
            m_field_printers["tStart"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetTStartArray(), m_max_array_display);
            };
            m_field_printers["tEnd"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetTEndArray(), m_max_array_display);
            };
            m_field_printers["versionStat"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetVersionStatArray(), m_max_array_display);
            };
            m_field_printers["positionStat"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetPositionStatArray(), m_max_array_display);
            };

            // Detector/SH fields
            m_field_printers["nameDetector"] = [this](const FrTOC* toc, std::ostream& os) {
                printStringVector(os, toc->GetNameDetectorArray(), m_max_array_display);
            };
            m_field_printers["nDetector"] = [](const FrTOC* toc, std::ostream& os) {
                os << toc->GetNameDetectorArray().size();
            };
            m_field_printers["positionDetector"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetPositionDetectorArray(), m_max_array_display);
            };
            m_field_printers["nSH"] = [](const FrTOC* toc, std::ostream& os) {
                os << toc->GetSHidArray().size();
            };
            m_field_printers["SHid"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetSHidArray(), m_max_array_display);
            };
            m_field_printers["SHname"] = [this](const FrTOC* toc, std::ostream& os) {
                printStringVector(os, toc->GetSHnameArray(), m_max_array_display);
            };

            // Other common fields that might appear
            m_field_printers["dataQuality"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetDataQuality(), m_max_array_display);
            };
            m_field_printers["GTimeS"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetGTimeS(), m_max_array_display);
            };
            m_field_printers["GTimeN"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetGTimeN(), m_max_array_display);
            };
            m_field_printers["dt"] = [this](const FrTOC* toc, std::ostream& os) {
                printVector(os, toc->GetDt(), m_max_array_display);
            };
        }

        void
        DynamicTOCPrinter::print( const FrTOC* toc, const Description& desc )
        {
            m_os << "=== FrTOC - Description-Driven Printer ===" << std::endl;
            m_os << "Structure: " << desc.GetName() << std::endl << std::endl;

            // Iterate over Description elements (FrSH and FrSE objects)
            for ( const auto& element : desc )
            {
                // Check if this is an FrSE (field entry)
                if ( const auto* frse = dynamic_cast<const FrSE*>(element.get()) )
                {
                    std::string field_name = frse->GetName();

                    // Look up the printer for this field
                    auto it = m_field_printers.find(field_name);
                    if (it != m_field_printers.end())
                    {
                        m_os << std::setw(25) << std::left << field_name << ": ";
                        try
                        {
                            it->second(toc, m_os);
                            m_os << std::endl;
                        }
                        catch (const std::exception& e)
                        {
                            m_os << "<error: " << e.what() << ">" << std::endl;
                        }
                    }
                    else
                    {
                        // Field exists in Description but we don't have a printer for it
                        m_os << std::setw(25) << std::left << field_name
                             << ": <no helper>" << std::endl;
                    }
                }
                // FrSH objects are structure headers - we can optionally print them
                // but for now we just skip them as they're already shown above
            }

            m_os << std::endl;
        }

    } // namespace Test
} // namespace FrameCPP
