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

#include "framecpp/Common/FrameBufferInterface.hh"
#include "framecpp/Common/FrHeader.hh"
#include "framecpp/Common/FrameH.hh"
#include "framecpp/Common/FrEndOfFile.hh"
#include "framecpp/Common/FrSH.hh"

namespace FrameCPP
{
    namespace Common
    {
        // ==================================================================

        const FrameBufferInterface::Scanner::size_type
            FrameBufferInterface::Scanner::RESET_NEXT_BLOCK_SIZE =
                FrHeader::SizeOf( );

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        FrameBufferInterface::Scanner::Scanner( )
            : frame_file_cached( false ), frame_spec( (FrameSpec::Info*)NULL ),
              frame_h_id( FrameSpec::Info::FSI_FR_NULL ),
              frame_h_name( (const char*)NULL ),
              fr_end_of_file_id( FrameSpec::Info::FSI_FR_NULL ),
              fr_end_of_file_name( (const char*)NULL ),
              next_block_size( RESET_NEXT_BLOCK_SIZE ), position( 0 ),
              frame_count( 0 )
        {
        }

        //-------------------------------------------------------------------
        //-------------------------------------------------------------------
        void
        FrameBufferInterface::Scanner::NextBlock( const char* Buffer,
                                                  size_type   Size )
        {
            if ( !file_header )
            {
                if ( !file_header_base )
                {
                    //-------------------------------------------------------------
                    // Handle the case where the FrHeader is being sought
                    //-------------------------------------------------------------
                    //-------------------------------------------------------------
                    // Check for the IGWD bytes at the beginning
                    //-------------------------------------------------------------
                    static const char ORIGINATOR[] = "IGWD";
                    if ( std::memcmp(
                             ORIGINATOR, Buffer, sizeof( ORIGINATOR ) ) != 0 )
                    {
                        //-----------------------------------------------------------
                        // Currently bail
                        //
                        /// \todo
                        /// Instead of just bailing when failing to read the
                        /// buffer, need to see if the buffer contains some
                        /// subset of the ORIGINATOR string so as to allow
                        /// resyncing to happen
                        //-----------------------------------------------------------
                        return;
                    }
                    std::istringstream buffer;

                    buffer.str( std::string( Buffer, Size ) );
                    file_header_base.reset( new FrHeader( buffer ) );
                    //-------------------------------------------------------------
                    // Get the version specific table of known data structures
                    //-------------------------------------------------------------
                    frame_spec =
                        FrameSpec::SpecInfo( file_header_base->GetVersion( ) );
                    stream_ref.reset( dynamic_cast< StreamRefInterface* >(
                        frame_spec
                            ->FrameObject(
                                FrameSpec::Info::FSI_COMMON_ELEMENTS )
                            ->Clone( ) ) );
                    fr_sh.reset( static_cast< FrSH* >(
                        frame_spec->FrameObject( FrameSpec::Info::FSI_FR_SH )
                            ->Clone( ) ) );
                    frame_h.reset( dynamic_cast< FrameH* >(
                        frame_spec->FrameObject( FrameSpec::Info::FSI_FRAME_H )
                            ->Clone( ) ) );
                    frame_h_name = frame_h->ObjectStructName( );
                    fr_end_of_file.reset( dynamic_cast< FrEndOfFile* >(
                        frame_spec
                            ->FrameObject( FrameSpec::Info::FSI_FR_END_OF_FILE )
                            ->Clone( ) ) );
                    fr_end_of_file_name = fr_end_of_file->ObjectStructName( );
                    //-------------------------------------------------------------
                    // Get the number of bytes that are needed to read the
                    // version specific portion of the FrHeader block.
                    //-------------------------------------------------------------
                    next_block_size = dynamic_cast< const FrHeader* >(
                                          frame_spec->FrameObject(
                                              FrameSpec::Info::FSI_FR_HEADER ) )
                                          ->Bytes( ) -
                        Size;
                }
                else /* ( ! file_header_base ) */
                {
                    //-------------------------------------------------------------
                    // Read version specific portion of FrHeader
                    //-------------------------------------------------------------
                    try
                    {
                        std::istringstream buffer;

                        buffer.str( std::string( Buffer, Size ) );

                        std::unique_ptr< FrameSpec::Object > o(
                            frame_spec
                                ->FrameObject( FrameSpec::Info::FSI_FR_HEADER )
                                ->Create( buffer ) );

                        FrameSpec::Object* fr_header_object( o.get( ) );
                        file_header.reset(
                            dynamic_cast< FrHeader* >( fr_header_object ) );
                        if ( file_header )
                        {
                            o.release( );
                        }
                        //-----------------------------------------------------------
                        // Copy the base information into the version specific
                        // FrHeader instance so all following operations only
                        // needs to reference file_header.
                        //-----------------------------------------------------------
                        file_header->operator=( *file_header_base );
                        //-----------------------------------------------------------
                        // Next thing to read would be the structure header
                        //-----------------------------------------------------------
                        next_block_size =
                            stream_ref->SizeOf( frame_spec->Version( ) );
                    }
                    catch ( const std::exception& Error )
                    {
                        //-----------------------------------------------------------
                        // Something went wrong. Resync with the stream.
                        //-----------------------------------------------------------
                        Reset( );
                    }
                    catch ( ... )
                    {
                        //-----------------------------------------------------------
                        // Something went wrong. Resync with the stream.
                        //-----------------------------------------------------------
                        Reset( );
                    }
                }
                //---------------------------------------------------------------
                // Validate the rest of the buffer
                //---------------------------------------------------------------
                //---------------------------------------------------------------
                // Learn how to read FrSH and FrSE structures.
                //---------------------------------------------------------------
            }
            else /* if ( ! file_header ) */
            {
                //---------------------------------------------------------------
                // Handle all other cases
                //---------------------------------------------------------------
                IStringStream buffer(
                    Buffer, Size, file_header->ByteSwapping( ) );

                if ( stream_ref->SizeOf( frame_spec->Version( ) ) == Size )
                {
                    //-------------------------------------------------------------
                    // When the number of bytes is the size of the stream
                    // reference class, then it is the first one being read.
                    //-------------------------------------------------------------
                    stream_ref->assign( buffer, frame_spec->Version( ) );
                    //-------------------------------------------------------------
                    // Want to read the entirety of the object and then read the
                    // next stream ref element.
                    //-------------------------------------------------------------
                    next_block_size = stream_ref->GetLength( );
                }
                else
                {
                    //-------------------------------------------------------------
                    // We are dependant upon the stream_ref instance containing
                    // information from the previous release.
                    //-------------------------------------------------------------
                    // Check to see if the structure is an FrSH
                    //-------------------------------------------------------------
                    if ( stream_ref->GetClass( ) == FrameSpec::Info::FSI_FR_SH )
                    {
                        //-----------------------------------------------------------
                        // Read the FrSH information
                        //-----------------------------------------------------------
                        fr_sh->assign( buffer, frame_spec->Version( ) );
                        if ( fr_sh->name( ).compare( fr_end_of_file_name ) ==
                             0 )
                        {
                            //---------------------------------------------------------
                            // Learn the numeric reference for an FrEndOfFile
                            // frame structure.
                            //---------------------------------------------------------
                            fr_end_of_file_id = static_cast<
                                FrameSpec::Info::frame_object_types >(
                                fr_sh->classId( ) );
                        }
                        else if ( frame_h_name &&
                                  ( fr_sh->name( ).compare( frame_h_name ) ==
                                    0 ) )
                        {
                            //---------------------------------------------------------
                            // Learn the numeric reference for an FrEndOfFile
                            // frame structure.
                            //---------------------------------------------------------
                            frame_h_id = static_cast<
                                FrameSpec::Info::frame_object_types >(
                                fr_sh->classId( ) );
                        }
                    }
                    else if ( stream_ref->GetClass( ) == frame_h_id )
                    {
                        //-----------------------------------------------------------
                        // Read the FrameH structure so as to extract
                        // information
                        //-----------------------------------------------------------
                        frame_h->assign( buffer, frame_spec->Version( ) );
                        if ( frame_count == 0 )
                        {
                            start_time = frame_h->normalizedStartTime( );
                            end_time = ( frame_h->normalizedStartTime( ) +
                                         frame_h->normalizedDeltaT( ) );
                        }
                        else
                        {
                            if ( frame_h->normalizedStartTime( ) < start_time )
                            {
                                start_time = frame_h->normalizedStartTime( );
                            }
                        }
                        if ( end_time < ( frame_h->normalizedStartTime( ) +
                                          frame_h->normalizedDeltaT( ) ) )
                        {
                            end_time = ( frame_h->normalizedStartTime( ) +
                                         frame_h->normalizedDeltaT( ) );
                        }
                    }
                    else if ( stream_ref->GetClass( ) == fr_end_of_file_id )
                    {
                        //-----------------------------------------------------------
                        // All data has been read for the frame file stream.
                        // Read the FrEndOfFile structucture and validate the
                        // size of the read buffer with the value stored in
                        // the FrEndOfFile structure
                        //-----------------------------------------------------------
                        fr_end_of_file->assign( file_header->GetVersion( ),
                                                buffer );
                        if ( ( fr_end_of_file->NBytes( ) - Size ) !=
                             ( FrEndOfFile::nBytes_cmn_type )( position ) )
                        {
                            std::ostringstream msg;

                            msg << "Failed to read the correct number of bytes"
                                   " to create a proper frame file stream"
                                   " Actual size: "
                                << ( position + std::streampos( Size ) )
                                << " Expected size: "
                                << ( fr_end_of_file->NBytes( ) );
                            try
                            {
                                //-------------------------------------------------------
                                // Prepare the system for next attempt at
                                // reading a frame file stream.
                                //-------------------------------------------------------
                                Reset( );
                            }
                            catch ( ... )
                            {
                            }
                            throw std::runtime_error( msg.str( ) );
                        }
                        //-----------------------------------------------------------
                        // Everything looks good so report back that a frame
                        // file object is ready for processing.
                        //-----------------------------------------------------------
                        frame_file_cached = true;
                        next_block_size = 0;
                    }
                    if ( frame_file_cached == false )
                    {
                        //-----------------------------------------------------------
                        // Read the next stream reference instance
                        //-----------------------------------------------------------
                        size_type stream_ref_start(
                            Size -
                            stream_ref->SizeOf( frame_spec->Version( ) ) );
                        IStringStream buffer(
                            &Buffer[ stream_ref_start ],
                            stream_ref->SizeOf( frame_spec->Version( ) ),
                            file_header->ByteSwapping( ) );
                        stream_ref->assign( buffer, frame_spec->Version( ) );
                        //-----------------------------------------------------------
                        // Specify the number of bytes to expect for the
                        // structure
                        //-----------------------------------------------------------
                        next_block_size = stream_ref->GetLength( );
                        if ( stream_ref->GetClass( ) == fr_end_of_file_id )
                        {
                            //---------------------------------------------------------
                            // The last buffer is the size of the structure
                            // minus the size of the stream reference header
                            // since no objects should follow.
                            //---------------------------------------------------------
                            next_block_size -=
                                stream_ref->SizeOf( frame_spec->Version( ) );
                        }
                    }
                }
            }
            position += Size;
        }

