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

// #include <framecpp_config.h>

extern "C" {
#include <sys/statvfs.h>

#include <unistd.h>
}

#include <algorithm>
#include <iostream>
#include <sstream>

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

#include <boost/shared_ptr.hpp>

#include "ldastoolsal/fstream.hh"
#include "ldastoolsal/types.hh"

#include "framecpp/Common/CheckSum.hh"
#include "framecpp/Common/FrameBuffer.hh"
#include "framecpp/Common/Verify.hh"

#include "framecpp/FrameH.hh"
#include "framecpp/GPSTime.hh"
#include "framecpp/FrAdcData.hh"
#include "framecpp/FrRawData.hh"
#include "framecpp/FrVect.hh"
#include "framecpp/OFrameStream.hh"
#include "framecpp/FrameCPP.hh"
#include "framecpp/FrameCPPDebugManager.hh"

using FrameCPP::GPSTime;
using std::filebuf;
using std::ostringstream;
using namespace FrameCPP;
using FrameCPP::Common::CheckSum;
using FrameCPP::Common::FrameBuffer;
using FrameCPP::Common::Verify;
using FrameCPP::Common::VerifyException;

static const INT_4U FIRST_FRAME = 600000000;
static const INT_4U FRAME_INC = 1024;
static const INT_4U FRAME_COUNT = 4096;
static const INT_8U DATA_SET_SIZE = 0x20000;
static const INT_4U DATA_SET_DT = 1024;

namespace
{
    template < typename Type >
    void
    data_fill( FrameCPP::FrVect::data_type& Data,
               INT_4U                       Size,
               const Type                   Value )
    {
        Data.reset(
            new FrameCPP::FrVect::data_type::element_type[ Size *
                                                           sizeof( Type ) ] );
        std::fill(
            reinterpret_cast< Type* >( &( Data[ 0 ] ) ),
            reinterpret_cast< Type* >( &( Data[ Size * sizeof( Type ) ] ) ),
            Value );
    }

} // namespace

class fs_too_small : public std::runtime_error
{
public:
    fs_too_small( const std::string& Message ) : std::runtime_error( Message )
    {
    }
};

