//
// FrameL Sample - Create Test Frames with FrameL Library
//
// Creates test frames using the FrameL library for comparison with
// frameCPP output and validation of frame specification compatibility.
//
// Copyright (C) 2025 California Institute of Technology
//

#include <FrameL.h> // FrameL C library
#include <iostream>
#include <string>
#include <sstream>
#include <cstdlib>
#include <cstring>

// Include common frame sample utilities
#include "../src/Utilities/FrSampleCommon.hh"

namespace po = boost::program_options;
using FrameSampleCommon::Config;

//=============================================================================
// FrameL Policy Class - Defines FrameL-specific behavior
//=============================================================================
struct FrameLPolicy
{
    //-------------------------------------------------------------------------
    // Type definitions
    //-------------------------------------------------------------------------
    using frame_type = FrameH*;
    using logger_type = void*;  // FrameL doesn't use logger

    //-------------------------------------------------------------------------
    /// \brief Get version description for command-line help
    //-------------------------------------------------------------------------
    static const char* getVersionDescription()
    {
        return "Frame specification version to use for the output frame (3-8)";
    }

    //-------------------------------------------------------------------------
    /// \brief Get program description for usage message
    //-------------------------------------------------------------------------
    static const char* getProgramDescription()
    {
        return "FrameL Sample - Test Frame Creator\n"
               "Create test frames using FrameL library (v3-v8)";
    }

    //-------------------------------------------------------------------------
    /// \brief Get example usage for help message
    //-------------------------------------------------------------------------
    static const char* getExampleUsage()
    {
        return "Examples:\n"
               "  # Full test frame (all structures):\n"
               "  framel_sample --test-frame-mode --version 6\n"
               "\n"
               "  # Minimal frames (single object, --test-frame-mode not required):\n"
               "  framel_sample --enable-detector --version 7\n"
               "  framel_sample --enable-history --with-history-msg \"My test\" --version 8\n"
               "  framel_sample --enable-detector --enable-history --version 6\n";
    }

    //-------------------------------------------------------------------------
    /// \brief Add library-specific command-line options
    ///
    /// FrameL does not support --disable-toc, so no additional options needed
    //-------------------------------------------------------------------------
    static void addSpecificOptions(
        boost::program_options::options_description& /*desc*/,
        Config& /*config*/)
    {
        // FrameL library does not support additional options like --disable-toc
    }

    //-------------------------------------------------------------------------
    /// \brief Validate frame specification version for FrameL
    ///
    /// FrameL only supports versions 3-8
    //-------------------------------------------------------------------------
    static bool validateVersion(int version, std::ostream& err)
    {
        if (version < 3 || version > 8)
        {
            err << "Error: FrameL library only supports versions 3-8, requested version: "
                << version << "\n";
            return false;
        }
        return true;
    }

    //-------------------------------------------------------------------------
    /// \brief Validate configuration options for FrameL
    ///
    /// Warns about options that FrameL does not support
    //-------------------------------------------------------------------------
    static bool validateOptions(const Config& config, std::ostream& err)
    {
        // Warn about unsupported FrameL options
        // FrameL DOES support: FrDetector, FrHistory, FrAdcData, FrProcData, FrSerData, FrSimData, FrStatData, FrRawData
        // FrameL does NOT support: FrEvent, FrMsg, FrSimEvent, FrSummary, FrTable
        if (config.enable_fr_event || config.enable_fr_msg ||
            config.enable_fr_sim_event || config.enable_fr_summary ||
            config.enable_fr_table)
        {
            err << "⚠ Warning: FrameL library does not support FrEvent, FrMsg, FrSimEvent, FrSummary, FrTable\n";
            err << "   These flags are ignored. Use framecpp_sample for these object types.\n";
        }
        return true;
    }

