//
// 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__FrDetectorData_HH
#define FrameCPP_UNIVERSAL__IMPL__FrDetectorData_HH

#include "ldastoolsal/types.hh"

#define FR_DETECTOR_LONGITUDE_TYPE REAL_8
#define FR_DETECTOR_LATITUDE_TYPE REAL_8
#define FR_DETECTOR_ELEVATION_TYPE REAL_4
#define FR_DETECTOR_ARM_X_AZIMUTH_TYPE REAL_4
#define FR_DETECTOR_ARM_Y_AZIMUTH_TYPE REAL_4
#define FR_DETECTOR_ARM_X_ALTITUDE_TYPE REAL_4
#define FR_DETECTOR_ARM_Y_ALTITUDE_TYPE REAL_4
#define FR_DETECTOR_ARM_X_MIDPOINT_TYPE REAL_4
#define FR_DETECTOR_ARM_Y_MIDPOINT_TYPE REAL_4
#define FR_DETECTOR_LOCAL_TIME_TYPE INT_4S
#define FR_DETECTOR_DATA_QUALITY_OFFSET_TYPE INT_2U

#if !defined( SWIGIMPORTED )
#include "boost/bimap.hpp"

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

#include "framecpp/Common/SearchContainer.hh"

#include "framecpp/Universal/FrTable.hh"
#include "framecpp/Universal/FrVect.hh"

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

#endif /* ! defined(SWIGIMPORTED) */

#if !defined( SWIGIMPORTED )
namespace FrameCPP
{
    namespace Universal
    {
        namespace FrDetectorImpl
        {
            class Data : public Impl::FrObjectMetaData< Data >
            {
            public:
                //-------------------------------------------------------
                // Legacy types
                //-------------------------------------------------------

                struct v3
                {
                    using name_type = std::string;
                    using longitudeD_type = INT_2S;
                    using longitudeM_type = INT_2S;
                    using longitudeS_type = REAL_4;
                    using latitudeD_type = INT_2S;
                    using latitudeM_type = INT_2S;
                    using latitudeS_type = REAL_4;
                    using elevation_type = REAL_4;
                    using armXazimuth_type = REAL_4;
                    using armYazimuth_type = REAL_4;
                    using armLength_type = REAL_4;

                    using more_type =
                        Common::SearchContainer< FrVect, &FrVect::GetName >;
                    using more_iterator = more_type::iterator;
                    using const_more_iterator = more_type::const_iterator;
                    using more_hash_iterator = more_type::hash_iterator;
                    using const_more_hash_iterator =
                        more_type::const_hash_iterator;
                };
                struct v4
                {
                    using name_type = v3::name_type;
                    using longitudeD_type = v3::longitudeD_type;
                    using longitudeM_type = v3::longitudeM_type;
                    using longitudeS_type = v3::longitudeS_type;
                    using latitudeD_type = v3::latitudeD_type;
                    using latitudeM_type = v3::latitudeM_type;
                    using latitudeS_type = v3::latitudeS_type;
                    using elevation_type = v3::elevation_type;
                    using armXazimuth_type = v3::armXazimuth_type;
                    using armYazimuth_type = v3::armYazimuth_type;

                    using more_type = v3::more_type;
                    using more_iterator = v3::more_iterator;
                    using const_more_iterator = v3::const_more_iterator;
                    using more_hash_iterator = v3::more_hash_iterator;
                    using const_more_hash_iterator =
                        v3::const_more_hash_iterator;

                    using table_type =
                        Common::SearchContainer< FrTable,
                                                 &FrTable::GetNameSlow >;
                    using table_iterator = table_type::iterator;
                    using const_table_iterator = table_type::const_iterator;
                };
                struct v5
                {
                    using name_type = v4::name_type;
                    using prefix_type = char[ 2 ];
                    using longitude_type = REAL_8;
                    using latitude_type = REAL_8;
                    using elevation_type = v4::elevation_type;
                    using armXazimuth_type = v4::armXazimuth_type;
                    using armYazimuth_type = v4::armYazimuth_type;
                    using armXaltitude_type = REAL_4;
                    using armYaltitude_type = REAL_4;
                    using armXmidpoint_type = REAL_4;
                    using armYmidpoint_type = REAL_4;
                    using localTime_type = INT_4S;

