//
// LDASTools frameCPP - FrCheck Conditional Usage Test
//
// Tests that FrCheck validation is used if and only if FrameL library is
// available. This ensures proper conditional compilation and runtime behavior.
//
// 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 <iostream>
#include <string>
#include <sstream>
#include <cstdlib>
#include <fstream>
#include <sys/wait.h>

#include "framecpp/IFrameStream.hh"
#include "framecpp/OFrameStream.hh"
#include "framecpp/FrameH.hh"
#include "framecpp/FrAdcData.hh"
#include "framecpp/FrRawData.hh"
#include "framecpp/FrVect.hh"
#include "framecpp/Dimension.hh"
#include "ldastoolsal/gpstime.hh"

// Conditional FrameL support detection
#ifdef FRAMEL_FOUND
    #define HAVE_FRAMEL 1
    #include <FrameL.h>
#else
    #define HAVE_FRAMEL 0
#endif

// Test configuration using FrSample constants
struct TestConfig
{
    static constexpr int          TEST_NDATA = 100;
    static constexpr double       TEST_SAMPLE_RATE = 16384.0;
    static constexpr unsigned int TEST_GPS_TIME = 1000000000;
    static constexpr unsigned int TEST_GPS_NSEC = 0;

    std::string GetTestFilename() const
    {
        return "test_frcheck_conditional.gwf";
    }
};

//=============================================================================
// Helper: Create test frame with FrameCPP
// Note: Works for v4-9. v3 requires additional structures not yet implemented.
//=============================================================================
bool CreateTestFrame(const std::string& filename, int version)
{
    try
    {
        auto frame = boost::make_shared<FrameCPP::FrameH>();
        frame->SetName("FrCheckTest");
        frame->SetRun(12345);
        frame->SetGTime(LDASTools::AL::GPSTime(TestConfig::TEST_GPS_TIME,
                                               TestConfig::TEST_GPS_NSEC));
        frame->SetDt(1.0);

        // Create simple ADC data
        auto adc = boost::make_shared<FrameCPP::FrAdcData>(
            "TestChannel", 0, 0, -64, TestConfig::TEST_SAMPLE_RATE);

        auto dim = boost::make_shared<FrameCPP::Dimension>(
            TestConfig::TEST_NDATA,
            1.0 / TestConfig::TEST_SAMPLE_RATE,
            "sample", 0.0);

        auto vect = boost::make_shared<FrameCPP::FrVect>(
            "TestChannel", FrameCPP::FrVect::FR_VECT_8R, 1, dim.get(), "");

        // Fill with simple test data
        auto data = vect->GetData();
        double* dptr = reinterpret_cast<double*>(data.get());
        for (int i = 0; i < TestConfig::TEST_NDATA; i++)
        {
            dptr[i] = static_cast<double>(i);
        }

        adc->RefData().append(vect);

        // Create FrRawData container
        auto rawData = boost::make_shared<FrameCPP::FrRawData>();
        frame->SetRawData(rawData);
        rawData->RefFirstAdc().append(adc);

        // Write frame
        FrameCPP::OFrameFStream ostream(filename.c_str(), version);
        ostream.WriteFrame(frame);

        std::cout << "  ✓ Created test frame v" << version << ": " << filename << "\n";
        return true;
    }
    catch (const std::exception& e)
    {
        std::cerr << "  ✗ Failed to create test frame: " << e.what() << "\n";
        return false;
    }
}

//=============================================================================
// Helper: Validate frame with FrCheck (conditional on availability)
//=============================================================================
bool ValidateFrameWithFrCheck(const std::string& filename)
{
#if HAVE_FRAMEL
    // FrCheck validation available - execute it
    std::cout << "  ℹ FrameL detected - validating with FrCheck\n";

    std::ostringstream cmd;
    cmd << "FrCheck -i " << filename << " 2>&1";

    FILE* pipe = popen(cmd.str().c_str(), "r");
    if (!pipe)
    {
        std::cerr << "  ✗ Failed to execute FrCheck\n";
        return false;
    }

    // Capture output
    char buffer[256];
    std::string output;
    while (fgets(buffer, sizeof(buffer), pipe))
    {
        output += buffer;
    }

    int exit_code = pclose(pipe);
    int status = WEXITSTATUS(exit_code);

    // Handle case where FrCheck command is not found
    if (status == 127)
    {
        std::cout << "  ℹ FrCheck command not found in PATH - treating as PASS\n";
        std::cout << "  ℹ FrameL is available but FrCheck utility is not installed\n";
        return true;
    }

    // Check FRCHECK_NFRAME environment variable
    const char* nframe_str = std::getenv("FRCHECK_NFRAME");
    if (nframe_str)
    {
        std::cout << "  ℹ FrCheck found " << nframe_str << " frame(s)\n";
    }

    if (status == 0)
    {
        std::cout << "  ✓ FrCheck validation PASSED\n";
        return true;
    }
    else
    {
        std::cerr << "  ✗ FrCheck validation FAILED (exit code: " << status << ")\n";
        std::cerr << "  Output: " << output << "\n";
        return false;
    }
#else
    // FrameL not available - skip FrCheck validation
    std::cout << "  ℹ FrameL NOT detected - skipping FrCheck validation\n";
    std::cout << "  ✓ Test passed (FrCheck correctly skipped)\n";
    return true;
#endif
}

