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

#if HAVE_CONFIG_H
#include <framecpp_config.h>
#endif /* HAVE_CONFIG_H */

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

#include "ldastoolsal/fstream.hh"

#include "framecpp/Common/FrameBuffer.hh"
#include "framecpp/FrameStreamPlan.hh"
#include "framecpp/FrameCPP.hh"
#include "framecpp/GPSTime.hh"

using FrameCPP::GPSTime;

bool
do_init( )
{
    FrameCPP::Initialize( );
    return ( 1 );
}

static const bool HAS_BEEN_INITIALIZED = do_init( );

typedef FrameCPP::Common::FrameBuffer< LDASTools::AL::filebuf > FrameBuffer;

class Timer : public GPSTime
{
public:
    typedef GPSTime elapsed_type;
    Timer( );

    elapsed_type Elapsed( );
};

inline Timer::Timer( )
{
    this->Now( );
}

inline Timer::elapsed_type
Timer::Elapsed( )
{
    elapsed_type retval;
    retval += elapsed_type::NowGPSTime( ) - *this;

    return retval;
}

template < int ID >
std::string
filename( )
{
    static const char*  s = "Z";
    static const char*  d = "R_std_test_frame_ver";
    static const INT_8U g = 600000000;
    static const INT_8U t = 1;
    static const char*  ext = ".gwf";

    std::ostringstream ofn;

    ofn << "../src/Utilities/" << s << "-" << d << ID << "-" << g << "-" << t
        << ext;
    return ofn.str( );
}

template < int ID, typename FrameStreamPlan >
void
FraCfg( )
{
    typedef FrameStreamPlan                               plan_type;
    typedef typename plan_type::fr_raw_data_type          fr_raw_data_type;
    typedef typename plan_type::frame_h_type              frame_h_type;
    typedef typename plan_type::fr_adc_data_type          fr_adc_data_type;
    typedef FrameCPP::Common::FrameBuffer< std::filebuf > buffer_type;

    std::ostringstream test_name;

    test_name << "FraCfg (version " << ID << ")";

    //---------------------------------------------------------------------
    // Simulate reading multiple files by reading the same
    //   one over and over.
    //---------------------------------------------------------------------
    typedef std::vector< std::string > frames_type;
    frames_type                        frames;

    frames.push_back( filename< ID >( ) );
    frames.push_back( filename< ID >( ) );
    frames.push_back( filename< ID >( ) );

    //---------------------------------------------------------------------
    // Generate a list of channels to read frommm the files.
    //---------------------------------------------------------------------
    typedef std::vector< std::string > names_type;
    names_type                         names;

    names.push_back( std::string( "TesT_DIFF_GZIP_1_CHAR" ) );

    //---------------------------------------------------------------------
    // Initialize some storage for results and state information
    //---------------------------------------------------------------------
    fr_raw_data_type rawData( new typename fr_raw_data_type::element_type( ) );
    frame_h_type     first_frame;
    plan_type*       first_reader = 0;

    //---------------------------------------------------------------------
    // Process each frame
    //---------------------------------------------------------------------
    for ( frames_type::const_iterator frame_cur = frames.begin( ),
                                      frame_last = frames.end( );
          frame_cur != frame_last;
          ++frame_cur )
    {
        plan_type*   reader = 0; // frame reader
        buffer_type* ibuf = new buffer_type( std::ios::in );

        ibuf->open( *frame_cur, std::ios::in | std::ios::binary );

        try
        {
            reader = new plan_type( ibuf, first_reader );
        }
        catch ( const std::exception& Exception )
        {
            BOOST_TEST_MESSAGE(
                test_name.str( )
                << ": Failed to create frame reader.: exception: "
                << Exception.what( ) );
            BOOST_CHECK( false );
            return;
        }
        catch ( ... )
        {
            BOOST_TEST_MESSAGE(
                test_name.str( )
                << ": Failed to create frame reader.: unknown exception" );
            BOOST_CHECK( false );
            return;
        }

        // reader->setErrorWatch(error_watch);
        try
        {
            if ( frame_cur == frames.begin( ) )
            {
                first_frame = reader->ReadFrameH( 0, 0 );
                first_frame->SetRawData( rawData );
                if ( ( first_reader ) && ( first_reader != reader ) )
                {
                    // Prevent memory leak
                    delete first_reader;
                }
                first_reader = reader;
            }

            for ( names_type::const_iterator name_cur = names.begin( ),
                                             name_last = names.end( );
                  name_cur != name_last;
                  ++name_cur )
            {
                fr_adc_data_type adc = reader->ReadFrAdcData( 0, *name_cur );

                adc->RefData( )[ 0 ]->Uncompress( );
                first_frame->GetRawData( )->RefFirstAdc( ).erase( 0 );
                first_frame->GetRawData( )->RefFirstAdc( ).append( adc );
                BOOST_TEST_MESSAGE( test_name.str( )
                                    << ":"
                                    << " Added: " << adc->GetName( ) );
            }
        }
        catch ( std::exception& e )
        {
            BOOST_TEST_MESSAGE( test_name.str( )
                                << ": Failed to read file: " << e.what( ) );
            BOOST_CHECK( false );
        }
        if ( first_reader != reader )
        {
            delete reader;
            reader = 0;
        }
    }
    delete first_reader;
    first_reader = 0;
}

