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

#include <boost/shared_ptr.hpp>

#include "framecpp/Universal/FrStatData.hh"

#include "framecpp/Common/Description.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;

//-----------------------------------------------------------------------

FR_OBJECT_META_DATA_DEFINE_UNIVERSAL( FrStatDataImpl,
                                      FSI_FR_STAT_DATA,
                                      "FrStatData",
                                      "Statistical Data Structure" )

namespace FrameCPP
{
    namespace Universal
    {
        //===============================================================
        // FrStatData
        //===============================================================

        FrStatData::FrStatData( const FrStatData& Source )
        {
            Data::name = Source.GetName( );
            Data::comment = Source.GetComment( );
            Data::representation = Source.GetRepresentation( );
            Data::timeStart = Source.GetTimeStart( );
            Data::timeEnd = Source.GetTimeEnd( );
            Data::version = Source.GetVersion( );

            //-------------------------------------------------------------------
            // Copy references
            //-------------------------------------------------------------------
            SetDetector( Source.Data::GetDetector( ) );
            RefData( ) = Source.RefData( );
            RefTable( ) = Source.RefTable( );
            updateDebugTypeName();
        }

        FrStatData::FrStatData( const name_type&           Name,
                                const comment_type&        Comment,
                                const representation_type& Representation,
                                const timeStart_type       TimeStart,
                                const timeEnd_type         TimeEnd,
                                const version_type         Version )
        {
            Data::name = Name;
            Data::comment = Comment;
            Data::representation = Representation;
            Data::timeStart = TimeStart;
            Data::timeEnd = TimeEnd;
            Data::version = Version;
            updateDebugTypeName();
        }

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

        FrStatData&
        FrStatData::Merge( const FrStatData& 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
        FrStatData::operator==( const Common::FrameSpec::Object& RHS ) const
        {
            return FrameCPP::compare( *this, RHS );
        }

    } // namespace Universal
} // namespace FrameCPP

//=======================================================================
//               C L A S S I C   I O
//=======================================================================
namespace FrameCPP
{
    namespace Universal
    {
        namespace FrStatDataImpl
        {
            using FrameCPP::Common::Description;
            using FrameCPP::Universal::FrStatData;