//=============================================================================
// Helper: Verify frame with FrameCPP (always available)
//=============================================================================
bool ValidateFrameWithFrameCPP(const std::string& filename)
{
    try
    {
        FrameCPP::IFrameFStream istream(filename.c_str());
        auto frame = istream.ReadNextFrame();

        if (!frame)
        {
            std::cerr << "  ✗ FrameCPP failed to read frame\n";
            return false;
        }

        // Basic validation
        if (frame->GetRun() != 12345)
        {
            std::cerr << "  ✗ Run number mismatch\n";
            return false;
        }

        if (frame->GetGTime().GetSeconds() != TestConfig::TEST_GPS_TIME)
        {
            std::cerr << "  ✗ GPS time mismatch\n";
            return false;
        }

        std::cout << "  ✓ FrameCPP validation passed\n";
        return true;
    }
    catch (const std::exception& e)
    {
        std::cerr << "  ✗ FrameCPP exception: " << e.what() << "\n";
        return false;
    }
}

//=============================================================================
// Test: FrCheck is used iff FrameL is available
//=============================================================================
bool TestFrCheckConditional(int version)
{
    std::cout << "\n--- Testing FrCheck conditional usage for v" << version << " ---\n";

    TestConfig config;
    std::string filename = config.GetTestFilename();

    // Step 1: Create test frame
    if (!CreateTestFrame(filename, version))
    {
        return false;
    }

    // Step 2: Validate with FrameCPP (always available)
    if (!ValidateFrameWithFrameCPP(filename))
    {
        return false;
    }

    // Step 3: Conditionally validate with FrCheck
    if (!ValidateFrameWithFrCheck(filename))
    {
        return false;
    }

    // Clean up
    std::remove(filename.c_str());

    return true;
}

//=============================================================================
// Test: Report FrameL availability status
//=============================================================================
void ReportFrameLStatus()
{
    std::cout << "\n========================================\n";
    std::cout << "FrameL Availability Report\n";
    std::cout << "========================================\n";

#if HAVE_FRAMEL
    std::cout << "  FrameL library: AVAILABLE\n";
    std::cout << "  FrCheck validation: ENABLED\n";

    // Try to get FrCheck version info
    FILE* pipe = popen("FrCheck 2>&1 | head -1", "r");
    if (pipe)
    {
        char buffer[256];
        if (fgets(buffer, sizeof(buffer), pipe))
        {
            std::cout << "  FrCheck status: " << buffer;
        }
        pclose(pipe);
    }
#else
    std::cout << "  FrameL library: NOT AVAILABLE\n";
    std::cout << "  FrCheck validation: DISABLED\n";
    std::cout << "  Reason: FRAMEL_FOUND not defined during compilation\n";
#endif

    std::cout << "========================================\n";
}

//=============================================================================
// Main test runner
//=============================================================================
int main(int argc, char* argv[])
{
    std::cout << "========================================\n";
    std::cout << "FrCheck Conditional Usage Test\n";
    std::cout << "========================================\n";

    ReportFrameLStatus();

    // Test versions 6-8 only (FrCheck in FrameL only supports these versions)
    // Note: FrameL supports 3-4, 6-8 overall, but FrCheck validation is limited to 6-8
    std::vector<int> test_versions = {6, 7, 8};

    int total_tests = 0;
    int passed_tests = 0;
    int failed_tests = 0;

    for (int version : test_versions)
    {
        total_tests++;

        if (TestFrCheckConditional(version))
        {
            passed_tests++;
            std::cout << "✓ PASSED: v" << version << " FrCheck conditional test\n";
        }
        else
        {
            failed_tests++;
            std::cout << "✗ FAILED: v" << version << " FrCheck conditional test\n";
        }
    }

    // Summary
    std::cout << "\n========================================\n";
    std::cout << "TEST SUMMARY\n";
    std::cout << "========================================\n";
    std::cout << "Total tests:  " << total_tests << "\n";
    std::cout << "Passed:       " << passed_tests << " ✓\n";
    std::cout << "Failed:       " << failed_tests << " ✗\n";
    std::cout << "========================================\n";

    if (failed_tests == 0)
    {
        std::cout << "\n✓ ALL CONDITIONAL TESTS PASSED\n";
        return 0;
    }
    else
    {
        std::cout << "\n✗ SOME CONDITIONAL TESTS FAILED\n";
        return 1;
    }
}
