//
// LDASTools frameCPP - Utility to dump frameCPP structure definitions
//
// 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.
//

#include <boost/program_options.hpp>
#include <iostream>
#include <map>
#include <string>
#include <cstdlib>

#include "framecpp/FrameCPP.hh"
#include "framecpp/Common/Description.hh"
#include "framecpp/Common/FrameSpec.hh"
#include "framecpp/Universal/FrSH.hh"
#include "framecpp/Universal/FrSE.hh"

namespace po = boost::program_options;

using FrameCPP::Common::Description;
using FrameCPP::Common::FrameSpec;

// Map structure names to their FSI types
std::map<std::string, FrameSpec::Info::frame_object_types> getObjectMap() {
    std::map<std::string, FrameSpec::Info::frame_object_types> map;
    map["FrameH"] = FrameSpec::Info::FSI_FRAME_H;
    map["FrAdcData"] = FrameSpec::Info::FSI_FR_ADC_DATA;
    map["FrDetector"] = FrameSpec::Info::FSI_FR_DETECTOR;
    map["FrEndOfFile"] = FrameSpec::Info::FSI_FR_END_OF_FILE;
    map["FrEndOfFrame"] = FrameSpec::Info::FSI_FR_END_OF_FRAME;
    map["FrEvent"] = FrameSpec::Info::FSI_FR_EVENT;
    map["FrHistory"] = FrameSpec::Info::FSI_FR_HISTORY;
    map["FrMsg"] = FrameSpec::Info::FSI_FR_MSG;
    map["FrProcData"] = FrameSpec::Info::FSI_FR_PROC_DATA;
    map["FrRawData"] = FrameSpec::Info::FSI_FR_RAW_DATA;
    map["FrSE"] = FrameSpec::Info::FSI_FR_SE;
    map["FrSerData"] = FrameSpec::Info::FSI_FR_SER_DATA;
    map["FrSH"] = FrameSpec::Info::FSI_FR_SH;
    map["FrSimData"] = FrameSpec::Info::FSI_FR_SIM_DATA;
    map["FrSimEvent"] = FrameSpec::Info::FSI_FR_SIM_EVENT;
    map["FrStatData"] = FrameSpec::Info::FSI_FR_STAT_DATA;
    map["FrSummary"] = FrameSpec::Info::FSI_FR_SUMMARY;
    map["FrTable"] = FrameSpec::Info::FSI_FR_TABLE;
    map["FrTOC"] = FrameSpec::Info::FSI_FR_TOC;
    map["FrVect"] = FrameSpec::Info::FSI_FR_VECT;
    return map;
}

void dumpDefinition(const std::string& objectName, unsigned int version) {
    // Get object type from name
    auto objectMap = getObjectMap();
    auto it = objectMap.find(objectName);

    if (it == objectMap.end()) {
        std::cerr << "Error: Unknown object type '" << objectName << "'" << std::endl;
        std::cerr << "Available types: ";
        for (const auto& pair : objectMap) {
            std::cerr << pair.first << " ";
        }
        std::cerr << std::endl;
        std::exit(1);
    }

    FrameSpec::Info::frame_object_types objectType = it->second;

    // Get the FrameSpec info for this version
    auto* specInfo = FrameSpec::SpecInfo(version);
    if (!specInfo) {
        std::cerr << "Error: No spec info for version " << version << std::endl;
        std::exit(1);
    }

    // Get the frame object template
    auto* frameObject = specInfo->FrameObject(objectType);
    if (!frameObject) {
        std::cerr << "Error: Object type '" << objectName
                  << "' not available in version " << version << std::endl;
        std::exit(1);
    }

    // Get description using public virtual method
    const Description* desc = frameObject->GetDescription(version);
    if (!desc || desc->size() == 0) {
        std::cerr << "Error: No description available for " << objectName
                  << " version " << version << std::endl;
        std::exit(1);
    }

    // Iterate through the description (FrSH + FrSE elements)
    for (const auto& element : *desc) {
        // Try casting to FrSH (structure header)
        if (auto* frsh = dynamic_cast<const FrameCPP::Universal::FrSH*>(element.get())) {
            unsigned short class_id = frsh->FrameCPP::Universal::FrSHImpl::Data::GetClass();
            std::cout << "FrSH: " << frsh->GetName()
                      << " (id=" << class_id << ") "
                      << "\"" << frsh->GetComment() << "\"" << std::endl;
        }
        // Try casting to FrSE (structure element/field)
        else if (auto* frse = dynamic_cast<const FrameCPP::Universal::FrSE*>(element.get())) {
            auto type_str = frse->FrameCPP::Universal::FrSEImpl::Data::GetClass();
            std::cout << "  FrSE: " << frse->GetName()
                      << " (" << type_str << ") "
                      << "\"" << frse->GetComment() << "\"" << std::endl;
        }
    }
}

int main(int argc, char* argv[]) {
    try {
        unsigned int version;
        std::string objectName;

        // Define command line options
        po::options_description desc("Dump frameCPP structure definitions");
        desc.add_options()
            ("help,h", "Show this help message")
            ("version,v", po::value<unsigned int>(&version)->required(),
             "Frame format version (3, 4, 6, 7, 8, 9)")
            ("object,o", po::value<std::string>(&objectName)->required(),
             "Object type name (e.g., FrDetector, FrVect)");

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

        if (vm.count("help")) {
            std::cout << desc << std::endl;
            std::cout << "\nExample usage:\n";
            std::cout << "  framecpp_dump_definition -v 3 -o FrDetector\n";
            std::cout << "\nThis tool queries the frameCPP library for version-specific\n";
            std::cout << "structure definitions using the FrameSpec system.\n";
            return 0;
        }

        po::notify(vm); // Throws if required options are missing

        // Validate version
        if (version < 3 || version > 9 || version == 5) {
            std::cerr << "Error: Invalid version " << version
                      << " (must be 3, 4, 6, 7, 8, or 9)" << std::endl;
            return 1;
        }

        // Initialize frameCPP
        FrameCPP::Initialize();

        // Dump the definition
        dumpDefinition(objectName, version);

        return 0;

    } catch (const po::error& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        std::cerr << "Use --help for usage information" << std::endl;
        return 1;
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }
}
