//
// 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 <assert.h>

#include <framecpp_config.h>

#include <boost/pointer_cast.hpp>

#include "ldastoolsal/fstream.hh"
#include "ldastoolsal/MemChecker.hh"
#include "ldastoolsal/Logger.hh"

#include "framecpp/Common/IOStream.hh"
#include "framecpp/Common/FrHeader.hh"
#include "framecpp/Common/Dictionary.hh"
#include "framecpp/Common/PTR_STRUCT.hh"

using LDASTools::AL::MemChecker;

#if LM_DEBUG_OUTPUT || LM_DEBUG_INPUT
bool filter_on = true;
#endif /* LM_DEBUG_OUTPUT || LM_DEBUG_INPUT */

namespace {
    /// @brief Get logger for IOStream debugging
    inline SimpleLogger* logger() {
        static SimpleLogger* instance = get_channel_logger("framecpp.iostream");
        return instance;
    }
}

namespace FrameCPP
{
    namespace Common
    {
        //===================================================================
        // Resolver logger
        //===================================================================
        SimpleLogger* get_resolver_logger()
        {
            static SimpleLogger* instance = get_channel_logger("framecpp.resolver");
            return instance;
        }

        //===================================================================
        // StreamBase
        //===================================================================

        StreamBase::cntocid_mapping_type StreamBase::m_class_name_to_class_id;

        const bool StreamBase::m_initialized = StreamBase::initialize( );

        StreamBase::StreamBase( ) : m_ptr_struct_bytes( 0 )
        {
            // Frame specifications are initialized via direct static initialization
            // in each Version*/FrameSpec.cc file - no registry needed

            m_stream_id_to_fsi_id[ FrameSpec::Info::FSI_FR_SH ] =
                FrameSpec::Info::FSI_FR_SH;
            m_stream_id_to_fsi_id[ FrameSpec::Info::FSI_FR_SE ] =
                FrameSpec::Info::FSI_FR_SE;
        }

        StreamBase::~StreamBase( )
        {
        }

        INT_2U
        StreamBase::GetClassId( const std::string& ClassName ) const
        {
            cntocid_mapping_type::const_iterator retval =
                m_class_name_to_class_id.find( ClassName );

            if ( retval != m_class_name_to_class_id.end( ) )
            {
                logger_log_debug( logger(), 45, "GetClassId('", ClassName,
                                 "') found FSI ID ", retval->second );
                return retval->second;
            }
            logger_log_debug( logger(), 35, "GetClassId('", ClassName,
                             "') NOT FOUND (returning FSI_FR_NULL)" );
            return FrameSpec::Info::FSI_FR_NULL;
        }

        StreamBase::const_stream_ref_ptr_type
        StreamBase::ReferenceStreamRef( ) const
        {
            return dynamic_cast< const_stream_ref_ptr_type >(
                frameSpecInfo( ).FrameObject(
                    FrameSpec::Info::FSI_COMMON_ELEMENTS ) );
        }

        void
        StreamBase::cleanup_at_exit( )
        {
            m_class_name_to_class_id.erase( m_class_name_to_class_id.begin( ),
                                            m_class_name_to_class_id.end( ) );
        }

        bool
        StreamBase::initialize( )
        {
            //-----------------------------------------------------------------
            // Initialize mapping table
            //-----------------------------------------------------------------
            m_class_name_to_class_id[ "FrSH" ] = FrameSpec::Info::FSI_FR_SH;
            m_class_name_to_class_id[ "FrSE" ] = FrameSpec::Info::FSI_FR_SE;
            m_class_name_to_class_id[ "FrameH" ] = FrameSpec::Info::FSI_FRAME_H;
            m_class_name_to_class_id[ "FrAdcData" ] =
                FrameSpec::Info::FSI_FR_ADC_DATA;
            m_class_name_to_class_id[ "FrDetector" ] =
                FrameSpec::Info::FSI_FR_DETECTOR;
            m_class_name_to_class_id[ "FrEndOfFile" ] =
                FrameSpec::Info::FSI_FR_END_OF_FILE;
            m_class_name_to_class_id[ "FrEndOfFrame" ] =
                FrameSpec::Info::FSI_FR_END_OF_FRAME;
            m_class_name_to_class_id[ "FrEvent" ] =
                FrameSpec::Info::FSI_FR_EVENT;
            m_class_name_to_class_id[ "FrHistory" ] =
                FrameSpec::Info::FSI_FR_HISTORY;
            m_class_name_to_class_id[ "FrMsg" ] = FrameSpec::Info::FSI_FR_MSG;
            m_class_name_to_class_id[ "FrProcData" ] =
                FrameSpec::Info::FSI_FR_PROC_DATA;
            m_class_name_to_class_id[ "FrRawData" ] =
                FrameSpec::Info::FSI_FR_RAW_DATA;
            m_class_name_to_class_id[ "FrSerData" ] =
                FrameSpec::Info::FSI_FR_SER_DATA;
            m_class_name_to_class_id[ "FrSimData" ] =
                FrameSpec::Info::FSI_FR_SIM_DATA;
            m_class_name_to_class_id[ "FrSimEvent" ] =
                FrameSpec::Info::FSI_FR_SIM_EVENT;
            m_class_name_to_class_id[ "FrStatData" ] =
                FrameSpec::Info::FSI_FR_STAT_DATA;
            m_class_name_to_class_id[ "FrSummary" ] =
                FrameSpec::Info::FSI_FR_SUMMARY;
            m_class_name_to_class_id[ "FrTable" ] =
                FrameSpec::Info::FSI_FR_TABLE;
            m_class_name_to_class_id[ "FrTOC" ] = FrameSpec::Info::FSI_FR_TOC;
            m_class_name_to_class_id[ "FrVect" ] = FrameSpec::Info::FSI_FR_VECT;
            //-----------------------------------------------------------------
            // Aliases for earlier versions of the frame specification
            //-----------------------------------------------------------------
            m_class_name_to_class_id[ "FrTrigData" ] =
                FrameSpec::Info::FSI_FR_EVENT;

            //-----------------------------------------------------------------
            // Release resources
            //-----------------------------------------------------------------
            MemChecker::Append( StreamBase::cleanup_at_exit,
                                "FrameCPP::Common::StreamBase::cleanup_at_exit",
                                0 );
            return true;
        }

