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

#ifndef FRAMECPP_UTILITIES_FR_SAMPLE_COMMON_HH
#define FRAMECPP_UTILITIES_FR_SAMPLE_COMMON_HH

#include <boost/program_options.hpp>
#include <string>
#include <sstream>
#include <iostream>

namespace FrameSampleCommon {

//=============================================================================
/// \brief Common configuration structure for frame sample utilities
///
/// This structure holds all configuration options that are shared between
/// framecpp_sample (FrameCPP library) and framel_sample (FrameL library).
/// Library-specific options should be added in the policy classes.
//=============================================================================
struct Config
{
    //-------------------------------------------------------------------------
    /// \name GPS Time Constants
    /// @brief Standard GPS time values shared across both utilities
    //-------------------------------------------------------------------------
    ///@{

    /// @brief Default GPS time for FrameCPP test frames
    /// @details Corresponds to 1999-01-14 00:00:00 UTC
    static constexpr unsigned int GPS_TIME_FRAMECPP = 600000000;

    /// @brief Default GPS time for FrameL test frames
    /// @details Corresponds to 2011-09-14 01:46:40 UTC
    static constexpr unsigned int GPS_TIME_FRAMEL = 1000000000;

    /// @brief GPS time nanosecond component
    /// @details Fractional second component for GPS timestamps
    static constexpr unsigned int GPS_NSEC = 0;

    /// @brief Default frame duration in seconds
    static constexpr unsigned int DURATION = 1;

    /// @brief Default run number
    /// @details Negative values indicate simulated/test data
    static constexpr int RUN = -1;

    ///@}

    //-------------------------------------------------------------------------
    /// \name Basic Frame Configuration Options
    //-------------------------------------------------------------------------
    ///@{

    /// @brief Frame specification version
    /// @details Valid values: 3, 4, 5, 6, 7, 8, 9
    /// @note Default: 8 (current production version)
    int version = 8;

    /// @brief Description string for filename generation
    /// @details Used in filename pattern: Z-{description}-{gps_time}-{duration}.gwf
    /// @note Default: "ilwd_test_frame" (triggers legacy filename format)
    std::string description = "ilwd_test_frame";

    /// @brief Generate full test frame with all structures
    /// @details When true, generates comprehensive test frame.
    /// Mutually exclusive with minimal_frame_mode.
    /// @note Default: false (set to true if no --enable-* flags specified)
    bool test_frame_mode = false;

    /// @brief Disable Table of Contents (FrTOC) generation
    /// @details When true, frame files are generated without TOC structure.
    /// Results in slower random access but smaller file size.
    /// @note Default: false (TOC enabled)
    bool disable_toc = false;

    /// @brief GPS start time in seconds
    /// @details Frame start time in GPS time (seconds since 1980-01-06 00:00:00 UTC)
    /// @note Default: 600000000 (1999-01-14 for FrameCPP), 1000000000 (2011-09-14 for FrameL)
    unsigned int start_time = GPS_TIME_FRAMECPP;

    /// @brief Frame duration in seconds
    /// @details Length of the frame time interval
    /// @note Default: 1 second
    unsigned int duration = DURATION;

    /// @brief Minimal frame mode (auto-set by --enable-* flags)
    /// @details When true, creates empty frame and adds only requested structures.
    /// Automatically set to true when any --enable-* flag is used.
    /// @warning Do not set manually; use --enable-* flags instead
    bool minimal_frame_mode = false;

    /// @brief All-type frame mode (auto-set by channel-*-type options)
    /// @details When true, generates frame with custom channel configurations.
    /// Automatically set when channel-*-type options are provided.
    /// @warning Do not set manually; use channel-*-type options instead
    bool use_all_type_frame = false;

    ///@}

