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

#include <cstring>

#include <algorithm>
#include <iomanip>
#include <list>
#include <map>
#include <memory>
#include <set>
#include <sstream>
#include <typeinfo>

#include <boost/scoped_array.hpp>

#include "ldastoolsal/fstream.hh"
#include "ldastoolsal/gpstime.hh"
#include "ldastoolsal/InfixIterator.hh"
#include "ldastoolsal/Logger.hh"

#include "framecpp/Common/FrameSpec.hh"
#include "framecpp/Common/FrHeader.hh"
#include "framecpp/Common/FrEndOfFile.hh"
#include "framecpp/Common/FrTOC.hh"
#include "framecpp/Common/MD5Sum.hh"
#include "framecpp/Common/CheckSum.hh"
#include "framecpp/Common/IOStream.hh"
#include "framecpp/Common/FrameBuffer.hh"
#include "framecpp/Common/FrameStream.hh"
#include "framecpp/Common/STRING.hh"
#include "framecpp/Common/TOCInfo.hh"
#include "framecpp/Common/Verify.hh"

#define DEBUG_MD5SUM 0
#define DEBUG_FILE_CHECKSUM 0

using LDASTools::AL::GPSTime;

using FrameCPP::Common::CheckSum;
using FrameCPP::Common::CheckSumCRC;
using FrameCPP::Common::FrameBuffer;
using FrameCPP::Common::FrameSpec;
using FrameCPP::Common::FrHeader;
using FrameCPP::Common::FrTOC;
using FrameCPP::Common::IFrameStream;
using FrameCPP::Common::IStream;
using FrameCPP::Common::MD5Sum;
using FrameCPP::Common::Verify;
using FrameCPP::Common::VerifyException;

using FrameCPP::stream_base_type;

typedef FrameCPP::Common::STRING< INT_2U > VERIFY_STRING;

typedef FrHeader::version_type version_type;

namespace
{
    //=======================================================================
    // Static logger for Verify class with hierarchical channels
    //=======================================================================
    static SimpleLogger* verify_logger()
    {
        static SimpleLogger* instance = get_channel_logger("framecpp.verify");
        return instance;
    }

    static SimpleLogger* verify_magic_logger()
    {
        static SimpleLogger* instance = get_channel_logger("framecpp.verify.magic");
        return instance;
    }

    const bool DEFAULT_CHECK_MD5SUM( false );

    typedef std::set< char >                           site_container_type;
    typedef std::list< site_container_type::key_type > detector_prefix_err_type;

    template < typename T >
    inline bool
    IsNonZero( const T& Value )
    {
        return ( Value.second != 0 );
    }

    template < typename T >
    inline bool
    IsZero( const T& Value )
    {
        return ( Value.second == 0 );
    }

    template < class T,
               class charT = char,
               class traits = std::char_traits< charT > >

    class dump_first_of_pair
        : public LDASTools::AL::infix_ostream_iterator< T, charT, traits >
    {
    public:
        typedef
            typename LDASTools::AL::infix_ostream_iterator< T, charT, traits >
                                                     base_iterator;
        typedef typename base_iterator::ostream_type ostream_type;

        dump_first_of_pair( ostream_type& s ) : base_iterator( s )
        {
        }

        dump_first_of_pair( ostream_type& s, charT const* d )
            : base_iterator( s, d )
        {
        }

        dump_first_of_pair< T, charT, traits >&
        operator=( T const& item )
        {
            return base_iterator::operator=( item.first );
        }
    };

    class channel_site_info : public FrTOC::FunctionString
    {
    public:
        virtual ~channel_site_info( );

        site_container_type& Sites( );

        virtual void operator( )( const std::string& ChannelName );

    private:
        std::set< char > m_sites;
    };

    channel_site_info::~channel_site_info( )
    {
    }

    inline site_container_type&
    channel_site_info::Sites( )
    {
        return m_sites;
    }

    void
    channel_site_info::operator( )( const std::string& ChannelName )
    {
        if ( ( ChannelName.length( ) > 2 ) && ( ChannelName[ 2 ] == ':' ) &&
             ( ChannelName[ 0 ] != 'Z' ) && ( ChannelName[ 0 ] != 'z' ) )
        {
            m_sites.insert( ChannelName[ 0 ] );
        }
    }

    //---------------------------------------------------------------------
    //
    //---------------------------------------------------------------------
    class detector_info : public FrTOC::FunctionC
    {
    public:
        virtual ~detector_info( );

        site_container_type& Sites( );

        virtual void operator( )( char Prefix );

    private:
        std::set< char > m_prefixes;
    };

    detector_info::~detector_info( )
    {
    }

    inline site_container_type&
    detector_info::Sites( )
    {
        return m_prefixes;
    }

    void
    detector_info::operator( )( char Prefix )
    {
        m_prefixes.insert( Prefix );
    }

    //---------------------------------------------------------------------
    //
    //---------------------------------------------------------------------
    class fr_structs_info : public FrTOC::FunctionSI
    {
    public:
        typedef std::map< std::string, INT_4U > fr_structs_container_type;
        virtual ~fr_structs_info( );

        const fr_structs_container_type& operator( )( ) const;

        virtual void operator( )( const std::string& Name, INT_4U Id );

    private:
        fr_structs_container_type m_structs;
    };

    fr_structs_info::~fr_structs_info( )
    {
    }

    inline const fr_structs_info::fr_structs_container_type&
    fr_structs_info::operator( )( ) const
    {
        return m_structs;
    }

    void
    fr_structs_info::operator( )( const std::string& Name, INT_4U Id )
    {
        m_structs[ Name ] = Id;
    }

    //---------------------------------------------------------------------
    //
    //---------------------------------------------------------------------
    class IChecksumOnlyStream : public FrameCPP::Common::IStream
    {
    public:
        typedef boost::shared_ptr< FrameCPP::Common::FrHeader > header_type;

        IChecksumOnlyStream( buffer_type* Buffer, bool AutoDelete = true );

        IChecksumOnlyStream( FrameCPP::Common::IStream& Source );

        virtual const FrHeader& GetFrHeader( ) const override;

        header_type Header;

    private:
        void init( );

        // Temporary base header used during initialization before Create() completes
        // We use a pointer to allow delayed construction
        mutable std::unique_ptr< FrHeader > m_base_header;
        mutable bool m_initializing;
    };

    IChecksumOnlyStream::IChecksumOnlyStream( buffer_type* Buffer,
                                              bool         AutoDelete )
        : IStream( Buffer, AutoDelete ), m_initializing( true )
    {
        init( );
    }

    IChecksumOnlyStream::IChecksumOnlyStream(
        FrameCPP::Common::IStream& Source )
        : IStream( Source.rdbuf( ), false ), m_initializing( true )
    {
        init( );
    }

    const FrameCPP::Common::FrHeader&
    IChecksumOnlyStream::GetFrHeader( ) const
    {
        // During initialization, return base header to avoid NULL dereference
        if ( m_initializing && m_base_header )
        {
            return ( *m_base_header );
        }
        return ( *Header );
    }

