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

#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>  // for _exit()

#include <iostream>
#include <memory>
#include <stdexcept>
#include <sstream>

// Boost libraries
#include <boost/program_options.hpp>

#include "ldastoolsal/MemChecker.hh"
#include "ldastoolsal/LoggerManager.hh"

#include "framecpp/Common/FrameSpec.hh"
#include "framecpp/Common/Verify.hh"
#include "framecpp/Common/FrameCPPServices.hh"
#include "framecpp/FrameCPPDebugManager.hh"

#include "framecpp/FrameCPP.hh"

#include "../src/Utilities/StandardOptions.hh"

#define DEFAULT_BUFFER_SIZE 256

using LDASTools::AL::MemChecker;
using FrameCPP::Common::Verify;
using FrameCPP::Common::VerifyException;
namespace po = boost::program_options;

namespace
{
    enum error_type
    {
        MIN_ERROR = VerifyException::MAX_ERROR + 1,
        USAGE = MIN_ERROR,
        UNHANDLED_EXCEPTION,
        MAX_ERROR = UNHANDLED_EXCEPTION
    };

    // boost::program_options consumes the next token as the option's value
    // even when implicit_value() is set, so `--verbose file.gwf` is parsed as
    // `verbose=file.gwf` and rejected as a non-integer. Rewrite bare
    // `--verbose` / `-v` (when not followed by an integer literal) into the
    // `--verbose=1` form, which is the only form that reliably triggers
    // implicit_value with positional arguments following.
    bool
    looks_like_int( const char* s )
    {
        if ( !s || !*s ) return false;
        const char* p = s;
        if ( *p == '+' || *p == '-' ) ++p;
        if ( !*p ) return false;
        for ( ; *p; ++p )
            if ( *p < '0' || *p > '9' ) return false;
        return true;
    }

    // Returns ArgV[1..ArgC-1] (program name skipped) with bare --verbose / -v
    // rewritten as --verbose=1, suitable for the std::vector<std::string>
    // overload of po::command_line_parser.
    std::vector<std::string>
    rewrite_bare_verbose( int ArgC, char** ArgV )
    {
        std::vector<std::string> out;
        out.reserve( static_cast<size_t>( ArgC > 0 ? ArgC - 1 : 0 ) );
        for ( int i = 1; i < ArgC; ++i )
        {
            const char* tok = ArgV[ i ];
            const bool is_bare = ( tok && ( std::string( tok ) == "--verbose" ||
                                            std::string( tok ) == "-v" ) );
            if ( is_bare )
            {
                const char* next =
                    ( i + 1 < ArgC ) ? ArgV[ i + 1 ] : nullptr;
                if ( !looks_like_int( next ) )
                {
                    out.emplace_back( "--verbose=1" );
                    continue;
                }
            }
            out.emplace_back( tok ? tok : "" );
        }
        return out;
    }

} // namespace

//-----------------------------------------------------------------------
// Common tasks to be performed when exiting.
//-----------------------------------------------------------------------
inline void
depart( int ExitCode )
{
    // Use _exit() instead of exit() to bypass static destructor cleanup
    // which can trigger mutex bugs in legacy code
    _exit( ExitCode );
}

//-----------------------------------------------------------------------
/// \brief Class to handle command line options for this application
//-----------------------------------------------------------------------
class CommandLine
{
public:
    CommandLine( int ArgC, char** ArgV );
    
    bool empty() const { return m_remaining_args.empty(); }
    std::string Pop() { 
        if (m_remaining_args.empty()) return "";
        std::string result = m_remaining_args.front();
        m_remaining_args.erase(m_remaining_args.begin());
        return result;
    }
    using option_type = std::string;

    inline void
    Usage( int ExitValue ) const
    {
        std::cerr << "Usage: " << m_program_name << " [options] <file> [<file> ...]\n\n";
        std::cerr << "This verifies the contents of each file on the command line.\n\n";
        std::cerr << m_desc << std::endl;
        depart( ExitValue );
    }


    inline size_t
    BlockSize( )
    {
        return m_block_size;
    }

    inline void
    BlockSize( size_t Value )
    {
        m_block_size = Value;
    }

    inline bool
    ChecksumFileOnly( )
    {
        return m_checksum_file_only;
    }

    inline void
    ChecksumFileOnly( bool Value )
    {
        m_checksum_file_only = Value;
    }

    inline bool
    ChecksumFrames( )
    {
        return m_checksum_frames;
    }

    inline void
    ChecksumFrames( bool Value )
    {
        m_checksum_frames = Value;
    }

