
//
// LDASTools frameCPP - A library implementing the LIGO/Virgo frame
// specification
//
// Copyright (C) 2022 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
//

#include <sstream>

#include <boost/filesystem.hpp>

#define BOOST_TEST_MODULE StaticFrDetectorTest
#include <boost/test/included/unit_test.hpp>

#include "ldastoolsal/fstream.hh"

#include "framecpp/Common/CheckSum.hh"
#include "framecpp/Common/Verify.hh"
#include "framecpp/FrameH.hh"
#include "framecpp/IFrameStream.hh"
#include "framecpp/OFrameStream.hh"

#include "ramp.hh"
#include "BoostTestDebugHelper.hh"

using FrameCPP::Common::Verify;
using FrameCPP::Common::VerifyException;

typedef FrameCPP::FrDetector::FrDetectorCache_element FrDetectorPtr;

// Enable debug command-line options for this test
// Usage: ./test_static_frdetector -- --debug-help
//        ./test_static_frdetector -- --debug-level 30
//        ./test_static_frdetector -- --debug-channels "framecpp.frdetector:20"
FRAMECPP_TEST_DEBUG_HELPER("test_static_frdetector");

// Custom comparator for two fixed-length non-null terminated array
boost::test_tools::assertion_result
compare_prefix( const char* prefix1, const char* prefix2, std::size_t length )
{
    if ( std::memcmp( prefix1, prefix2, length ) == 0 )
    {
        return boost::test_tools::assertion_result( true );
    }

    // If comparison fails, provide detailed output
    boost::test_tools::assertion_result result( false );
    result.message( ) << "Expected prefix: [" << std::string( prefix2, length )
                      << "], but got: [" << std::string( prefix1, length )
                      << "]";
    return result;
}

// Helper function to accept either std::string or const char*
boost::test_tools::assertion_result
compare_prefix( const std::string& prefix1,
                const char*        prefix2,
                std::size_t        length )
{
    return compare_prefix( prefix1.c_str( ), prefix2, length );
}

boost::test_tools::assertion_result
compare_prefix( const char*        prefix1,
                const std::string& prefix2,
                std::size_t        length )
{
    return compare_prefix( prefix1, prefix2.c_str( ), length );
}

boost::test_tools::assertion_result
compare_prefix( const std::string& prefix1,
                const std::string& prefix2,
                std::size_t        length )
{
    return compare_prefix( prefix1.c_str( ), prefix2.c_str( ), length );
}

// Custom BOOST_TEST predicate with detailed output
#define BOOST_TEST_CUSTOM_ARRAY_EQUAL( prefix1, prefix2, length )              \
    BOOST_TEST_CONTEXT( "Checking prefix equality" )                           \
    BOOST_TEST( ( compare_prefix( prefix1, prefix2, length ) ) )

void
create_frame( const std::string& Filename, bool IncludeFrDetector )
{
    using namespace FrameCPP;
    // --------------------------------------------------------------------
    // Create the frame
    // --------------------------------------------------------------------
    FrameH::GTime_type gpsstart;
    gpsstart.Now( );
    auto frame = boost::make_shared< FrameH >(
        "TestStaticFrDetector", -1, 0, gpsstart, 1 );
    if ( IncludeFrDetector )
    {
        frame->RefDetectProc( ).append(
            FrDetector::GetDetector( FrDetector::DQO_LHO_4K ) );
    }

    OFrameFStream output( Filename.c_str( ) );

    output.WriteFrame( frame );
}

