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

/**
 * @file test_frvect_types_cpp.cc
 * @brief Test the reading of different types of FrVects from frames in native C++
 * 
 * This test mimics the Python test_frameCPP_frvect_types.py but validates
 * expected C++ data types instead of numpy types. It serves as a reference
 * implementation to compare against SWIG Python bindings.
 */

#define BOOST_TEST_MAIN
#include <boost/test/included/unit_test.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <fstream>
#include <typeinfo>
#include <map>
#include <vector>
#include <string>

#include "framecpp/IFrameStream.hh"
#include "framecpp/Universal/FrAdcData.hh"
#include "framecpp/Universal/FrVect.hh"
#include "framecpp/Universal/FrameH.hh"
#include "framecpp/FrameCPP.hh"

using namespace FrameCPP::Universal;

BOOST_AUTO_TEST_SUITE( FrVectTypesTest )

/**
 * @brief Test class that mimics the Python frame class
 */
class FrameTypeChecker {
public:
    // Channel set with expected C++ data types (equivalent to Python numpy types)
    struct ChannelTypeMapping {
        std::string channel_name;
        std::string expected_cpp_type;
        size_t expected_type_size;
    };
    
    std::vector<ChannelTypeMapping> channel_set = {
        // Commented out complex types for now (may need special handling)
        // { "Z0:RAMPED_COMPLEX_16_1", "std::complex<double>", sizeof(std::complex<double>) },
        // { "Z0:RAMPED_COMPLEX_8_1", "std::complex<float>", sizeof(std::complex<float>) },
        { "Z0:RAMPED_INT_2S_1", "int16_t", sizeof(int16_t) },
        { "Z0:RAMPED_INT_2U_1", "uint16_t", sizeof(uint16_t) },
        { "Z0:RAMPED_INT_4S_1", "int32_t", sizeof(int32_t) },
        { "Z0:RAMPED_INT_4U_1", "uint32_t", sizeof(uint32_t) },
        { "Z0:RAMPED_INT_8S_1", "int64_t", sizeof(int64_t) },
        { "Z0:RAMPED_INT_8U_1", "uint64_t", sizeof(uint64_t) },
        { "Z0:RAMPED_REAL_4_1", "float", sizeof(float) },
        { "Z0:RAMPED_REAL_8_1", "double", sizeof(double) }
    };
    
    boost::shared_ptr<FrameCPP::IFrameFStream> fs;
    
    /**
     * @brief Constructor taking the name of the frame file
     */
    FrameTypeChecker(const std::string& filename) {
        // Initialize FrameCPP library
        FrameCPP::Initialize();
        
        // Open frame stream
        fs = boost::shared_ptr<FrameCPP::IFrameFStream>(new FrameCPP::IFrameFStream(filename.c_str()));
    }
    
    /**
     * @brief Destructor to ensure proper cleanup
     */
    ~FrameTypeChecker() {
        fs.reset();
    }
    
    /**
     * @brief Iterate over channels and verify types
     */
    bool check() {
        bool retval = true;
        for (const auto& channel_mapping : channel_set) {
            retval = verifyChannelType(channel_mapping) && retval;
        }
        return retval;
    }
    
private:
    /**
     * @brief Read the channel and verify the data type matches expected C++ type
     */
    bool verifyChannelType(const ChannelTypeMapping& mapping) {
        try {
            // Read FrAdcData
            boost::shared_ptr<FrAdcData> adc = fs->ReadFrAdcData(0, mapping.channel_name);
            
            if (!adc) {
                std::cout << "Failed to read FrAdcData: " << mapping.channel_name << std::endl;
                return false;
            }
            
            std::cout << "Read FrAdcData: " << adc->GetName() << " data size: " << adc->RefData().size() << std::endl;
            
            // Handle empty data containers gracefully
            if (adc->RefData().size() == 0) {
                std::cout << "  WARNING: No data vectors in container for channel " << mapping.channel_name << std::endl;
                return false;
            }
            
            // Access first vector
            boost::shared_ptr<FrVect> first_vect = adc->RefData()[0];
            if (!first_vect) {
                std::cout << "  ERROR: First vector is null for channel " << mapping.channel_name << std::endl;
                return false;
            }
            
            // REGRESSION TEST: Check nDim before accessing data
            if (first_vect->GetNDim() <= 0) {
                std::cout << "  ERROR: Vector has nDim=" << first_vect->GetNDim() 
                          << ", indicating a regression in frameCPP" << std::endl;
                return false;
            }
            
            // Get vector data type information
            size_t type_size = first_vect->GetTypeSize();
            
            std::cout << "  Channel: " << mapping.channel_name << std::endl;
            std::cout << "    Type size: " << type_size << " bytes" << std::endl;
            std::cout << "    Expected: " << mapping.expected_cpp_type 
                      << " (" << mapping.expected_type_size << " bytes)" << std::endl;
            
            // Also try to get some data to verify the vector is readable
            try {
                // Get the first dimension to know data size
                if (first_vect->GetNDim() > 0) {
                    auto dims = first_vect->GetDims();
                    if (dims.size() > 0) {
                        size_t num_elements = dims[0].GetNx();
                        std::cout << "    Data elements: " << num_elements << " (from dimension 0)" << std::endl;
                    }
                }
            } catch (const std::exception& e) {
                std::cout << "    WARNING: Could not get dimension info: " << e.what() << std::endl;
            }
            
            // Verify type size matches expected
            bool size_matches = (type_size == mapping.expected_type_size);
            if (!size_matches) {
                std::cout << "    ERROR: Type size mismatch! Expected " 
                          << mapping.expected_type_size << ", got " << type_size << std::endl;
            }
            
            std::cout << "    Result: " << (size_matches ? "PASS" : "FAIL") << std::endl;
            
            return size_matches;
            
        } catch (const std::exception& e) {
            std::cout << "Exception reading channel " << mapping.channel_name 
                      << ": " << e.what() << std::endl;
            return false;
        }
    }
};