    //-------------------------------------------------------------------------
    /// \name Enable Flags for Minimal Frame Mode
    ///
    /// @details All --enable-* flags automatically trigger minimal_frame_mode
    /// via the notifier mechanism in addCommonOptions(). When minimal mode is
    /// active, utilities create an empty frame and selectively add ONLY the
    /// structures specified by these flags.
    ///
    /// Usage Examples:
    /// @code
    ///   framecpp_sample --version 6 --enable-fr-adc
    ///     → Minimal frame with ONLY FrAdcData
    ///   framecpp_sample --version 6 --enable-detector --enable-fr-adc
    ///     → Minimal frame with FrDetector + FrAdcData
    ///   framecpp_sample --version 6 --enable-empty
    ///     → Empty frame (just FrameH, no data objects)
    /// @endcode
    ///
    /// @note Without any --enable-* flags: defaults to test_frame_mode (full frame)
    //-------------------------------------------------------------------------
    ///@{

    /// @brief Generate empty frame (just FrameH, no data objects)
    /// @details Triggers minimal_frame_mode but adds no structures.
    /// Useful for TOC validation and baseline I/O testing.
    /// @since Frame specification v3 (all versions)
    bool enable_empty = false;

    /// @brief Add FrDetector structure (detector metadata)
    /// @details Contains detector location (lat/lon/elevation), orientation
    /// (arm azimuth), and geometric parameters (arm length).
    /// @since Frame specification v3 (all versions)
    bool enable_detector = false;

    /// @brief Add FrHistory structure (processing history log)
    /// @details Records processing steps applied to data, including
    /// GPS time, name, and comment fields.
    /// @since Frame specification v3 (all versions)
    bool enable_history = false;

    /// @brief Add FrAdcData structure (ADC channel data)
    /// @details ⭐ CRITICAL for data analysis workflows.
    /// Contains raw analog-to-digital converter channel data with
    /// time series, sample rate, and calibration information.
    /// @since Frame specification v3 (all versions)
    bool enable_fr_adc = false;

    /// @brief Add FrEvent structure (event/trigger data)
    /// @details Contains event triggers with GPS time, amplitude,
    /// and associated metadata.
    /// @note Called FrTrigData in frame specification v3-v4
    /// @since Frame specification v3 (all versions)
    bool enable_fr_event = false;

    /// @brief Add FrMsg structure (message log entries)
    /// @details Contains timestamped text messages for logging
    /// and diagnostic information.
    /// @since Frame specification v3 (all versions)
    bool enable_fr_msg = false;

    /// @brief Add FrProcData structure (processed/calibrated data)
    /// @details Contains post-processed data with physical units,
    /// calibration factors, and processing history.
    /// @since Frame specification v3 (all versions)
    bool enable_fr_proc_data = false;

    /// @brief Add FrSerData structure (serial data streams)
    /// @details Contains serial data channels for auxiliary sensors
    /// and slow control systems.
    /// @since Frame specification v3 (all versions)
    bool enable_fr_ser_data = false;

    /// @brief Add FrSimData structure (simulated data)
    /// @details Contains simulated signals for software injections
    /// and testing purposes.
    /// @since Frame specification v3 (all versions)
    bool enable_fr_sim_data = false;

    /// @brief Add FrSimEvent structure (simulated events)
    /// @details Contains simulated event triggers for injection
    /// testing and validation.
    /// @since Frame specification v4 (introduced in v4, not available in v3)
    bool enable_fr_sim_event = false;

    /// @brief Add FrStatData structure (static detector data)
    /// @details Contains time-invariant detector configuration data.
    /// @deprecated Deprecated in frame specification v9
    /// @since Frame specification v3-v8 only
    bool enable_fr_stat_data = false;

    /// @brief Add FrSummary structure (summary/trend data)
    /// @details Contains statistical summaries and trend data for
    /// monitoring and data quality assessment.
    /// @since Frame specification v3 (all versions)
    bool enable_fr_summary = false;

