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

/* -*- mode: C++; c-basic-offset: 2; -*- */
#if HAVE_CONFIG_H
#include <framecpp_config.h>
#endif /* HAVE_CONFIG_H */

#include <exception>
#include <string.h>
#include <unistd.h>

#include <iostream>

#include "ldastoolsal/MemChecker.hh"

#include <boost/program_options.hpp>

#include "framecpp/Common/FrameSpec.hh"
#include "framecpp/Common/IOStream.hh"
#include "framecpp/Common/FrameBuffer.hh"
#include "framecpp/Common/FrameStream.hh"
#include "framecpp/Common/FrTOC.hh"

#include "framecpp/Universal/FrTOC.hh"

#include "framecpp/FrameCPP.hh"
#include "framecpp/FrameCPPDebugManager.hh"
#include "ldastoolsal/DebugLevelManager.hh"

#include "LegacyTOCPrinter.hh"
#include "ModernTOCPrinter.hh"

using LDASTools::AL::MemChecker;

using namespace FrameCPP::Common;

namespace po = boost::program_options;

using std::filebuf;

inline void
depart( int ExitCode )
{
    // Use _exit() to bypass static destructors which have a mutex bug
    // when no TOC is present in any processed files
    _exit( ExitCode );
}

//-----------------------------------------------------------------------
/// \brief Class to handle command line options for this application
//-----------------------------------------------------------------------
class CommandLine
{
public:
    typedef std::list< std::string > channel_container_type;

    CommandLine( int ArgC, char** ArgV );

    inline bool
    BadOption( ) const
    {
        // With boost::program_options, bad options are handled during parsing
        // This method is kept for compatibility but always returns false
        return false;
    }

    inline void
    Usage( int ExitValue ) const
    {
        std::cout << "Usage: " << m_program_name << " [options] <file> [<file> ...]\n";
        std::cout << m_visible_options << std::endl;
        depart( ExitValue );
    }

    inline bool
    empty( ) const
    {
        return m_input_files.empty();
    }

    inline std::string
    Pop( )
    {
        if (!m_input_files.empty()) {
            std::string file = m_input_files.front();
            m_input_files.pop_front();
            return file;
        }
        return "";
    }

    inline size_t
    size( ) const
    {
        return m_input_files.size();
    }

    inline const std::string&
    ProgramName( ) const
    {
        return m_program_name;
    }

private:
    std::string m_program_name;
    po::options_description m_visible_options;
    po::options_description m_all_options;
    po::variables_map m_vm;
    std::list<std::string> m_input_files;
};

CommandLine::CommandLine( int ArgC, char** ArgV )
    : m_program_name( ArgV[0] ),
      m_visible_options( "Options" ),
      m_all_options( "All options" )
{
    try {
        // Define visible options (shown in --help)
        m_visible_options.add_options()
            ("help,h", "Show this help message")
        ;

        // Add debug options using base class (callbacks already registered by FrameCPPDebugManager::initialize)
        LDASTools::AL::DebugLevelManager::add_debug_options(m_visible_options, m_vm);

        // Hidden options (input files)
        po::options_description hidden_options("Hidden options");
        hidden_options.add_options()
            ("input-files", po::value<std::vector<std::string>>(), "Input files")
        ;

        // All options (visible + hidden)
        m_all_options.add(m_visible_options).add(hidden_options);

        // Positional options (input files)
        po::positional_options_description pos_options;
        pos_options.add("input-files", -1);

        // Parse command line
        po::store(po::command_line_parser(ArgC, ArgV)
                  .options(m_all_options)
                  .positional(pos_options)
                  .run(), m_vm);
        po::notify(m_vm);

        // Handle help
        if (m_vm.count("help")) {
            Usage(0);
        }

        // Configure debug manager from parsed options (uses callbacks for enhanced help)
        LDASTools::AL::DebugLevelManager::load_from_environment();
        LDASTools::AL::DebugLevelManager::configure_from_options(m_vm);

        // Process input files
        if (m_vm.count("input-files")) {
            std::vector<std::string> files = m_vm["input-files"].as<std::vector<std::string>>();
            for (const auto& file : files) {
                m_input_files.push_back(file);
            }
        }
        
    } catch (const po::error& e) {
        std::cerr << "Error parsing command line: " << e.what() << std::endl;
        Usage(1);
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        Usage(1);
    }
}

