//
// LDASTools frameCPP - A library implementing the LIGO/Virgo frame
// specification
//
// Copyright (C) 2018-2020 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 <boost/shared_ptr.hpp>

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

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

#include "framecpp/Universal/FrSerData.hh"
#include "framecpp/Universal/FrSE.hh"
#include "framecpp/Universal/FrSH.hh"
#include "framecpp/Universal/PTR_STRUCT.hh"


using FrameCPP::Common::Description;
using FrameCPP::Common::FrameSpec;

//=======================================================================
// Static
//=======================================================================
//-----------------------------------------------------------------------
// Local functions and variables
//-----------------------------------------------------------------------

FR_OBJECT_META_DATA_DEFINE_UNIVERSAL( FrSerDataImpl,
                                      FSI_FR_SER_DATA,
                                      "FrSerData",
                                      "Serial Data Structure" )

//=======================================================================
// FrSerData
//=======================================================================

namespace FrameCPP
{
    namespace Universal
    {
        const FrSerData::name_type&
        FrSerData::GetNameSlow( ) const
        {
            return GetName( );
        }

        namespace FrSerDataImpl
        {
        } // namespace FrSerDataImpl

        FrSerData::FrSerData( const Data::name_type&      Name,
                              const Data::time_type&      Time,
                              const Data::sampleRate_type SampleRate )
        {
            Data::name = Name;
            Data::time = Time;
            Data::sampleRate = SampleRate;
        }

        FrSerData&
        FrSerData::Merge( const FrSerData& RHS )
        {
            /// \todo
            ///   Need to implement Merge routine
            std::string msg( "Merge currently not implemented for " );
            msg += StructName( );

            throw std::domain_error( msg );
            return *this;
        }

        bool
        FrSerData::operator==( const Common::FrameSpec::Object& Obj ) const
        {
            return FrameCPP::compare( *this, Obj );
        }

    } // namespace Universal

} // namespace FrameCPP

//=======================================================================
//               C L A S S I C   I O
//=======================================================================
namespace FrameCPP
{
    namespace Universal
    {
        namespace FrSerDataImpl
        {