void
validate( const std::string& FrameFilename,
          bool               ShouldHaveFrDetector,
          bool               IsFrameFileValid )
{
    using namespace FrameCPP;

    try
    {
        IFrameFStream input( FrameFilename.c_str( ) );

        auto toc = input.GetTOC( );

        // Verify number of detectors in the file
        if ( ShouldHaveFrDetector )
        {
            BOOST_CHECK( 1 == toc->GetNameDetector( ).size( ) );
        }
        else
        {
            BOOST_CHECK( 0 == toc->GetNameDetector( ).size( ) );
        }
        // Verify frame file metadata
        {
            Verify verifier;

            verifier.ValidateMetadata( false );
            try
            {
                verifier( FrameFilename );
                BOOST_CHECK_MESSAGE(
                    IsFrameFileValid,
                    "Checking if frame validation was suppose to succeed" );
            }
            catch ( const VerifyException& Exception )
            {
                BOOST_TEST_MESSAGE(
                    "Exception ErrorCode: "
                    << Exception.StrError( Exception.ErrorCode( ) ) );
                BOOST_CHECK_MESSAGE(
                    ( !IsFrameFileValid ) &&
                        ( Exception.ErrorCode( ) ==
                          Exception.METADATA_INVALID ),
                    "Checking if frame validation was suppose to throw meta "
                    "data exception" );
            }
            catch ( ... )
            {
                BOOST_CHECK_MESSAGE(
                    false, "Frame verification threw an unexpected exception" );
            }
        }
    }
    catch ( ... )
    {
    }
}

void
remove_frame( const std::string& Filename )
{
    // boost::filesystem::remove( Filename );
}

bool
validate_fr_detector( FrDetectorPtr      Detector,
                      const std::string& Name,
                      const std::string& Prefix,
                      REAL_8             Longitude,
                      REAL_8             Latitude,
                      REAL_4             Elevation,
                      REAL_4             ArmXazimuth,
                      REAL_4             ArmYazimuth,
                      REAL_4             ArmXaltitude,
                      REAL_4             ArmYaltitude,
                      REAL_4             ArmXmidpoint,
                      REAL_4             ArmYmidpoint,
                      INT_2U             DataQualityOffset )
{
    bool retval = true;

#define REAL_4_TOLERANCE 0.00000001
#define REAL_8_TOLERANCE 0.000000000001

    BOOST_TEST_CHECKPOINT( "Comparing FrDetector " << Name << ".Name:" );
    BOOST_CHECK_EQUAL( Detector->GetName( ), Name );
    BOOST_TEST_CHECKPOINT( "Comparing FrDetector " << Name << ".Prefix:" );
    BOOST_TEST_CUSTOM_ARRAY_EQUAL( Detector->GetPrefix( ), Prefix, 2 );
    BOOST_TEST_CHECKPOINT( "Comparing FrDetector " << Name << ".Longitute:" );
    BOOST_CHECK_CLOSE( Detector->GetLongitude( ), Longitude, REAL_8_TOLERANCE );
    BOOST_TEST_CHECKPOINT( "Comparing FrDetector " << Name << ".Latitute:" );
    BOOST_CHECK_CLOSE( Detector->GetLatitude( ), Latitude, REAL_8_TOLERANCE );
    BOOST_TEST_CHECKPOINT( "Comparing FrDetector " << Name << ".armXazimuth:" );
    BOOST_CHECK_CLOSE(
        Detector->GetArmXazimuth( ), ArmXazimuth, REAL_4_TOLERANCE );
    BOOST_TEST_CHECKPOINT( "Comparing FrDetector " << Name << ".armYazimuth:" );
    BOOST_CHECK_CLOSE(
        Detector->GetArmYazimuth( ), ArmYazimuth, REAL_4_TOLERANCE );
    BOOST_TEST_CHECKPOINT( "Comparing FrDetector " << Name
                                                   << ".armXaltitude:" );
    BOOST_CHECK_CLOSE(
        Detector->GetArmXaltitude( ), ArmXaltitude, REAL_4_TOLERANCE );
    BOOST_TEST_CHECKPOINT( "Comparing FrDetector " << Name
                                                   << ".armYaltitude:" );
    BOOST_CHECK_CLOSE(
        Detector->GetArmYaltitude( ), ArmYaltitude, REAL_4_TOLERANCE );
    BOOST_TEST_CHECKPOINT( "Comparing FrDetector " << Name
                                                   << ".armXmidpoint:" );
    BOOST_CHECK_CLOSE(
        Detector->GetArmXmidpoint( ), ArmXmidpoint, REAL_4_TOLERANCE );
    BOOST_TEST_CHECKPOINT( "Comparing FrDetector " << Name
                                                   << ".armYmidpoint:" );
    BOOST_CHECK_CLOSE(
        Detector->GetArmYmidpoint( ), ArmYmidpoint, REAL_4_TOLERANCE );
    BOOST_TEST_CHECKPOINT( "Comparing FrDetector " << Name
                                                   << ".dataQualityOffset:" );
    BOOST_CHECK_EQUAL( Detector->GetDataQualityOffset( ), DataQualityOffset );

    return retval;
}

