//
// LDASTools frameCPP - Frame Object Structure Description Utility
//
// 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.
//

#include <iostream>
#include <map>
#include <string>
#include <exception>
#include <typeinfo>
#include <cxxabi.h>

// Boost libraries
#include <boost/program_options.hpp>
#include <boost/format.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>

// FrameCPP includes
#include "framecpp/Common/Description.hh"
#include "framecpp/FrAdcData.hh"
#include "framecpp/FrDetector.hh"
#include "framecpp/FrEvent.hh"
#include "framecpp/FrHistory.hh"
#include "framecpp/FrameH.hh"
#include "framecpp/FrProcData.hh"
#include "framecpp/FrRawData.hh"
#include "framecpp/FrSerData.hh"
#include "framecpp/FrSimData.hh"
#include "framecpp/FrSimEvent.hh"
#include "framecpp/FrStatData.hh"
#include "framecpp/FrSummary.hh"
#include "framecpp/FrTable.hh"
#include "framecpp/FrTOC.hh"
#include "framecpp/FrVect.hh"
#include "framecpp/Universal/FrSH.hh"
#include "framecpp/Universal/FrSE.hh"

using namespace FrameCPP;
using boost::format;
namespace po = boost::program_options;
namespace alg = boost::algorithm;

//=======================================================================
// Object Type Mapping
//=======================================================================

std::map< std::string, Common::FrameSpec::Info::frame_object_types >
create_object_map( )
{
    return { // Core frame objects
             { "FrameH", Common::FrameSpec::Info::FSI_FRAME_H },
             { "FrAdcData", Common::FrameSpec::Info::FSI_FR_ADC_DATA },
             { "FrDetector", Common::FrameSpec::Info::FSI_FR_DETECTOR },
             { "FrEndOfFile", Common::FrameSpec::Info::FSI_FR_END_OF_FILE },
             { "FrEndOfFrame", Common::FrameSpec::Info::FSI_FR_END_OF_FRAME },
             { "FrEvent", Common::FrameSpec::Info::FSI_FR_EVENT },
             { "FrHistory", Common::FrameSpec::Info::FSI_FR_HISTORY },
             { "FrMsg", Common::FrameSpec::Info::FSI_FR_MSG },
             { "FrProcData", Common::FrameSpec::Info::FSI_FR_PROC_DATA },
             { "FrRawData", Common::FrameSpec::Info::FSI_FR_RAW_DATA },
             { "FrSerData", Common::FrameSpec::Info::FSI_FR_SER_DATA },
             { "FrSimData", Common::FrameSpec::Info::FSI_FR_SIM_DATA },
             { "FrSimEvent", Common::FrameSpec::Info::FSI_FR_SIM_EVENT },
             { "FrStatData", Common::FrameSpec::Info::FSI_FR_STAT_DATA },
             { "FrSummary", Common::FrameSpec::Info::FSI_FR_SUMMARY },
             { "FrTable", Common::FrameSpec::Info::FSI_FR_TABLE },
             { "FrTOC", Common::FrameSpec::Info::FSI_FR_TOC },
             { "FrVect", Common::FrameSpec::Info::FSI_FR_VECT },

             // Meta objects
             { "FrSH", Common::FrameSpec::Info::FSI_FR_SH },
             { "FrSE", Common::FrameSpec::Info::FSI_FR_SE },

             // Aliases and common variations
             { "ADC", Common::FrameSpec::Info::FSI_FR_ADC_DATA },
             { "Proc", Common::FrameSpec::Info::FSI_FR_PROC_DATA },
             { "Vect", Common::FrameSpec::Info::FSI_FR_VECT },
             { "Vector", Common::FrameSpec::Info::FSI_FR_VECT },
             { "Detector", Common::FrameSpec::Info::FSI_FR_DETECTOR },
             { "Event", Common::FrameSpec::Info::FSI_FR_EVENT },
             { "TOC", Common::FrameSpec::Info::FSI_FR_TOC }
    };
}

