//
// 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 "framecpp/Common/IOStream.hh"
#include "framecpp/Common/FrameStream.hh"
#include "framecpp/Common/Description.hh"
#include "framecpp/Universal/Dimension.hh"
#include "framecpp/Universal/impl/FrameSpec.hh"

namespace FrameCPP
{
    namespace Universal
    {
        //=======================================================================
        // Dimension - Constructors
        //=======================================================================

        Dimension::Dimension( )
            : DimensionImpl::Data( Dimension::DEFAULT_NX,
                                   Dimension::DEFAULT_DX,
                                   "", // DEFAULT_UNITX (empty string)
                                   Dimension::DEFAULT_STARTX )
        {
        }

        Dimension::Dimension( const Dimension& dim )
            : DimensionImpl::Data( dim ),
              DimensionImpl::ClassicIO< Dimension >( dim ),
              DimensionImpl::SerialIO< Dimension >( dim )
        {
        }

        Dimension::Dimension( nx_type     nx,
                              dx_type     dx,
                              const char* unitX,
                              startX_type startX )
            : DimensionImpl::Data( nx, dx, unitX, startX )
        {
        }

        //=======================================================================
        // Dimension - Getter Methods
        //=======================================================================

        Dimension::nx_type
        Dimension::GetNx( ) const
        {
            return DimensionImpl::Data::GetNx( );
        }

        Dimension::dx_type
        Dimension::GetDx( ) const
        {
            return DimensionImpl::Data::GetDx( );
        }

        Dimension::startX_type
        Dimension::GetStartX( ) const
        {
            return DimensionImpl::Data::GetStartX( );
        }

        const std::string&
        Dimension::GetUnitX( ) const
        {
            return DimensionImpl::Data::GetUnitX( );
        }

        //=======================================================================
        // Dimension - Setter Methods
        //=======================================================================

        void
        Dimension::SetDx( dx_type dx )
        {
            DimensionImpl::Data::SetDx( dx );
        }

        void
        Dimension::SetNx( nx_type nx )
        {
            DimensionImpl::Data::SetNx( nx );
        }

        void
        Dimension::SetStartX( startX_type startX )
        {
            DimensionImpl::Data::SetStartX( startX );
        }

        //=======================================================================
        // Dimension - Operators
        //=======================================================================

        const Dimension&
        Dimension::operator=( const Dimension& dim )
        {
            if ( this != &dim )
            {
                DimensionImpl::Data::operator=( dim );
            }
            return *this;
        }

        bool
        Dimension::operator==( const Dimension& dim ) const
        {
            return DimensionImpl::Data::operator==( dim );
        }

        bool
        Dimension::operator!=( const Dimension& dim ) const
        {
            return DimensionImpl::Data::operator!=( dim );
        }

        bool
        Dimension::operator==( const Common::FrameSpec::Object& Obj ) const
        {
            const Dimension* other = dynamic_cast< const Dimension* >( &Obj );
            if ( other )
            {
                return DimensionImpl::Data::operator==( *other );
            }
            return false;
        }

        const Common::Description*
        Dimension::GetDescription( unsigned int version ) const
        {
            // Dimension doesn't have a frame structure description
            // Return empty description
            static Common::Description empty_description;
            return &empty_description;
        }

    } // namespace Universal
} // namespace FrameCPP

//=======================================================================
// DimensionImpl::Data - Methods moved from impl/DimensionData.hh
//=======================================================================

namespace FrameCPP
{
    namespace Universal
    {
        namespace DimensionImpl
        {
            //===================================================================
            // Assignment and comparison operators (moved from DimensionData.hh)
            //===================================================================

            const Data&
            Data::operator=( const Data& dim )
            {
                if ( this != &dim )
                {
                    mNx = dim.mNx;
                    mDx = dim.mDx;
                    mStartX = dim.mStartX;
                    mUnitX = dim.mUnitX;
                }
                return *this;
            }

            bool
            Data::operator==( const Data& dim ) const
            {
                return ( ( this == &dim ) ||
                         ( ( mNx == dim.mNx ) && ( mDx == dim.mDx ) &&
                           ( mStartX == dim.mStartX ) &&
                           ( mUnitX == dim.mUnitX ) ) );
            }

            bool
            Data::operator!=( const Data& dim ) const
            {
                return !( dim == *this );
            }

            //===================================================================
            // Size calculation methods (moved from DimensionData.hh)
            //===================================================================

            cmn_streamsize_type
            Data::Bytes( const Common::StreamBase& Stream ) const
            {
                auto frame_stream_version = StreamVersion( Stream );
                return ( ( ( frame_stream_version >= 5 ) //
                               ? sizeof( mNx )
                               : sizeof( v3::nx_type ) ) + //
                         sizeof( mDx ) + //
                         io_unitX_type::Bytes( mUnitX ) + //
                         ( ( frame_stream_version >= 4 ) //
                               ? sizeof( mStartX )
                               : 0 ) //
                );
            }

            Data::nx_type
            Data::CalcNData( nDim_type NDims, const Data* Dims )
            {
                nx_type retval = 0;

                if ( NDims > 0 )
                {
                    retval = 1;

                    for ( nDim_type cur = 0, last = NDims; cur != last; ++cur )
                    {
                        retval *= Dims[ cur ].GetNx( );
                    }
                }
                return retval;
            }

        } // namespace DimensionImpl
    } // namespace Universal
} // namespace FrameCPP

//=======================================================================
//               C L A S S I C   I O   T E M P L A T E   S P E C I A L I Z A T I
//               O N S
//=======================================================================
namespace FrameCPP
{
    namespace Universal
    {
        namespace DimensionImpl
        {
            template <>
            cmn_streamsize_type
            ClassicIO< Dimension >::pBytes(
                const Common::StreamBase& Stream ) const
            {
                return Data::Bytes( Stream );
            }

            template <>
            ClassicIO< Dimension >::self_type*
            ClassicIO< Dimension >::pCreate( istream_type& Stream ) const
            {
                Dimension* obj = new Dimension( );
                // For now, basic creation - could be enhanced to read from
                // stream
                return obj;
            }

            template <>
            void
            ClassicIO< Dimension >::pWrite( ostream_type& Stream ) const
            {
                // For now, basic write - could be enhanced to write to stream
            }

        } // namespace DimensionImpl
    } // namespace Universal
} // namespace FrameCPP
