//
// 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__FrDetectorClassicIO_HH
#define FrameCPP_UNIVERSAL__IMPL__FrDetectorClassicIO_HH

#ifndef SWIGIMPORTED
#include <cmath>

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

#ifndef SWIGIMPORTED
#include "framecpp/Common/Description.hh"
#include "framecpp/Common/FrameSpec.hh"
#include "framecpp/Common/FrameSpec.tcc"
#endif /* SWIGIMPORTED */

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

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

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

#endif /* ! defined(SWIGIMPORTED) */

#if !defined( SWIGIMPORTED )
#include "framecpp/Common/Description.hh"
#include "framecpp/Common/TOCInfo.hh"

#include "framecpp/Common/FrDetector.hh"

#endif /* !defined( SWIGIMPORTED ) */

#if !defined( SWIGIMPORTED )

#endif /* ! defined(SWIGIMPORTED) */

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

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

                //-------------------------------------------------------
                //
                //-------------------------------------------------------
                ClassicIO( )
                    : ObjectWithChecksum< Common::FrDetector >( nullptr )
                {
                }

            protected:
                //-----------------------------------------------------------------
                /// \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( ) );
                }

                //-----------------------------------------------------------------
                /// @brief The data structure description
                ///
                /// @return
                ///     The description of the data structure used to represent
                ///     this object when written to a GWF stream
                //-----------------------------------------------------------------
                virtual const Common::Description*
                GetDescription( unsigned int version ) const;

                //-----------------------------------------------------------------
                /// \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.
                //-----------------------------------------------------------------
                cmn_streamsize_type
                pBytes( const Common::StreamBase& Stream ) const
                {
                    auto frame_stream_version = StreamVersion( Stream );

                    return (
                        io_name_type::Bytes( name ) //
                        + ( ( frame_stream_version >= 5 ) //
                                ? sizeof( prefix )
                                : 0 ) //
                        + ( ( frame_stream_version >= 5 ) // longitude/latitude
                                ? ( sizeof( longitude ) + sizeof( latitude ) )
                                : ( ( frame_stream_version >= 3 )
                                        ? ( sizeof( v3::longitudeD_type ) +
                                            sizeof( v3::longitudeM_type ) +
                                            sizeof( v3::longitudeS_type ) +
                                            sizeof( v3::latitudeD_type ) +
                                            sizeof( v3::latitudeM_type ) +
                                            sizeof( v3::latitudeS_type ) //
                                            )
                                        : 0 ) ) //
                        + sizeof( elevation ) //
                        + sizeof( armXazimuth ) //
                        + sizeof( armYazimuth ) //
                        + ( ( frame_stream_version >= 5 )
                                ? ( sizeof( armXaltitude ) //
                                    + sizeof( armYaltitude ) //
                                    + sizeof( armXmidpoint ) //
                                    + sizeof( armYmidpoint ) )
                                : 0 ) //
                        + ( ( frame_stream_version < 4 ) //
                                ? sizeof( v3::armLength_type )
                                : 0 ) //
                        + ( ( ( frame_stream_version < 9 ) &&
                              ( frame_stream_version >= 5 ) ) //
                                ? sizeof( v5::localTime_type )
                                : 0 ) //
                        + ( ( frame_stream_version >= 9 ) //
                                ? sizeof( dataQualityOffset )
                                : 0 ) //
                        + Stream.PtrStructBytes( ) // aux
                        + ( ( frame_stream_version >= 4 ) // table
                                ? Stream.PtrStructBytes( )
                                : 0 ) //
                        + ( ( frame_stream_version >= 5 )
                                ? Stream.PtrStructBytes( )
                                : 0 ) // next
                    );
                }

                //-----------------------------------------------------------------
                /// \brief Virtual constructor
                //-----------------------------------------------------------------
                virtual self_type*
                pCreate( istream_type& Stream ) const
                {
                    auto frame_stream_version = StreamVersion( Stream );

                    T* obj( new T( ) );

                    io_name_type io_name;

                    if ( frame_stream_version >= 3 )
                    {
                        Stream >> io_name;
                    }
                    if ( frame_stream_version >= 5 )
                    {
                        Stream.read( obj->prefix, sizeof( obj->prefix ) );
                    }
                    if ( frame_stream_version >= 5 )
                    {
                        Stream //
                            >> obj->longitude //
                            >> obj->latitude //
                            ;
                    }
                    else if ( frame_stream_version >= 3 )
                    {
                        DMS longitude_v3;
                        DMS latitude_v3;

                        Stream // Longitude
                            >> longitude_v3.degrees //
                            >> longitude_v3.minutes //
                            >> longitude_v3.seconds //
                            // Latitude
                            >> latitude_v3.degrees //
                            >> latitude_v3.minutes //
                            >> latitude_v3.seconds //
                            ;

                        if ( frame_stream_version == 3 )
                        {
                            correct_v3( longitude_v3.degrees,
                                        longitude_v3.minutes,
                                        longitude_v3.seconds,
                                        latitude_v3.degrees,
                                        latitude_v3.minutes,
                                        latitude_v3.seconds );
                        }
                        obj->longitude = convertCoordinate( longitude_v3 );
                        obj->latitude = convertCoordinate( latitude_v3 );
                    }
                    if ( frame_stream_version >= 3 )
                    {
                        Stream //
                            >> obj->elevation //
                            >> obj->armXazimuth >> obj->armYazimuth //
                            ;
                    }
                    if ( frame_stream_version >= 5 )
                    {
                        Stream //
                            >> obj->armXaltitude >> obj->armYaltitude //
                            >> obj->armXmidpoint >> obj->armYmidpoint //
                            ;
                    }
                    if ( ( frame_stream_version < 9 ) &&
                         ( frame_stream_version >= 5 ) )
                    {
                        v5::localTime_type localTime;

                        Stream //
                            >> localTime //
                            ;
                    }
                    if ( ( frame_stream_version < 4 ) &&
                         ( frame_stream_version >= 3 ) )
                    {
                        v3::armLength_type armLength;

                        Stream //
                            >> armLength //
                            ;
                    }

                    if ( frame_stream_version >= 9 )
                    {
                        Stream //
                            >> obj->dataQualityOffset //
                            ;
                    }
                    if ( frame_stream_version >= 3 )
                    {
                        Stream //
                            >> obj->aux //
                            ;
                    }
                    if ( frame_stream_version >= 4 )
                    {
                        Stream //
                            >> obj->table //
                            ;
                    }
                    if ( frame_stream_version >= 5 )
                    {

                        Stream.Next( obj );
                    }

                    obj->name = io_name;

                    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 );

                    Stream << io_name_type( name );

                    if ( frame_stream_version >= 5 )
                    {
                        Stream.write( prefix, sizeof( prefix ) );
                    }
                    if ( frame_stream_version >= 5 )
                    {
                        Stream << longitude << latitude;
                    }
                    else if ( frame_stream_version >= 3 )
                    {
                        auto longitude_v3 = convertCoordinate( longitude );
                        auto latitude_v3 = convertCoordinate( latitude );

                        Stream << longitude_v3.degrees //
                               << longitude_v3.minutes //
                               << longitude_v3.seconds //
                               << latitude_v3.degrees //
                               << latitude_v3.minutes //
                               << latitude_v3.seconds //
                            ;
                    }
                    Stream << elevation << armXazimuth << armYazimuth;
                    if ( frame_stream_version >= 5 )
                    {
                        Stream << armXaltitude << armYaltitude //
                               << armXmidpoint << armYmidpoint;
                    }
                    if ( frame_stream_version < 4 )
                    {
                        Stream << v3::armLength_type( 0 );
                    }
                    if ( ( frame_stream_version < 9 ) &&
                         ( frame_stream_version >= 5 ) )
                    {
                        Stream << v5::localTime_type( 0 );
                    }
                    if ( frame_stream_version >= 9 )
                    {
                        Stream << dataQualityOffset;
                    }
                    Stream << aux;
                    if ( frame_stream_version >= 4 )
                    {
                        Stream << table;
                    }
                    if ( frame_stream_version >= 5 )
                    {
                        WriteNext( Stream );
                    }
                }

            private:
                typedef STRING io_name_type;

                inline static void
                correct_v3( INT_2S& LongitudeD,
                            INT_2S& LongitudeM,
                            REAL_4& LongitudeS,
                            INT_2S& LatitudeD,
                            INT_2S& LatitudeM,
                            REAL_4& LatitudeS )
                {
                    if ( ( LatitudeD == -119 ) && ( LongitudeD == 46 ) )
                    {
                        // Hanford
                        LongitudeD = -119;
                        LongitudeM = 24;
                        LongitudeS = 27.565704;

                        LatitudeD = 46;
                        LatitudeM = 27;
                        LatitudeS = 18.52812;
                    }
                    else if ( ( LatitudeD == -90 ) && ( LongitudeD == 30 ) )
                    {
                        // Livingston
                        LongitudeD = -90;
                        LongitudeM = 46;
                        LongitudeS = 27.265404;

                        LatitudeD = 30;
                        LatitudeM = 33;
                        LatitudeS = 46.419588;
                    }
                }