std::map< Common::FrameSpec::Info::frame_object_types, std::string >
create_reverse_object_map( )
{
    return { // Core frame objects (ID -> canonical name)
             { Common::FrameSpec::Info::FSI_PTR_STRUCT, "FSI_PTR_STRUCT" },
             { Common::FrameSpec::Info::FSI_COMMON_ELEMENTS, "FSI_COMMON_ELEMENTS" },
             { Common::FrameSpec::Info::FSI_FR_HEADER, "FSI_FR_HEADER" },
             { Common::FrameSpec::Info::FSI_FR_NULL, "FSI_FR_NULL" },
             { Common::FrameSpec::Info::FSI_FR_SH, "FrSH" },
             { Common::FrameSpec::Info::FSI_FR_SE, "FrSE" },
             { Common::FrameSpec::Info::FSI_FRAME_H, "FrameH" },
             { Common::FrameSpec::Info::FSI_FR_ADC_DATA, "FrAdcData" },
             { Common::FrameSpec::Info::FSI_FR_DETECTOR, "FrDetector" },
             { Common::FrameSpec::Info::FSI_FR_END_OF_FILE, "FrEndOfFile" },
             { Common::FrameSpec::Info::FSI_FR_END_OF_FRAME, "FrEndOfFrame" },
             { Common::FrameSpec::Info::FSI_FR_EVENT, "FrEvent" },
             { Common::FrameSpec::Info::FSI_FR_HISTORY, "FrHistory" },
             { Common::FrameSpec::Info::FSI_FR_MSG, "FrMsg" },
             { Common::FrameSpec::Info::FSI_FR_PROC_DATA, "FrProcData" },
             { Common::FrameSpec::Info::FSI_FR_RAW_DATA, "FrRawData" },
             { Common::FrameSpec::Info::FSI_FR_SER_DATA, "FrSerData" },
             { Common::FrameSpec::Info::FSI_FR_SIM_DATA, "FrSimData" },
             { Common::FrameSpec::Info::FSI_FR_SIM_EVENT, "FrSimEvent" },
             { Common::FrameSpec::Info::FSI_FR_STAT_DATA, "FrStatData" },
             { Common::FrameSpec::Info::FSI_FR_SUMMARY, "FrSummary" },
             { Common::FrameSpec::Info::FSI_FR_TABLE, "FrTable" },
             { Common::FrameSpec::Info::FSI_FR_TOC, "FrTOC" },
             { Common::FrameSpec::Info::FSI_FR_VECT, "FrVect" }
    };
}

//=======================================================================
// Constants
//=======================================================================

// Formatting constants
constexpr int FIELD_LABEL_WIDTH = 12;
constexpr int FIELD_INDENT = 2;
constexpr char SEPARATOR_CHAR = '-';
constexpr int SEPARATOR_LENGTH = 50;

//=======================================================================
// Utility Functions
//=======================================================================

std::string demangle_type_name(const char* mangled_name) {
    int status = 0;
    char* demangled = abi::__cxa_demangle(mangled_name, nullptr, nullptr, &status);
    
    if (status == 0 && demangled) {
        std::string result(demangled);
        free(demangled);
        return result;
    } else {
        // If demangling fails, return the original name
        return std::string(mangled_name);
    }
}

// DRY helper functions for output formatting
void print_csv_row(const std::string& object_name, unsigned int version, 
                   const std::string& type, const std::string& name,
                   const std::string& data_type, const std::string& comment) {
    std::cout << format("%1%,%2%,%3%,%4%,%5%,%6%\n") 
                 % object_name % version % type % name % data_type % comment;
}

void print_field_label(const std::string& label, const std::string& value) {
    std::cout << format("%1%%2%: %3%\n") 
                 % std::string(FIELD_INDENT, ' ') 
                 % (label + std::string(FIELD_LABEL_WIDTH - label.length(), ' '))
                 % value;
}

void print_separator() {
    std::cout << std::string(SEPARATOR_LENGTH, SEPARATOR_CHAR) << "\n";
}

std::string get_class_id_string(const Universal::FrSH* frsh) {
    return str(format("%1%") % frsh->Universal::FrSHImpl::Data::GetClass());
}

std::string get_class_id_string(const Universal::FrSE* frse) {
    return str(format("%1%") % frse->Universal::FrSEImpl::Data::GetClass());
}

//=======================================================================
// Input Processing Functions
//=======================================================================

std::string
normalize_object_name( const std::string& input )
{
    std::string normalized = alg::trim_copy( input );

    // Handle common underscore patterns: "fr_adc_data" -> "FrAdcData"
    if ( alg::starts_with( normalized, "fr_" ) ||
         alg::starts_with( normalized, "Fr_" ) )
    {
        alg::replace_all( normalized, "_", "" );
        normalized[ 0 ] = std::toupper( normalized[ 0 ] );
        normalized[ 2 ] =
            std::toupper( normalized[ 2 ] ); // First letter after "Fr"
    }

    return normalized;
}