    void
    IChecksumOnlyStream::init( )
    {
        using FrameCPP::Common::FrameSpec;

        //-------------------------------------------------------------------
        // Start at the begining of the file.
        //-------------------------------------------------------------------
        this->seekg( 0, this->beg );

        //-------------------------------------------------------------------
        // FIX: Allocate space for base header and construct it from stream.
        // We keep it in m_base_header so GetFrHeader() can return it during
        // Create() if StreamVersion() is called.
        //-------------------------------------------------------------------
        m_base_header.reset( new FrHeader( *this ) );

        logger_log_debug(verify_logger(), 25,
            "IChecksumOnlyStream::init: Created m_base_header (Common::FrHeader), ",
            "version=", m_base_header->GetVersion(),
            ", ByteSwapping()=", (m_base_header->ByteSwapping() ? "TRUE" : "FALSE"),
            ", stream position after read: ", this->tellg());

        //-------------------------------------------------------------------
        // Setup to use the appropriate frame specification table
        //-------------------------------------------------------------------
        frameSpecInfo( FrameSpec::SpecInfo( m_base_header->GetVersion( ) ) );

        //-------------------------------------------------------------------
        // Read version specific portion of FrHeader - DON'T seek back!
        // Stream is already positioned after Common::FrHeader (6 bytes),
        // which is exactly where Create() expects to start reading.
        //-------------------------------------------------------------------
        logger_log_debug(verify_logger(), 25,
            "IChecksumOnlyStream::init: Stream positioned after Common::FrHeader, ",
            "stream position: ", this->tellg(),
            ", about to call Create() for version-specific FrHeader");

        FrameSpec::ObjectInterface::unique_object_type o(
            frameSpecInfo( )
                .FrameObject( FrameSpec::Info::FSI_FR_HEADER )
                ->Create( *this ) );

        logger_log_debug(verify_logger(), 25,
            "IChecksumOnlyStream::init: Created version-specific FrHeader via Create(), ",
            "stream position after Create(): ", this->tellg());

        //-------------------------------------------------------------------
        // Move back to the begining of the file.
        //-------------------------------------------------------------------
        /* this->seekg( 0, this->beg ); */
        Header.reset( dynamic_cast< FrameCPP::Common::FrHeader* >( o.get( ) ) );
        if ( Header )
        {
            // FIX: Save ByteSwapping() value BEFORE assignment from m_base_header
            // The assignment is needed to copy base class fields (version, originator, etc.)
            // but we must preserve the correct ByteSwapping determination from Create()
            bool correct_byte_swapping = Header->ByteSwapping();

            logger_log_debug(verify_logger(), 25,
                "IChecksumOnlyStream::init: BEFORE assignment - ",
                "Header->ByteSwapping()=", (correct_byte_swapping ? "TRUE" : "FALSE"),
                ", Header->GetVersion()=", Header->GetVersion());

            o.release( );
            Header->operator=( *m_base_header );

            logger_log_debug(verify_logger(), 25,
                "IChecksumOnlyStream::init: AFTER assignment from m_base_header - ",
                "Header->ByteSwapping()=", (Header->ByteSwapping() ? "TRUE" : "FALSE"),
                ", Header->GetVersion()=", Header->GetVersion());

            // VERIFY: Check if operator= preserved ByteSwapping (it should, since
            // m_reorder_bytes is not in the base class and operator= only touches base fields)
            if ( Header->ByteSwapping() != correct_byte_swapping )
            {
                logger_log_debug(verify_logger(), 10,
                    "IChecksumOnlyStream::init: ERROR - ByteSwapping was corrupted by assignment! ",
                    "Expected: ", (correct_byte_swapping ? "TRUE" : "FALSE"),
                    ", Got: ", (Header->ByteSwapping() ? "TRUE" : "FALSE"));
                // This should never happen if operator= only touches base class fields
            }
        }
        this->byteSwapping( Header->ByteSwapping( ) );

        logger_log_debug(verify_logger(), 20,
            "IChecksumOnlyStream::init: ByteSwapping flag set to ",
            (this->ByteSwapping() ? "TRUE" : "FALSE"),
            " from FrHeader, version=", Header->GetVersion());

        // Clear initialization state
        m_initializing = false;
        m_base_header.reset();

        //-------------------------------------------------------------------
        // Move back to the begining of the file.
        //-------------------------------------------------------------------
        this->seekg( 0, this->beg );
    }
} // namespace

const char* VerifyException::m_error_strings[] = {
    "no failure",
    "checksum error",
    "unreadable file error",
    "unsupported frame version error",
    "frame does not conform to the frame specification",
    "the type of checksum being requested was unknown.",
    "no checksum was supplied at the end of the file",
    "one of the internal frame structures does not pass its own consitancy "
    "check",
    "the file is missing the FrEndOfFile structure required by the frame "
    "specification.",
    "One or more channels has a non-zero dataValid field.",
    "One or more pieces of the filename meta data is invalid.",
    "One or more channel names have been duplicated within a frame."
};

const char* VerifyException::m_error_type_strings[] = {
    "NO_ERROR",
    "CHECKSUM_ERROR",
    "FILE_OPEN_ERROR",
    "UNSUPPORTED_FRAME_SPEC",
    "FRAME_SPEC_CONFORMANCE",
    "UNSUPPORTED_CHECKSUM_TYPE",
    "NO_CHECKSUM",
    "INVALID_FRAME_STRUCTURE",
    "FILE_TRUNCATION",
    "DATA_INVALID",
    "METADATA_INVALID",
    "DUPLICATE_CHANNEL_NAME",
};

VerifyException*
VerifyException::Clone( ) const
{
    return new VerifyException( *this );
}

std::string
VerifyException::StrError( Verify::error_type ErrNum )
{
    if ( ( ErrNum >= NO_ERROR ) && ( ErrNum <= MAX_ERROR ) )
    {
        return m_error_strings[ ErrNum ];
    }
    std::ostringstream msg;

    msg << "Verify: Unknown error code: " << ErrNum;

    return msg.str( );
}

std::string
VerifyException::StrErrorType( Verify::error_type ErrNum )
{
    if ( ( ErrNum >= NO_ERROR ) && ( ErrNum <= MAX_ERROR ) )
    {
        return m_error_type_strings[ ErrNum ];
    }
    std::ostringstream msg;

    msg << "Verify: Unknown error_type code: " << ErrNum;

    return msg.str( );
}

VerifyException&
VerifyException::operator=( const VerifyException& Error )
{
    *( static_cast< std::runtime_error* >( this ) ) =
        std::runtime_error::operator=( Error );
    return *this;
}

void
VerifyException::extendError( const std::string& Message )
{
    std::ostringstream msg;
    msg << what( ) << Message;

    *this = VerifyException( ErrorCode( ), msg.str( ) );
}

//=======================================================================
//
//=======================================================================

Verify::frame_filename_ptr_type Verify::NULL_FRAME_FILENAME;

Verify::Verify( )
    : m_buffer_size( INT_8U( 0 ) ), frame_offset( ~0 ),
      check_for_duplicate_channel_names( true ), m_check_data_valid( false ),
      m_check_data_valid_all( false ), m_check_expandability( false ),
      m_check_fast( false ), m_check_file_checksum( true ),
      m_check_frame_checksum( false ), m_check_md5sum( DEFAULT_CHECK_MD5SUM ),
      m_checksum_file_only( false ), collect_all_errors( false ),
      m_must_have_eof_checksum( true ), m_strict( true ),
      m_use_memory_mapped_io( true ), m_validate_metadata( true ), //
      m_state_has_seen_fr_end_of_file( false ), //
      m_checksum_scheme( 0 ), m_checksum_frheader( 0 )
{
    logger_log_debug(verify_logger(), 20, 
        "Verify constructor: strict=", m_strict, 
        " check_file_checksum=", m_check_file_checksum,
        " must_have_eof_checksum=", m_must_have_eof_checksum);
}