                    using aux_type = v4::more_type;
                    using aux_iterator = v4::more_iterator;
                    using const_aux_iterator = v4::const_more_iterator;
                    using aux_hash_iterator = v4::more_hash_iterator;
                    using const_aux_hash_iterator =
                        v4::const_more_hash_iterator;

                    using table_type = v4::table_type;
                    using table_iterator = v4::table_iterator;
                    using const_table_iterator = v4::const_table_iterator;
                };
                using v6 = v5;
                using v7 = v6;
                using v8 = v7;
                struct v9
                {
                    using name_type = v8::name_type;
                    using prefix_type = v8::prefix_type;
                    using longitude_type = v8::longitude_type;
                    using latitude_type = v8::latitude_type;
                    using elevation_type = v8::elevation_type;
                    using armXazimuth_type = v8::armXazimuth_type;
                    using armYazimuth_type = v8::armYazimuth_type;
                    using armXaltitude_type = v8::armXaltitude_type;
                    using armYaltitude_type = v8::armYaltitude_type;
                    using armXmidpoint_type = v8::armXmidpoint_type;
                    using armYmidpoint_type = v8::armYmidpoint_type;
                    using dataQualityOffset_type = INT_2U;

                    using aux_type = v8::aux_type;
                    using aux_iterator = v8::aux_iterator;
                    using const_aux_iterator = v8::const_aux_iterator;

                    using table_type = v8::table_type;
                    using table_iterator = v8::table_iterator;
                    using const_table_iterator = v8::const_table_iterator;
                };

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

                using current_version = v9;

                using name_type = current_version::name_type;
                using prefix_type = current_version::prefix_type;
                using longitude_type = current_version::longitude_type;
                using latitude_type = current_version::latitude_type;
                using elevation_type = current_version::elevation_type;
                using armXazimuth_type = current_version::armXazimuth_type;
                using armYazimuth_type = current_version::armYazimuth_type;
                using armXaltitude_type = current_version::armXaltitude_type;
                using armYaltitude_type = current_version::armYaltitude_type;
                using armXmidpoint_type = current_version::armXmidpoint_type;
                using armYmidpoint_type = current_version::armYmidpoint_type;
                using dataQualityOffset_type =
                    current_version::dataQualityOffset_type;

                using aux_type = current_version::aux_type;
                using aux_iterator = current_version::aux_iterator;
                using const_aux_iterator = current_version::const_aux_iterator;

                using table_type = current_version::table_type;
                using table_iterator = current_version::table_iterator;
                using const_table_iterator =
                    current_version::const_table_iterator;
                //-------------------------------------------------------

                typedef enum
                {
                    DQO_TAMA_300 = 0, ///< TAMA 300
                    DQO_VIRGO = 4, ///< Virgo 3 km
                    DQO_GEO_600 = 6, ///< GEO 600
                    DQO_LHO_4K = 10, ///< LIGO LHO 4 km
                    DQO_LLO_4K = 12, ///< LIGO LLO 4 km
                    DQO_CIT_40 = 14, ///< Caltech 40 meters
                    DQO_ACIGA = 26, ///< ACIGA
                    DQO_KAGRA = 28, ///< KAGRA
                    DQO_LIGO_INDIA = 30, ///< LIGO India
                    DQO_DYNAMIC_01 = 2,
                    DQO_DYNAMIC_02 = 8,
                    DQO_DYNAMIC_03 = 16,
                    DQO_DYNAMIC_04 = 18,
                    DQO_DYNAMIC_05 = 20,
                    DQO_DYNAMIC_06 = 22,
                    DQO_DYNAMIC_07 = 24,
                    DQO_UNSET = 32,
                    DQO_CALTECH_40_METERS = DQO_CIT_40 ///< Caltech 40 meters
                } dataQualityOffsets;