    //-------------------------------------------------------------------------
    /// \brief Create a new frame using FrameL
    //-------------------------------------------------------------------------
    static frame_type createFrame(int version, const Config& config, logger_type /*logger*/)
    {
        FrLibSetVersion(version);
        FrameH* frame = FrameNew(const_cast<char*>("TestFrame"));
        if (!frame)
        {
            throw std::runtime_error("Failed to create FrameL frame");
        }

        // Set frame metadata
        frame->run = Config::RUN;
        frame->frame = 0;
        frame->GTimeS = config.start_time;  // Use config value instead of hardcoded
        frame->GTimeN = Config::GPS_NSEC;
        frame->dt = static_cast<double>(config.duration);  // Use config value instead of hardcoded

        return frame;
    }

    //-------------------------------------------------------------------------
    /// \brief Apply --enable-* flags to add minimal objects to frame
    //-------------------------------------------------------------------------
    static void applyEnableFlags(
        frame_type& frame,
        const Config& config,
        logger_type logger)
    {
        // Add FrDetector if requested
        if (config.enable_detector)
        {
            const char* det_name = config.detector_name.empty()
                ? "TestDetector"
                : config.detector_name.c_str();

            FrDetector* detector = FrDetectorNew(const_cast<char*>(det_name));
            if (!detector)
            {
                throw std::runtime_error("Failed to create FrDetector");
            }
            frame->detectProc = detector;
        }

        // Add FrHistory if requested
        if (config.enable_history)
        {
            const char* msg = config.history_msg.empty()
                ? "framel_sample test frame"
                : config.history_msg.c_str();

            FrHistory* history = FrHistoryAdd(frame, const_cast<char*>(msg));
            if (!history)
            {
                throw std::runtime_error("Failed to add FrHistory");
            }
        }

        // Add FrAdcData if requested
        if (config.enable_fr_adc)
        {
            addAdcData(frame, config, logger);
        }

        // Add FrProcData if requested
        if (config.enable_fr_proc_data)
        {
            addProcData(frame, config, logger);
        }

        // Add FrSerData if requested
        if (config.enable_fr_ser_data)
        {
            addSerData(frame, config, logger);
        }

        // Add FrSimData if requested
        if (config.enable_fr_sim_data)
        {
            addSimData(frame, config, logger);
        }

        // Add FrStatData if requested (v3-v8 only)
        if (config.enable_fr_stat_data)
        {
            addStatData(frame, config, logger);
        }

        // Add FrRawData container test if requested
        if (config.enable_fr_rawdata)
        {
            addRawDataContainer(frame, config, logger);
        }
    }

    /// Add FrProcData to frame
    static void addProcData(frame_type& frame, const Config& /*config*/, logger_type /*logger*/)
    {
        // Create sine wave data: 50 samples, REAL_8 type, pattern: sin(i * 0.1)
        const int nSamples = 50;

        // FrVectNew1D allocates the data array automatically
        FrVect* vect = FrVectNew1D(
            const_cast<char*>("ProcData"),
            FR_VECT_8R,
            nSamples,
            1.0 / nSamples,  // dx
            const_cast<char*>("s"),      // unitX
            const_cast<char*>("strain")  // unitY
        );

        if (!vect)
        {
            throw std::runtime_error("Failed to create FrVect for FrProcData");
        }

        // Populate the data array (FrVectNew1D allocated vect->dataD)
        for (int i = 0; i < nSamples; i++) {
            vect->dataD[i] = std::sin(i * 0.1);
        }

        FrProcData* procData = FrProcDataNew(
            frame,
            const_cast<char*>("H1:TEST_PROC"),
            1.0 / nSamples,  // sampleRate = 1/dx
            0.0,             // timeOffset
            0.0              // tRange
        );

        if (!procData)
        {
            FrVectFree(vect);
            throw std::runtime_error("Failed to create FrProcData");
        }

        procData->data = vect;
    }