            template <>
            const Common::Description*
            ClassicIO< FrSerData >::GetDescription( unsigned int frame_stream_version ) const
            {
                using Common::Description;
                using Universal::PTR_STRUCT;

                Description* description{ nullptr };


                if ( frame_stream_version >= 8 )
                {
                    static Description v8_description;

                    if ( v8_description.size( ) == 0 )
                    {
                        v8_description( FrSH( Data::FrameSpecName( ),
                                              Data::FrameSpecId( ),
                                              Data::FrameSpecDescription( ) ) );
                        v8_description(
                            FrSE( "name",
                                  Description::Ascii< io_name_type >( ),
                                  "Name of station producing serial data "
                                  "stream" ) );
                        v8_description(
                            FrSE( "timeSec",
                                  Description::Ascii< io_timeSec_type >( ),
                                  "Time of data acquisition" ) );
                        v8_description(
                            FrSE( "timeNsec",
                                  Description::Ascii< io_timeNsec_type >( ),
                                  "Frame start time residual" ) );
                        v8_description(
                            FrSE( "sampleRate",
                                  Description::Ascii< v8::sampleRate_type >( ),
                                  "Sample rate, samples/s" ) );
                        v8_description(
                            FrSE( "data",
                                  Description::Ascii< io_data_type >( ),
                                  "Pointer to string for ASCII-based data" ) );

                        v8_description(
                            FrSE( "serial",
                                  PTR_STRUCT::Desc(
                                      Universal::FrVect::StructName( ) ),
                                  "Identifier for serial data vector" ) );
                        v8_description(
                            FrSE( "table",
                                  PTR_STRUCT::Desc( FrTable::StructName( ) ),
                                  "Identifier for user-defined table "
                                  "structure" ) );

                        v8_description( FrSE(
                            "next", PTR_STRUCT::Desc( Data::StructName( ) ) ) );

                        v8_description( FrSE( "chkSum",
                                              CheckSumDataClass( ),
                                              CheckSumDataComment( ) ) );
                    }
                    description = &v8_description;
                }
                else if ( frame_stream_version >= 7 )
                {
                    static Description v7_description;

                    if ( v7_description.size( ) == 0 )
                    {
                        v7_description( FrSH( Data::FrameSpecName( ),
                                              Data::FrameSpecId( ),
                                              Data::FrameSpecDescription( ) ) );
                        v7_description(
                            FrSE( "name",
                                  Description::Ascii< io_name_type >( ),
                                  "Name of station producing serial data "
                                  "stream" ) );
                        v7_description(
                            FrSE( "timeSec",
                                  Description::Ascii< io_timeSec_type >( ),
                                  "Time of data acquisition" ) );
                        v7_description(
                            FrSE( "timeNsec",
                                  Description::Ascii< io_timeNsec_type >( ),
                                  "Frame start time residual" ) );
                        v7_description(
                            FrSE( "sampleRate",
                                  Description::Ascii< v7::sampleRate_type >( ),
                                  "Sample rate, samples/s" ) );
                        v7_description(
                            FrSE( "data",
                                  Description::Ascii< io_data_type >( ),
                                  "Pointer to string for ASCII-based data" ) );

                        v7_description(
                            FrSE( "serial",
                                  PTR_STRUCT::Desc(
                                      Universal::FrVect::StructName( ) ),
                                  "Identifier for serial data vector" ) );
                        v7_description(
                            FrSE( "table",
                                  PTR_STRUCT::Desc( FrTable::StructName( ) ),
                                  "Identifier for user-defined table "
                                  "structure" ) );

                        v7_description( FrSE(
                            "next", PTR_STRUCT::Desc( Data::StructName( ) ) ) );
                    }

                    description = &v7_description;
                }
                else if ( frame_stream_version >= 4 )
                {
                    static Description v4_description;

                    if ( v4_description.size( ) == 0 )
                    {
                        v4_description( FrSH( Data::FrameSpecName( ),
                                              Data::FrameSpecId( ),
                                              Data::FrameSpecDescription( ) ) );
                        v4_description(
                            FrSE( "name",
                                  Description::Ascii< io_name_type >( ),
                                  "Name of station producing serial data "
                                  "stream" ) );
                        v4_description(
                            FrSE( "timeSec",
                                  Description::Ascii< io_timeSec_type >( ),
                                  "Time of data acquisition" ) );
                        v4_description(
                            FrSE( "timeNsec",
                                  Description::Ascii< io_timeNsec_type >( ),
                                  "Frame start time residual" ) );
                        v4_description(
                            FrSE( "sampleRate",
                                  Description::Ascii< v4::sampleRate_type >( ),
                                  "Sample rate, samples/s" ) );
                        v4_description(
                            FrSE( "data",
                                  Description::Ascii< io_data_type >( ),
                                  "Pointer to string for ASCII-based data" ) );

                        v4_description(
                            FrSE( "serial",
                                  PTR_STRUCT::Desc(
                                      Universal::FrVect::StructName( ) ),
                                  "Identifier for serial data vector" ) );
                        v4_description(
                            FrSE( "table",
                                  PTR_STRUCT::Desc( FrTable::StructName( ) ),
                                  "Identifier for user-defined table "
                                  "structure" ) );

                        v4_description( FrSE(
                            "next", PTR_STRUCT::Desc( Data::StructName( ) ) ) );
                    }
                    description = &v4_description;
                }
                else if ( frame_stream_version >= 3 )
                {
                    static Description v3_description;

                    if ( v3_description.size( ) == 0 )
                    {
                        v3_description( FrSH( Data::FrameSpecName( ),
                                              Data::FrameSpecId( ),
                                              Data::FrameSpecDescription( ) ) );
                        v3_description(
                            FrSE( "name",
                                  Description::Ascii< io_name_type >( ),
                                  "Name of station producing serial data "
                                  "stream" ) );
                        v3_description(
                            FrSE( "timeSec",
                                  Description::Ascii< io_timeSec_type >( ),
                                  "Time of data acquisition" ) );
                        v3_description(
                            FrSE( "timeNsec",
                                  Description::Ascii< io_timeNsec_type >( ),
                                  "Frame start time residual" ) );
                        v3_description(
                            FrSE( "sampleRate",
                                  Description::Ascii< v3::sampleRate_type >( ),
                                  "Sample rate, samples/s" ) );
                        v3_description(
                            FrSE( "data",
                                  Description::Ascii< io_data_type >( ),
                                  "Pointer to string for ASCII-based data" ) );

                        v3_description(
                            FrSE( "serial",
                                  PTR_STRUCT::Desc(
                                      Universal::FrVect::StructName( ) ),
                                  "Identifier for serial data vector" ) );
                        v3_description(
                            FrSE( "more",
                                  PTR_STRUCT::Desc( FrVect::StructName( ) ),
                                  "Identifier for user-defined data "
                                  "structure" ) );

                        v3_description( FrSE(
                            "next", PTR_STRUCT::Desc( Data::StructName( ) ) ) );
                    }
                    description = &v3_description;
                }
                return ( description );
            }

#if WORKING_VIRTUAL_TOCQUERY
            /**
             * \brief Answer requests for information as needed by TOC
             *
             * \param[in] InfoClass
             *     Class of information being requested.
             */
            template <>
            void
            ClassicIO< FrSerData >::TOCQuery( int InfoClass, ... ) const
#else
            /**
             * \brief Answer requests for information as needed by TOC
             *
             * \param[in] InfoClass
             *     Class of information being requested.
             * \param[in] vl
             *     Variable argument list of requested data.
             */
            template <>
            void
            ClassicIO< FrSerData >::vTOCQuery( int InfoClass, va_list vl ) const
#endif /*  WORKING_VIRTUAL_TOCQUERY */
            {
                using Common::TOCInfo;

#if WORKING_VIRTUAL_TOCQUERY
                va_list vl;
                va_start( vl, InfoClass );
#endif /*  WORKING_VIRTUAL_TOCQUERY */
                while ( InfoClass != TOCInfo::IC_EOQ )
                {
                    int data_type = va_arg( vl, int );
                    switch ( data_type )
                    {
                    case TOCInfo::DT_STRING_2:
                    {
                        STRING* data = va_arg( vl, STRING* );
                        switch ( InfoClass )
                        {
                        case TOCInfo::IC_NAME:
                            *data = GetName( );
                            break;
                        default:
                            goto cleanup;
                            break;
                        }
                    }
                    break;
                    default:
                        // Stop processing
                        goto cleanup;
                    }
                    InfoClass = va_arg( vl, int );
                }
            cleanup:
#if WORKING_VIRTUAL_TOCQUERY
                va_end( vl )
#endif /*  WORKING_VIRTUAL_TOCQUERY */
                    ;
            }