unsigned int
parse_version( const std::string& version_str )
{
    try
    {
        unsigned int version =
            boost::lexical_cast< unsigned int >( version_str );
        if ( version < FRAME_SPEC_MIN || version > FRAME_SPEC_MAX )
        {
            throw std::out_of_range(
                str(format("Frame specification version must be between %1% and %2%") % FRAME_SPEC_MIN % FRAME_SPEC_MAX));
        }
        return version;
    }
    catch ( const boost::bad_lexical_cast& )
    {
        throw std::invalid_argument( "Invalid version number: " + version_str );
    }
}

Common::FrameSpec::Info::frame_object_types
parse_object_id( const std::string& id_str )
{
    try
    {
        int id = boost::lexical_cast< int >( id_str );
        
        // Validate that the ID corresponds to a known frame object type
        const auto reverse_map = create_reverse_object_map();
        auto object_type = static_cast< Common::FrameSpec::Info::frame_object_types >( id );
        
        if ( reverse_map.find( object_type ) == reverse_map.end() )
        {
            throw std::out_of_range(
                str(format("Unknown frame object ID: %1%. Use --list to see available types.") % id));
        }
        
        return object_type;
    }
    catch ( const boost::bad_lexical_cast& )
    {
        throw std::invalid_argument( "Invalid object ID number: " + id_str );
    }
}

//=======================================================================
// Output Formatting Functions
//=======================================================================

void
list_available_objects( )
{
    std::cout << "Available frame object types:\n";
    std::cout << std::string( 50, '-' ) << "\n";
    std::cout << format( "  %-20s %s\n" ) % "Name" % "ID";
    std::cout << std::string( 50, '-' ) << "\n";

    const auto reverse_map = create_reverse_object_map( );
    
    // Show all canonical frame objects with their IDs
    for ( const auto& pair : reverse_map )
    {
        auto object_type = pair.first;
        auto canonical_name = pair.second;
        int id_value = static_cast< int >( object_type );
        
        std::cout << format( "  %-20s %d\n" ) % canonical_name % id_value;
    }

    std::cout << "\nAliases and variations:\n";
    std::cout << std::string( 30, '-' ) << "\n";
    
    const auto object_map = create_object_map( );
    for ( const auto& pair : object_map )
    {
        // Find the canonical name for this object type
        auto canonical_it = reverse_map.find( pair.second );
        if ( canonical_it != reverse_map.end() && pair.first != canonical_it->second )
        {
            std::cout << format( "  %-15s -> %s (ID: %d)\n" ) % pair.first % 
                         canonical_it->second % static_cast< int >( pair.second );
        }
    }
    
    std::cout << "\nUsage examples:\n";
    std::cout << "  By name: --object FrAdcData\n";
    std::cout << "  By ID:   --object 4\n";
    std::cout << "  Negative IDs: --object -1\n";
}

void
print_description( const Common::Description& desc,
                   const std::string&         object_name,
                   unsigned int               version,
                   const std::string&         format_type,
                   bool                       verbose )
{

    if ( format_type == "csv" )
    {
        // CSV header
        std::cout << "Object,Version,Type,Name,DataType,Comment\n";
    }
    else
    {
        // Pretty/compact header
        std::cout << format( "Frame Object: %1%\n" ) % object_name;
        std::cout << format( "Frame Specification Version: %1%\n" ) % version;
        print_separator();
    }

    if ( desc.size( ) == 0 )
    {
        std::cout
            << "  (No structure description available for this version)\n";
        return;
    }

    for ( const auto& element : desc )
    {
        // Try FrSH (structure header)
        if ( auto* frsh =
                 dynamic_cast< const Universal::FrSH* >( element.get( ) ) )
        {
            const std::string class_id = get_class_id_string(frsh);
            
            if ( format_type == "csv" )
            {
                print_csv_row(object_name, version, "STRUCT", frsh->GetName(), class_id, frsh->GetComment());
            }
            else if ( format_type == "compact" )
            {
                std::cout << format( "STRUCT %1% (ID:%2%)\n" ) % frsh->GetName( ) % class_id;
            }
            else
            {
                print_field_label("Structure", frsh->GetName());
                print_field_label("Class ID", class_id);
                if ( !frsh->GetComment( ).empty( ) )
                {
                    print_field_label("Description", frsh->GetComment());
                }
                std::cout << "\n";
            }
        }
        // Try FrSE (structure entry/field)
        else if ( auto* frse =
                      dynamic_cast< const Universal::FrSE* >( element.get( ) ) )
        {
            const std::string class_id = get_class_id_string(frse);
            
            if ( format_type == "csv" )
            {
                print_csv_row(object_name, version, "FIELD", frse->GetName(), class_id, frse->GetComment());
            }
            else if ( format_type == "compact" )
            {
                std::cout << format( "  %1% [%2%]\n" ) % frse->GetName( ) % class_id;
            }
            else
            {
                auto line = format( "  %1% [%2%]" ) % frse->GetName( ) % class_id;
                if ( !frse->GetComment( ).empty( ) )
                {
                    line = format( "%1%: %2%" ) % line % frse->GetComment( );
                }
                std::cout << line << "\n";
            }
        }
        // CORNER CASE: Handle unexpected object types
        else
        {
            std::string type_name = "null";
            if ( element )
            {
                // Suppress warning about typeid side effects - we've already checked element is non-null
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpotentially-evaluated-expression"
#endif
                type_name = demangle_type_name( typeid( *element ).name( ) );
#ifdef __clang__
#pragma clang diagnostic pop
#endif
            }
            if ( verbose )
            {
                std::cerr << format( "WARNING: Unexpected object type in "
                                     "Description: %1%\n" ) %
                        type_name;
                std::cerr << "  This may indicate a new frame type or library "
                             "version mismatch.\n";
            }

            if ( format_type == "csv" )
            {
                print_csv_row(object_name, version, "UNKNOWN", "?", type_name, "Unknown object type");
            }
            else
            {
                std::cout << format( "  [UNKNOWN TYPE]: %1%\n" ) % type_name;
            }
        }
    }
}