    /// Add FrSimData to frame
    static void addSimData(frame_type& frame, const Config& /*config*/, logger_type /*logger*/)
    {
        // Create cosine wave data: 50 samples, REAL_4 type, pattern: cos(i * 0.1)
        const int nSamples = 50;

        // FrVectNew1D allocates the data array automatically
        FrVect* vect = FrVectNew1D(
            const_cast<char*>("SimData"),
            FR_VECT_4R,
            nSamples,
            1.0 / nSamples,  // dx
            const_cast<char*>("s"),      // unitX
            const_cast<char*>("strain")  // unitY
        );

        if (!vect)
        {
            throw std::runtime_error("Failed to create FrVect for FrSimData");
        }

        // Populate the data array (FrVectNew1D allocated vect->dataF)
        for (int i = 0; i < nSamples; i++) {
            vect->dataF[i] = std::cos(i * 0.1);
        }

        FrSimData* simData = FrSimDataNew(
            frame,
            const_cast<char*>("H1:TEST_SIM"),
            1.0 / nSamples,  // sampleRate = 1/dx
            0.0,             // timeOffset
            0.0              // phase
        );

        if (!simData)
        {
            FrVectFree(vect);
            throw std::runtime_error("Failed to create FrSimData");
        }

        simData->data = vect;
    }

    /// Add FrAdcData to frame
    static void addAdcData(frame_type& frame, const Config& /*config*/, logger_type /*logger*/)
    {
        // Create ramp data: 100 samples, INT_2S type, pattern: 0-99
        const int nSamples = 100;

        // FrVectNew1D allocates the data array automatically
        FrVect* vect = FrVectNew1D(
            const_cast<char*>("AdcData"),
            FR_VECT_2S,
            nSamples,
            1.0 / nSamples,  // dx
            const_cast<char*>("s"),        // unitX
            const_cast<char*>("counts")    // unitY
        );

        if (!vect)
        {
            throw std::runtime_error("Failed to create FrVect for FrAdcData");
        }

        // Populate the data array (FrVectNew1D allocated vect->dataS)
        for (int i = 0; i < nSamples; i++) {
            vect->dataS[i] = static_cast<short>(i);
        }

        FrAdcData* adcData = FrAdcDataNew(
            frame,
            const_cast<char*>("H1:TEST_ADC"),
            static_cast<double>(nSamples),  // sampleRate
            nSamples,                        // nData
            16                               // nBits (INT_2S is 16-bit)
        );

        if (!adcData)
        {
            FrVectFree(vect);
            throw std::runtime_error("Failed to create FrAdcData");
        }

        adcData->data = vect;
    }

    /// Add FrSerData to frame
    static void addSerData(frame_type& frame, const Config& /*config*/, logger_type /*logger*/)
    {
        // Create integer sequence data: 20 samples, INT_4U type, pattern: 0-19
        const int nSamples = 20;

        // FrVectNew1D allocates the data array automatically
        FrVect* vect = FrVectNew1D(
            const_cast<char*>("SerData"),
            FR_VECT_4U,
            nSamples,
            1.0 / nSamples,  // dx
            const_cast<char*>("s"),        // unitX
            const_cast<char*>("counts")    // unitY
        );

        if (!vect)
        {
            throw std::runtime_error("Failed to create FrVect for FrSerData");
        }

        // Populate the data array (FrVectNew1D allocated vect->dataI)
        for (int i = 0; i < nSamples; i++) {
            vect->dataI[i] = static_cast<int>(i);
        }

        FrSerData* serData = FrSerDataNew(
            frame,
            const_cast<char*>("H1:TEST_SER"),
            0.0,                            // serTime (time offset)
            const_cast<char*>(""),          // data (ASCII data, empty for vector-only)
            static_cast<double>(nSamples)   // sampleRate
        );

        if (!serData)
        {
            FrVectFree(vect);
            throw std::runtime_error("Failed to create FrSerData");
        }

        serData->serial = vect;
    }