        //-------------------------------------------------------------------
        ///
        //-------------------------------------------------------------------
        void
        FrameBufferInterface::Scanner::Reset( )
        {
            //-----------------------------------------------------------------
            // Seek to the beginning of the buffer;
            //-----------------------------------------------------------------
            file_header.reset( (FrHeader*)NULL );
            file_header_base.reset( (FrHeader*)NULL );
            frame_file_cached = false;
            frame_spec = (FrameSpec::Info*)NULL;
            frame_h.reset( (FrameH*)NULL );
            frame_h_id = FrameSpec::Info::FSI_FR_NULL;
            frame_h_name = (const char*)NULL;
            fr_end_of_file.reset( (FrEndOfFile*)NULL );
            fr_end_of_file_id = FrameSpec::Info::FSI_FR_NULL;
            fr_end_of_file_name = (const char*)NULL;
            next_block_size = RESET_NEXT_BLOCK_SIZE;
            position = size_type( 0 );
            frame_count = 0;
        }
        // ==================================================================

        /// \brief The default size for buffered input and output.
        const FrameBufferInterface::buffer_size_type
            FrameBufferInterface::M_BUFFER_SIZE_DEFAULT = 256 * 1024; // 256K

        FrameBufferInterface::FrameBufferInterface( ) : m_auto_delete( false )
        {
        }

        FrameBufferInterface::~FrameBufferInterface( )
        {
        }

        void
        FrameBufferInterface::FilterAdd(
            FrameCPP::Common::StreamFilter* Filter )
        {
            m_filters.push_back( Filter );
        }

        void
        FrameBufferInterface::FilterRemove(
            FrameCPP::Common::StreamFilter* Filter )
        {
            m_filters.remove( Filter );
        }

        void
        FrameBufferInterface::FilterBuffer( const char* Begin, const char* End )
        {
            if ( ( Begin == (const char*)0 ) || ( Begin == End ) )
            {
                return;
            }

            for ( filters_type::const_iterator cur = m_filters.begin( ),
                                               last = m_filters.end( );
                  cur != last;
                  ++cur )
            {
                if ( *cur )
                {
                    INT_8U length = End - Begin;

                    ( *cur )->pubfilter( Begin, length * sizeof( char ) );
                }
            }
        }

    } // namespace Common
} // namespace FrameCPP