            template <>
            cmn_streamsize_type
            ClassicIO< FrSerData >::pBytes(
                const Common::StreamBase& Stream ) const
            {
                auto frame_stream_version = StreamVersion( Stream );

                return ( io_name_type::Bytes( name ) +
                         sizeof( io_timeSec_type ) // timeSec
                         + sizeof( io_timeNsec_type ) // timeNsec
                         + ( ( frame_stream_version >= 7 ) // sampleRate
                                 ? sizeof( sampleRate )
                                 : sizeof( v3::sampleRate_type ) ) +
                         io_data_type::Bytes( data ) + // data
                         Stream.PtrStructBytes( ) // serial
                         + ( ( frame_stream_version >= 4 ) // table
                                 ? Stream.PtrStructBytes( )
                                 : 0 ) +
                         ( ( frame_stream_version < 4 ) // more
                               ? Stream.PtrStructBytes( )
                               : 0 ) +
                         Stream.PtrStructBytes( ) // next
                );
            }

            template <>
            ClassicIO< FrSerData >::self_type*
            ClassicIO< FrSerData >::pCreate( istream_type& Stream ) const
            {
                auto frame_stream_version = StreamVersion( Stream );

                auto* obj( new FrSerData( ) );

                io_name_type io_name;
                io_data_type io_data;

                Stream >> io_name >> obj->time;
                if ( frame_stream_version >= 7 )
                {
                    Stream >> obj->sampleRate;
                }
                else
                {
                    v3::sampleRate_type sampleRate_v3;
                    Stream >> sampleRate_v3;
                    obj->sampleRate = sampleRate_v3;
                }
                Stream >> io_data >> obj->serial;
                if ( frame_stream_version >= 4 )
                {
                    Stream >> obj->table;
                }
                else
                {
                    v3::more_type more;
                    Stream >> more;
                }

                Stream.Next( obj );

                //-------------------------------------------------------
                //-------------------------------------------------------
                obj->name = io_name;
                obj->data = io_data;
                //-------------------------------------------------------
                //-------------------------------------------------------
                return ( obj );
            }

            template <>
            void
            ClassicIO< FrSerData >::pWrite( ostream_type& Stream ) const
            {
                auto frame_stream_version = StreamVersion( Stream );

                Stream << io_name_type( name )
                       << io_timeSec_type( time.GetSeconds( ) )
                       << io_timeNsec_type( time.GetNanoseconds( ) );
                if ( frame_stream_version >= 7 )
                {
                    Stream << sampleRate;
                }
                else
                {
                    Stream << v3::sampleRate_type( sampleRate );
                }
                Stream << io_data_type( data ) << serial;
                if ( frame_stream_version >= 4 )
                {
                    Stream << table;
                }
                else
                {
                    v3::more_type more;
                    Stream << more;
                }
                WriteNext( Stream );
            }

        } // namespace FrSerDataImpl
    } // namespace Universal
} // namespace FrameCPP