    /// Add FrStatData to frame (v3-v8 only, deprecated in v9)
    static void addStatData(frame_type& frame, const Config& config, logger_type /*logger*/)
    {
        // FrStatData requires a detector - create one if not present
        if (!frame->detectProc)
        {
            const char* det_name = config.detector_name.empty()
                ? "TestDetector"
                : config.detector_name.c_str();

            frame->detectProc = FrDetectorNew(const_cast<char*>(det_name));
            if (!frame->detectProc)
            {
                throw std::runtime_error("Failed to create FrDetector for FrStatData");
            }
        }

        // Create simple FrVect data: 5 samples, REAL_4 type, constant value pattern
        const int nSamples = 5;
        FrVect* vect = FrVectNew1D(
            const_cast<char*>("StatData"),
            FR_VECT_4R,
            nSamples,
            1.0,                                     // dx
            const_cast<char*>(""),                   // unitX (empty)
            const_cast<char*>("units")               // unitY
        );

        if (!vect)
        {
            throw std::runtime_error("Failed to create FrVect for FrStatData");
        }

        // Populate with constant value for easy verification
        for (int i = 0; i < nSamples; i++) {
            vect->dataF[i] = 42.0f;
        }

        // Create FrStatData with standard test parameters
        // FrStatDataNew requires: name, comment, represent, tStart, tEnd, version, data, table
        FrStatData* statData = FrStatDataNew(
            const_cast<char*>("TestStatData"),
            const_cast<char*>("Test static data for minimal frame"),
            const_cast<char*>("Test representation"),
            config.start_time,                        // timeStart
            config.start_time + config.duration,      // timeEnd
            1,                                        // version
            vect,                                     // data (FrVect)
            NULL                                      // table (not used)
        );

        if (!statData)
        {
            FrVectFree(vect);
            throw std::runtime_error("Failed to create FrStatData");
        }

        // Add FrStatData to frame (links to detector by name)
        const char* det_name = config.detector_name.empty()
            ? "TestDetector"
            : config.detector_name.c_str();

        int result = FrameAddStatData(frame, const_cast<char*>(det_name), statData);
        if (result != FR_OK)
        {
            FrVectFree(vect);
            throw std::runtime_error("Failed to add FrStatData to frame");
        }
    }

    /// Add FrRawData container test (ADC + SerData + Msg)
    static void addRawDataContainer(frame_type& frame, const Config& /*config*/, logger_type /*logger*/)
    {
        // 1. FrAdcData: 50 samples, REAL_4, sine pattern
        {
            const int nSamples = 50;

            FrVect* vect = FrVectNew1D(
                const_cast<char*>("ContainerADC"),
                FR_VECT_4R,
                nSamples,
                1.0 / nSamples,  // dx
                const_cast<char*>("s"),        // unitX
                const_cast<char*>("strain")    // unitY
            );

            if (!vect) {
                throw std::runtime_error("Failed to create FrVect for FrAdcData");
            }

            // Populate with sine pattern
            for (int i = 0; i < nSamples; i++) {
                vect->dataF[i] = std::sin(i * 0.1);
            }

            FrAdcData* adcData = FrAdcDataNew(
                frame,
                const_cast<char*>("H1:CONTAINER_ADC"),
                static_cast<double>(nSamples),  // sampleRate
                nSamples,                        // nData
                32                               // nBits (REAL_4 is 32-bit)
            );

            if (!adcData) {
                FrVectFree(vect);
                throw std::runtime_error("Failed to create FrAdcData");
            }

            adcData->data = vect;
        }

        // 2. FrSerData: 10 samples, INT_4U, sequence
        {
            const int nSamples = 10;

            FrVect* vect = FrVectNew1D(
                const_cast<char*>("ContainerSer"),
                FR_VECT_4U,
                nSamples,
                1.0 / nSamples,  // dx
                const_cast<char*>("s"),        // unitX
                const_cast<char*>("counts")    // unitY
            );

            if (!vect) {
                throw std::runtime_error("Failed to create FrVect for FrSerData");
            }

            // Populate with sequence
            for (int i = 0; i < nSamples; i++) {
                vect->dataI[i] = static_cast<int>(i);
            }

            FrSerData* serData = FrSerDataNew(
                frame,
                const_cast<char*>("H1:CONTAINER_SER"),
                0.0,                            // serTime (time offset)
                const_cast<char*>(""),          // data (ASCII data, empty)
                static_cast<double>(nSamples)   // sampleRate
            );

            if (!serData) {
                FrVectFree(vect);
                throw std::runtime_error("Failed to create FrSerData");
            }

            serData->serial = vect;
        }

        // 3. FrMsg: Single message
        {
            FrMsg* msg = FrMsgAdd(
                frame,
                const_cast<char*>("CONTAINER_TEST"),  // alarm
                const_cast<char*>("Container test"),  // message
                3                                     // severity
            );

            if (!msg) {
                throw std::runtime_error("Failed to create FrMsg");
            }
        }
    }