BOOST_AUTO_TEST_CASE( test_frvect_types_comprehensive ) {
    // Get test frame path from command line argument (if provided)
    // This follows the same pattern as the Python tests
    std::string test_frame_path;
    
    // Check if test frame path was passed as argument (following Python test pattern)
    if (boost::unit_test::framework::master_test_suite().argc > 1) {
        test_frame_path = boost::unit_test::framework::master_test_suite().argv[1];
    } else {
        // Use default path to match Python test behavior
        test_frame_path = "../src/Utilities/Z-std_fr_vect_types_ver9-700000000-1.gwf";
    }
    
    // Check if test frame exists
    std::ifstream test_file(test_frame_path);
    if (!test_file.good()) {
        // Try alternative path for fr_vect_types frames
        test_frame_path = "frames/Z-std_fr_vect_types_ver9-700000000-1.gwf";
        test_file.open(test_frame_path);
        if (!test_file.good()) {
            BOOST_WARN_MESSAGE(false, "Test frame file not found. Tried: " + test_frame_path);
            return;
        }
    }
    
    std::cout << "\n=== Testing FrVect Types in Native C++ ===" << std::endl;
    std::cout << "Frame file: " << test_frame_path << std::endl;
    
    // Create frame type checker
    FrameTypeChecker checker(test_frame_path);
    
    // Run comprehensive type checking
    bool all_tests_passed = checker.check();
    
    std::cout << "\n=== Test Results ===" << std::endl;
    std::cout << "Overall result: " << (all_tests_passed ? "PASS" : "FAIL") << std::endl;
    
    // Assert that all tests passed
    BOOST_REQUIRE_MESSAGE(all_tests_passed, 
        "One or more FrVect type checks failed. "
        "This indicates a discrepancy between expected and actual data types in the frame file.");
}

BOOST_AUTO_TEST_CASE( test_compare_with_python_expectations ) {
    std::cout << "\n=== C++ vs Python Type Mapping Reference ===" << std::endl;
    std::cout << "This test documents the expected mapping between numpy and C++ types:" << std::endl;
    std::cout << "  np.int16    <-> int16_t     (2 bytes)" << std::endl;
    std::cout << "  np.uint16   <-> uint16_t    (2 bytes)" << std::endl;
    std::cout << "  np.int32    <-> int32_t     (4 bytes)" << std::endl;
    std::cout << "  np.uint32   <-> uint32_t    (4 bytes)" << std::endl;
    std::cout << "  np.int64    <-> int64_t     (8 bytes)" << std::endl;
    std::cout << "  np.uint64   <-> uint64_t    (8 bytes)" << std::endl;
    std::cout << "  np.float32  <-> float       (4 bytes)" << std::endl;
    std::cout << "  np.float64  <-> double      (8 bytes)" << std::endl;
    std::cout << "  np.complex64  <-> std::complex<float>   (8 bytes)" << std::endl;
    std::cout << "  np.complex128 <-> std::complex<double>  (16 bytes)" << std::endl;
    
    // This is a documentation test - always passes
    BOOST_CHECK(true);
}

BOOST_AUTO_TEST_SUITE_END()