#if WORKING_VIRTUAL_TOCQUERY
            public:
                //-----------------------------------------------------------------
                /// \brief Answer requests for information as needed by TOC
                ///
                /// \param[in] InfoClass
                ///     Class of information being requested.
                //-----------------------------------------------------------------
                virtual void TOCQuery( int InfoClass, ... ) const;
#else /*  WORKING_VIRTUAL_TOCQUERY */
            protected:
                //-----------------------------------------------------------------
                /// \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.
                //-----------------------------------------------------------------
                virtual void vTOCQuery( int InfoClass, va_list vl ) const;
#endif /* ! WORKING_VIRTUAL_TOCQUERY */

                struct DMS
                {
                    v3::longitudeD_type degrees;
                    v3::longitudeM_type minutes;
                    v3::longitudeS_type seconds;
                };

                DMS static convertCoordinate( double coordinate )
                {
                    // Ensure the coordinate is within valid range (-90 to 90)
                    coordinate = std::fmod( coordinate + 90, 180 ) - 90;

                    // Extract degrees
                    auto degrees =
                        static_cast< decltype( DMS::degrees ) >( coordinate );

                    // Calculate remaining minutes and seconds
                    auto remainingMinutes = ( coordinate - degrees ) * 60;
                    auto minutes = static_cast< decltype( DMS::minutes ) >(
                        remainingMinutes );
                    auto seconds = static_cast< decltype( DMS::seconds ) >(
                        ( remainingMinutes - minutes ) * 60 );

                    DMS result{ degrees, minutes, seconds };
                    return result;
                }

                static REAL_8
                convertCoordinate( const DMS& coordinate )
                {
                    REAL_8 retval = 0.0;

                    if ( coordinate.degrees >= 0 )
                    {
                        retval = (REAL_8)coordinate.degrees +
                            (REAL_8)coordinate.minutes / 60. +
                            (REAL_8)coordinate.seconds / 3600.;
                    }
                    else
                    {
                        retval = (REAL_8)coordinate.degrees -
                            (REAL_8)coordinate.minutes / 60. -
                            (REAL_8)coordinate.seconds / 3600.;
                    }
                    retval = ( retval * M_PI ) / 180.;

                    return retval;
                }

            }; // namespace FrDetectorImpl

            template < typename T >
            void
            ClassicIO< T >::
#if WORKING_VIRTUAL_TOCQUERY
                TOCQuery( int InfoClass, ... ) const
#else /*  WORKING_VIRTUAL_TOCQUERY */
                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_PREFIX:
                        {
                            const char* p = GetPrefix( );
                            if ( p[ 0 ] )
                            {
                                if ( p[ 1 ] )
                                {
                                    data->assign( p, 2 );
                                }
                                else
                                {
                                    data->assign( p, 1 );
                                }
                            }
                        }
                        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 */
                    ;
            } // TOCQuery
        } // namespace FrDetectorImpl
    } // namespace Universal
} // namespace FrameCPP
#endif /* ! defined(SWIGIMPORTED) */

#endif /* FrameCPP_UNIVERSAL__IMPL__FrDetectorClassicIO_HH */