        //===================================================================
        // class IStream::resolver
        //===================================================================

        IStream::resolver::resolver( ptr_struct_base_type PtrStruct,
                                     const char*          ObjectName )
            : m_ptr_struct( PtrStruct ), m_object_name( ObjectName )
        {
        }

        IStream::resolver::~resolver( )
        {
        }

        //===================================================================
        // class IStream
        //===================================================================

        void
        IStream::Cleanup( )
        {
            GetDictionary( ).Clear( );
            m_objects.clear( );
            m_resolver_container.erase( m_resolver_container.begin( ),
                                        m_resolver_container.end( ) );
            m_next_container.clear( );
            StreamBase::Cleanup( );
        }

        const FrHeader&
        IStream::GetFrHeader( ) const
        {
            throw std::logic_error( "GetFrHeader call not valid for IStream" );
        }

        IStream::ptr_struct_base_type
        IStream::Next( object_type Obj )
        {
            //-----------------------------------------------------------------
            // Read the PTR_STRUCT from the stream
            //-----------------------------------------------------------------
            ptr_struct_base_type next( ReadPtrStruct( ) );
            if ( m_logNextPtr )
            {
                //---------------------------------------------------------------
                // Catalog the information
                //---------------------------------------------------------------
                m_next_container[ Obj ] = Dictionary::ptr_struct_key_type(
                    next->Class( ), next->Instance( ) );
            }
            return next;
        }

        IStream::ptr_struct_base_type
        IStream::Next( object_type::element_type* Obj )
        {
            //-----------------------------------------------------------------
            // Read the PTR_STRUCT from the stream
            //-----------------------------------------------------------------
            object_type obj( objectLookup( Obj ) );

            ptr_struct_base_type next( ReadPtrStruct( ) );
            if ( m_logNextPtr )
            {
                //---------------------------------------------------------------
                // Catalog the information
                //---------------------------------------------------------------
                m_next_container[ obj ] = Dictionary::ptr_struct_key_type(
                    next->Class( ), next->Instance( ) );
            }
            return next;
        }

        //-------------------------------------------------------------------
        /// Reads the next group of bytes from the stream as a PTR_STRUCT
        ///   as defined by the active frame specification associated with
        ///   the stream.
        //-------------------------------------------------------------------
        IStream::ptr_struct_base_type
        IStream::ReadPtrStruct( )
        {
            ptr_struct_base_type retval;

            retval.reset( dynamic_cast< PTR_STRUCT_BASE* >(
                m_ptr_struct_object->Create( *this ) ) );

            //-----------------------------------------------------------------
            // Debug: Show what was actually read from file
            //-----------------------------------------------------------------
            if ( retval )
            {
                // retval->Class() already returns the FSI ID (internal class identifier)
                logger_log_debug( logger(), 8, "Read PtrStruct with FSI ID ", retval->Class() );
            }

            return retval;
        }

        /// Method to resolve links
        void
        IStream::Resolve( )
        {
            for ( resolver_container_type::reverse_iterator
                      cur = m_resolver_container.rbegin( ),
                      last = m_resolver_container.rend( );
                  cur != last;
                  ++cur )
            {
                ( *cur )->Resolve( *this );
            }

            m_resolver_container.erase( m_resolver_container.begin( ),
                                        m_resolver_container.end( ) );
        }

        void
        IStream::pushResolver( resolver_type Resolver )
        {
            m_resolver_container.push_front( Resolver );
        }