template < int ID, typename FrameStreamPlan >
void
ReadFile( )
{
    std::ostringstream test_name;

    test_name << "ReadFile (version " << ID << ")";

    BOOST_TEST_MESSAGE( test_name.str( ) << ": " //
                                         << "Preparing test using frame file: "
                                         << filename< ID >( ) );
    try
    {
        std::unique_ptr< FrameBuffer > b1( new FrameBuffer( std::ios::in ) );
        std::unique_ptr< FrameBuffer > b2( new FrameBuffer( std::ios::in ) );
        std::unique_ptr< FrameBuffer > b3( new FrameBuffer( std::ios::in ) );

        b1->open( filename< ID >( ).c_str( ), std::ios::in | std::ios::binary );
        b2->open( filename< ID >( ).c_str( ), std::ios::in | std::ios::binary );
        b3->open( filename< ID >( ).c_str( ), std::ios::in | std::ios::binary );

        Timer               t;
        Timer::elapsed_type t_previous;
        Timer::elapsed_type t_current;

        FrameStreamPlan r1( b1.release( ) );
        t_previous = t.Elapsed( );
        BOOST_TEST_MESSAGE( test_name.str( ) << ":"
                                             << " Elapsed: " << t_previous
                                             << " difference: " << t_previous );
        FrameStreamPlan r2( b2.release( ), &r1 );
        t_current = t.Elapsed( );
        BOOST_TEST_MESSAGE( test_name.str( )
                            << ":"
                            << " Elapsed: " << t_current << " difference: "
                            << ( GPSTime( ) + ( t_current - t_previous ) ) );
        FrameStreamPlan r3( b3.release( ), &r2 );
        t_previous = t_current;
        t_current = t.Elapsed( );
        BOOST_TEST_MESSAGE( test_name.str( )
                            << ":"
                            << " Elapsed: " << t_current << " difference: "
                            << ( GPSTime( ) + ( t_current - t_previous ) ) );
    }
    catch ( const std::exception& Exception )
    {
        BOOST_TEST_MESSAGE( test_name.str( ) << " [Excpetion thrown: "
                                             << Exception.what( ) << "]" );
        BOOST_CHECK( false );
    }
}

BOOST_AUTO_TEST_CASE( FrameStreamPlanTest )
{
    //---------------------------------------------------------------------
    // Perform tests
    //---------------------------------------------------------------------
    ReadFile< 6, FrameCPP::IFrameStreamPlan >( );
    ReadFile< 7, FrameCPP::IFrameStreamPlan >( );
    ReadFile< 8, FrameCPP::IFrameStreamPlan >( );
    ReadFile< 9, FrameCPP::IFrameStreamPlan >( );

    FraCfg< 6, FrameCPP::IFrameStreamPlan >( );
    FraCfg< 7, FrameCPP::IFrameStreamPlan >( );
    FraCfg< 8, FrameCPP::IFrameStreamPlan >( );
    FraCfg< 9, FrameCPP::IFrameStreamPlan >( );
}