    inline bool
    CollectAllErrors( ) const
    {
        return collect_all_errors;
    }

    inline void
    CollectAllErrors( bool Value )
    {
        collect_all_errors = Value;
    }

    inline bool
    DataValid( )
    {
        return m_data_valid;
    }

    inline void
    DataValid( bool Value )
    {
        m_data_valid = Value;
    }

    inline bool
    DataValidAll( )
    {
        return m_data_valid_all;
    }

    inline void
    DataValidAll( bool Value )
    {
        m_data_valid_all = Value;
    }

    inline bool
    DuplicateChannelNames( )
    {
        return duplicate_channel_names;
    }

    inline void
    DuplicateChannelNames( bool Value )
    {
        duplicate_channel_names = Value;
    }

    inline bool
    Force( )
    {
        return m_force;
    }

    inline void
    Force( bool Value )
    {
        m_force = Value;
    }

    inline bool
    MetadataCheck( )
    {
        return m_metadata_check;
    }

    inline void
    MetadataCheck( bool Value )
    {
        m_metadata_check = Value;
    }

    inline bool
    DecompressCheck( )
    {
        return m_decompress_check;
    }

    inline void
    DecompressCheck( bool Value )
    {
        m_decompress_check = Value;
    }

    inline bool
    Relax( )
    {
        return m_relax;
    }

    inline void
    Relax( bool Value )
    {
        m_relax = Value;
    }

    inline bool
    RelaxEOFChecksum( )
    {
        return m_relax_eof_checksum;
    }

    inline void
    RelaxEOFChecksum( bool Value )
    {
        m_relax_eof_checksum = Value;
    }

    inline bool
    UseMemoryMappedIO( )
    {
        return m_use_memory_mapped_io;
    }

    inline void
    UseMemoryMappedIO( bool Value )
    {
        m_use_memory_mapped_io = Value;
    }

    inline size_t
    Verbose( )
    {
        return m_verbose;
    }

    inline void
    VerboseInc( )
    {
        ++m_verbose;
    }

private:
    po::options_description m_desc;
    po::variables_map m_vm;
    std::string m_program_name;
    std::vector<std::string> m_remaining_args;
    size_t    m_block_size = DEFAULT_BUFFER_SIZE;
    bool      m_checksum_file_only = false;
    bool      m_checksum_frames = false;
    bool      collect_all_errors = false;
    bool      m_data_valid = false;
    bool      m_data_valid_all = false;
    bool      duplicate_channel_names = true;
    bool      m_force = false;
    bool      m_metadata_check = false;
    // Issue #381: default OFF — decompression check is expensive and
    // most verify runs want CRC + metadata only. Opt in with
    // --decompress-check.
    bool      m_decompress_check = false;
    bool      m_relax = false;
    bool      m_relax_eof_checksum = false;
    bool      m_use_memory_mapped_io = false;
    size_t    m_verbose = 0;
};