        //===================================================================
        //
        //===================================================================
        INT_4U
        OStream::GetTOCChecksum( ) const
        {
            return 0;
        }

        //===================================================================
        //
        //===================================================================
        INT_8U
        OStream::GetTOCOffset( ) const
        {
            return 0;
        }

        //===================================================================
        //
        //===================================================================
        const FrHeader*
        OStream::GetFrHeader( ) const
        {
            return dynamic_cast< const FrHeader* >( m_file_header.get( ) );
        }

        void
        OStream::Next( object_type Obj, object_type NextObj )
        {
            m_dictionary.Next( Obj, NextObj );
        }

        OStream::object_type
        OStream::Next( object_type Obj ) const
        {
            return m_dictionary.Next( Obj );
        }

        void
        StreamBase::Cleanup( )
        {
            m_dictionary.Clear( );
        }

        void
        StreamBase::frameSpecInfo( const FrameSpec::Info* Spec )
        {
            if ( Spec == (const FrameSpec::Info*)NULL )
            {
                throw std::invalid_argument( "FrameCPP::Common::frameSpecInfo:"
                                             " NULL pointer passed for"
                                             " frame specification" );
            }
            m_frame_spec = Spec;
            m_null_stream_ref.reset(
                frameSpecInfo( )
                    .FrameObject( FrameSpec::Info::FSI_COMMON_ELEMENTS )
                    ->Clone( ) );
            const PTR_STRUCT_BASE* ptr_struct =
                dynamic_cast< const PTR_STRUCT_BASE* >(
                    frameSpecInfo( ).FrameObject(
                        FrameSpec::Info::FSI_PTR_STRUCT ) );
            if ( ptr_struct )
            {
                m_ptr_struct_object.reset(
                    dynamic_cast< PTR_STRUCT_BASE* >( ptr_struct->Clone( ) ) );
            }
            else
            {
                std::ostringstream msg;

                msg << "FATAL: No PTR_STRUCT class defined for version "
                    << frameSpecInfo( ).Version( ) << ":"
                    << frameSpecInfo( ).VersionMinor( )
                    << " of the frame spec.";
                ;
                throw std::runtime_error( msg.str( ) );
            }
        }

        const PTR_STRUCT_BASE&
        OStream::NextPtrStruct( object_type Cur ) const
        {
            object_type next( Next( Cur ) );
            return Reference( next );
        }

        const PTR_STRUCT_BASE&
        OStream::NextPtrStruct( const object_type::element_type* Cur ) const
        {
            object_type next( m_dictionary.Next( Cur ) );
            return Reference( next );
        }

        const PTR_STRUCT_BASE&
        OStream::Reference( object_type Obj ) const
        {
            LDASTools::AL::GPSTime start, now;

            //-----------------------------------------------------------------
            // Retrieve the stream reference
            //-----------------------------------------------------------------
            streamref_type sr;

            if ( Obj )
            {
                sr = m_dictionary.Ref( Obj );
            }
            if ( sr )
            {
                m_ptr_struct_object->Class( sr->GetStreamClass( ) );
                m_ptr_struct_object->Instance( sr->GetInstance( ) );
            }
            else
            {
                streamref_type null_ref =
                    boost::dynamic_pointer_cast< streamref_type::element_type >(
                        m_null_stream_ref );

                m_ptr_struct_object->Class( null_ref->GetStreamClass( ) );
                m_ptr_struct_object->Instance( null_ref->GetInstance( ) );
            }
            //-----------------------------------------------------------------
            // Create a PTR_STRUCT
            //-----------------------------------------------------------------
            return *m_ptr_struct_object;
        }

        //-------------------------------------------------------------------
        // IStream::SuspendFileChecksum
        //-------------------------------------------------------------------
        ChecksumState
        IStream::SuspendFileChecksum( )
        {
            CheckSumFilter* filter = GetCheckSumFile( );
            if ( filter )
            {
                // Capture the filter type before removing it
                CheckSum::kind_type filter_type = filter->Type( );
                CheckSum::self_ptr_type saved_state = filter->SaveState( );
                SetCheckSumFile( CheckSum::NONE );

                // Return state with filter information
                return ChecksumState( std::move( saved_state ), filter_type, true );
            }

            // No filter was active
            return ChecksumState( );
        }

        //-------------------------------------------------------------------
        // IStream::ResumeFileChecksum
        //-------------------------------------------------------------------
        void
        IStream::ResumeFileChecksum( ChecksumState saved )
        {
            // Only restore if a filter was actually active when suspended
            if ( saved.had_filter )
            {
                // Restore the filter with the correct type
                SetCheckSumFile( saved.type );
                CheckSumFilter* filter = GetCheckSumFile( );
                if ( filter )
                {
                    // Transfer ownership of the saved state
                    filter->RestoreState( std::move( saved.state ) );
                }
            }
        }
    } // namespace Common
} // namespace FrameCPP