                static constexpr char TAMA_300_PREFIX[ 2 ] = { 'T', '1' };
                static constexpr char VIRGO_PREFIX[ 2 ] = { 'V', '1' };
                static constexpr char GEO_600_PREFIX[ 2 ] = { 'G', '1' };
                static constexpr char LHO_4K_PREFIX[ 2 ] = { 'H', '1' };
                static constexpr char LLO_4K_PREFIX[ 2 ] = { 'L', '1' };
                static constexpr char CIT_40_PREFIX[ 2 ] = { 'C', '1' };
                static constexpr char ACIGA_PREFIX[ 2 ] = { 'U', '1' };
                static constexpr char KAGRA_PREFIX[ 2 ] = { 'K', '1' };
                static constexpr char LIGO_INDIA_PREFIX[ 2 ] = { 'A', '1' };
                static constexpr char UNKNOWN_PREFIX[ 2 ] = { ' ', ' ' };

                static std::map< char[ 2 ], int > prefix_mapping;

                //-----------------------------------------------------------------
                /// \brief Default constructor
                ///
                /// \return
                ///     A new instance of a Data object
                //-----------------------------------------------------------------

                Data( )
                {
                }

                Data( const Data& Source ) = default;

                //-----------------------------------------------------------------
                /// \brief Convert degrees/minutes/seconds to decimal degrees
                ///
                /// Utility function to convert legacy v3-v7 DMS format to
                /// modern v8+ decimal degree format.
                ///
                /// \param[in] Degrees
                ///     Integer degrees (-180 to 180 for longitude, -90 to 90 for latitude)
                /// \param[in] Minutes
                ///     Integer minutes (0 to 59)
                /// \param[in] Seconds
                ///     Decimal seconds (0.0 to 59.999...)
                ///
                /// \return
                ///     Decimal degrees as REAL_8
                ///
                /// \note
                ///     For negative coordinates (West/South), Degrees should be negative.
                ///     Minutes and Seconds are always positive.
                //-----------------------------------------------------------------
                static inline REAL_8
                ConvertDMSToDecimal( INT_2S Degrees, INT_2S Minutes, REAL_4 Seconds )
                {
                    REAL_8 sign = ( Degrees < 0 ) ? -1.0 : 1.0;
                    REAL_8 absDegrees = std::abs( Degrees );
                    return sign * ( absDegrees + Minutes / 60.0 + Seconds / 3600.0 );
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the name of the instrument.
                ///
                /// \return
                ///     The name of the instrument.
                //-----------------------------------------------------------------
                inline const std::string&
                GetName( ) const
                {
                    return name;
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the name of the instrument.
                ///
                /// \return
                ///     The name of the instrument.
                //-----------------------------------------------------------------
                const std::string&
                GetNameSlow( ) const
                {
                    return ( GetName( ) );
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the prefix of the instrument.
                ///
                /// \return
                ///     The prefix for the instrument.
                //-----------------------------------------------------------------
                const CHAR*
                GetPrefix( ) const
                {
                    return prefix;
                }

                //-----------------------------------------------------------------
                /// @brief
                ///
                /// @param[in] DataQualityOffset
                ///   The data quality offset of the left bit.
                ///
                /// @return
                ///     Prefix
                //-----------------------------------------------------------------
                static const CHAR*
                GetPrefix( const dataQualityOffset_type DataQualityOffset )
                {
                    switch ( DataQualityOffset )
                    {
                    case DQO_TAMA_300:
                        return ( TAMA_300_PREFIX );
                    case DQO_VIRGO:
                        return ( VIRGO_PREFIX );
                    case DQO_GEO_600:
                        return ( GEO_600_PREFIX );
                    case DQO_LHO_4K:
                        return ( LHO_4K_PREFIX );
                    case DQO_LLO_4K:
                        return ( LLO_4K_PREFIX );
                    case DQO_CALTECH_40_METERS:
                        return ( CIT_40_PREFIX );
                    case DQO_ACIGA:
                        return ( ACIGA_PREFIX );
                    case DQO_KAGRA:
                        return ( KAGRA_PREFIX );
                    case DQO_LIGO_INDIA:
                        return ( LIGO_INDIA_PREFIX );
                    default:
                        return ( UNKNOWN_PREFIX );
                    }
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the longitude of the detector vertex.
                ///
                /// \return
                ///     The longitude of the detector vertex.
                //-----------------------------------------------------------------
                longitude_type
                GetLongitude( ) const
                {
                    return longitude;
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the latitude of the detector vertex.
                ///
                /// \return
                ///     The latitude of the detector vertex.
                //-----------------------------------------------------------------
                latitude_type
                GetLatitude( ) const
                {
                    return latitude;
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the vertex elevation of the detector.
                ///
                /// \return
                ///     The vertex elevation of the detector.
                //-----------------------------------------------------------------
                elevation_type
                GetElevation( ) const
                {
                    return elevation;
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the orientation of X arm of the detector.
                ///
                /// \return
                ///     The orientation of the X arm of the detector.
                //-----------------------------------------------------------------
                armXazimuth_type
                GetArmXazimuth( ) const
                {
                    return armXazimuth;
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the orientation of Y arm of the detector.
                ///
                /// \return
                ///     The orientation of the Y arm of the detector.
                //-----------------------------------------------------------------
                armYazimuth_type
                GetArmYazimuth( ) const
                {
                    return armYazimuth;
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the altitude angle of X arm of the detector.
                ///
                /// \return
                ///     The altitude angle of the X arm of the detector.
                //-----------------------------------------------------------------
                armXaltitude_type
                GetArmXaltitude( ) const
                {
                    return armXaltitude;
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the altitude angle of Y arm of the detector.
                ///
                /// \return
                ///     The altitude angle of the Y arm of the detector.
                //-----------------------------------------------------------------
                armYaltitude_type
                GetArmYaltitude( ) const
                {
                    return armYaltitude;
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the midpoint of the X arm of the detector.
                ///
                /// \return
                ///     The midpoint of the X arm of the detector.
                //-----------------------------------------------------------------
                armXmidpoint_type
                GetArmXmidpoint( ) const
                {
                    return armXmidpoint;
                }

                //-----------------------------------------------------------------
                /// \brief Retrieve the midpoint of the Y arm of the detector.
                ///
                /// \return
                ///     The midpoint of the Y arm of the detector.
                //-----------------------------------------------------------------
                armYmidpoint_type
                GetArmYmidpoint( ) const
                {
                    return armYmidpoint;
                }

                //-----------------------------------------------------------------
                /// \brief Bit offset of the low order bit of the data quality
                /// mask.
                ///
                /// \return
                ///     The bit offset.
                //-----------------------------------------------------------------
                dataQualityOffset_type
                GetDataQualityOffset( ) const
                {
                    return dataQualityOffset;
                }

                static dataQualityOffsets
                GetDataQualityOffset( const std::string& Name )
                {
                    auto location = name_map.left.find( Name );
                    if ( location != name_map.left.end( ) )
                    {
                        return ( location->second );
                    }
                    if ( Name.size( ) == 2 )
                    {
                        auto location = prefix_map.left.find( Name );
                        if ( location != prefix_map.left.end( ) )
                        {
                            return ( location->second );
                        }
                    }
                    return ( DQO_UNSET );
                }

#if 0
                //-----------------------------------------------------------------
                /// @brief Bit offset of the data quality mask.
                ///
                /// Retrieve the bit offset of the low order bit of the data
                /// quality mask for the given Prefix.
                ///
                /// @param[in] Prefix
                ///   The prefix of the instrument to be queried
                ///
                /// @return
                ///     The bit offset.
                //-----------------------------------------------------------------
                static dataQualityOffset_type
                GetDataQualityOffset( const char* Prefix )
                {
                    if ( Prefix )
                    {
                        if ( prefix_equal( Prefix, TAMA_300_PREFIX ) )
                        {
                            return ( DQO_TAMA_300 );
                        }
                        else if ( prefix_equal( Prefix, VIRGO_PREFIX ) )
                        {
                            return ( DQO_VIRGO );
                        }
                        else if ( prefix_equal( Prefix, GEO_600_PREFIX ) )
                        {
                            return ( DQO_GEO_600 );
                        }
                        else if ( prefix_equal( Prefix, LHO_4K_PREFIX ) )
                        {
                            return ( DQO_LHO_4K );
                        }
                        else if ( prefix_equal( Prefix, LLO_4K_PREFIX ) )
                        {
                            return ( DQO_LLO_4K );
                        }
                        else if ( prefix_equal( Prefix, CIT_40_PREFIX ) )
                        {
                            return ( DQO_CALTECH_40_METERS );
                        }
                        else if ( prefix_equal( Prefix, ACIGA_PREFIX ) )
                        {
                            return ( DQO_ACIGA );
                        }
                        else if ( prefix_equal( Prefix, KAGRA_PREFIX ) )
                        {
                            return ( DQO_KAGRA );
                        }
                        else if ( prefix_equal( Prefix, LIGO_INDIA_PREFIX ) )
                        {
                            return ( DQO_LIGO_INDIA );
                        }
                    }
                    return ( DQO_UNSET );
                }
#endif /* 0 */

                //-----------------------------------------------------------------
                /// \brief Identifier for vector for user-provided information.
                ///
                /// \return
                ///     A constant reference to the user-provided information.
                //-----------------------------------------------------------------
                const aux_type&
                RefAux( ) const
                {
                    return aux;
                }

                //-----------------------------------------------------------------
                /// \brief Identifier for vector for user-provided information.
                ///
                /// \return
                ///     A reference to the user-provided information.
                //-----------------------------------------------------------------
                aux_type&
                RefAux( )
                {
                    return aux;
                }

                //-----------------------------------------------------------------
                /// \brief Identifier for user-provided information in table
                /// format.
                ///
                /// \return
                ///     A constant reference to the user-provided table
                ///     information.
                //-----------------------------------------------------------------
                const table_type&
                RefTable( ) const
                {
                    return table;
                }

                //-----------------------------------------------------------------
                /// \brief Identifier for user-provided information in table
                /// format.
                ///
                /// \return
                ///     A reference to the user-provided table information.
                //-----------------------------------------------------------------
                table_type&
                RefTable( )
                {
                    return table;
                }

                inline bool
                operator==( Data const& RHS ) const
                {
#define CMP__( X ) ( X == RHS.X )

                    return ( ( &RHS == this ) ||
                             ( CMP__( name ) && CMP__( prefix[ 0 ] ) &&
                               CMP__( prefix[ 1 ] ) && CMP__( longitude ) &&
                               CMP__( latitude ) && CMP__( elevation ) &&
                               CMP__( armXazimuth ) && CMP__( armYazimuth ) &&
                               CMP__( armXaltitude ) && CMP__( armYaltitude ) &&
                               CMP__( armXmidpoint ) && CMP__( armYmidpoint ) &&
                               CMP__( aux ) && CMP__( table ) ) );

#undef CMP__
                }

            protected:
                typedef boost::bimap< std::string, dataQualityOffsets >
                    name_map_type;
                typedef boost::bimap< std::string, dataQualityOffsets >
                    prefix_map_type;

                static const constexpr INT_2U MAX_REF = 2;

                //---------------------------------------------------------------
                /// Instrument name as described here
                /// (e.g., Virgo; GEO_600; TAMA_300; LHO_2k; LLO_4k; 40M;
                /// PNI; simulated pseudo data - model version etc.)
                //---------------------------------------------------------------
                name_type name;
                //---------------------------------------------------------------
                /// Channel prefix for this detector as described here.
                //---------------------------------------------------------------
                CHAR prefix[ 2 ] = { ' ', ' ' };
                //---------------------------------------------------------------
                /// Detector vertex longitude, geographical coordinates:
                /// radians; Value > 0 >= E of Greenwich
                /// (-pi < Longitude <= +pi)
                //---------------------------------------------------------------
                longitude_type longitude;
                //---------------------------------------------------------------
                /// Detector vertex latitude, geographical coordinated:
                /// radians; Value >= 0 >= N of Equator
                /// (-pi/2 < Latitude <= +pi/2).
                //---------------------------------------------------------------
                latitude_type latitude;
                //---------------------------------------------------------------
                /// Vertex elevation, in meters, relative to WGS84
                /// ellipsoid.
                //---------------------------------------------------------------
                elevation_type elevation;
                //---------------------------------------------------------------
                /// Orientation of X arm, measured in radians East of North
                /// (0 <= ArmXazimuth < 2pi)
                //---------------------------------------------------------------
                armXazimuth_type armXazimuth;
                //---------------------------------------------------------------
                /// Orientation of Y arm, measured in radians East of North
                /// (0 <= ArmYazimuth < 2pi)
                //---------------------------------------------------------------
                armYazimuth_type armYazimuth;
                //---------------------------------------------------------------
                /// Altitude (pitch) angle to X arm, measured in radians
                /// above horizon (local tangent to WGS84 ellipsoid)
                /// -pi/2 < ArmXaltitude <= pi/2
                //---------------------------------------------------------------
                armXaltitude_type armXaltitude;
                //---------------------------------------------------------------
                /// Altitude (pitch) angle to Y arm, measured in radians
                /// above horizon (local tangent to WGS84 ellipsoid)
                /// -pi/2 < ArmXaltitude <= pi/2
                //---------------------------------------------------------------
                armYaltitude_type armYaltitude;
                //---------------------------------------------------------------
                /// Distance between the detector vertex and the middle of
                /// the X cavity (meters) (should be zero for bars).
                //---------------------------------------------------------------
                armXmidpoint_type armXmidpoint;
                //---------------------------------------------------------------
                /// Distance between the detector vertex and the middle of
                /// the Y cavity (meters) (should be zero for bars).
                //---------------------------------------------------------------
                armYmidpoint_type armYmidpoint;
                //---------------------------------------------------------------
                /// Data quality offset
                /// Bit offset of the low order bit of the data quality mask.
                //---------------------------------------------------------------
                dataQualityOffset_type dataQualityOffset = DQO_UNSET;
                //---------------------------------------------------------------
                /// Indentifier for user-provided structure for additional
                /// detector data.
                //---------------------------------------------------------------
                aux_type aux;
                //---------------------------------------------------------------
                /// Identifier fo ruser-provided table structure for
                /// additional detector data.
                //---------------------------------------------------------------
                table_type table;

                static inline bool
                prefix_equal( const char* RHS, const char* LHS )
                {
                    return ( ( RHS[ 0 ] == LHS[ 0 ] ) &&
                             ( RHS[ 1 ] == LHS[ 1 ] ) );
                }

                static name_map_type   name_map;
                static prefix_map_type prefix_map;
            };
        } // namespace FrDetectorImpl
    } // namespace Universal
} // namespace FrameCPP

FR_OBJECT_META_DATA_DECLARE_UNIVERSAL( FrDetectorImpl )

#endif /* ! defined(SWIGIMPORTED) */

#endif /* FrameCPP_UNIVERSAL__IMPL__FrDetectorData_HH */
