//
// LDASTools frameCPP - A library implementing the LIGO/Virgo frame
// specification
//
// Copyright (C) 2019 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
//

#ifndef FrameCPP_UNIVERSAL__IMPL__FrEndOfFrameClassicIO_HH
#define FrameCPP_UNIVERSAL__IMPL__FrEndOfFrameClassicIO_HH

#ifndef SWIGIMPORTED
#include <string>
#include <vector>
#endif /* SWIGIMPORTED */

#if !defined( SWIGIMPORTED )
#include "ldastoolsal/types.hh"

#include "framecpp/Common/CheckSumFilter.hh"
#include "framecpp/Common/FrameStream.hh"

#include "framecpp/Common/Description.hh"
#include "framecpp/Common/FrameSpec.hh"
#include "framecpp/Common/FrameSpec.tcc"

#endif /* !defined( SWIGIMPORTED ) */

#include "framecpp/Universal/impl/FrEndOfFrameData.hh"

#if !defined( SWIGIMPORTED )
namespace FrameCPP
{
    namespace Universal
    {
        class FrEndOfFrame;

        namespace FrEndOfFrameImpl
        {
            template < typename T >
            class ClassicIO
                : public virtual Data,
                  public ObjectWithChecksum< Common::FrameSpec::Object >
            {
            public:
                // Bring base class GetDescription methods into scope
                using FrameCPP::Common::FrameSpec::Object::GetDescription;

                using ObjectWithChecksum< Common::FrameSpec::Object >::Bytes;

                //-------------------------------------------------------
                //
                //-------------------------------------------------------
                ClassicIO( )
                    : ObjectWithChecksum< Common::FrameSpec::Object >(
                          FrameSpecId( ), nullptr )
                {
                }

                //-------------------------------------------------------
                /// \brief The name structure name of this object.
                ///
                /// \return
                ///     The name of the structure as specified by the frame
                ///     specification.
                //-------------------------------------------------------
                virtual const char*
                ObjectStructName( ) const
                {
                    return ( FrameSpecName( ) );
                }

                virtual const Common::Description*
                GetDescription( unsigned int version ) const;

            protected:
                //-----------------------------------------------------------------
                /// \brief Number of bytes needed to write this structure
                ///
                /// \param[in] Stream
                ///     The stream from which to the object is being read or
                ///     written.
                ///
                /// \return
                ///     The number of bytes need to read or write this object.
                //-----------------------------------------------------------------
                virtual cmn_streamsize_type
                pBytes( const Common::StreamBase& Stream ) const
                {
                    auto frame_stream_version = StreamVersion( Stream );

                    return ( sizeof( run ) // run
                             + sizeof( frame ) // frame
                             + ( // GTimeS
                                 // GTimeN
                                   ( frame_stream_version >= 8 )
                                       ? ( sizeof( GTimeS_type ) +
                                           sizeof( GTimeN_type ) )
                                       : 0 ) //
                             + ( // chkType
                                 // chkSum
                                   ( ( frame_stream_version >= 5 ) &&
                                     ( frame_stream_version < 8 ) )
                                       ? ( sizeof( v5::chkType_type ) +
                                           sizeof( v5::chkSum_type ) )
                                       : 0 ) //
                    );
                }

                //-----------------------------------------------------------------
                /// \brief Virtual constructor.
                ///
                /// \param[in] Stream
                ///     The input stream from which to read the object.
                ///
                /// \return
                ///    A new instance of this object.
                //-----------------------------------------------------------------
                virtual self_type*
                pCreate( istream_type& Stream ) const
                {
                    auto frame_stream_version = StreamVersion( Stream );

                    T* obj( new T( ) );

                    if ( frame_stream_version >= 3 )
                    {
                        Stream >> obj->run >> obj->frame;
                    }
                    if ( ( frame_stream_version >= 5 ) &&
                         ( frame_stream_version < 8 ) )
                    {
                        using FrameCPP::Common::CheckSum;
                        using FrameCPP::Common::CheckSumFilter;

                        v5::chkType_type chkType;
                        v5::chkSum_type  chkSum;

                        Stream >> chkType;
                        //---------------------------------------------------------------------
                        // Check for checksum calculations
                        //---------------------------------------------------------------------
                        CheckSumFilter* crc = Stream.GetCheckSumFrame( );

                        if ( crc )
                        {
                            static const chkSum_type chkSumZero = 0;

                            crc->Filter( &chkSumZero,
                                         sizeof( chkSumZero ) ); // Use 0 value
                            crc->Value( ); // Stop processing
                        }
                        Stream >> chkSum;
                    }
                    if ( frame_stream_version >= 8 )
                    {
                        Stream >> obj->GTime;
                    }

                    return ( obj );
                }

                //-----------------------------------------------------------------
                /// \brief Write the structure to the stream
                ///
                /// \param[in] Stream
                ///     The output stream where the object is to be written.
                //-----------------------------------------------------------------
                virtual void
                pWrite( ostream_type& Stream ) const
                {
                    auto frame_stream_version = StreamVersion( Stream );

                    //---------------------------------------------------------------------
                    // Write out to the stream
                    //---------------------------------------------------------------------
                    Stream << run << frame;
                    if ( frame_stream_version >= 8 )
                    {
                        Stream << GTimeS_type( GTime.GetSeconds( ) )
                               << GTimeN_type( GTime.GetNanoseconds( ) );
                    }
                    if ( ( frame_stream_version >= 5 ) &&
                         ( frame_stream_version < 8 ) )
                    {
                        using FrameCPP::Common::CheckSum;
                        using FrameCPP::Common::CheckSumFilter;

                        v5::chkType_type chkType = CheckSum::NONE;
                        v5::chkSum_type  chkSum = 0;

                        CheckSumFilter* crc = Stream.GetCheckSumFrame( );

                        if ( crc )
                        {
                            chkType = crc->Type( );

                            // NOTE: run and frame are already checksummed via Stream << above
                            // Only manually filter chkType and chkSum to match read path behavior
                            crc->Filter( &chkType, sizeof( chkType ) );
                            crc->Filter( &chkSum, sizeof( chkSum ) );

                            chkSum = crc->Value( );

                            Stream.SetCheckSumFrame( CheckSum::NONE );
                        }

                        Stream << chkType << chkSum;
                    }
                }

            private:
                //-----------------------------------------------------------------
                //-----------------------------------------------------------------
                typedef ObjectWithChecksum< Common::FrameSpec::Object >
                    object_type;

                using Common::FrameSpec::Object::Create;

            }; // Class - FrEndOfFrame

        } // namespace FrEndOfFrameImpl
    } // namespace Universal
} // namespace FrameCPP
#endif /* ! defined(SWIGIMPORTED) */

#endif /* FrameCPP_UNIVERSAL__IMPL__FrEndOfFrameClassicIO_HH */