    /// @brief Add FrTable structure (tabular data with FrVect columns)
    /// @details Contains multi-column tabular data using FrVect
    /// structures for each column.
    /// @since Frame specification v4 (introduced in v4, not available in v3)
    bool enable_fr_table = false;

    /// @brief Add FrRawData container (multi-object raw data container)
    /// @details Container structure that holds both FrAdcData and
    /// FrSerData objects in a unified collection.
    /// @since Frame specification v3 (all versions)
    bool enable_fr_rawdata = false;

    ///@}

    //-------------------------------------------------------------------------
    /// \name Convenience Options for Minimal Frame Testing
    /// @brief High-level options that set appropriate flags for common test scenarios
    //-------------------------------------------------------------------------
    ///@{

    /// @brief Generate minimal FrameCPP frame (just FrHeader + FrEndOfFile)
    /// @details Creates the absolute minimum valid GWF file for checksum validation.
    /// Equivalent to opening a frame file for writing and immediately closing it.
    /// File structure:
    ///   - FrHeader (40 bytes, file header)
    ///   - FrEndOfFile (checksums and file structure info)
    /// No FrameH or other structures - just file header and EOF.
    /// @note Use with --disable-toc for systematic Verify class testing
    /// @note Use for systematic Verify class testing and checksum validation
    /// @since Frame specification v3 (all versions)
    bool minimal_framecpp_frame = false;

    /// @brief Generate minimal FrameL-compatible frame
    /// @details Convenience option equivalent to --enable-detector --enable-history.
    /// Creates a minimal frame that meets FrameL requirements.
    /// File structure:
    ///   - FrHeader (40 bytes, file header - always present)
    ///   - FrameH (frame metadata structure)
    ///   - FrDetector (detector configuration, required by FrameL library)
    ///   - FrHistory (processing history, required by FrameL library)
    ///   - FrEndOfFile (checksums and file structure info)
    /// @note Use with --disable-toc for systematic checksum validation testing
    /// @note Use for FrameL compatibility testing
    /// @since Frame specification v3 (all versions)
    bool minimal_framel_frame = false;

    ///@}

    //-------------------------------------------------------------------------
    /// \name Customization Options
    /// @brief Fine-grained control over specific structure contents
    //-------------------------------------------------------------------------
    ///@{

    /// @brief Custom detector name
    /// @details Sets the name field in FrDetector structure.
    /// Requires --enable-detector flag to be set.
    /// @note Default: "TestDetector"
    std::string detector_name;

    /// @brief Custom history message
    /// @details Sets the comment field in FrHistory structure.
    /// Requires --enable-history flag to be set.
    /// @note Default: "Created by framecpp_sample" or "Created by framel_sample"
    std::string history_msg;

    ///@}

