//
// 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__DimensionClassicIO_HH
#define FrameCPP_UNIVERSAL__IMPL__DimensionClassicIO_HH

#include <vector>

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

#if !defined( SWIGIMPORTED )

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

namespace FrameCPP
{
    namespace Universal
    {
        class Dimension;

        namespace DimensionImpl
        {
            template < typename T >
            class ClassicIO
                : public virtual Data,
                  public ObjectWithChecksum< Common::FrameSpec::Object >
            {
            public:
                ClassicIO( )
                    : ObjectWithChecksum< Common::FrameSpec::Object >(
                          FrameSpecId( ), nullptr )
                {
                }

                ClassicIO( const ClassicIO& ) = default;
                ClassicIO( ClassicIO&& ) = default;
                ClassicIO& operator=( const ClassicIO& ) = delete;
                ClassicIO& operator=( ClassicIO&& ) = delete;

                virtual const char*
                ObjectStructName( ) const
                {
                    return StructName( );
                }

                static const char*
                StructName( )
                {
                    return Data::StructName( );
                }

                static INT_4U
                FrameSpecId( )
                {
                    return Data::FrameSpecId( );
                }

                Common::FrameSpec::Object*
                Clone( ) const
                {
                    return ( new T( *( reinterpret_cast< const T* >( this ) ) ) );
                }

            protected:
                cmn_streamsize_type
                pBytes( const Common::StreamBase& Stream ) const;

                virtual self_type* pCreate( istream_type& Stream ) const;

                virtual void pWrite( ostream_type& Stream ) const;
            };
            template < typename StreamType, //
                       typename NxType, //
                       typename DxType, //
                       typename StartXType, //
                       typename UnitXType >
            void
            Read( StreamType& Stream, std::vector< Data >& dimension )
            {
                using EffectiveStartXType = typename std::conditional<
                    std::is_void< StartXType >::value,
                    int,
                    StartXType >::type;

                INT_4U size = 0;

                Stream >> size;

                std::vector< NxType >              nx_elements( size );
                std::vector< DxType >              dx_elements( size );
                std::vector< EffectiveStartXType > startX_elements;
                std::vector< UnitXType >           unitX_elements( size );

                if
                    constexpr( !std::is_void< StartXType >::value )
                    {
                        startX_elements.resize( size );
                    }

                Stream >> nx_elements >> dx_elements;
                if
                    constexpr //
                        ( !std::is_same< StartXType, void >::value )
                    {
                        Stream >> startX_elements;
                    }
                Stream >> unitX_elements;

                dimension.resize( size );
                for ( size_t index = 0; index < dimension.size( ); index++ )
                {
                    dimension[ index ].SetNx( nx_elements[ index ] );
                    dimension[ index ].SetDx( dx_elements[ index ] );
                    if
                        constexpr( !std::is_void< StartXType >::value )
                        {
                            dimension[ index ].SetStartX(
                                startX_elements[ index ] );
                        }
                    dimension[ index ].SetUnitX( unitX_elements[ index ] );
                }
            }

            template < typename StreamType > //
            void
            Read( StreamType& Stream, std::vector< Data >& Dimension )
            {
                auto frame_stream_version = StreamVersion( Stream );

                if ( frame_stream_version >= 5 )
                {
                    FrameCPP::Universal::DimensionImpl::Read<
                        StreamType, //
                        Data::v5::nx_type, //
                        Data::v5::dx_type, //
                        Data::v5::startX_type, //
                        Data::v5::io_unitX_type //
                        > //
                        ( Stream, Dimension );
                }
                else if ( frame_stream_version >= 4 )
                {
                    FrameCPP::Universal::DimensionImpl::Read<
                        StreamType, //
                        Data::v4::nx_type, //
                        Data::v4::dx_type, //
                        Data::v4::startX_type, //
                        Data::v4::io_unitX_type //
                        > //
                        ( Stream, Dimension );
                }
                else if ( frame_stream_version >= 3 )
                {
                    FrameCPP::Universal::DimensionImpl::Read<
                        StreamType, //
                        Data::v3::nx_type, //
                        Data::v3::dx_type, //
                        void, //
                        Data::v3::io_unitX_type //
                        > //
                        ( Stream, Dimension );
                }
            }

            template < typename StreamType, //
                       typename NxType, //
                       typename DxType, //
                       typename StartXType, //
                       typename UnitXType >
            void
            Write( StreamType& Stream, const std::vector< Data >& dimension )
            {
                using EffectiveStartXType = typename std::conditional<
                    std::is_void< StartXType >::value,
                    int,
                    StartXType >::type;

                std::vector< NxType > nx_elements( dimension.size( ) );
                std::vector< DxType > dx_elements( dimension.size( ) );
                std::vector< EffectiveStartXType > startX_elements;
                std::vector< UnitXType > unitX_elements( dimension.size( ) );

                if
                    constexpr( !std::is_void< StartXType >::value )
                    {
                        startX_elements.resize( dimension.size( ) );
                    }

                for ( size_t index = 0; index < dimension.size( ); index++ )
                {
                    nx_elements[ index ] = dimension[ index ].GetNx( );
                    dx_elements[ index ] = dimension[ index ].GetDx( );
                    if
                        constexpr( !std::is_void< StartXType >::value )
                        {
                            startX_elements[ index ] =
                                dimension[ index ].GetStartX( );
                        }
                    unitX_elements[ index ] = dimension[ index ].GetUnitX( );
                }

                Stream << INT_4U( dimension.size( ) ) << nx_elements
                       << dx_elements;
                if
                    constexpr //
                        ( !std::is_same< StartXType, void >::value )
                    {
                        Stream << startX_elements;
                    }
                Stream << unitX_elements;
            }

            template < typename StreamType > //
            void
            Write( StreamType& Stream, const std::vector< Data >& Dimension )
            {
                auto frame_stream_version = StreamVersion( Stream );

                if ( frame_stream_version >= 5 )
                {
                    FrameCPP::Universal::DimensionImpl::Write<
                        StreamType, //
                        Data::v5::nx_type, //
                        Data::v5::dx_type, //
                        Data::v5::startX_type, //
                        Data::v5::io_unitX_type //
                        > //
                        ( Stream, Dimension );
                }
                else if ( frame_stream_version >= 4 )
                {
                    FrameCPP::Universal::DimensionImpl::Write<
                        StreamType, //
                        Data::v4::nx_type, //
                        Data::v4::dx_type, //
                        Data::v4::startX_type, //
                        Data::v4::io_unitX_type //
                        > //
                        ( Stream, Dimension );
                }
                else if ( frame_stream_version >= 3 )
                {
                    FrameCPP::Universal::DimensionImpl::Write<
                        StreamType, //
                        Data::v3::nx_type, //
                        Data::v3::dx_type, //
                        void, //
                        Data::v3::io_unitX_type //
                        > //
                        ( Stream, Dimension );
                }
            }
        } // namespace DimensionImpl
    } // namespace Universal
} // namespace FrameCPP

#endif /*  !defined( SWIGIMPORTED ) */

#endif /* FrameCPP_UNIVERSAL__IMPL__DimensionClassicIO_HH */