BOOST_AUTO_TEST_CASE( LargeFrame )
{
    FrameCPP::Initialize();
    FrameBuffer< filebuf >* ofb( new FrameBuffer< filebuf >( std::ios::out ) );

    struct statvfs buf;

    try
    {
        if ( statvfs( ".", &buf ) == 0 )
        {
            INT_8U avail( buf.f_frsize );

            avail *= buf.f_bavail;

            if ( ( avail >> 32 ) == 0 )
            {
                std::ostringstream msg;

                msg << "The file system does not have enough free space: "
                    << " avail: " << avail
                    << " minimum needed: " << ( INT_8U( 1 ) << 32 )
                    << " difference: " << ( ( INT_8U( 1 ) << 32 ) - avail );

                throw fs_too_small( msg.str( ) );
            }
        }

        try
        {
            std::ostringstream frame_filename;

            //---------------------------------------------------------------
            // Create an appropriate filename
            //---------------------------------------------------------------
            frame_filename << "Z-LargeFrame"
                           << "-" << FIRST_FRAME << "-"
                           << ( DATA_SET_DT * FRAME_COUNT ) << ".gwf";

            //---------------------------------------------------------------
            // Create a channel of data that is at least 1 Mbyte in length
            //---------------------------------------------------------------

            boost::shared_ptr< FrRawData > raw_data( new FrRawData );
            boost::shared_ptr< FrAdcData > adc( new FrAdcData );
            FrVect::dimension_type         dims[ 1 ] = { FrVect::dimension_type(
                DATA_SET_SIZE, DATA_SET_DT ) };

            FrameCPP::FrVect::data_type data;
            data_fill< REAL_8 >( data, DATA_SET_SIZE, REAL_8( 3.1415 ) );

            boost::shared_ptr< FrVect > vect(
                new FrVect( "fr_vect",
                            FrVect::RAW, /* Compress */
                            FrVect::FR_VECT_8R, /* Type */
                            1, /* nDim */
                            dims, /* dims */
                            DATA_SET_SIZE, /* NData */
                            DATA_SET_SIZE * sizeof( REAL_8 ), /* NBytes */
                            data, /* Data */
                            "unitY" /* unitY */
                            ) );

            adc->SetDataValid( 0 );
            adc->RefData( ).append( vect );
            raw_data->RefFirstAdc( ).append( adc );

            //---------------------------------------------------------------
            // Loop enough times to create a file larger than
            //    0x100000000 bytes.
            //---------------------------------------------------------------
            ofb->open( frame_filename.str( ).c_str( ),
                       std::ios::out | std::ios::binary );

            OFrameStream frame_stream( ofb );
            GPSTime      frame_start( FIRST_FRAME, 0 );

            for ( INT_4U frame_number = 0; frame_number < FRAME_COUNT;
                  ++frame_number )
            {
                //-------------------------------------------------------------
                // Create frame
                //-------------------------------------------------------------
                boost::shared_ptr< FrameH > frame(
                    new FrameH( "LargeFrame",
                                -1,
                                frame_number,
                                frame_start,
                                frame_start.GetLeapSeconds( ),
                                FRAME_INC ) );
                //-------------------------------------------------------------
                // Add in the channel
                //-------------------------------------------------------------
                frame->SetRawData( raw_data );

                //-------------------------------------------------------------
                // Write frame to frame file
                //-------------------------------------------------------------
                frame_stream.WriteFrame( frame );

                //-------------------------------------------------------------
                // Advance data for next iteration
                //-------------------------------------------------------------
                frame_start += FRAME_INC;
            }

            frame_stream.Close( );
            ofb->close( );

            //---------------------------------------------------------------
            // Check the integrity of the file
            //---------------------------------------------------------------
            VerifyException::error_type status = VerifyException::NO_ERROR;
            Verify                      v;

            v.BufferSize( 64 * 1024 );
            v.CheckDataValid( false );
            v.CheckFileChecksum( true );
            v.CheckFrameChecksum( true );
            v.MustHaveEOFChecksum( true );
            v.Strict( true );
            v.ValidateMetadata( true );

            try
            {
                v( frame_filename.str( ) );
            }
            catch ( const VerifyException& E )
            {
                status = E.ErrorCode( );
            }

            BOOST_TEST_MESSAGE( "Verify large frame file data validation: "
                                << VerifyException::StrError( status ) );
            BOOST_CHECK( status == 0 );

            //---------------------------------------------------------------
            // Remove the file from the system.
            //---------------------------------------------------------------
#if 0
            unlink( frame_filename.str( ).c_str( ) );
#endif /* 0 */
        }
        catch ( ... )
        {
            //------------------------------------------------------------------
            // Remove the large file if an error occurred while generating
            //   the file.
            //------------------------------------------------------------------
#if 0
          unlink( frame_filename.str( ).c_str( ) );
#endif /* 0 */
            // Rethrow
            throw;
        }
    }
    catch ( const fs_too_small& e )
    {
    }
    catch ( const std::exception& e )
    {
        //-------------------------------------------------------------------
        // Output the error
        //-------------------------------------------------------------------
        BOOST_TEST_MESSAGE( "WARNING: SKIPPED"
                            << ": " << e.what( ) );
    }
    catch ( ... )
    {
        //-------------------------------------------------------------------
        // Output the error
        //-------------------------------------------------------------------
        ostringstream msg;

        msg << "Caught an unexpected exception";

        BOOST_TEST_MESSAGE( msg.str( ) );
        BOOST_CHECK( false );
    }
}

// Global test fixture to initialize debug logging
struct DebugInitFixture
{
    DebugInitFixture()
    {
        // Set up frameCPP-specific configuration before loading environment
        FrameCPP::Utilities::FrameCPPDebugManager::set_env_prefix("FRAMECPP_DEBUG");
        FrameCPP::Utilities::FrameCPPDebugManager::set_library_name("framecpp");

        // Load debug settings from environment
        FrameCPP::Utilities::FrameCPPDebugManager::load_from_environment();

        // Sync all cached loggers (global function from ldastoolsal/Logger.hh)
        ::sync_all_cached_loggers();
    }

    ~DebugInitFixture() {}
};

BOOST_GLOBAL_FIXTURE(DebugInitFixture);
