//
// 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/Common/IOStream.hh"
#include "framecpp/Common/MD5SumFilter.hh"

using namespace FrameCPP::Common;
using std::ios;

#define BUFFER_SIZE 2048

static const char* FILENAME_SOURCE =
    "../src/Utilities/Z-R_std_test_frame_ver6-600000000-1.gwf";

static const char* FILENAME_SINK = "/dev/null";

MD5Sum tmd5sum;

template < typename BT >
inline void
OpenException( const std::string& Header,
               const std::string& StreamLabel,
               std::ios::openmode Mode )
{
    try
    {
        using std::filebuf;

        FrameBuffer< filebuf > b( Mode );
        b.open( StreamLabel, Mode );
        BOOST_TEST_MESSAGE( Header << "No exception thown" );
        BOOST_CHECK( false );
    }
    catch ( const std::runtime_error& Except )
    {
        BOOST_TEST_MESSAGE( Header << "Exception: " << Except.what( ) );
        BOOST_CHECK( true );
    }
    catch ( const std::exception& Except )
    {
        BOOST_TEST_MESSAGE( Header << "Exception: " << Except.what( ) );
        BOOST_CHECK( false );
    }
    catch ( ... )
    {
        BOOST_TEST_MESSAGE( Header << "Exception: Unknown" );
        BOOST_CHECK( false );
    }
}

BOOST_AUTO_TEST_CASE( IOStreamExceptions )
{
    std::ostringstream hdr;
    const char         bad_file[] = "/bad_directory/bad_file";

    //---------------------------------------------------------------------
    // Test opening using std::filebuf
    //---------------------------------------------------------------------
    hdr.str( "std::filebuf::open: " );
    OpenException< std::filebuf >( hdr.str( ), bad_file, std::ios::in );
    OpenException< std::filebuf >( hdr.str( ), bad_file, std::ios::out );
    //---------------------------------------------------------------------
    // Test opening using LDASTools::AL::filebuf
    //---------------------------------------------------------------------
    hdr.str( "LDASTools::AL::filebuf::open: " );
    OpenException< LDASTools::AL::filebuf >(
        hdr.str( ), bad_file, std::ios::in );
    OpenException< LDASTools::AL::filebuf >(
        hdr.str( ), bad_file, std::ios::out );
}

template < class BT >
void
filetest( const std::string& BufferType, INT_4U BufferSize )
{
    std::ostringstream     lead;
    typename BT::char_type ibuffer[ BUFFER_SIZE ];
    typename BT::char_type obuffer[ BUFFER_SIZE ];

    lead << "filetest:"
         << " BufferType: " << BufferType << " BufferSize: " << BufferSize;

    try
    {
        MD5SumFilter imd5sum;
        MD5SumFilter omd5sum;

        imd5sum.Reset( );
        omd5sum.Reset( );

        FrameBuffer< BT >* ibuf( new FrameBuffer< BT >( std::ios::in ) );
        FrameBuffer< BT >* obuf( new FrameBuffer< BT >( std::ios::out ) );

        IStream ifs( ibuf );
        OStream ofs( obuf );

        ibuf->pubsetbuf( ibuffer, sizeof( ibuffer ) / sizeof( *ibuffer ) );
        obuf->pubsetbuf( obuffer, sizeof( obuffer ) / sizeof( *obuffer ) );

        ibuf->FilterAdd( &imd5sum );
        obuf->FilterAdd( &omd5sum );

        ibuf->open( FILENAME_SOURCE, std::ios::in ); // input
        if ( !ibuf->is_open( ) )
        {
            std::ostringstream msg;

            msg << "Unable to open the file: " << FILENAME_SOURCE;
            throw std::runtime_error( msg.str( ) );
        }
        obuf->open( FILENAME_SINK,
                    std::ios::out | std::ios::binary ); // output

        boost::scoped_array< typename BT::char_type > buffer(
            new char[ BufferSize ] );

        ifs.seekg( 0 );
        ifs.clear( );
        while ( ( ifs.eof( ) == false ) )
        {
            MD5Sum m;

            ifs.read( buffer.get( ), BufferSize );
            ofs.write( buffer.get( ), ifs.gcount( ) );
        }

        obuf->close( );
        ibuf->close( );

        imd5sum.Finalize( );
        omd5sum.Finalize( );

        BOOST_TEST_MESSAGE( " imd5sum: " << imd5sum
                                         << " omd5sum: " << omd5sum );
        BOOST_TEST_MESSAGE(
            lead.str( )
            << " Verifying the reader md5sum with control md5sum value" );
        BOOST_CHECK( imd5sum == tmd5sum )

            ;
        BOOST_TEST_MESSAGE(
            lead.str( )
            << " Verifying the writer md5sum with control md5sum value" );
        BOOST_CHECK( omd5sum == tmd5sum );
    }
    catch ( const std::exception& e )
    {
    }
    catch ( ... )
    {
    }
}