void
Verify::operator( )( const std::string& Filename )
{
    logger_log_debug(verify_logger(), 10, "Verify::operator() entry: file=", Filename);

    // Per-file reset of the checksum-comparison log (issue #151).
    // Note: error_info_container is NOT cleared here, matching the
    // pre-existing accumulate-across-files behavior of ErrorInfoQueue.
    checksum_info_container.clear( );

    try
    {
        //-------------------------------------------------------------------
        // Create the read buffer and open it
        //-------------------------------------------------------------------
        FrameBuffer< LDASTools::AL::filebuf >* ibuf(
            new FrameBuffer< LDASTools::AL::filebuf >( std::ios::in ) );
        ibuf->open( Filename.c_str( ), std::ios::in | std::ios::binary );

        //-------------------------------------------------------------------
        // Establish buffering
        //-------------------------------------------------------------------
        boost::scoped_array< char > io_buffer;

        if ( m_buffer_size == ~INT_8U( 0 ) )
        {
            // Do Nothing
        }
        else if ( m_buffer_size > 0 )
        {
            io_buffer.reset( new char[ m_buffer_size ] );
            ibuf->pubsetbuf( io_buffer.get( ), m_buffer_size );
        }
        else
        {
            io_buffer.reset( (char*)NULL );
            ibuf->pubsetbuf( io_buffer.get( ), m_buffer_size );
        }
        ibuf->UseMemoryMappedIO( m_use_memory_mapped_io );

        if ( CheckFileChecksumOnly( ) )
        {
            IChecksumOnlyStream stream( ibuf );

            return crc_file_checksum_only( stream, stream.Header );
        }
        //-------------------------------------------------------------------
        // Open the frame file
        //-------------------------------------------------------------------
        IFrameStream ifs( ibuf, 0 );

        if ( !ifs.good( ) )
        {
            // An error has happened in opening the stream.
            throw VerifyException( VerifyException::FILE_OPEN_ERROR );
        }

        //-------------------------------------------------------------------
        // Establish the buffer size
        //-------------------------------------------------------------------
        frame_filename_ptr_type ff;

        if ( ValidateMetadata( ) )
        {
            ff.reset( new FrameFilename( Filename.c_str( ) ) );
        }
        this->operator( )( ifs, ff );
    }
    catch ( const VerifyException& e )
    {
        throw;
    }
    catch ( const std::exception& e )
    {
        throw VerifyException( VerifyException::FILE_OPEN_ERROR, e.what( ) );
    }
    catch ( ... )
    {
        throw VerifyException( VerifyException::FILE_OPEN_ERROR );
    }
}