    //-------------------------------------------------------------------------
    /// \brief Generate standard filename for test frames
    ///
    /// Constructs filename following LIGO/Virgo frame naming conventions:
    ///   Z-{description}[{library_suffix}][_notoc]-{gps_time}-{duration}.gwf
    ///
    /// Examples:
    ///   description="test_adc_v6", library_suffix="", disable_toc=false
    ///     → Z-test_adc_v6-600000000-1.gwf
    ///   description="test_adc_v6", library_suffix="", disable_toc=true
    ///     → Z-test_adc_v6_notoc-600000000-1.gwf
    ///   description="test_framel_adc_v6", library_suffix="l", disable_toc=false
    ///     → Z-test_framel_adc_v6l-1000000000-1.gwf
    ///
    /// \param[in] library_suffix Suffix to distinguish library ("" for FrameCPP, "l" for FrameL)
    /// \param[in] gps_time GPS start time in seconds (typically 600000000 or 1000000000)
    /// \return Formatted filename string following frame conventions
    //-------------------------------------------------------------------------
    std::string GetFilename(const std::string& library_suffix, unsigned int gps_time) const
    {
        // Filename schema: Z-{description}{library_suffix}[_notoc]-{gps_time}-{duration}.gwf
        //
        // The description comes straight from the --description option (default
        // "ilwd_test_frame", per the conda-forge feedstock contract dating back
        // to 3.0.4). Callers that need a per-version filename should pass a
        // versioned --description string (see framecpp/test/CMakeLists.txt's
        // downconvert tests, which pass "framecpp_sample_downconvert_ver_${version}").
        //
        // Issue #283: an earlier "backward compatibility" branch silently
        // rewrote description "ilwd_test_frame" to "R_std_test_frame_ver${version}"
        // here, which broke the conda-forge linux_64 feedstock round-trip
        // (it generated Z-R_std_test_frame_ver8-600000000-1.gwf but the
        // feedstock test then ran framecpp_checksum on Z-ilwd_test_frame-...
        // and got FILE_OPEN_ERROR). That special case is gone.
        std::ostringstream oss;
        oss << "Z-" << description;
        if (!library_suffix.empty()) {
            oss << library_suffix;
        }
        if (disable_toc) {
            oss << "_notoc";
        }
        oss << "-" << gps_time << "-" << duration << ".gwf";
        return oss.str();
    }

};

//=============================================================================
/// \brief Add common command-line options shared by both utilities
///
/// This function template adds all command-line options that are common to
/// both framecpp_sample and framel_sample. Library-specific options should
/// be added by the LibraryPolicy::addSpecificOptions() method.
///
/// \tparam LibraryPolicy Policy class that defines library-specific behavior
/// \param[in,out] desc Boost program_options description to add options to
/// \param[in,out] config Configuration structure to bind options to
//=============================================================================
template<typename LibraryPolicy>
inline void addCommonOptions(
    boost::program_options::options_description& desc,
    Config& config)
{
    namespace po = boost::program_options;

    // Notifier callback: automatically set minimal_frame_mode when any --enable-* flag is used
    // This eliminates the need to manually maintain a list of flags
    auto set_minimal_mode = [&config](bool value) {
        if (value) {
            config.minimal_frame_mode = true;
        }
    };

    // Notifier callback: set flags for minimal FrameL-compatible frame (with FrDetector + FrHistory)
    auto set_minimal_framel = [&config](bool value) {
        if (value) {
            config.minimal_framel_frame = true;
            config.enable_detector = true;
            config.enable_history = true;
            config.minimal_frame_mode = true;
        }
    };

    desc.add_options()
        ("help,h",
         "Print help messages")
        ("description",
         po::value<std::string>(&config.description)->default_value("ilwd_test_frame"),
         "Specify the contents of the description portion of the frame filename")
        ("test-frame-mode",
         po::value<bool>(&config.test_frame_mode)->default_value(false)->implicit_value(true),
         "Generate standard full test frame with all structures (convenience option). "
         "Not required when using --enable-* flags for minimal frames.")
        ("version",
         po::value<int>(&config.version)->default_value(8),
         LibraryPolicy::getVersionDescription())
        ("start-time",
         po::value<unsigned int>(&config.start_time)->default_value(Config::GPS_TIME_FRAMECPP),
         "GPS start time for the frame (seconds)")
        ("duration",
         po::value<unsigned int>(&config.duration)->default_value(Config::DURATION),
         "Frame duration (seconds)")
        ("enable-empty",
         po::value<bool>(&config.enable_empty)
            ->default_value(false)->implicit_value(true)->notifier(set_minimal_mode),
         "Generate truly empty frame (just FrameH, no data objects). Useful for TOC validation tests.")
        ("enable-detector",
         po::value<bool>(&config.enable_detector)
            ->default_value(false)->implicit_value(true)->notifier(set_minimal_mode),
         "Add a minimal FrDetector structure to output frame")
        ("with-detector-name",
         po::value<std::string>(&config.detector_name)->default_value(""),
         "Set detector name (requires --enable-detector)")
        ("enable-history",
         po::value<bool>(&config.enable_history)
            ->default_value(false)->implicit_value(true)->notifier(set_minimal_mode),
         "Add a minimal FrHistory structure to output frame")
        ("with-history-msg",
         po::value<std::string>(&config.history_msg)->default_value(""),
         "Set history message text (requires --enable-history)")
        ("enable-fr-adc",
         po::value<bool>(&config.enable_fr_adc)
            ->default_value(false)->implicit_value(true)->notifier(set_minimal_mode),
         "Add a minimal FrAdcData structure to output frame")
        ("enable-fr-event",
         po::value<bool>(&config.enable_fr_event)
            ->default_value(false)->implicit_value(true)->notifier(set_minimal_mode),
         "Add a minimal FrEvent structure to output frame")
        ("enable-fr-msg",
         po::value<bool>(&config.enable_fr_msg)
            ->default_value(false)->implicit_value(true)->notifier(set_minimal_mode),
         "Add a minimal FrMsg structure to output frame")
        ("enable-fr-proc-data",
         po::value<bool>(&config.enable_fr_proc_data)
            ->default_value(false)->implicit_value(true)->notifier(set_minimal_mode),
         "Add a minimal FrProcData structure to output frame")
        ("enable-fr-ser-data",
         po::value<bool>(&config.enable_fr_ser_data)
            ->default_value(false)->implicit_value(true)->notifier(set_minimal_mode),
         "Add a minimal FrSerData structure to output frame")
        ("enable-fr-sim-data",
         po::value<bool>(&config.enable_fr_sim_data)
            ->default_value(false)->implicit_value(true)->notifier(set_minimal_mode),
         "Add a minimal FrSimData structure to output frame")
        ("enable-fr-sim-event",
         po::value<bool>(&config.enable_fr_sim_event)
            ->default_value(false)->implicit_value(true)->notifier(set_minimal_mode),
         "Add a minimal FrSimEvent structure to output frame")
        ("enable-fr-stat-data",
         po::value<bool>(&config.enable_fr_stat_data)
            ->default_value(false)->implicit_value(true)->notifier(set_minimal_mode),
         "Add a minimal FrStatData structure (v3-v8 only, deprecated in v9)")
        ("enable-fr-summary",
         po::value<bool>(&config.enable_fr_summary)
            ->default_value(false)->implicit_value(true)->notifier(set_minimal_mode),
         "Add a minimal FrSummary structure to output frame")
        ("enable-fr-table",
         po::value<bool>(&config.enable_fr_table)
            ->default_value(false)->implicit_value(true)->notifier(set_minimal_mode),
         "Add a minimal FrTable structure to the FrameH::uxTable of output frame")
        ("enable-fr-rawdata",
         po::value<bool>(&config.enable_fr_rawdata)
            ->default_value(false)->implicit_value(true)->notifier(set_minimal_mode),
         "Add FrRawData container test with FrAdcData + FrSerData + FrMsg (multi-object validation)")
        ("minimal-framel-frame",
         po::value<bool>(&config.minimal_framel_frame)
            ->default_value(false)->implicit_value(true)->notifier(set_minimal_framel),
         "Convenience option: Generate minimal FrameL-compatible frame "
         "(FrameH + FrDetector + FrHistory + FrEndOfFile). Equivalent to --enable-detector --enable-history.");

    // Add library-specific options (e.g., --disable-toc for FrameCPP only)
    LibraryPolicy::addSpecificOptions(desc, config);
}

//=============================================================================
/// \brief Validate configuration using library-specific policy
///
/// \tparam LibraryPolicy Policy class that defines library-specific behavior
/// \param[in] config Configuration to validate
/// \param[out] err Output stream for error messages
/// \return true if configuration is valid, false otherwise
//=============================================================================
template<typename LibraryPolicy>
inline bool validateConfig(const Config& config, std::ostream& err)
{
    return LibraryPolicy::validateVersion(config.version, err) &&
           LibraryPolicy::validateOptions(config, err);
}

//=============================================================================
/// \brief Print usage message
///
/// \tparam LibraryPolicy Policy class that defines library-specific behavior
/// \param[in] program_name Name of the program
/// \param[in] desc Program options description
/// \param[out] os Output stream (default: std::cout)
//=============================================================================
template<typename LibraryPolicy>
inline void printUsage(
    const std::string& program_name,
    const boost::program_options::options_description& desc,
    std::ostream& os = std::cout)
{
    os << "Usage: " << program_name << " [OPTIONS]\n\n";
    os << LibraryPolicy::getProgramDescription() << "\n\n";
    os << desc << "\n";
    os << LibraryPolicy::getExampleUsage() << "\n";
}

//=============================================================================
/// \brief Policy Interface for Minimal Frame Generation
///
/// Both FrameCPPPolicy and FrameLPolicy must implement these methods:
///
/// Required Policy Methods:
/// -----------------------
/// static frame_type createEmptyFrame(int version, const Config& config, logger_type logger);
///   - Creates minimal frame with only metadata (FrameH)
///   - No data objects attached
///
/// static void addDetector(frame_type& frame, const Config& config, logger_type logger);
///   - Adds single FrDetector to frame
///   - Uses config.detector_name (or default)
///
/// static void addHistory(frame_type& frame, const Config& config, logger_type logger);
///   - Adds single FrHistory to frame
///   - Uses config.history_msg (or default)
///
/// static void addEvent(frame_type& frame, const Config& config, logger_type logger);
///   - Adds single FrEvent to frame (v5+)
///   - Library-specific: FrameL doesn't support
///
/// static void addMsg(frame_type& frame, const Config& config, logger_type logger);
///   - Adds single FrMsg to FrRawData
///   - Library-specific: FrameL doesn't support
///
/// static void addProcData(frame_type& frame, const Config& config, logger_type logger);
///   - Adds single FrProcData to frame
///   - Library-specific: FrameL doesn't support
///
/// static void addSimData(frame_type& frame, const Config& config, logger_type logger);
///   - Adds single FrSimData to frame
///   - Library-specific: FrameL doesn't support
///
/// static void addSimEvent(frame_type& frame, const Config& config, logger_type logger);
///   - Adds single FrSimEvent to frame
///   - Library-specific: FrameL doesn't support
///
/// static void addSummary(frame_type& frame, const Config& config, logger_type logger);
///   - Adds single FrSummary to frame
///   - Library-specific: FrameL doesn't support
///
/// static void addTable(frame_type& frame, const Config& config, logger_type logger);
///   - Adds single FrTable to frame
///   - Library-specific: FrameL doesn't support
///
/// Automatic Minimal Frame Detection:
/// ----------------------------------
/// When any --enable-* flag is used, boost::program_options notifiers
/// automatically set config.minimal_frame_mode = true during po::notify().
/// No manual detection code needed!
///
/// The common workflow (shared by both utilities):
/// -----------------------------------------------
/// if (config.test_frame_mode || config.minimal_frame_mode) {
///     if (config.minimal_frame_mode) {
///         // Minimal frame: createEmptyFrame() + add*() for each enabled flag
///     } else {
///         // Full test frame (--test-frame-mode without --enable-* flags)
///     }
/// }
///
/// This allows both:
///   framecpp_sample --test-frame-mode --version 6              # Full frame
///   framecpp_sample --enable-detector --version 6              # Minimal (auto-enables)
///   framecpp_sample --test-frame-mode --enable-detector --v 6  # Minimal (explicit)
///
/// Adding New --enable-* Flags:
/// ---------------------------
/// 1. Add bool flag to Config struct
/// 2. Add option in addCommonOptions() with ->notifier(set_minimal_mode)
/// 3. Implement add*() method in both policies
/// That's it! No other code changes needed.
//=============================================================================

} // namespace FrameSampleCommon

#endif // FRAMECPP_UTILITIES_FR_SAMPLE_COMMON_HH