template < class BT >
void
filetest2( const std::string& BufferType, INT_4U BufferSize )
{
#if 0
  std::ostringstream	lead;
  typename BT::char_type ibuffer[ 2048 ];
  typename BT::char_type obuffer[ 2048 ];

  lead << "filetest2:"
       << " BufferType: " << BufferType
       << " BufferSize: " << BufferSize;

  try
  {
    MD5SumFilter			imd5sum;
    MD5SumFilter			omd5sum;

    imd5sum.Reset( );
    omd5sum.Reset( );

    IStream< BT >	ifs( std::ios::in );
    OStream< BT >	ofs( std::ios::out );

    ifs.rdbuf( )->pubsetbuf( ibuffer, sizeof( ibuffer ) / sizeof( *ibuffer ) );
    ofs.rdbuf( )->pubsetbuf( obuffer, sizeof( obuffer ) / sizeof( *obuffer ) );

    ifs.rdbuf( )->open( FILENAME_SOURCE, std::ios::in ); // input
    if ( ! ifs.rdbuf( )->is_open( ) )
    {
      std::ostringstream	msg;

      msg << "Unable to open the file: " << FILENAME_SOURCE;
      throw std::runtime_error( msg.str( ) );
    }
    ofs.rdbuf( )->open( FILENAME_SINK,
			std::ios::out | std::ios::binary ); // output

    boost::scoped_array< typename BT::char_type >
      buffer( new char[ BufferSize ] );

    ifs.seekg( 0 );
    ifs.clear( );
    while ( ( ifs.eof( ) == false ) )
    {
      MD5Sum m;

      ifs.rdbuf( )->FilterAdd( &imd5sum );
      ofs.rdbuf( )->FilterAdd( &omd5sum );

      ifs.read( buffer.get( ), BufferSize );
      ofs.write( buffer.get( ), ifs.gcount( ) );

      ifs.rdbuf( )->FilterRemove( &imd5sum );
      ofs.rdbuf( )->FilterRemove( &omd5sum );
    }

    ofs.rdbuf( )->close( );
    ifs.rdbuf( )->close( );

    imd5sum.Finalize( );
    omd5sum.Finalize( );

    BOOST_TEST_MESSAGE( 
      << " imd5sum: " << imd5sum
      << " omd5sum: " << omd5sum )
      ;
    BOOST_TEST_MESSAGE(  lead.str( )
                         << " Verifying the reader md5sum with control md5sum value"
                         );
    BOOST_CHECK( imd5sum == tmd5sum )
      ;
    BOOST_TEST_MESSAGE( lead.str( )
                        << " Verifying the writer md5sum with control md5sum value"
                        << std::endl);
    BOOST_CHECK( omd5sum == tmd5sum )
       ;
  }
  catch( const std::exception& e )
  {
  }
  catch( ... )
  {
  }
#endif /* 0 */
}

BOOST_AUTO_TEST_CASE( IOStream )
{
    //---------------------------------------------------------------------
    // Calculate the md5sum of VERSION_6_SAMPLE to use to validate
    //   streaming implementation.
    //---------------------------------------------------------------------
    char buffer[ 2048 ];

    std::ifstream f( FILENAME_SOURCE, std::ios::in );
    if ( !f.is_open( ) )
    {
        std::ostringstream msg;

        msg << "Unable to open the file: " << FILENAME_SOURCE;
        BOOST_TEST_MESSAGE( "Aborting all tests because the base md5sum "
                            "cannot be calculated: "
                            << msg.str( ) );
    }
    BOOST_REQUIRE( f.is_open( ) );

    while ( ( f.eof( ) == false ) )
    {
        f.read( &( buffer[ 0 ] ), sizeof( buffer ) );
        tmd5sum.Update( reinterpret_cast< unsigned char* >( &( buffer[ 0 ] ) ),
                        INT_4U( f.gcount( ) ) );
    }
    f.close( );

    tmd5sum.Finalize( );

    //---------------------------------------------------------------------
    // Start to perform tests
    //---------------------------------------------------------------------
    try
    {
#define GENERIC_TEST( f, a, s ) f< a >( #a, s )
#define STD_TEST( f, s ) GENERIC_TEST( f, std::filebuf, s )
#define GENERAL_TEST( f, s ) GENERIC_TEST( f, LDASTools::AL::filebuf, s )

#define TEST( s )                                                              \
    GENERAL_TEST( filetest, s );                                               \
    GENERAL_TEST( filetest2, s );                                              \
    STD_TEST( filetest, s );                                                   \
    STD_TEST( filetest2, s )

        //-------------------------------------------------------------------
        // Testing of corner cases
        //-------------------------------------------------------------------
        TEST( BUFFER_SIZE - 3 );
        TEST( BUFFER_SIZE - 2 );
        TEST( BUFFER_SIZE - 1 );
        TEST( BUFFER_SIZE );
        TEST( BUFFER_SIZE + 1 );
        TEST( BUFFER_SIZE + 2 );
        TEST( BUFFER_SIZE + 3 );
        TEST( BUFFER_SIZE / 2 );

        //-------------------------------------------------------------------
        // Testing of other size buffers
        //-------------------------------------------------------------------
        TEST( 512 );
        TEST( 1024 * 1 );
        TEST( 1024 * 8 );
        TEST( 1024 * 20 );
        TEST( 1024 * 2000 );
    }
    catch ( ... )
    {
        BOOST_TEST_MESSAGE( "Caught an exception" );
        BOOST_CHECK( false );
    }
}