CommandLine::CommandLine( int ArgC, char** ArgV ) 
    : m_desc("Allowed options"), m_program_name(ArgV[0])
{
    // Setup all options using boost::program_options
    m_desc.add_options()
        ("help,h", "Show this help message")
        ("force,f", "Verify each of the files instead of exiting on first exit")
        ("relax,r", "Use a relaxed interpretation of the frame specification")
        ("relax-eof-checksum", "Accepts file that lack an end of file checksum")
        ("collect-all-errors,a", "Collect all errors associated with the stream")
        ("data-valid", "Check the dataValid bit in FrAdcData structures for non-zero values")
        ("data-valid-all", "Check the dataValid bit in FrAdcData structures for non-zero values (reports all channels)")
        ("check-for-duplicate-channel-names", po::value<bool>()->default_value(true), "Check for duplicate channel names within a frame")
        ("metadata-check", "Enable the checking of the frame's meta data")
        ("no-metadata-check", "Disable the checking of the frame's meta data")
        ("decompress-check", "Also decompress every compressed FrVect as part of validation (expensive; off by default — CRC and metadata checks catch on-disk corruption without touching the decompression path)")
        ("verbose,v", po::value<int>()->implicit_value(1)->default_value(0), "Display file name and exit status of each file processed (0=silent, 1=basic, 2=detailed with calculated and stored checksum values)")
        ("debug-level", po::value<int>(), "Set global debug level (0-100)")
        ("debug-channel", po::value<std::string>(), "Set debug channel(s) with optional levels")
        ("debug-level-channel", po::value<std::string>(), "Set debug level for specific channel")
        ("help-debug", "Show available debug channels and usage examples")
        ("checksum-file-only", "Only validate the file checksum")
        ("checksum-frames", "Run checksums on individual frames")
        ("use-memory-mapped-io,m", "Use memory mapped I/O for reading files")
        ("block-size,b", po::value<size_t>()->default_value(DEFAULT_BUFFER_SIZE), "Set the block size for I/O operations")
    ;

    // Add positional arguments for files
    po::options_description hidden("Hidden options");
    hidden.add_options()
        ("input-files", po::value<std::vector<std::string>>(), "Input files to verify")
    ;

    po::options_description all_options;
    all_options.add(m_desc).add(hidden);

    po::positional_options_description p;
    p.add("input-files", -1);

    try {
        const std::vector<std::string> rewritten =
            rewrite_bare_verbose( ArgC, ArgV );
        po::store(po::command_line_parser(rewritten)
                 .options(all_options)
                 .positional(p)
                 .run(), m_vm);
        po::notify(m_vm);

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

        if (m_vm.count("help-debug")) {
            std::cout << FrameCPP::Utilities::FrameCPPDebugManager::get_help_text() << std::endl;
            depart(0);
        }

        // Set boolean flags based on presence
        if (m_vm.count("force")) Force(true);
        if (m_vm.count("relax")) Relax(true);
        if (m_vm.count("relax-eof-checksum")) RelaxEOFChecksum(true);
        if (m_vm.count("collect-all-errors")) CollectAllErrors(true);
        if (m_vm.count("data-valid")) DataValid(true);
        if (m_vm.count("data-valid-all")) {
            DataValid(true);
            DataValidAll(true);
        }
        if (m_vm.count("check-for-duplicate-channel-names")) {
            DuplicateChannelNames(m_vm["check-for-duplicate-channel-names"].as<bool>());
        }
        if (m_vm.count("metadata-check")) MetadataCheck(true);
        if (m_vm.count("no-metadata-check")) MetadataCheck(false);
        if (m_vm.count("decompress-check")) DecompressCheck(true);
        if (m_vm.count("checksum-file-only")) ChecksumFileOnly(true);
        if (m_vm.count("checksum-frames")) ChecksumFrames(true);
        if (m_vm.count("use-memory-mapped-io")) UseMemoryMappedIO(true);

        // Handle verbose option with explicit level
        if (m_vm.count("verbose")) {
            int verbose_level = m_vm["verbose"].as<int>();
            m_verbose = verbose_level;
        }

        // Handle valued options
        if (m_vm.count("block-size")) {
            BlockSize(m_vm["block-size"].as<size_t>());
        }

        if (m_vm.count("debug-level")) {
            int level = m_vm["debug-level"].as<int>();
            FrameCPP::Utilities::FrameCPPDebugManager::set_global_level_with_cache(level);
        }

        if (m_vm.count("debug-channel")) {
            FrameCPP::Utilities::FrameCPPDebugManager::parse_channel_spec(m_vm["debug-channel"].as<std::string>());
        }

        if (m_vm.count("debug-level-channel")) {
            FrameCPP::Utilities::FrameCPPDebugManager::parse_channel_spec(m_vm["debug-level-channel"].as<std::string>());
        }

        // Store remaining arguments (files)
        if (m_vm.count("input-files")) {
            m_remaining_args = m_vm["input-files"].as<std::vector<std::string>>();
        }

    } catch (const po::error& e) {
        std::cerr << "Error: " << e.what() << "\n";
        Usage(1);
    }
}