static bool METADATA_IS_VALID = true;
static bool FR_DETECTOR_IS_PRESENT = true;

BOOST_AUTO_TEST_CASE( static_fr_detector_validate_detectors )
{
    using namespace FrameCPP;

    // ACIGA
    BOOST_CHECK(
        validate_fr_detector( FrDetector::GetDetector( FrDetector::DQO_ACIGA ),
                              "ACIGA",
                              "U1",
                              0.0,
                              0.0,
                              0.0,
                              0.0,
                              0.0,
                              0.0,
                              0.0,
                              0.0,
                              0.0,
                              26 ) );
    // CIT_40
    BOOST_CHECK(
        validate_fr_detector( FrDetector::GetDetector( FrDetector::DQO_CIT_40 ),
                              "CIT_40",
                              "C1",
                              0.0,
                              0.0,
                              0.0,
                              0.0,
                              0.0,
                              0.0,
                              0.0,
                              0.0,
                              0.0,
                              14 ) );
    // CIT_40
    BOOST_CHECK( validate_fr_detector(
        FrDetector::GetDetector( FrDetector::DQO_CALTECH_40_METERS ),
        "CIT_40",
        "C1",
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        14 ) );
    // GEO_600
    BOOST_CHECK( validate_fr_detector(
        FrDetector::GetDetector( FrDetector::DQO_GEO_600 ),
        "GEO_600", // name
        "G1", // prefix
        0.17116780435, // longitude
        0.91184982752, // latitutde
        114.425, // elevation
        1.19360100484, // armXazimuth
        5.83039279401, // armYazimuth
        0.00000000000, // armXaltitude
        0.00000000000, // armYaltitude
        300.00000000000, // armXmidpoint
        300.00000000000, // armYmidpoint
        6 // dataQualityOffset
        ) );
    // KAGRA
    BOOST_CHECK(
        validate_fr_detector( FrDetector::GetDetector( FrDetector::DQO_KAGRA ),
                              "KAGRA", // name
                              "K1", // prefix
                              2.396441015, // longitude
                              0.6355068497, // latitude
                              414.181, // elevation
                              1.054113, // armXazimuth
                              -0.5166798, // armYazimuth
                              0.0031414, // armXaltitude
                              -0.0036270, // armYaltitude
                              1513.2535, // armXmidpoint
                              1511.611, // armYmidpoint
                              28 // dataQualityOffset
                              ) );
    // LHO_4k
    BOOST_CHECK(
        validate_fr_detector( FrDetector::GetDetector( FrDetector::DQO_LHO_4K ),
                              "LHO_4k", // name
                              "H1", // prefix
                              -2.08406, // longitute
                              0.810795, // latitude
                              142.554, // elevation
                              5.65488, // armXazimuth
                              4.084808, // armYazimuth
                              -0.0006195, // armXaltitude
                              0.0000125, // armYaltitude
                              1997.54, // armXmidpoint
                              1997.52, // armYmidpoint
                              10 // dataQualityOffset
                              ) );
    // LIGO_India
    BOOST_CHECK( validate_fr_detector(
        FrDetector::GetDetector( FrDetector::DQO_LIGO_INDIA ),
        "LIGO_India",
        "A1",
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        0.0,
        30 ) );
    // LLO_4k
    BOOST_CHECK(
        validate_fr_detector( FrDetector::GetDetector( FrDetector::DQO_LLO_4K ),
                              "LLO_4k", // name
                              "L1", // prefix
                              -1.58431, // longitude
                              0.533423, // latitude
                              -6.574, // elevation
                              4.40318, // armXazimuth
                              2.83238, // armYazimuth
                              -0.0003121, // armXaltitude
                              -0.0006107, // armYaltitude
                              1997.57, // armXmidpoint
                              1997.57, // armYmidpoint
                              12 // dataQualityOffset
                              ) );
    // TAMA 300
    BOOST_CHECK( validate_fr_detector(
        FrDetector::GetDetector( FrDetector::DQO_TAMA_300 ),
        "TAMA_300", // name
        "T1", // prefix
        2.43536359469, // longitude
        0.62267336022, // latitude
        90.0, // elevation
        4.71238898038, // armXazimuth
        3.14159265359, // armYazimuth
        0.00000000000, // armXaltitude
        0.00000000000, // armYaltitude
        150.00000000000, // armXmidpoint
        150.00000000000, // armYmidpoint
        0 // dataQualityOffset
        ) );
    // Virgo
    BOOST_CHECK(
        validate_fr_detector( FrDetector::GetDetector( FrDetector::DQO_VIRGO ),
                              "Virgo",
                              "V1", // prefix
                              -10.5045, // longitude
                              43.6316, // latitude
                              51.884, // elevation
                              -0.3229, // armXazimuth
                              4.3895, // armYazimuth
                              0.0, // armXaltitude
                              0.0, // armYaltitude
                              1500.0, // armXmidpoint
                              1500.0, // armYmidpoint
                              4 // dataQualityOffset
                              ) );
}