            //-----------------------------------------------------------------
            /// @brief The data structure description
            ///
            /// @return
            ///     The description of the data structure used to represent
            ///     this object when written to a GWF stream
            //-----------------------------------------------------------------
            template <>
            const Description*
            ClassicIO< FrStatData >::GetDescription(
                unsigned int frame_stream_version ) const
            {
                Description* description{ nullptr };


                if ( frame_stream_version >= 9 )
                {
                    //---------------------------------------------------
                    // Removal of structure from the frame spec
                    //---------------------------------------------------
                }
                else if ( frame_stream_version >= 8 )
                {
                    static Description v8_description;

                    if ( v8_description.size( ) == 0 )
                    {
                        //-----------------------------------------------
                        v8_description( FrSH( Data::StructName( ),
                                              Data::FrameSpecId( ),
                                              Data::FrameSpecDescription( ) ) );

                        v8_description(
                            FrSE( "name",
                                  Description::Ascii< io_name_type >( ),
                                  "" ) );
                        v8_description(
                            FrSE( "comment",
                                  Description::Ascii< io_comment_type >( ),
                                  "" ) );
                        v8_description( FrSE(
                            "representation",
                            Description::Ascii< io_representation_type >( ),
                            "" ) );
                        v8_description(
                            FrSE( "timeStart",
                                  Description::Ascii< timeStart_type >( ),
                                  "" ) );
                        v8_description(
                            FrSE( "timeEnd",
                                  Description::Ascii< timeEnd_type >( ),
                                  "" ) );
                        v8_description(
                            FrSE( "version",
                                  Description::Ascii< version_type >( ),
                                  "" ) );

                        v8_description( FrSE(
                            "detector",
                            PTR_STRUCT::Desc( FrDetector::StructName( ) ) ) );
                        v8_description(
                            FrSE( "data",
                                  PTR_STRUCT::Desc( FrVect::StructName( ) ) ) );
                        v8_description( FrSE(
                            "table",
                            PTR_STRUCT::Desc( FrTable::StructName( ) ) ) );

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

                    if ( v4_description.size( ) == 0 )
                    {
                        //-----------------------------------------------
                        v4_description( FrSH( Data::StructName( ),
                                              Data::FrameSpecId( ),
                                              Data::FrameSpecDescription( ) ) );

                        v4_description(
                            FrSE( "name",
                                  Description::Ascii< io_name_type >( ),
                                  "" ) );
                        v4_description(
                            FrSE( "comment",
                                  Description::Ascii< io_comment_type >( ),
                                  "" ) );
                        v4_description( FrSE(
                            "representation",
                            Description::Ascii< io_representation_type >( ),
                            "" ) );
                        v4_description(
                            FrSE( "timeStart",
                                  Description::Ascii< timeStart_type >( ),
                                  "" ) );
                        v4_description(
                            FrSE( "timeEnd",
                                  Description::Ascii< timeEnd_type >( ),
                                  "" ) );
                        v4_description(
                            FrSE( "version",
                                  Description::Ascii< version_type >( ),
                                  "" ) );

                        v4_description( FrSE(
                            "detector",
                            PTR_STRUCT::Desc( FrDetector::StructName( ) ) ) );
                        v4_description(
                            FrSE( "data",
                                  PTR_STRUCT::Desc( FrVect::StructName( ) ) ) );
                        v4_description( FrSE(
                            "table",
                            PTR_STRUCT::Desc( FrTable::StructName( ) ) ) );
                    }
                    description = &v4_description;
                }
                else if ( frame_stream_version >= 3 )
                {
                    static Description v3_description;

                    if ( v3_description.size( ) == 0 )
                    {
                        //-----------------------------------------------
                        v3_description( FrSH( Data::StructName( ),
                                              Data::FrameSpecId( ),
                                              Data::FrameSpecDescription( ) ) );

                        v3_description(
                            FrSE( "name",
                                  Description::Ascii< io_name_type >( ),
                                  "" ) );
                        v3_description(
                            FrSE( "comment",
                                  Description::Ascii< io_comment_type >( ),
                                  "" ) );
                        v3_description(
                            FrSE( "timeStart",
                                  Description::Ascii< timeStart_type >( ),
                                  "" ) );
                        v3_description(
                            FrSE( "timeEnd",
                                  Description::Ascii< timeEnd_type >( ),
                                  "" ) );
                        v3_description(
                            FrSE( "version",
                                  Description::Ascii< version_type >( ),
                                  "" ) );

                        v3_description( FrSE(
                            "detector",
                            PTR_STRUCT::Desc( FrDetector::StructName( ) ) ) );
                        v3_description(
                            FrSE( "data",
                                  PTR_STRUCT::Desc( FrVect::StructName( ) ) ) );
                    }
                    description = &v3_description;
                }

                return description;
            }

#if WORKING_VIRTUAL_TOCQUERY
            template <>
            void
            ClassicIO< FrStatData >::TOCQuery( int InfoClass, ... ) const
#else
            template <>
            void
            ClassicIO< FrStatData >::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;
                        case TOCInfo::IC_DETECTOR:
                            if ( Data::GetDetector( ) )
                            {
                                boost::shared_ptr< FrDetector > detector(
                                    boost::dynamic_pointer_cast< FrDetector >(
                                        Data::GetDetector( ) ) );

                                *data = detector->GetName( );
                            }
                            else
                            {
                                *data = "";
                            }
                            break;
                        default:
                            goto cleanup;
                            break;
                        }
                    }
                    break;
                    case TOCInfo::DT_INT_4U:
                    {
                        INT_4U* data = va_arg( vl, INT_4U* );
                        switch ( InfoClass )
                        {
                        case TOCInfo::IC_START:
                            *data = GetTimeStart( );
                            break;
                        case TOCInfo::IC_END:
                            *data = GetTimeEnd( );
                            break;
                        case TOCInfo::IC_VERSION:
                            *data = GetVersion( );
                            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< FrStatData >::pBytes(
                const Common::StreamBase& Stream ) const
            {
                auto frame_stream_version = StreamVersion( Stream );

                return ( io_name_type::Bytes( name ) +
                         io_comment_type::Bytes( comment ) +
                         ( ( frame_stream_version >= 4 )
                               ? io_representation_type::Bytes( representation )
                               : 0 ) +
                         sizeof( timeStart ) + sizeof( timeEnd ) +
                         sizeof( version ) //
                         + Stream.PtrStructBytes( ) // detector
                         + Stream.PtrStructBytes( ) // data
                         + ( ( frame_stream_version >= 4 )
                                 ? Stream.PtrStructBytes( ) // table
                                 : 0 ) );
            }

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

                if ( frame_stream_version >= 9 )
                {
                    return nullptr;
                }
                Universal::FrStatData* obj( new Universal::FrStatData( ) );

                io_name_type           io_name;
                io_comment_type        io_comment;
                io_representation_type io_representation;

                Stream //
                    >> io_name //
                    >> io_comment //
                    ;
                if ( frame_stream_version >= 4 )
                {
                    Stream >> io_representation //
                        ;
                }
                Stream >> obj->timeStart //
                    >> obj->timeEnd //
                    >> obj->version //
                    ;

                Stream //
                    >> &( obj->detector ) //
                    >> obj->data //
                    ;
                if ( frame_stream_version >= 4 )
                {
                    Stream >> obj->table //
                        ;
                }

                obj->name = io_name;
                obj->comment = io_comment;
                obj->representation = io_representation;

                return ( obj );
            }

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

                if ( frame_stream_version >= 9 )
                {
                    // Removed in version 9 of frame spec
                    return;
                }
                Stream //
                    << io_name_type( name ) //
                    << io_comment_type( comment ) //
                    ;
                if ( frame_stream_version >= 4 )
                {
                    Stream << io_representation_type( representation ) //
                        ;
                }
                Stream << timeStart //
                       << timeEnd //
                       << version //
                    ;

                Stream //
                    << ostream_type::Pointer( detector ) //
                    << data //
                    ;
                if ( frame_stream_version >= 4 )
                {
                    Stream << table //
                        ;
                }
            }

        } // namespace FrStatDataImpl
    } // namespace Universal
} // namespace FrameCPP