//=======================================================================
// Core Processing Function
//=======================================================================

void
dump_description( unsigned int       version,
                  const std::string& object_name,
                  const std::string& format_type,
                  bool               verbose )
{
    Common::FrameSpec::Info::frame_object_types object_type;
    std::string canonical_name;

    // Check if input is numeric (including negative numbers)
    bool is_numeric = true;
    std::string trimmed_input = alg::trim_copy( object_name );
    
    if ( trimmed_input.empty() )
    {
        is_numeric = false;
    }
    else
    {
        // Allow leading minus sign
        size_t start_pos = ( trimmed_input[0] == '-' ) ? 1 : 0;
        if ( start_pos >= trimmed_input.length() )
        {
            is_numeric = false;  // Just a minus sign
        }
        else
        {
            for ( size_t i = start_pos; i < trimmed_input.length(); ++i )
            {
                if ( !std::isdigit( trimmed_input[i] ) )
                {
                    is_numeric = false;
                    break;
                }
            }
        }
    }

    if ( is_numeric )
    {
        // Parse as object ID
        object_type = parse_object_id( trimmed_input );
        
        // Get canonical name from reverse mapping
        const auto reverse_map = create_reverse_object_map();
        auto reverse_it = reverse_map.find( object_type );
        if ( reverse_it != reverse_map.end() )
        {
            canonical_name = reverse_it->second;
        }
        else
        {
            canonical_name = str(format("ObjectID_%1%") % static_cast< int >( object_type ));
        }
    }
    else
    {
        // Parse as object name
        const auto object_map = create_object_map( );
        std::string normalized_name = normalize_object_name( object_name );

        auto it = object_map.find( normalized_name );
        if ( it == object_map.end( ) )
        {
            // Try case-insensitive match
            for ( const auto& pair : object_map )
            {
                if ( alg::iequals( pair.first, normalized_name ) )
                {
                    it = object_map.find( pair.first );
                    break;
                }
            }
        }

        if ( it == object_map.end( ) )
        {
            throw std::invalid_argument(
                str( format( "Unknown frame object type: '%1%'. Use --list to see "
                             "available types." ) %
                     object_name ) );
        }
        
        object_type = it->second;
        canonical_name = it->first;
    }

    // Get frame specification for version
    auto* spec_info = Common::FrameSpec::SpecInfo( version );
    if ( !spec_info )
    {
        throw std::runtime_error(
            str( format( "Unsupported frame specification version: %1%" ) %
                 version ) );
    }

    // Get frame object template
    auto* frame_object = spec_info->FrameObject( object_type );
    if ( !frame_object )
    {
        throw std::runtime_error(
            str( format( "Object type '%1%' (ID: %2%) is not available in frame "
                         "specification version %3%" ) %
                 canonical_name % static_cast< int >( object_type ) % version ) );
    }

    // Debug: Check what type we're actually dealing with
    if ( verbose )
    {
        std::cerr << "Debug: Object input: " << object_name << std::endl;
        std::cerr << "Debug: Parsed as: " << (is_numeric ? "ID" : "Name") << std::endl;
        std::cerr << "Debug: Object type ID: " << static_cast< int >( object_type ) << std::endl;
        std::cerr << "Debug: Canonical name: " << canonical_name << std::endl;
        std::cerr << "Debug: Object type: " << demangle_type_name(typeid( *frame_object ).name( ))
                  << std::endl;
        std::cerr << "Debug: Object class: " << frame_object->GetClass( )
                  << std::endl;
    }

    // Get description using virtual dispatch (method hiding resolved by using
    // declaration in FrameSpec.hh.in)
    const auto* description = frame_object->GetDescription( version );
    if ( !description )
    {
        throw std::runtime_error(
            str( format( "No description available for '%1%' (ID: %2%) in version %3%" ) %
                 canonical_name % static_cast< int >( object_type ) % version ) );
    }

    // Print formatted description
    print_description( *description, canonical_name, version, format_type, verbose );
}