    //-------------------------------------------------------------------------
    /// \brief Write frame to file using FrameL
    //-------------------------------------------------------------------------
    static void writeFrame(
        frame_type& frame,
        const std::string& filename,
        int /*version*/,
        logger_type /*logger*/)
    {
        FrFile* oFile = FrFileONew(filename.c_str(), 0); // no compression
        if (!oFile)
        {
            FrameFree(frame);
            throw std::runtime_error("Failed to create FrameL output file: " + filename);
        }

        int write_result = FrameWrite(frame, oFile);
        FrFileOEnd(oFile);
        FrameFree(frame);

        if (write_result != FR_OK)
        {
            throw std::runtime_error("FrameL write failed with error: " + std::to_string(write_result));
        }
    }

    //-------------------------------------------------------------------------
    /// \brief Get library-specific GPS time
    //-------------------------------------------------------------------------
    static unsigned int getGPSTime()
    {
        return Config::GPS_TIME_FRAMEL;
    }

    //-------------------------------------------------------------------------
    /// \brief Get library-specific filename suffix
    //-------------------------------------------------------------------------
    static const char* getLibrarySuffix()
    {
        return "l";
    }
};

//=============================================================================
// Main Function
//=============================================================================
int main(int argc, char* argv[])
try
{
    using Policy = FrameLPolicy;

    // Configuration structure
    Config config;

    // Command-line options
    po::options_description desc("Options");
    FrameSampleCommon::addCommonOptions<Policy>(desc, config);

    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, desc), vm);
    po::notify(vm);  // Notifiers automatically set config.minimal_frame_mode

    // Handle --help
    if (vm.count("help"))
    {
        FrameSampleCommon::printUsage<Policy>("framel_sample", desc);
        return 0;
    }

    // Validate configuration
    if (!FrameSampleCommon::validateConfig<Policy>(config, std::cerr))
    {
        return 1;
    }

    // Generate frame if requested via --test-frame-mode OR any --enable-* flag
    // This allows: framel_sample --enable-detector --version 6
    // Without requiring: framel_sample --test-frame-mode --enable-detector --version 6
    if (config.test_frame_mode || config.minimal_frame_mode)
    {
        std::cout << "Generating ";
        if (config.minimal_frame_mode) {
            std::cout << "minimal ";
        }
        std::cout << "test frame, version " << config.version << "\n";

        // Create frame with config values (start_time, duration)
        auto frame = Policy::createFrame(config.version, config, nullptr);

        // Apply --enable-* flags to add minimal objects
        Policy::applyEnableFlags(frame, config, nullptr);

        // Write frame to file (use config.start_time instead of hardcoded value)
        std::string filename = config.GetFilename(Policy::getLibrarySuffix(), config.start_time);
        Policy::writeFrame(frame, filename, config.version, nullptr);

        std::cout << "  ✓ Successfully generated: " << filename << "\n";
    }

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