//-----------------------------------------------------------------------
//-----------------------------------------------------------------------
int
main( int ArgC, char** ArgV )
{
    MemChecker::Trigger gc_trigger( true );
    
    try
    {
        // SERVICES-BASED APPROACH: Following Peter Muldoon's "Retiring the Singleton Pattern"
        // - Explicit service lifetime management
        // - No global/static state that can cause destructor ordering issues
        // - Eliminates need for std::_Exit workarounds
        auto logger_mgr = std::make_unique<LDASTools::AL::LoggerManager>();
        FrameCPP::Common::FrameCPPServices services(*logger_mgr);
        
        // Initialize debug system with services (eliminates global static state)
        if ( !FrameCPP::Utilities::FrameCPPDebugManager::initialize_with_services(
                 ArgC, ArgV, services, "framecpp_verify" ) )
        {
            // Clean shutdown before return (no std::_Exit needed)
            logger_mgr->shutdown();
            return 0; // Help was requested
        }

        CommandLine         cl( ArgC, ArgV );

    Verify verifier;
    INT_4U exit_status = VerifyException::NO_ERROR;

    FrameCPP::Initialize( );

    //---------------------------------------------------------------------
    // Disable memory mapped I/O so failed reads can return without
    //   causing a signal.
    //---------------------------------------------------------------------
    verifier.BufferSize( cl.BlockSize( ) * 1024 );

    verifier.CollectAllErrors( cl.CollectAllErrors( ) );
    verifier.CheckFrameChecksum( cl.ChecksumFrames( ) );
    verifier.CheckDataValid( cl.DataValid( ) );
    verifier.CheckDataValidAll( cl.DataValidAll( ) );
    verifier.CheckForDuplicateChannelNames( cl.DuplicateChannelNames( ) );
    verifier.MustHaveEOFChecksum( cl.RelaxEOFChecksum( ) == false );
    verifier.Strict( cl.Relax( ) == false );
    verifier.UseMemoryMappedIO( cl.UseMemoryMappedIO( ) );
    verifier.ValidateMetadata( cl.MetadataCheck( ) );
    verifier.Expandability( cl.DecompressCheck( ) );

    if ( cl.empty( ) )
    {
        cl.Usage( USAGE );
    }

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

        if ( cl.Verbose( ) )
        {
            std::cout << filename << std::flush;
        }
        INT_4U new_exit_status = VerifyException::NO_ERROR;
        try
        {
            verifier( filename );
        }
        catch ( const VerifyException& Exception )
        {
            new_exit_status = Exception.ErrorCode( );
            error_msg = Exception.what( );
        }
        catch ( const std::exception& Exception )
        {
            error_msg = Exception.what( );
            if ( cl.Verbose( ) )
            {
                std::cout << "ABORT: Caught exception: " << Exception.what( );
            }
            std::cout << std::endl;
            new_exit_status = UNHANDLED_EXCEPTION;
            continue;
        }
        catch ( ... )
        {
            if ( cl.Verbose( ) )
            {
                std::cout << "ABORT: Caught unknown exception: ";
            }
            std::cout << std::endl;
            new_exit_status = UNHANDLED_EXCEPTION;
            continue;
        }

        if ( cl.Verbose( ) )
        {
            Verify::error_info_container_type errors(
                verifier.ErrorInfoQueue( ) );

            std::cout << " " << new_exit_status;
            if ( !errors.empty( ) )
            {
                for ( Verify::error_info_container_type::const_iterator
                          cur = errors.begin( ),
                          last = errors.end( );
                      cur != last;
                      ++cur )
                {
                    std::cout << std::endl << '\t' << ( *cur )->what( );
                }
            }
            if ( cl.Verbose( ) > 1 )
            {
                if ( ( errors.empty( ) ) && ( !error_msg.empty( ) ) )
                {
                    std::cout << std::endl << '\t' << error_msg;
                }
                std::cout << std::endl
                          << '\t'
                          << VerifyException::StrError(
                                 VerifyException::error_type(
                                     new_exit_status ) );

                // Issue #151: surface every calculated/stored checksum
                // comparison performed during this file's verify pass.
                // verbose >= 2 (--verbose 2) opts in.
                Verify::checksum_info_container_type checksums(
                    verifier.ChecksumInfoQueue( ) );
                for ( const auto& info : checksums )
                {
                    std::cout << std::endl
                              << "\tchecksum " << info.structure
                              << ": stored=0x" << std::hex << info.stored
                              << " calculated=0x" << info.calculated
                              << std::dec << " ("
                              << ( info.matched ? "match" : "MISMATCH" )
                              << ")";
                }
            }
            std::cout << std::endl << std::flush;
        }
        //------------------------------------------------------------------
        // Check to see if processing should continue
        //------------------------------------------------------------------
        if ( ( cl.Force( ) == false ) &&
             ( new_exit_status != VerifyException::NO_ERROR ) )
        {
            //----------------------------------------------------------------
            // No, so just exit with the exit status
            //----------------------------------------------------------------
            depart( new_exit_status );
        }
        if ( exit_status < new_exit_status )
        {
            exit_status = new_exit_status;
        }
        }
        // Clean shutdown - explicit logger manager cleanup before main() returns
        logger_mgr->shutdown();
        depart( exit_status );
    }
    catch ( std::exception& e )
    {
        std::cerr << std::endl
                  << "ABORT: Caught exception: " << e.what( ) << std::endl;
        depart( UNHANDLED_EXCEPTION );
    }
    catch ( ... )
    {
        std::cerr << std::endl << "ABORT: Caught unknown exception: " << std::endl;
        depart( UNHANDLED_EXCEPTION );
    }
}