//=======================================================================
// Main Function
//=======================================================================

int
main( int argc, char* argv[] )
{
    FrameCPP::Initialize( );
    try
    {
        // Command-line options
        po::options_description desc( "framecpp_dump_descriptions - Frame "
                                      "Object Structure Viewer\n\nOptions" );
        desc.add_options( )( "help,h", "Show this help message" )(
            "version,v",
            po::value< std::string >( ),
            str(format("Frame specification version (%1%-%2%)") % FRAME_SPEC_MIN % FRAME_SPEC_MAX).c_str() )(
            "object,o",
            po::value< std::string >( ),
            "Frame object name or ID (e.g., 'FrAdcData' or '4' or '-1')" )(
            "list,l", "List all available object types with their IDs" )(
            "format,f",
            po::value< std::string >( )->default_value( "pretty" ),
            "Output format: pretty, compact, csv" )(
            "verbose", "Show detailed diagnostic information" );

        po::variables_map vm;
        po::store( po::parse_command_line( argc, argv, desc ), vm );

        // Handle help
        if ( vm.count( "help" ) || argc == 1 )
        {
            std::cout << desc << "\n";
            std::cout << "Examples:\n";
            std::cout << "  framecpp_dump_descriptions -v 9 -o FrAdcData\n";
            std::cout << "  framecpp_dump_descriptions -v 9 -o 4          # Same as above, using ID\n";
            std::cout << "  framecpp_dump_descriptions -v 8 -o FrVect --format compact\n";
            std::cout << "  framecpp_dump_descriptions -v 9 -o -1         # Meta object by ID\n";
            std::cout << "  framecpp_dump_descriptions --list\n";
            std::cout << "  framecpp_dump_descriptions -v 9 -o adc --verbose\n";
            return 0;
        }

        // Handle list
        if ( vm.count( "list" ) )
        {
            list_available_objects( );
            return 0;
        }

        // Validate required options
        po::notify( vm );

        if ( !vm.count( "version" ) )
        {
            throw po::required_option( "version" );
        }
        if ( !vm.count( "object" ) )
        {
            throw po::required_option( "object" );
        }

        // Parse and validate inputs
        unsigned int version =
            parse_version( vm[ "version" ].as< std::string >( ) );
        std::string object_name = vm[ "object" ].as< std::string >( );
        std::string format_type = vm[ "format" ].as< std::string >( );
        bool        verbose = vm.count( "verbose" ) > 0;

        // Validate format
        if ( format_type != "pretty" && format_type != "compact" &&
             format_type != "csv" )
        {
            throw std::invalid_argument(
                "Format must be one of: pretty, compact, csv" );
        }

        // Process and display
        dump_description( version, object_name, format_type, verbose );

        return 0;
    }
    catch ( const po::required_option& e )
    {
        std::cerr << format( "Error: Missing required option: %1%\n" ) %
                e.get_option_name( );
        std::cerr << "Use --help for usage information.\n";
        return 1;
    }
    catch ( const po::error& e )
    {
        std::cerr << format( "Command line error: %1%\n" ) % e.what( );
        return 1;
    }
    catch ( const boost::bad_lexical_cast& e )
    {
        std::cerr << format( "Invalid input: %1%\n" ) % e.what( );
        return 1;
    }
    catch ( const std::exception& e )
    {
        std::cerr << format( "Error: %1%\n" ) % e.what( );
        return 1;
    }
    catch ( ... )
    {
        std::cerr << "Unknown error occurred.\n";
        return 1;
    }
}