BOOST_AUTO_TEST_CASE( static_fr_detector_search_by_name )
{
    using namespace FrameCPP;

    BOOST_CHECK( FrDetector::GetDataQualityOffset( "ACIGA" ) ==
                 FrDetector::DQO_ACIGA );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "CIT_40" ) ==
                 FrDetector::DQO_CIT_40 );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "GEO_600" ) ==
                 FrDetector::DQO_GEO_600 );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "KAGRA" ) ==
                 FrDetector::DQO_KAGRA );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "LHO_4k" ) ==
                 FrDetector::DQO_LHO_4K );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "LIGO_India" ) ==
                 FrDetector::DQO_LIGO_INDIA );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "LLO_4k" ) ==
                 FrDetector::DQO_LLO_4K );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "TAMA_300" ) ==
                 FrDetector::DQO_TAMA_300 );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "VIRGO" ) ==
                 FrDetector::DQO_VIRGO );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "BAD_IFO" ) ==
                 FrDetector::DQO_UNSET );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "" ) ==
                 FrDetector::DQO_UNSET );
}

BOOST_AUTO_TEST_CASE( static_fr_detector_search_by_prefix )
{
    using namespace FrameCPP;

    BOOST_CHECK( FrDetector::GetDataQualityOffset( "U1" ) ==
                 FrDetector::DQO_ACIGA );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "C1" ) ==
                 FrDetector::DQO_CIT_40 );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "G1" ) ==
                 FrDetector::DQO_GEO_600 );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "K1" ) ==
                 FrDetector::DQO_KAGRA );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "H1" ) ==
                 FrDetector::DQO_LHO_4K );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "A1" ) ==
                 FrDetector::DQO_LIGO_INDIA );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "L1" ) ==
                 FrDetector::DQO_LLO_4K );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "T1" ) ==
                 FrDetector::DQO_TAMA_300 );
    BOOST_CHECK( FrDetector::GetDataQualityOffset( "V1" ) ==
                 FrDetector::DQO_VIRGO );
}

BOOST_AUTO_TEST_CASE( static_fr_detector_written )
{
    std::string framename( "static_fr_detector_written.gwf" );
    create_frame( framename, true );
    // Read the frame file and check for:
    //   1 FrDetector structure
    //   non-zero FrAdcData structures
    validate( framename, FR_DETECTOR_IS_PRESENT, METADATA_IS_VALID );
    remove_frame( framename );
}

BOOST_AUTO_TEST_CASE( static_fr_detector_absent )
{
    std::string framename( "static_fr_detector_absent.gwf" );
    create_frame( framename, false );
    // Read the frame file and check for:
    //   0 FrDetector structures
    //   non-zero FrAdcData structures
    validate( framename, !FR_DETECTOR_IS_PRESENT, METADATA_IS_VALID );
    remove_frame( framename );
}