void
Verify::operator( )( IFrameStream& Stream, frame_filename_ptr_type& Filename )
{
    if ( CheckFileChecksumOnly( ) )
    {
        IChecksumOnlyStream stream( Stream );

        crc_file_checksum_only( stream, stream.Header );
        return;
    }
    //---------------------------------------------------------------------
    // Do initialization
    //---------------------------------------------------------------------
    m_state_has_seen_fr_end_of_file = false;

    const FrTOC* toc = (const FrTOC*)NULL;

    try
    {
        //-------------------------------------------------------------------
        // Ensure that the steam is good.
        //-------------------------------------------------------------------
        if ( !Stream.good( ) )
        {
            // An error has happened in opening the stream.
            throw VerifyException( VerifyException::FILE_OPEN_ERROR );
        }

        //-----------------------------------------------------------------
        // File checksum filter setup
        // IMPORTANT: Must be set up BEFORE GetTOC() so that FrHeader bytes
        // are accumulated. GetTOC() reads the FrHeader (first 40 bytes),
        // and those bytes must be included in the file checksum.
        // load_toc() handles suspend/resume internally during TOC reading.
        //-----------------------------------------------------------------
        if ( m_check_file_checksum == false )
        {
            Stream.SetCheckSumFile( CheckSum::NONE );
        }
        else
        {
            //---------------------------------------------------------------
            // Need to completely reset. Be sure the checksum is cleared
            // by first setting it to none and then to the desired type.
            //---------------------------------------------------------------
            logger_log_debug(verify_logger(), 25,
                "Verify::operator(): About to reset file checksum filter");

            Stream.SetCheckSumFile( CheckSum::NONE );
            logger_log_debug(verify_logger(), 25,
                "Verify::operator(): After SetCheckSumFile(NONE), stream position: ",
                Stream.tellg());

            //---------------------------------------------------------------
            // Flush stream buffer before adding checksum filter
            // The buffer may have read-ahead bytes that would be pushed
            // through the filter when it's added, corrupting the checksum.
            // Seeking to current position forces the buffer to flush.
            //---------------------------------------------------------------
            std::streampos current_pos = Stream.tellg();
            Stream.seekg(current_pos);

            Stream.SetCheckSumFile( CheckSum::CRC );
            CheckSumFilter* fcs = Stream.GetCheckSumFile();
            if (fcs && fcs->GetChecksum()) {
                CheckSum::value_type initial_value = fcs->GetChecksum()->peek();

                //---------------------------------------------------------------
                // WORKAROUND: Reset checksum to zero
                // The filter chain adds spurious bytes when the filter is added.
                // We'll manually seed the header bytes next, so start from zero.
                //---------------------------------------------------------------
                fcs->GetChecksum()->Reset();

                logger_log_debug(verify_logger(), 25,
                    "Verify::operator(): After SetCheckSumFile(CRC), ",
                    "stream position: ", Stream.tellg(),
                    ", initial checksum value: 0x", std::hex, initial_value, std::dec,
                    " (", initial_value, ")");
            }

            //---------------------------------------------------------------
            // Seed the file checksum with the header bytes that were read
            // during stream initialization (before the checksum filter existed)
            //---------------------------------------------------------------
            Stream.SeedFileChecksumWithHeader();

            // Log checksum after seeding
            CheckSumFilter* fcs_after_seed = Stream.GetCheckSumFile();
            if (fcs_after_seed && fcs_after_seed->GetChecksum()) {
                CheckSum::value_type seeded_value = fcs_after_seed->GetChecksum()->peek();
                logger_log_debug(verify_logger(), 25,
                    "Verify::operator(): After SeedFileChecksumWithHeader(), ",
                    "checksum value: 0x", std::hex, seeded_value, std::dec,
                    " (", seeded_value, ")");
            }
        }

        //-------------------------------------------------------------------
        // Get TOC (load_toc() now handles filter suspend/resume internally)
        //-------------------------------------------------------------------
        try
        {
            toc = Stream.GetTOC( );
        }
        catch ( ... )
        {
        }

        //-------------------------------------------------------------------
        // DEBUG: Peek at file checksum after reading FrHeader (via GetTOC)
        //-------------------------------------------------------------------
        if ( ( CheckFast( ) == false ) || ( Stream.Version( ) < 8 ) )
        {
            //-----------------------------------------------------------------
            // MD5Sum setup (only for non-fast mode)
            //-----------------------------------------------------------------
            Stream.SetMD5Sum( m_check_md5sum );
        }

        bool                      calculating_frame_checksum = false;
        IFrameStream::object_type frame_object;

        //-------------------------------------------------------------------
        // Validate the first 40 bytes of file.
        //-------------------------------------------------------------------
        Stream.VerifyHeader( *this );
        logger_log_debug(verify_logger(), 25,
            "Verify::operator(): After VerifyHeader(), stream position: ", Stream.tellg());
        //-------------------------------------------------------------------
        // Determine if fast checking should be done.
        //-------------------------------------------------------------------
        if ( ( CheckFast( ) == true ) && ( Stream.Version( ) >= 8 ) &&
             ( toc != (FrTOC*)NULL ) )
        {
            //-----------------------------------------------------------------
            // All conditions have been satisfied to do fast checking
            //-----------------------------------------------------------------
            if ( ValidateMetadata( ) )
            {
                //---------------------------------------------------------------
                // Gather information about each FrameH structure in TOC
                //---------------------------------------------------------------
                //---------------------------------------------------------------
                // Gather the site information from each channel
                //---------------------------------------------------------------
                channel_site_info channel_sites;

                toc->ForEach( FrTOC::TOC_CHANNEL_NAMES, channel_sites );
                //---------------------------------------------------------------
                // Validate detector data using information gleaned from
                // the TOC.
                //---------------------------------------------------------------
                detector_info detectors;

                toc->ForEach( FrTOC::TOC_DETECTOR, detectors );
                //---------------------------------------------------------------
                // Validate Detector Prefix
                //---------------------------------------------------------------
                {
                    //-------------------------------------------------------------
                    // Validate filename information
                    //-------------------------------------------------------------
                    if ( Filename.get( ) )
                    {
                        const std::string& s = Filename->S( );

                        detector_prefix_err_type sdata;
                        detector_prefix_err_type results;
                        std::insert_iterator< detector_prefix_err_type > ins(
                            results, results.begin( ) );

                        for ( std::string::const_iterator cur = s.begin( ),
                                                          last = s.end( );
                              cur != last;
                              ++cur )
                        {
                            sdata.push_back( *cur );
                        }
                        std::set_difference( detectors.Sites( ).begin( ),
                                             detectors.Sites( ).end( ),
                                             sdata.begin( ),
                                             sdata.end( ),
                                             ins );
                        if ( results.empty( ) == false )
                        {
                            std::ostringstream msg;
                            msg << " Detector information missing in site "
                                   "component of the filename:";
                            for ( detector_prefix_err_type::const_iterator
                                      cur = results.begin( ),
                                      last = results.end( );
                                  cur != last;
                                  ++cur )
                            {
                                if ( cur != results.begin( ) )
                                {
                                    msg << ",";
                                }
                                msg << " " << *cur;
                            }
                            msg << std::endl;

                            thrower( VerifyException(
                                VerifyException::METADATA_INVALID,
                                msg.str( ) ) );
                        }
                    }
                    //-------------------------------------------------------------
                    // Looking for missing detector information
                    //-------------------------------------------------------------
                    detector_prefix_err_type results;

                    std::insert_iterator< detector_prefix_err_type > ins(
                        results, results.begin( ) );
                    std::set_difference( channel_sites.Sites( ).begin( ),
                                         channel_sites.Sites( ).end( ),
                                         detectors.Sites( ).begin( ),
                                         detectors.Sites( ).end( ),
                                         ins );
                    if ( results.empty( ) == false )
                    {
                        std::ostringstream msg;

                        msg << " No detector information for site:";
                        for ( detector_prefix_err_type::const_iterator
                                  cur = results.begin( ),
                                  last = results.end( );
                              cur != last;
                              ++cur )
                        {
                            if ( cur != results.begin( ) )
                            {
                                msg << ",";
                            }
                            msg << " " << *cur;
                        }
                        msg << std::endl;

                        thrower( VerifyException(
                            VerifyException::METADATA_INVALID, msg.str( ) ) );
                    }
                }
                {
                    //-------------------------------------------------------------
                    // Looking for detector information that is not referenced
                    // by any of the channels
                    //-------------------------------------------------------------
                    detector_prefix_err_type                         results;
                    std::insert_iterator< detector_prefix_err_type > ins(
                        results, results.begin( ) );

                    std::set_difference( detectors.Sites( ).begin( ),
                                         detectors.Sites( ).end( ),
                                         channel_sites.Sites( ).begin( ),
                                         channel_sites.Sites( ).end( ),
                                         ins );
                    if ( results.empty( ) == false )
                    {
                        std::ostringstream msg;

                        msg << " No channels refer to site:";
                        for ( detector_prefix_err_type::const_iterator
                                  cur = results.begin( ),
                                  last = results.end( );
                              cur != last;
                              ++cur )
                        {
                            if ( cur != results.begin( ) )
                            {
                                msg << ",";
                            }
                            msg << " " << *cur;
                        }
                        msg << std::endl;

                        thrower( VerifyException(
                            VerifyException::METADATA_INVALID, msg.str( ) ) );
                    }
                }
            }
            {
                //---------------------------------------------------------------
                // Ensure there is an FrEndOfFile element
                /// \todo
                /// A true check for the end of file structure needs to be done
                /// for the fast check.
                /// A proposed method would be to do a seek to the end of the
                /// file minus the size of the structure.
                /// Invoke the read object call and verify the object that
                /// was read was an FrEndOfFile object.
                /// Seek back to the previous file position.
                //---------------------------------------------------------------
                m_state_has_seen_fr_end_of_file = true;
            }
        }
        else
        {
            //-----------------------------------------------------------------
            // Read through all objects
            //-----------------------------------------------------------------
            std::ostringstream dataValidAll;

            while ( Stream.good( ) )
            {
                if ( ( m_check_frame_checksum == true ) &&
                     ( calculating_frame_checksum == false ) &&
                     ( Stream.Version( ) < 8 ) )
                {
                    // Clear the buffer and get read for the time when the
                    // checksum will be calculated
                    Stream.SetCheckSumFrame( CheckSum::CRC );
                }

                try
                {
                    //-----------------------------------------------------------
                    // Try to read in the structure using real read routines.
                    //-----------------------------------------------------------
                    Stream.Cleanup( );
                    frame_object = Stream.Read( );
                    if ( !frame_object )
                    {
                        if ( Stream.eof( ) )
                        {
                            break;
                        }

                        thrower( VerifyException(
                            VerifyException::INVALID_FRAME_STRUCTURE,
                            "Failed to read object" ) );
                    }
                    //-------------------------------------------------------------
                    // Do object specific verification
                    //-------------------------------------------------------------
                    frame_object->VerifyObject( *this, Stream );

                    //-------------------------------------------------------------
                    // Take generic actions on specific objects
                    // qfo - query frame object
                    //-------------------------------------------------------------
                    const TOCInfo* qfo(
                        dynamic_cast< const TOCInfo* >( frame_object.get( ) ) );

                    switch ( frame_object->GetClass( ) )
                    {
                    case FrameSpec::Info::FSI_FRAME_H:
                        //-----------------------------------------------------------
                        // Initialization and setup actions to take when the
                        // FrameH structure is seen.
                        //-----------------------------------------------------------
                        name_cache.erase( name_cache.begin( ),
                                          name_cache.end( ) );
                        ++frame_offset;

                        //-----------------------------------------------------------
                        // DIAGNOSTIC: Log file checksum after reading FrHeader
                        //-----------------------------------------------------------
                        if ( CheckFileChecksum( ) && Stream.GetCheckSumFile( ) )
                        {
                            CheckSumFilter* file_filter = Stream.GetCheckSumFile( );
                            if ( file_filter && file_filter->GetChecksum( ) )
                            {
                                logger_log_debug(verify_logger(), 20,
                                    "Verify: After reading FrHeader #", frame_offset,
                                    ", file checksum: ", file_filter->GetChecksum()->PrettyPrint(),
                                    ", stream position: ", Stream.tellg());
                            }
                        }

                        if ( ValidateMetadata( ) && Filename.get( ) )
                        {
                            REAL_8 dt = 0;
                            INT_4U sec = 0;
                            INT_4U nsec = 0;

                            //-------------------------------------------------------
                            // Query for infomation
                            //-------------------------------------------------------
                            if ( qfo )
                            {
                                //-------------------------------------------------------
                                // Query the object
                                //-------------------------------------------------------
                                qfo->TOCQuery( TOCInfo::IC_GTIME_S,
                                               TOCInfo::DataType( sec ),
                                               &sec,

                                               TOCInfo::IC_GTIME_N,
                                               TOCInfo::DataType( nsec ),
                                               &nsec,

                                               TOCInfo::IC_DT,
                                               TOCInfo::DataType( dt ),
                                               &dt,

                                               TOCInfo::IC_EOQ );
                            }

                            //-------------------------------------------------------
                            // Validate frame name information
                            //-------------------------------------------------------

                            const GPSTime ff_start( Filename->G( ), 0 );
                            const GPSTime ff_end( ff_start + Filename->T( ) );
                            const GPSTime ogtime( sec, nsec );

                            if ( ( ff_start > ogtime ) ||
                                 ( ff_end < GPSTime( ogtime + dt ) ) )
                            {
                                std::ostringstream msg;
                                msg << "  METADATA_VALID ERROR:"
                                    << " Either the start time or the duration "
                                       "is invalid";

                                thrower( VerifyException(
                                    VerifyException::METADATA_INVALID,
                                    msg.str( ) ) );
                            }

                            //---------------------------------------------------------
                            // Setup for Detector validation
                            //---------------------------------------------------------
                            m_detector_validation.erase(
                                m_detector_validation.begin( ),
                                m_detector_validation.end( ) );
                            m_detector_validation_excess.erase(
                                m_detector_validation_excess.begin( ),
                                m_detector_validation_excess.end( ) );
                            m_channel_validation.erase(
                                m_channel_validation.begin( ),
                                m_channel_validation.end( ) );
                            m_channel_validation_excess.erase(
                                m_channel_validation_excess.begin( ),
                                m_channel_validation_excess.end( ) );
                            std::string s( Filename->S( ) );

                            for ( std::string::const_iterator cur = s.begin( ),
                                                              last = s.end( );
                                  cur != last;
                                  ++cur )
                            {
                                if ( *cur != 'Z' )
                                {
                                    m_detector_validation[ *cur ] = 0;
                                    m_channel_validation[ *cur ] = 0;
                                }
                            }
                        }
                        break;
                    case FrameSpec::Info::FSI_FR_DETECTOR:
                        if ( ValidateMetadata( ) && Filename.get( ) )

                        {
                            VERIFY_STRING name;
                            VERIFY_STRING prefix;

                            if ( qfo )
                            {
                                //---------------------------------------------------------
                                // Query the object
                                //---------------------------------------------------------
                                qfo->TOCQuery( TOCInfo::IC_NAME,
                                               TOCInfo::DataType( name ),
                                               &name,

                                               TOCInfo::IC_DETECTOR_PREFIX,
                                               TOCInfo::DataType( prefix ),
                                               &prefix,

                                               TOCInfo::IC_EOQ );
                            }
                            detector_validation_type::iterator pos =
                                m_detector_validation.find( prefix[ 0 ] );
                            if ( pos == m_detector_validation.end( ) )
                            {
                                // Don't store this if the prefix is unknown
                                if ( ( prefix.length( ) >= 1 ) &&
                                     !( prefix[ 0 ] == ' ' ) &&
                                     !( prefix[ 0 ] == 'Z' ) )
                                {
                                    m_detector_validation_excess
                                        [ prefix[ 0 ] ]++;
                                }
                            }
                            else
                            {
                                ( *pos ).second++;
                            }
                        }
                        break;
                        BEGIN_WIMPLICIT_FALLTHROUGH
                    case FrameSpec::Info::FSI_FR_ADC_DATA:
                        if ( m_check_data_valid && qfo )
                        {
                            VERIFY_STRING name;
                            INT_2U        dv;

                            qfo->TOCQuery( TOCInfo::IC_DATA_VALID,
                                           TOCInfo::DataType( dv ),
                                           &dv,

                                           TOCInfo::IC_NAME,
                                           TOCInfo::DataType( name ),
                                           &name,

                                           TOCInfo::IC_EOQ );

                            if ( dv != 0 )
                            {
                                std::ostringstream msg;

                                msg << "  DATA_VALID ERROR: value: 0x"
                                    << std::hex << std::setw( 4 )
                                    << std::setfill( '0' ) << dv
                                    << " channel: " << name;
                                thrower( VerifyException(
                                    VerifyException::DATA_INVALID,
                                    msg.str( ) ) );
                            }
                        }
                        // Fall through so as to check Detector information
                        END_WIMPLICIT_FALLTHROUGH
                    case FrameSpec::Info::FSI_FR_SIM_DATA:
                    case FrameSpec::Info::FSI_FR_PROC_DATA:
                        if ( qfo )
                        {
                            if ( ValidateMetadata( ) && Filename.get( ) )

                            {
                                VERIFY_STRING name;

                                qfo->TOCQuery( TOCInfo::IC_NAME,
                                               TOCInfo::DataType( name ),
                                               &name,
                                               TOCInfo::IC_EOQ );

                                if ( ( name.length( ) >= 3 ) &&
                                     ( name[ 2 ] == ':' ) )
                                {
                                    set_channel_site( name[ 0 ] );
                                }
                            }
                            if ( CheckForDuplicateChannelNames( ) )
                            {
                                //---------------------------------------------------------
                                // Validate the uniqueness of the channel name
                                //---------------------------------------------------------
                                VERIFY_STRING name;

                                qfo->TOCQuery( TOCInfo::IC_NAME,
                                               TOCInfo::DataType( name ),
                                               &name,
                                               TOCInfo::IC_EOQ );
                                is_unique( frame_object->GetClass( ), name );
                            }
                        } // if qfo
                        break;
                    }

#if WORKING
                    switch ( frame_object->GetClass( ) )
                    {
                    case CLASS_FRAME_H:
                        // Start of frame.
                        calculating_frame_checksum = true;
                        break;
                    case CLASS_FR_END_OF_FRAME:
                    {
                        if ( ( ValidateMetadata( ) ) &&
                             ( Filename.get( ) != (Filename*)NULL ) )
                        {
                            std::ostringstream msg;

                            detector_validation_type bad;

                            std::remove_copy_if( m_detector_validation.begin( ),
                                                 m_detector_validation.end( ),
                                                 bad,
                                                 IsZero );
                            if ( !bad.empty( ) )
                            {
                                msg << "No detector information for site: ";
                                std::copy( bad.begin( ),
                                           bad.end( ),
                                           dump_first_of_pair( msg, ", " ) );
                            }
                            if ( !msg.str( ).empty( ) )
                            {
                thrower( VerifyException( METADATA_INVALID, msg.str( ) ) ) );
                msg.str( "" );
                            }

                            if ( !m_detector_validation_excess.empty( ) )
                            {
                                msg << " Detector information missing in site "
                                       "component: ";
                                std::copy(
                                    m_detector_validation_excess.begin( ),
                                    m_detector_valication_excess.end( ),
                                    dump_first_of_pair( msg, ", " ) );
                                thrower( VerifyException( METADATA_INVALID,
                                                          msg.str( ) ) );
                                msg.str( "" );
                            }
                            if ( !m_channel_validation.empty( ) )
                            {
                                channel_validation_type bad;

                                std::remove_copy_if(
                                    m_channel_validation.begin( ),
                                    m_channel_validation.end( ),
                                    bad,
                                    IsZero );
                                if ( !bad.empty( ) )
                                {
                                    msg << " No channels refer to site: ";
                                    std::copy(
                                        bad.begin( ),
                                        bad.end( ),
                                        dump_first_of_pair( msg, ", " ) );
                                    thrower( VerifyException e(
                                        METADATA_INVALID, msg.str( ) ) );
                                    msg.str( "" );
                                }
                            }
                            if ( !m_channel_validation_excess.empty( ) )
                            {
                                msg << " Channel references unlisted site: ";
                                std::copy( m_channel_validation_excess.begin( ),
                                           m_channel_valication_excess.end( ),
                                           dump_first_of_pair( msg, ", " ) );
                                thrower( VerifyException( METADATA_INVALID,
                                                          msg.str( ) ) );
                                msg.str( "" );
                            }
                        }
                        if ( m_check_frame_checksum )
                        {
                            const FrEndOfFrame* eof(
                                dynamic_cast< const FrEndOfFrame* >(
                                    obj.get( ) ) );

                            calculating_frame_checksum = false;
                            if ( eof )
                            {
                                if ( eof->GetChkType( ) == CheckSum::NONE )
                                {
                                    // Do nothing
                                }
                                else if ( eof->GetChkType( ) ==
                                          (INT_4U)frame_stream
                                              .GetCheckSumFrame( )
                                              .GetType( ) )
                                {
                                    if ( eof->GetChkSum( ) !=
                                         frame_stream.GetCheckSumFrame( )
                                             .value( ) )
                                    {
                                        std::ostringstream msg;
                                        msg << "FR_END_OF_FRAME: "
                                            << CheckSum::FormatError(
                                                   eof->GetChkSum( ),
                                                   frame_stream
                                                       .GetCheckSumFrame( )
                                                       .value( ) );
                                        thrower( VerifyException(
                                            CHECKSUM_ERROR, msg.str( ) ) );
                                    }
                                }
                                else
                                {
                                    thrower( VerifyException e(
                                        UNSUPPORTED_CHECKSUM_TYPE ) );
                                    break;
                                }
                            }
                        }
                    }
                    break;
#endif /* WORKING */
                    }
                    catch ( VerifyException& E )
                    {
                        if ( ( E.ErrorCode( ) ==
                               VerifyException::DATA_INVALID ) &&
                             ( CheckDataValidAll( ) ) )
                        {
                            dataValidAll << std::endl
                                         << "\t\t" << E.what( )
                                         << " in frame: " << frame_offset;
                        }
                        else
                        {
                            std::ostringstream msg;

                            msg << " in frame: " << frame_offset;

                            E.extendError( msg.str( ) );

                            thrower( E );
                        }
                    }
                    catch ( const std::exception& e )
                    {
                        if ( !collect_all_errors )
                        {
                            throw;
                        }
                        error_info_container.push_back( error_info_type(
                            new error_info_type::element_type( e ) ) );
                    }
                    catch ( ... )
                    {
                        thrower( VerifyException(
                            VerifyException::INVALID_FRAME_STRUCTURE ) );
                    }
                } // while frame_stream.good
                if ( !dataValidAll.str( ).empty( ) )
                {
                    throw VerifyException( VerifyException::DATA_INVALID,
                                           dataValidAll.str( ) );
                }
            } // if - Fast Checking
        }
        catch ( const VerifyException& E )
        {
            thrower( E );
        }
        catch ( const std::exception& E )
        {
            thrower( VerifyException( VerifyException::FILE_OPEN_ERROR,
                                      E.what( ) ) );
        }
        catch ( ... )
        {
            thrower( VerifyException( VerifyException::FILE_OPEN_ERROR ) );
        }
        //---------------------------------------------------------------------
        // Ensure that the end of file structure was seen
        //---------------------------------------------------------------------
        if ( m_state_has_seen_fr_end_of_file == false )
        {
            throw( VerifyException( VerifyException::FILE_TRUNCATION ) );
        }
        if ( !error_info_container.empty( ) )
        {
            throw( *( error_info_container.front( ) ) );
        }
    }

    void Verify::ExamineFrEndOfFileChecksum(
        IFrameStream & Stream, chkType_type Type, chkSum_type Sum )
    {
        if ( m_check_file_checksum )
        {
            logger_log_debug(verify_logger(), 25,
                "ExamineFrEndOfFileChecksum: Stream position at entry: ", Stream.tellg());

            std::unique_ptr< CheckSum > recorded_checksum( CheckSum::Create(
                (CheckSum::kind_type)Type, (CheckSum::value_type)Sum ) );
            CheckSum*                   calculated_checksum(
                ( Stream.GetCheckSumFile( ) )
                    ? Stream.GetCheckSumFile( )->GetChecksum( )
                    : (CheckSum*)NULL );

            logger_log_debug(verify_logger(), 25,
                "ExamineFrEndOfFileChecksum: ",
                "recorded type=", Type, " value=0x", std::hex, Sum, std::dec, " (", Sum, ")");
            if (calculated_checksum) {
                CheckSum::value_type calc_value = calculated_checksum->peek();
                logger_log_debug(verify_logger(), 25,
                    "ExamineFrEndOfFileChecksum: ",
                    "calculated type=", calculated_checksum->GetType(),
                    " value=0x", std::hex, calc_value, std::dec,
                    " (", calc_value, ")");
            } else {
                logger_log_debug(verify_logger(), 25,
                    "ExamineFrEndOfFileChecksum: calculated_checksum is NULL!");
            }

            verify_checksum( recorded_checksum.get( ),
                             calculated_checksum,
                             "FR_END_OF_FILE",
                             MustHaveEOFChecksum( ),
                             false );
        }
    }

    //-----------------------------------------------------------------------
    /// Beginning with version eight of the frame specification,
    /// the FrHeader structure has its own checksum.
    /// This routine is intended to be used to verify that checksum.
    //-----------------------------------------------------------------------
    void Verify::ExamineFrHeaderChecksum( chkSum_type Checksum ) const
    {
        if ( ( Checksum ) && ( Checksum != m_checksum_frheader ) )
        {
            std::ostringstream msg;

            msg << "FR_HEADER: "
                << CheckSum::FormatError( Checksum, m_checksum_frheader );

            throw VerifyException( VerifyException::CHECKSUM_ERROR,
                                   msg.str( ) );
        }
    }

    //-----------------------------------------------------------------------
    /// Store the calculated FrHeader checksum for later verification.
    /// Called during FrEndOfFile::Load() to pass the calculated checksum
    /// to the Verify object so it can be compared with the stored value.
    //-----------------------------------------------------------------------
    void Verify::SetFrHeaderChecksum( chkSum_type Checksum )
    {
        m_checksum_frheader = Checksum;
        logger_log_debug(verify_logger(), 30,
            "Verify::SetFrHeaderChecksum() - stored calculated FrHeader checksum: ",
            Checksum, " (0x", std::hex, Checksum, std::dec, ")");
    }

    void Verify::crc_file_checksum_only( IStream & Stream,
                                         fr_header_type Header )
    {
        const std::streampos here( Stream.tellg( ) );

        std::unique_ptr< CheckSum > crc( (CheckSum*)NULL );

        if ( Stream.GetCheckSumFile( ) )
        {
            crc = Stream.GetCheckSumFile( )->GetChecksum( )->Clone( );
            Stream.SetCheckSumFile( CheckSum::NONE );
        }
        else
        {
            crc.reset( new CheckSumCRC );
        }

        const INT_4U                array_size( 1024 * 1024 );
        boost::scoped_array< char > buffer[ 2 ];

        buffer[ 0 ].reset( new char[ array_size ] );
        buffer[ 1 ].reset( new char[ array_size ] );

        size_t len = 0;
        int    bi = 0;

        const FrameSpec::Object* fr_end_of_file_obj;

        {
            FrameSpec::Info* s( FrameSpec::SpecInfo( Header->GetVersion( ) ) );

            fr_end_of_file_obj =
                s->FrameObject( FrameSpec::Info::FSI_FR_END_OF_FILE );
        }
        const FrEndOfFile* fr_end_of_file =
            dynamic_cast< const FrEndOfFile* >( fr_end_of_file_obj );
        FrameSpec::size_type sizeof_fr_eof( fr_end_of_file->Bytes( Stream ) );
        FrEndOfFile::chkType_cmn_type recorded_crc_type = CheckSum::UNSET;  // CRITICAL: Initialize to UNSET, not garbage!
        FrEndOfFile::chkSum_cmn_type  recorded_crc_value = CheckSum::UNSET;

        crc->Reset( );
        ChecksumScheme( crc->GetType( ) );

        SeenFrEndOfFile( true );
        Stream.read( buffer[ bi ].get( ), array_size );
        len = Stream.gcount( );
        while ( Stream.good( ) )
        {
            ++bi;
            Stream.read( &( buffer[ bi & 0x01 ][ 0 ] ), array_size );
            len = Stream.gcount( );
            if ( len >= sizeof_fr_eof )
            {
                crc->calc( &( buffer[ ( ( bi - 1 ) & 0x01 ) ][ 0 ] ),
                           array_size );
            }
        }
        if ( bi == 0 )
        {
            //-------------------------------------------------------------------
            // The case of a single buffer is handled special to ensure
            //   that there was enough information given.
            //-------------------------------------------------------------------
            if ( len >= sizeof_fr_eof )
            {
                crc->calc( &( buffer[ bi ][ 0 ] ), len - sizeof_fr_eof );
                recorded_crc_value = fr_end_of_file->Filter(
                    Stream,
                    *crc,
                    recorded_crc_type,
                    &( buffer[ bi ][ len - sizeof_fr_eof ] ),
                    sizeof_fr_eof );
            }
            else
            {
                throw std::runtime_error( "File size is too short to have a "
                                          "proper end of file checksum" );
            }
        }
        else
        {
            if ( len >= sizeof_fr_eof )
            {
                //-----------------------------------------------------------------
                // This is the easy case were the previous buffer has already
                // been processed.
                //-----------------------------------------------------------------
                size_t offset( len - sizeof_fr_eof );
                crc->calc( &( buffer[ bi & 0x1 ][ 0 ] ), offset );
                recorded_crc_value =
                    fr_end_of_file->Filter( Stream,
                                            *crc,
                                            recorded_crc_type,
                                            &( buffer[ bi & 0x1 ][ offset ] ),
                                            sizeof_fr_eof );
            }
            else
            {
                //-----------------------------------------------------------------
                // This is the more complicated case where the eof spans the
                // two buffers
                //-----------------------------------------------------------------
                // Modify the buffer index to be the starting buffer.
                //-----------------------------------------------------------------
                bi = ( bi & 0x1 ) ? 0 : 1;
                size_t offset( ( array_size + len ) - sizeof_fr_eof );
                crc->calc( &( buffer[ bi ][ 0 ] ), offset );

                //-----------------------------------------------------------------
                // Now move into the same buffer.
                //-----------------------------------------------------------------
                std::copy( &( buffer[ bi ][ offset ] ),
                           &( buffer[ bi ][ array_size ] ),
                           &( buffer[ bi ][ 0 ] ) );
                std::copy( &( buffer[ ( bi + 1 ) & 0x1 ][ 0 ] ),
                           &buffer[ ( bi + 1 ) & 0x1 ][ len ],
                           &( buffer[ bi ][ sizeof_fr_eof - len ] ) );
                // \todo EOF Checksum
                // &buffer [ bi ][ 0 ]

                logger_log_debug(verify_logger(), 25,
                    "crc_file_checksum_only: About to call Filter() with Stream.ByteSwapping()=",
                    (Stream.ByteSwapping() ? "TRUE" : "FALSE"),
                    ", sizeof_fr_eof=", sizeof_fr_eof);

                recorded_crc_value =
                    fr_end_of_file->Filter( Stream,
                                            *crc,
                                            recorded_crc_type,
                                            &( buffer[ bi ][ 0 ] ),
                                            sizeof_fr_eof );

                logger_log_debug(verify_logger(), 25,
                    "crc_file_checksum_only: Filter() returned checksum value: ",
                    recorded_crc_value, " (0x", std::hex, recorded_crc_value, std::dec, ")");
            }
        }
        {
            if ( ( CheckSum::kind_type )( recorded_crc_type ) ==
                 CheckSum::UNSET )
            {
                recorded_crc_type = ChecksumScheme( );
            }

            std::unique_ptr< CheckSum > recorded_checksum( CheckSum::Create(
                (CheckSum::kind_type)recorded_crc_type, recorded_crc_value ) );

            logger_log_debug(verify_logger(), 20,
                "crc_file_checksum_only: Checksum comparison - ",
                "recorded (from FrEndOfFile): ", recorded_crc_value, " (0x", std::hex, recorded_crc_value, std::dec, "), ",
                "calculated (from CRC filter): ", (crc ? crc->value() : 0), " (0x", std::hex, (crc ? crc->value() : 0), std::dec, ")");

            verify_checksum( recorded_checksum.get( ),
                             crc.get( ),
                             "FR_END_OF_FILE",
                             MustHaveEOFChecksum( ),
                             false );
        }
        // Rewind stream back to were the checksum was started
        Stream.seekg( here );
    }

    void Verify::is_unique( int ClassId, const std::string& Name )
    {
        if ( name_cache[ Name ].find( ClassId ) != name_cache[ Name ].end( ) )
        {
            //-------------------------------------------------------------------
            // Already exists so need to throw Exception
            //-------------------------------------------------------------------
            std::ostringstream msg;

            msg << "A channel named: " << Name << " already exists.";
            throw VerifyException( VerifyException::DUPLICATE_CHANNEL_NAME,
                                   msg.str( ) );
        }
        name_cache[ Name ].insert( ClassId );
    }

    void Verify::verify_checksum( CheckSum * ExpectedCRC,
                                  CheckSum * CalculatedCRC,
                                  const std::string& FrStructure,
                                  bool               MustBeNonZero,
                                  bool               InFrame ) const
    {
        if ( ( ( ExpectedCRC == (const CheckSum*)NULL ) ||
               ( CalculatedCRC == (const CheckSum*)NULL ) ) &&
             ( MustBeNonZero ) )

        {
            throw VerifyException( VerifyException::NO_CHECKSUM );
        }
        if ( ExpectedCRC && CalculatedCRC )
        {
            if ( ( !MustBeNonZero ) &&
                 ( ( ExpectedCRC->GetType( ) == CheckSum::NONE ) ||
                   ( ExpectedCRC->value( ) == 0 ) ) )
            {
                //---------------------------------------------------------
                // CRC not calculated for stream, and it is not required
                //---------------------------------------------------------
                return;
            }
            if ( MustBeNonZero &&
                 ( ( ExpectedCRC->GetType( ) == CheckSum::NONE ) ||
                   ( ExpectedCRC->value( ) == 0 ) ) )
            {
                throw VerifyException( VerifyException::NO_CHECKSUM );
            }
            if ( ExpectedCRC->GetType( ) == CalculatedCRC->GetType( ) )
            {
                if ( ( ( ExpectedCRC->value( ) == 0 ) ||
                       ( CalculatedCRC->value( ) == 0 ) ) &&
                     ( MustBeNonZero ) )
                {
                    throw VerifyException( VerifyException::NO_CHECKSUM );
                }
                // Record this comparison for verbose-mode reporting
                // (issue #151). Done before the mismatch check so a thrown
                // VerifyException leaves the log entry in place for the
                // caller to dump alongside the error.
                checksum_info_container.push_back(
                    { FrStructure,
                      static_cast< int >( ExpectedCRC->GetType( ) ),
                      static_cast< chkSum_type >( ExpectedCRC->value( ) ),
                      static_cast< chkSum_type >( CalculatedCRC->value( ) ),
                      ( ExpectedCRC->value( ) == CalculatedCRC->value( ) ) } );
                if ( ExpectedCRC->value( ) != CalculatedCRC->value( ) )
                {
                    std::ostringstream msg;
                    msg << FrStructure << ": "
                        << CheckSum::FormatError( ExpectedCRC->value( ),
                                                  CalculatedCRC->value( ) );
                    if ( ( InFrame ) && ( frame_offset != INT_4U( ~0 ) ) )
                    {
                        msg << " in frame: " << frame_offset;
                    }
                    throw VerifyException( VerifyException::CHECKSUM_ERROR,
                                           msg.str( ) );
                }
            }
            else
            {
                throw VerifyException(
                    VerifyException::UNSUPPORTED_CHECKSUM_TYPE );
            }
        }
    }

    // Magic number validation logging functions
    void Verify::logMagicNumberEntry(int bytes, bool strict, const unsigned char* Source) const
    {
        auto* magic_logger = verify_magic_logger();
        
        // Check for NULL pointer before dereferencing
        if (!Source) {
            logger_log_debug(magic_logger, 30, "ExamineMagicNumber<", bytes, ">: strict=", strict, 
                " Source=NULL (potential error condition)");
            return;
        }
        
        switch(bytes) {
            case 2:
                logger_log_debug(magic_logger, 30, "ExamineMagicNumber<2>: strict=", strict, 
                    " Source[0]=0x", std::hex, (int)Source[0], " Source[1]=0x", (int)Source[1], std::dec);
                if (strict) {
                    logger_log_debug(magic_logger, 40, "Checking 2-byte magic number: expected=0x1234");
                } else {
                    logger_log_debug(magic_logger, 30, "Skipping 2-byte magic number check (strict=false)");
                }
                break;
            case 4:
                logger_log_debug(magic_logger, 30, "ExamineMagicNumber<4>: strict=", strict, 
                    " Source[0-3]=0x", std::hex, (int)Source[0], (int)Source[1], (int)Source[2], (int)Source[3], std::dec);
                if (strict) {
                    logger_log_debug(magic_logger, 40, "Checking 4-byte magic number: expected=0x12345678");
                } else {
                    logger_log_debug(magic_logger, 30, "Skipping 4-byte magic number check (strict=false)");
                }
                break;
            case 8:
                logger_log_debug(magic_logger, 30, "ExamineMagicNumber<8>: strict=", strict);
                logger_log_debug(magic_logger, 30, "Source bytes (hex): 0x", 
                    std::hex, (int)Source[0], " ", (int)Source[1], " ", (int)Source[2], " ", (int)Source[3], " ",
                    (int)Source[4], " ", (int)Source[5], " ", (int)Source[6], " ", (int)Source[7], std::dec);
                if (strict) {
                    logger_log_debug(magic_logger, 40, "Checking 8-byte magic number: expected=0x123456789abcdef");
                } else {
                    logger_log_debug(magic_logger, 30, "Skipping 8-byte magic number check (strict=false)");
                }
                break;
        }
    }

    void Verify::logMagicNumberSuccess(int bytes) const
    {
        auto* magic_logger = verify_magic_logger();
        logger_log_debug(magic_logger, 40, bytes, "-byte magic number validation PASSED");
    }

    void Verify::logMagicNumberFailure(int bytes, const unsigned char* Source) const
    {
        auto* magic_logger = verify_magic_logger();
        
        // Check for NULL pointer before dereferencing
        if (!Source) {
            logger_log_debug(magic_logger, 10, bytes, "-byte magic number validation FAILED: Source=NULL");
            return;
        }
        
        switch(bytes) {
            case 2:
                logger_log_debug(magic_logger, 10, "2-byte magic number validation FAILED: expected=0x1234 actual=0x", 
                    std::hex, (int)Source[0], (int)Source[1], std::dec);
                break;
            case 4:
                logger_log_debug(magic_logger, 10, "4-byte magic number validation FAILED: expected=0x12345678 actual=0x", 
                    std::hex, (int)Source[0], (int)Source[1], (int)Source[2], (int)Source[3], std::dec);
                break;
            case 8:
                logger_log_debug(magic_logger, 10, "8-byte magic number validation FAILED");
                logger_log_debug(magic_logger, 10, "Expected: 0x123456789abcdef");
                logger_log_debug(magic_logger, 10, "Actual bytes: 0x", 
                    std::hex, (int)Source[0], (int)Source[1], (int)Source[2], (int)Source[3],
                    (int)Source[4], (int)Source[5], (int)Source[6], (int)Source[7], std::dec);
                break;
        }
    }