int
main( int ArgC, char* ArgV[] )
try
{
    // Note: MemChecker disabled - causes mutex errors during cleanup when
    // processing files without TOC
    // MemChecker::Trigger gc_trigger( true );

    // Register frameCPP-specific debug callbacks (must be done before parsing options)
    FrameCPP::Utilities::FrameCPPDebugManager::set_help_callback(FrameCPP::Utilities::FrameCPPDebugManager::get_available_channels_help);
    FrameCPP::Utilities::FrameCPPDebugManager::set_library_name("framecpp");
    FrameCPP::Utilities::FrameCPPDebugManager::set_env_prefix("FRAMECPP_DEBUG");
    FrameCPP::Utilities::FrameCPPDebugManager::set_example_channels({
        "framecpp.frtoc.*",
        "framecpp.frendoffile", 
        "framecpp.*"
    });
    
    CommandLine         cl( ArgC, ArgV );

    if ( cl.empty( ) || cl.BadOption( ) )
    {
        cl.Usage( 1 );
    }

    //---------------------------------------------------------------------
    // Process files
    //---------------------------------------------------------------------
    FrameCPP::Initialize( );

    while ( cl.empty( ) == false )
    {
        std::string filename( cl.Pop( ) );

        FrameBuffer< filebuf >* ibuf = nullptr;
        IFrameStream* ifs = nullptr;

        try
        {
            //-------------------------------------------------------------------
            // Creation of the frame structure by reading of frame file
            //-------------------------------------------------------------------
            ibuf = new FrameBuffer< filebuf >( std::ios::in );
            ibuf->open( filename.c_str( ), std::ios::in | std::ios::binary );

            ifs = new IFrameStream( ibuf, 0 );

            std::cout << "Frame file: " << filename << std::endl
                      << "Frame Specification: " << ifs->Version( ) << std::endl;

            // Get TOC - it's optional and may throw if not present
            const FrTOC* toc_base = nullptr;
            try
            {
                toc_base = ifs->GetTOC( );
            }
            catch ( const std::exception& toc_error )
            {
                std::cout << "No table of contents (TOC) present in this file."
                          << std::endl;
                // Clean up before continuing
                delete ifs;
                ifs = nullptr;
                // Note: ibuf is owned by ifs and will be deleted with it
                continue; // Skip to next file
            }

            if ( !toc_base )
            {
                std::cout << "No table of contents (TOC) present in this file."
                          << std::endl;
                delete ifs;
                ifs = nullptr;
                continue; // Skip to next file
            }

            // Cast to Universal::FrTOC
            const FrameCPP::Universal::FrTOC* toc =
                dynamic_cast<const FrameCPP::Universal::FrTOC*>( toc_base );
            if ( !toc )
            {
                std::cerr << "Warning: TOC exists but is not Universal::FrTOC type"
                          << std::endl;
                delete ifs;
                ifs = nullptr;
                continue;
            }

            unsigned int version = static_cast<unsigned int>( ifs->Version( ) );

            // Dispatch to appropriate printer based on version
            if ( version >= 8 )
            {
                // v8+: Use modern printer (includes nTotalEvent, no FrStatData in v9)
                FrameCPP::Test::ModernTOCPrinter printer( std::cout );
                printer.print( toc, version );
            }
            else
            {
                // v3-7: Use legacy printer (parallel arrays, v<8 field names)
                FrameCPP::Test::LegacyTOCPrinter printer( std::cout );
                printer.print( toc, version );
            }

            // Clean up at end of successful processing
            delete ifs;
            ifs = nullptr;
        }
        catch ( const std::exception& Error )
        {
            std::cerr << "Error reading: " << filename
                      << " exception: " << Error.what( ) << std::endl;
            // Clean up on error
            if ( ifs )
            {
                delete ifs;
                ifs = nullptr;
            }
        }
    }
    //---------------------------------------------------------------------
    // Exit
    //---------------------------------------------------------------------
    depart( 0 );
}
catch ( ... )
{
    depart( 1 );
}
