//
// LDASTools frameCPP - Utility to dump PDF-derived 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.
//
// This utility displays frame structure definitions extracted from
// LIGO frame specification PDF documents. These definitions are hard-coded
// into the executable and do not require the PDF files to be present.
//

#include "PDFDefinitions.hh"
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <cstdlib>

void printUsage(const char* progName) {
    std::cerr << "Usage: " << progName << " -v VERSION -o OBJECT\n\n";
    std::cerr << "Dump structure definitions derived from PDF specifications.\n";
    std::cerr << "This utility displays frame structure definitions extracted from\n";
    std::cerr << "LIGO frame specification PDF documents.\n\n";
    std::cerr << "Options:\n";
    std::cerr << "  -v VERSION    Frame specification version (3, 4, 6, 7, 8, 9)\n";
    std::cerr << "  -o OBJECT     Object type name (e.g., FrVect, FrAdcData, FrameH)\n";
    std::cerr << "  -l            List available object types for a version\n";
    std::cerr << "  -h, --help    Show this help message\n\n";
    std::cerr << "Examples:\n";
    std::cerr << "  " << progName << " -v 9 -o FrVect\n";
    std::cerr << "  " << progName << " -v 8 -o FrAdcData\n";
    std::cerr << "  " << progName << " -v 9 -l\n";
}

void listAvailableObjects(unsigned int version) {
    const auto& defs = FrameCPP::PDFDefinitions::getDefinitions();

    auto versionIt = defs.find(version);
    if (versionIt == defs.end()) {
        std::cerr << "Error: No definitions found for version " << version << std::endl;
        std::cerr << "Available versions: ";
        for (const auto& pair : defs) {
            std::cerr << pair.first << " ";
        }
        std::cerr << std::endl;
        return;
    }

    std::cout << "Available object types for version " << version << ":\n";
    for (const auto& objPair : versionIt->second) {
        std::cout << "  " << objPair.first;
        if (!objPair.second.comment.empty()) {
            std::cout << " - " << objPair.second.comment;
        }
        std::cout << std::endl;
    }
}

void dumpDefinition(unsigned int version, const std::string& objectName) {
    const auto& defs = FrameCPP::PDFDefinitions::getDefinitions();

    // Find version
    auto versionIt = defs.find(version);
    if (versionIt == defs.end()) {
        std::cerr << "Error: No definitions found for version " << version << std::endl;
        std::cerr << "Available versions: ";
        for (const auto& pair : defs) {
            std::cerr << pair.first << " ";
        }
        std::cerr << std::endl;
        std::exit(1);
    }

    // Find object
    auto objectIt = versionIt->second.find(objectName);
    if (objectIt == versionIt->second.end()) {
        std::cerr << "Error: Object '" << objectName << "' not found in version " << version << std::endl;
        std::cerr << "Available objects for version " << version << ":\n";
        for (const auto& objPair : versionIt->second) {
            std::cerr << "  " << objPair.first << std::endl;
        }
        std::exit(1);
    }

    const auto& structDef = objectIt->second;

    // Print structure header (FrSH equivalent)
    std::cout << "FrSH: " << structDef.name << " (id=?)";
    if (!structDef.comment.empty()) {
        std::cout << " \"" << structDef.comment << "\"";
    }
    std::cout << std::endl;

    // Print fields (FrSE equivalent)
    for (const auto& field : structDef.fields) {
        std::cout << "  FrSE: " << std::setw(20) << std::left << field.name
                  << " (" << std::setw(20) << std::left << field.type << ")";
        if (!field.comment.empty()) {
            std::cout << " \"" << field.comment << "\"";
        }
        std::cout << std::endl;
    }
}

int main(int argc, char* argv[]) {
    if (argc < 2) {
        printUsage(argv[0]);
        return 1;
    }

    unsigned int version = 0;
    std::string objectName;
    bool listObjects = false;

    // Parse command line arguments
    for (int i = 1; i < argc; ++i) {
        std::string arg = argv[i];

        if (arg == "-h" || arg == "--help") {
            printUsage(argv[0]);
            return 0;
        }
        else if (arg == "-v") {
            if (i + 1 < argc) {
                version = std::atoi(argv[++i]);
            } else {
                std::cerr << "Error: -v requires a version number" << std::endl;
                return 1;
            }
        }
        else if (arg == "-o") {
            if (i + 1 < argc) {
                objectName = argv[++i];
            } else {
                std::cerr << "Error: -o requires an object name" << std::endl;
                return 1;
            }
        }
        else if (arg == "-l") {
            listObjects = true;
        }
        else {
            std::cerr << "Error: Unknown option: " << arg << std::endl;
            printUsage(argv[0]);
            return 1;
        }
    }

    if (version == 0) {
        std::cerr << "Error: Version number is required (-v)" << std::endl;
        printUsage(argv[0]);
        return 1;
    }

    if (listObjects) {
        listAvailableObjects(version);
        return 0;
    }

    if (objectName.empty()) {
        std::cerr << "Error: Object name is required (-o)" << std::endl;
        printUsage(argv[0]);
        return 1;
    }

    dumpDefinition(version, objectName);

    return 0;
}
