//
// 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 <boost/make_shared.hpp>
#include <boost/unordered_map.hpp>

#include "framecpp/Universal/FrDetector.hh"
#include "framecpp/Universal/FrSH.hh"
#include "framecpp/Universal/FrSE.hh"
#include "framecpp/Universal/PTR_STRUCT.hh"


using FrameCPP::Common::Description;
using FrameCPP::Common::FrameSpec;

#define LM_DEBUG 0

#if LM_DEBUG
#define AT( ) std::cerr << __FILE__ << " " << __LINE__ << std::endl;
#else
#define AT( )
#endif

//=======================================================================
// Static
//=======================================================================

FR_OBJECT_META_DATA_DEFINE_UNIVERSAL( FrDetectorImpl,
                                      FSI_FR_DETECTOR,
                                      "FrDetector",
                                      "Detector Data Structure" )

namespace
{
    using namespace FrameCPP::Universal;

    typedef boost::unordered_map< FrDetector::dataQualityOffsets,
                                  FrDetector::FrDetectorCache_element >
        FrDetectorCache_type;

    typedef boost::bimap< std::string, FrDetector::dataQualityOffsets >
        FrDetectorPrefixMap_type;

    template < typename L, typename R >
    boost::bimap< L, R >
    make_bimap(
        std::initializer_list< typename boost::bimap< L, R >::value_type >
            list )
    {
        return boost::bimap< L, R >( list.begin( ), list.end( ) );
    }

    static FrDetectorCache_type FrDetectorCache{
        { FrDetector::DQO_ACIGA,
          boost::make_shared< FrDetector >(
              "ACIGA", // name
              "U1", // prefix
              0.0, // longitude
              0.0, // latitude
              0.0, // elevation
              0.0, // armXaltitude
              0.0, // armYaltitude
              0.0, // armXazimuth
              0.0, // armYazimuth
              0.0, // armXmidpoint
              0.0, // armYmidpoint
              FrDetector::DQO_ACIGA // dataQualityOffset
              ) },
        { FrDetector::DQO_CIT_40,
          boost::make_shared< FrDetector >(
              "CIT_40", // name
              "C1", // prefix
              0.0, // longitude
              0.0, // latitude
              0.0, // elevation
              0.0, // armXaltitude
              0.0, // armYaltitude
              0.0, // armXazimuth
              0.0, // armYazimuth
              0.0, // armXmidpoint
              0.0, // armYmidpoint
              FrDetector::DQO_CIT_40 // dataQualityOffset
              ) },
        { //---------------------------------------------------------------------
          // GEO -
          // Values taken from lal/src/tools/LALDetectors.h
          //---------------------------------------------------------------------
          FrDetector::DQO_GEO_600,
          boost::make_shared< FrDetector >(
              "GEO_600", // name
              "G1", // prefix
              0.17116780435, // longitude
              0.91184982752, // latitutde
              114.425, // elevation
              1.19360100484, // armXazimuth
              5.83039279401, // armYazimuth
              0.00000000000, // armXaltitude
              0.00000000000, // armYaltitude
              300.00000000000, // armXmidpoint
              300.00000000000, // armYmidpoint
              FrDetector::DQO_GEO_600 // dataQualityOffset
              ) },
        { //---------------------------------------------------------------------
          // KAGRA -
          // Values taken from lal/src/tools/LALDetectors.h
          //---------------------------------------------------------------------
          FrDetector::DQO_KAGRA,
          boost::make_shared< FrDetector >(
              "KAGRA", // name
              "K1", // prefix
              2.396441015, // longitude
              0.6355068497, // latitude
              414.181, // elevation
              1.054113, // armXazimuth
              -0.5166798, // armYazimuth
              0.0031414, // armXaltitude
              -0.0036270, // armYaltitude
              1513.2535, // armXmidpoint
              1511.611, // armYmidpoint
              FrDetector::DQO_KAGRA // dataQualityOffset
              ) },
        { //---------------------------------------------------------------------
          // HANFORD - Derived from:
          // /archive/frames/S5/L3/LHO/H-RDS_R_L3-8766/H-RDS_R_L3-876699904-256.gwf
          //---------------------------------------------------------------------
          FrDetector::DQO_LHO_4K,
          boost::make_shared< FrDetector >(
              "LHO_4k", // name
              "H1", // prefix
              -2.08406, // longitute
              0.810795, // latitude
              142.554, // elevation
              5.65488, // armXazimuth
              4.084808, // armYazimuth
              -0.0006195, // armXaltitude
              0.0000125, // armYaltitude
              1997.54, // armXmidpoint
              1997.52, // armYmidpoint
              FrDetector::DQO_LHO_4K // dataQualityOffset
              ) },
        { //---------------------------------------------------------------------
          // LIGO India
          //---------------------------------------------------------------------
          FrDetector::DQO_LIGO_INDIA,
          boost::make_shared< FrDetector >(
              "LIGO_India",
              "A1",
              0.0, // longitude
              0.0, // latitude
              0.0, // elevation
              0.0, // armXaltitude
              0.0, // armYaltitude
              0.0, // armXazimuth
              0.0, // armYazimuth
              0.0, // armXmidpoint
              0.0, // armYmidpoint
              FrDetector::DQO_LIGO_INDIA // dataQualityOffset
              ) },
        { //---------------------------------------------------------------------
          // LIVINGSTON - Derived from:
          // /archive/frames/S5/L3/LLO/L-RDS_R_L3-8766/L-RDS_R_L3-876699904-256.gwf
          //---------------------------------------------------------------------
          FrDetector::DQO_LLO_4K,
          boost::make_shared< FrDetector >(
              "LLO_4k", // name
              "L1", // prefix
              -1.58431, // longitude
              0.533423, // latitude
              -6.574, // elevation
              4.40318, // armXazimuth
              2.83238, // armYazimuth
              -0.0003121, // armXaltitude
              -0.0006107, // armYaltitude
              1997.57, // armXmidpoint
              1997.57, // armYmidpoint
              FrDetector::DQO_LLO_4K // dataQualityOffset
              ) },
        { //---------------------------------------------------------------------
          // TAMA -
          // Values taken from lal/src/tools/LALDetectors.h
          //---------------------------------------------------------------------
          FrDetector::DQO_TAMA_300,
          boost::make_shared< FrDetector >(
              "TAMA_300", // name
              "T1", // prefix
              2.43536359469, // longitude
              0.62267336022, // latitude
              90.0, // elevation
              4.71238898038, // armXazimuth
              3.14159265359, // armYazimuth
              0.00000000000, // armXaltitude
              0.00000000000, // armYaltitude
              150.00000000000, // armXmidpoint
              150.00000000000, // armYmidpoint
              FrDetector::DQO_TAMA_300 // dataQualityOffset
              ) },
        { //---------------------------------------------------------------------
          // VIRGO - Derived from:
          // /archive/frames/WSR8/HrecV3/Virgo/V-HrecV3-855/V-HrecV3-855302400-2240.gwf
          //---------------------------------------------------------------------
          FrDetector::DQO_VIRGO,
          boost::make_shared< FrDetector >(
              "Virgo",
              "V1", // prefix
              -10.5045, // longitude
              43.6316, // latitude
              51.884, // elevation
              -0.3229, // armXazimuth
              4.3895, // armYazimuth
              0.0, // armXaltitude
              0.0, // armYaltitude
              1500.0, // armXmidpoint
              1500.0, // armYmidpoint
              FrDetector::DQO_VIRGO // dataQualityOffset
              ) }

    };

    const FrameCPP::Common::DetectorNames&
    dnt_init( )
    {
        static FrameCPP::Common::DetectorNames retval;

        //-----------------------------------------------------------------
        // Setup the DetectorNameTable to be consistent with FrDetectorCache
        // This must match the entries in FrDetectorCache and the mappings
        // in Data::name_map and Data::prefix_map (lines 400-424)
        //-----------------------------------------------------------------
        typedef std::pair< int, int > dqb;

        // Add all detectors - ensure consistency with FrDetectorCache entries
        // and the name_map/prefix_map defined at lines 400-424
        retval( "ACIGA",
                "ACIGA",
                "U1:",
                dqb( FrDetector::DQO_ACIGA, FrDetector::DQO_ACIGA + 1 ) );
        retval( "CIT_40",
                "Caltech 40 meters",
                "C1:",
                dqb( FrDetector::DQO_CIT_40, FrDetector::DQO_CIT_40 + 1 ) );
        retval( "GEO_600",
                "GEO 600",
                "G1:",
                dqb( FrDetector::DQO_GEO_600, FrDetector::DQO_GEO_600 + 1 ) );
        retval( "KAGRA",
                "KAGRA",
                "K1:",
                dqb( FrDetector::DQO_KAGRA, FrDetector::DQO_KAGRA + 1 ) );
        retval( "LHO_4k",
                "LIGO LHO 4 km",
                "H1:",
                dqb( FrDetector::DQO_LHO_4K, FrDetector::DQO_LHO_4K + 1 ) );
        retval(
            "LIGO_India",
            "LIGO India",
            "A1:",
            dqb( FrDetector::DQO_LIGO_INDIA, FrDetector::DQO_LIGO_INDIA + 1 ) );
        retval( "LLO_4k",
                "LIGO LLO 4 km",
                "L1:",
                dqb( FrDetector::DQO_LLO_4K, FrDetector::DQO_LLO_4K + 1 ) );
        retval( "TAMA_300",
                "TAMA 300",
                "T1:",
                dqb( FrDetector::DQO_TAMA_300, FrDetector::DQO_TAMA_300 + 1 ) );
        retval( "VIRGO",
                "Virgo 3 km",
                "V1:",
                dqb( FrDetector::DQO_VIRGO, FrDetector::DQO_VIRGO + 1 ) );

        retval.Freeze( );

        return retval;
    }
} // namespace

namespace FrameCPP
{
    namespace Universal
    {
        const Common::DetectorNames& DetectorNameTable = dnt_init( );

        //=======================================================================
        // FrDetector
        //=======================================================================
        FrDetector::FrDetector( )
        {
            prefix[ 0 ] = ' ';
            prefix[ 1 ] = ' ';
            longitude = 0.0;
            latitude = 0.0;
            elevation = 0.0;
            armXazimuth = 0.0;
            armYazimuth = 0.0;
            armXaltitude = 0.0;
            armYaltitude = 0.0;
            armXmidpoint = 0.0;
            armYmidpoint = 0.0;
            updateDebugTypeName();
        }

        FrDetector::FrDetector( const name_type&             Name,
                                const char*                  Prefix,
                                const longitude_type         Longitude,
                                const latitude_type          Latitude,
                                const elevation_type         Elevation,
                                const armXazimuth_type       ArmXazimuth,
                                const armYazimuth_type       ArmYazimuth,
                                const armXaltitude_type      ArmXaltitude,
                                const armYaltitude_type      ArmYaltitude,
                                const armXmidpoint_type      ArmXmidpoint,
                                const armYmidpoint_type      ArmYmidpoint,
                                const dataQualityOffset_type DataQualityOffset )
        {
            name = Name;
            // Handle short/empty prefix strings safely
            prefix[ 0 ] = ( Prefix && Prefix[ 0 ] ) ? Prefix[ 0 ] : ' ';
            prefix[ 1 ] = ( Prefix && Prefix[ 0 ] && Prefix[ 1 ] ) ? Prefix[ 1 ] : ' ';
            longitude = Longitude;
            latitude = Latitude;
            elevation = Elevation;
            armXazimuth = ArmXazimuth;
            armYazimuth = ArmYazimuth;
            armXaltitude = ArmXaltitude;
            armYaltitude = ArmYaltitude;
            armXmidpoint = ArmXmidpoint;
            armYmidpoint = ArmYmidpoint;
            dataQualityOffset = DataQualityOffset;
            updateDebugTypeName();
        }

        FrDetector::FrDetector( const FrDetector& detector )
        {
            AT( );
            name = detector.name;
            AT( );
            prefix[ 0 ] = detector.prefix[ 0 ];
            AT( );
            prefix[ 1 ] = detector.prefix[ 1 ];
            AT( );
            longitude = detector.longitude;
            AT( );
            latitude = detector.latitude;
            AT( );
            elevation = detector.elevation;
            AT( );
            armXazimuth = detector.armXazimuth;
            AT( );
            armYazimuth = detector.armYazimuth;
            AT( );
            armXaltitude = detector.armXaltitude;
            AT( );
            armYaltitude = detector.armYaltitude;
            AT( );
            armXmidpoint = detector.armXmidpoint;
            AT( );
            armYmidpoint = detector.armYmidpoint;
            AT( );
            dataQualityOffset = detector.dataQualityOffset;
            AT( );
            aux = detector.aux;
            AT( );
            table = detector.table;
            AT( );
            updateDebugTypeName();
        }

        FrDetector::FrDetectorCache_element
        FrDetector::GetDetector( dataQualityOffsets Offset )
        {
            auto detector_iter = FrDetectorCache.find( Offset );
            if ( detector_iter != FrDetectorCache.end( ) )
            {
                return ( detector_iter->second );
            }
            throw std::range_error( "Unknown detector requested" );
        }

        const std::string&
        FrDetector::GetName( ) const
        {
            return Data::GetName( );
        }

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

    } // namespace Universal
} // namespace FrameCPP

namespace FrameCPP
{
    namespace Universal
    {
        namespace FrDetectorImpl
        {
            Data::name_map_type Data::name_map =
                make_bimap< Data::name_map_type::left_key_type,
                            Data::name_map_type::right_key_type >(
                    { { "ACIGA", DQO_ACIGA },
                      { "CIT_40", DQO_CIT_40 },
                      { "GEO_600", DQO_GEO_600 },
                      { "KAGRA", DQO_KAGRA },
                      { "LHO_4k", DQO_LHO_4K },
                      { "LIGO_India", DQO_LIGO_INDIA },
                      { "LLO_4k", DQO_LLO_4K },
                      { "TAMA_300", DQO_TAMA_300 },
                      { "VIRGO", DQO_VIRGO } } );

            Data::prefix_map_type Data::prefix_map =
                make_bimap< prefix_map_type::left_key_type,
                            prefix_map_type::right_key_type >(
                    { { "A1", DQO_LIGO_INDIA },
                      { "C1", DQO_CIT_40 },
                      { "G1", DQO_GEO_600 },
                      { "H1", DQO_LHO_4K },
                      { "K1", DQO_KAGRA },
                      { "L1", DQO_LLO_4K },
                      { "T1", DQO_TAMA_300 },
                      { "U1", DQO_ACIGA },
                      { "V1", DQO_VIRGO } } );

        } // namespace FrDetectorImpl
    } // namespace Universal
} // namespace FrameCPP

//=======================================================================
//               C L A S S I C   I O
//=======================================================================
namespace FrameCPP
{
    namespace Universal
    {
        namespace FrDetectorImpl
        {
            //-----------------------------------------------------------------
            /// @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 Common::Description*
            ClassicIO< FrDetector >::GetDescription(
                unsigned int frame_stream_version ) const
            {
                using Common::Description;

                Description* description = nullptr;

                if ( frame_stream_version >= 9 )
                {
                    static Description v9_description;

                    if ( v9_description.size( ) == 0 )
                    {
                        v9_description( FrSH( Data::FrameSpecName( ),
                                              Data::FrameSpecId( ),
                                              Data::FrameSpecDescription( ) ) );

                        v9_description(
                            FrSE( "name",
                                  Description::Ascii< io_name_type >( ),
                                  "Instrument name." ) );
                        v9_description(
                            FrSE( "prefix",
                                  Description::AsciiArray< CHAR_U >( "2" ).c_str(),
                                  "Channel prefix for this detector" ) );
                        v9_description(
                            FrSE( "longitude",
                                  Description::Ascii< v9::longitude_type >( ),
                                  "Detector vertex longitude, geographical "
                                  "coordinates" ) );
                        v9_description(
                            FrSE( "latitude",
                                  Description::Ascii< v9::latitude_type >( ),
                                  "Detector vertex latitude, geographical "
                                  "coordinates" ) );
                        v9_description(
                            FrSE( "elevation",
                                  Description::Ascii< v9::elevation_type >( ),
                                  "Vertex elevation, meters, relative to WGS84 "
                                  "ellipsoid." ) );
                        v9_description(
                            FrSE( "armXazimuth",
                                  Description::Ascii< v9::armXazimuth_type >( ),
                                  "Orientation of X arm, measured in radians "
                                  "East of North" ) );
                        v9_description(
                            FrSE( "armYazimuth",
                                  Description::Ascii< v9::armYazimuth_type >( ),
                                  "Orientation of Y arm, measured in radians "
                                  "East of North" ) );
                        v9_description( FrSE(
                            "armXaltitude",
                            Description::Ascii< v9::armXaltitude_type >( ),
                            "Altitude (pitch) angle of X arm, measured in "
                            "radians above horizon" ) );
                        v9_description( FrSE(
                            "armYaltitude",
                            Description::Ascii< v9::armYaltitude_type >( ),
                            "Altitude (pitch) angle of Y arm, measured in "
                            "radians above horizon" ) );
                        v9_description( FrSE(
                            "armXmidpoint",
                            Description::Ascii< v9::armXmidpoint_type >( ),
                            "Distance between the detector vertex and the "
                            "middle of the X cavity (meters) (should be zero "
                            "for bars)" ) );
                        v9_description( FrSE(
                            "armYmidpoint",
                            Description::Ascii< v9::armYmidpoint_type >( ),
                            "Distance between the detector vertex and the "
                            "middle of the Y cavity (meters) (should be zero "
                            "for bars)" ) );
                        v9_description( FrSE(
                            "dataQualityOffset",
                            Description::Ascii< v9::dataQualityOffset_type >( ),
                            "Bit offset of the low order bit of the data "
                            "quality mask." ) );

                        v9_description(
                            FrSE( "aux",
                                  PTR_STRUCT::Desc( FrVect::StructName( ) ),
                                  "Identifier for user-provided (presently "
                                  "undefined) structure for additional "
                                  "detector data." ) );
                        v9_description(
                            FrSE( "table",
                                  PTR_STRUCT::Desc( FrTable::StructName( ) ),
                                  "Identifier for user-provided (presently "
                                  "undefined) table structure for additional "
                                  "detector data." ) );

                        v9_description(
                            FrSE( "next",
                                  PTR_STRUCT::Desc( Data::FrameSpecName( ) ),
                                  "Identifier for next detector structure in "
                                  "the linked list" ) );

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

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

                        v8_description(
                            FrSE( "name",
                                  Description::Ascii< io_name_type >( ),
                                  "Instrument name." ) );
                        v8_description(
                            FrSE( "prefix",
                                  Description::AsciiArray< CHAR_U >( "2" ).c_str(),
                                  "Channel prefix for this detector" ) );
                        v8_description(
                            FrSE( "longitude",
                                  Description::Ascii< v8::longitude_type >( ),
                                  "Detector vertex longitude, geographical "
                                  "coordinates" ) );
                        v8_description(
                            FrSE( "latitude",
                                  Description::Ascii< v8::latitude_type >( ),
                                  "Detector vertex latitude, geographical "
                                  "coordinates" ) );
                        v8_description(
                            FrSE( "elevation",
                                  Description::Ascii< v8::elevation_type >( ),
                                  "Vertex elevation, meters, relative to WGS84 "
                                  "ellipsoid." ) );
                        v8_description(
                            FrSE( "armXazimuth",
                                  Description::Ascii< v8::armXazimuth_type >( ),
                                  "Orientation of X arm, measured in radians "
                                  "East of North." ) );
                        v8_description(
                            FrSE( "armYazimuth",
                                  Description::Ascii< v8::armYazimuth_type >( ),
                                  "Orientation of Y arm, measured in radians "
                                  "East of North" ) );
                        v8_description( FrSE(
                            "armXaltitude",
                            Description::Ascii< v8::armXaltitude_type >( ),
                            "Altitude (pitch) angle of X arm, measured in "
                            "radians above horizon" ) );
                        v8_description( FrSE(
                            "armYaltitude",
                            Description::Ascii< v8::armYaltitude_type >( ),
                            "Altitude (pitch) angle of Y arm, measured in "
                            "radians above horizon" ) );
                        v8_description( FrSE(
                            "armXmidpoint",
                            Description::Ascii< v8::armXmidpoint_type >( ),
                            "Distance between the detector vertex and the "
                            "middle of the X cavity (meters)" ) );
                        v8_description( FrSE(
                            "armYmidpoint",
                            Description::Ascii< v8::armYmidpoint_type >( ),
                            "Distance between the detector vertex and the "
                            "middle of the Y cavity (meters)" ) );
                        v8_description(
                            FrSE( "localTime",
                                  Description::Ascii< v8::localTime_type >( ),
                                  "Local seasonal time - UTC in seconds" ) );

                        v8_description(
                            FrSE( "aux",
                                  PTR_STRUCT::Desc( FrVect::StructName( ) ),
                                  "Identifier for user-provided (presently "
                                  "undefined) structure for additional "
                                  "detector data." ) );
                        v8_description(
                            FrSE( "table",
                                  PTR_STRUCT::Desc( FrTable::StructName( ) ),
                                  "Identifier for user-provided (presently "
                                  "undefined) table structure for additional "
                                  "detector data." ) );

                        v8_description(
                            FrSE( "next",
                                  PTR_STRUCT::Desc( Data::FrameSpecName( ) ),
                                  "Identifier for next detector structure in "
                                  "the linked list" ) );

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

                    if ( v5_description.size( ) == 0 )
                    {
                        v5_description( FrSH( Data::FrameSpecName( ),
                                              Data::FrameSpecId( ),
                                              Data::FrameSpecDescription( ) ) );

                        v5_description(
                            FrSE( "name",
                                  Description::Ascii< io_name_type >( ),
                                  "Instrument name." ) );
                        v5_description(
                            FrSE( "prefix",
                                  Description::AsciiArray< CHAR_U >( "2" ).c_str(),
                                  "Channel prefix for this detector" ) );
                        v5_description(
                            FrSE( "longitude",
                                  Description::Ascii< v5::longitude_type >( ),
                                  "Detector vertex longitude, geographical "
                                  "coordinates" ) );
                        v5_description(
                            FrSE( "latitude",
                                  Description::Ascii< v5::latitude_type >( ),
                                  "Detector vertex latitude, geographical "
                                  "coordinates" ) );
                        v5_description(
                            FrSE( "elevation",
                                  Description::Ascii< v5::elevation_type >( ),
                                  "Vertex elevation, meters. relative to WGS84 "
                                  "ellipsoid." ) );
                        v5_description(
                            FrSE( "armXazimuth",
                                  Description::Ascii< v5::armXazimuth_type >( ),
                                  "Orientation of X arm, measured in radians "
                                  "East of North" ) );
                        v5_description(
                            FrSE( "armYazimuth",
                                  Description::Ascii< v5::armYazimuth_type >( ),
                                  "Orientation of Y arm, measured in radians "
                                  "East of North " ) );
                        v5_description( FrSE(
                            "armXaltitude",
                            Description::Ascii< v5::armXaltitude_type >( ),
                            "Altitude (pitch) angle of X arm, measured in "
                            "radians above horizon" ) );
                        v5_description( FrSE(
                            "armYaltitude",
                            Description::Ascii< v5::armYaltitude_type >( ),
                            "Altitude (pitch) angle of Y arm, measured in "
                            "radians above horizon" ) );
                        v5_description( FrSE(
                            "armXmidpoint",
                            Description::Ascii< v5::armXmidpoint_type >( ),
                            "Distance between the detector vertex and the "
                            "middle of the X cavity (meters)" ) );
                        v5_description( FrSE(
                            "armYmidpoint",
                            Description::Ascii< v5::armYmidpoint_type >( ),
                            "Distance between the detector vertex and the "
                            "middle of the X cavity (meters)" ) );
                        v5_description(
                            FrSE( "localTime",
                                  Description::Ascii< v5::localTime_type >( ),
                                  "Local seasonal time - UTC in seconds" ) );

                        v5_description(
                            FrSE( "aux",
                                  PTR_STRUCT::Desc( FrVect::StructName( ) ),
                                  "Identifier for user-provided (presently "
                                  "undefined) structure for additional "
                                  "detector data." ) );
                        v5_description(
                            FrSE( "table",
                                  PTR_STRUCT::Desc( FrTable::StructName( ) ),
                                  "Identifier for user-provided (presently "
                                  "undefined) table structure for additional "
                                  "detector data" ) );

                        v5_description(
                            FrSE( "next",
                                  PTR_STRUCT::Desc( Data::FrameSpecName( ) ),
                                  "Identifier for next detector structure in "
                                  "the linked list." ) );
                    }
                    description = &v5_description;
                }
                else if ( frame_stream_version >= 4 )
                {
                    static Description v4_description;

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

                        v4_description(
                            FrSE( "name",
                                  Description::Ascii< io_name_type >( ),
                                  "Instrument name." ) );
                        v4_description(
                            FrSE( "longitudeD",
                                  Description::Ascii< v4::longitudeD_type >( ),
                                  "Detector vertex longitude, geographical "
                                  "coordinates: Integer degrees; DDD>0 => E of "
                                  "Greenwich" ) );
                        v4_description( FrSE(
                            "longitudeM",
                            Description::Ascii< v4::longitudeM_type >( ),
                            "Detector vertex longitude, geographical "
                            "coordinates: Integer minutes; 60 > MM >= 0" ) );
                        v4_description( FrSE(
                            "longitudeS",
                            Description::Ascii< v4::longitudeS_type >( ),
                            "Detector vertex longitude, geographical "
                            "coordinates: Decimal seconds; 60.0 > SS >= 0" ) );
                        v4_description(
                            FrSE( "latitudeD",
                                  Description::Ascii< v4::latitudeD_type >( ),
                                  "Detector vertex latitude, geographical "
                                  "coordinates: Integer degrees; DDD>0 => E of "
                                  "Greenwich" ) );
                        v4_description( FrSE(
                            "latitudeM",
                            Description::Ascii< v4::latitudeM_type >( ),
                            "Detector vertex latitude, geographical "
                            "coordinates: Integer minutes; 60 > MM >= 0" ) );
                        v4_description( FrSE(
                            "latitudeS",
                            Description::Ascii< v4::latitudeS_type >( ),
                            "Detector vertex latitude, geographical "
                            "coordinates: Decimal seconds; 60.0 > SS >= 0" ) );
                        v4_description(
                            FrSE( "elevation",
                                  Description::Ascii< v4::elevation_type >( ),
                                  "Vertex elevation, meters. relative to WGS84 "
                                  "ellipsoid." ) );
                        v4_description(
                            FrSE( "armXazimuth",
                                  Description::Ascii< v4::armXazimuth_type >( ),
                                  "Orientation of X arm, measured in radians "
                                  "East of North" ) );
                        v4_description(
                            FrSE( "armYazimuth",
                                  Description::Ascii< v4::armYazimuth_type >( ),
                                  "Orientation of Y arm, measured in radians "
                                  "East of North " ) );

                        v4_description(
                            FrSE( "more",
                                  PTR_STRUCT::Desc( FrVect::StructName( ) ),
                                  "Identifier for user-provided (presently "
                                  "undefined) structure for additional "
                                  "detector data." ) );
                        v4_description(
                            FrSE( "moreTable",
                                  PTR_STRUCT::Desc( FrTable::StructName( ) ),
                                  "Identifier for user-provided (presently "
                                  "undefined) table structure for additional "
                                  "detector data" ) );
                    }
                    description = &v4_description;
                }
                else if ( frame_stream_version >= 3 )
                {
                    static Description v3_description;

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

                        v3_description(
                            FrSE( "name",
                                  Description::Ascii< io_name_type >( ),
                                  "Instrument name." ) );
                        v3_description(
                            FrSE( "longitudeD",
                                  Description::Ascii< v3::longitudeD_type >( ),
                                  "Detector vertex longitude, geographical "
                                  "coordinates: Integer degrees; DDD>0 => E of "
                                  "Greenwich" ) );
                        v3_description( FrSE(
                            "longitudeM",
                            Description::Ascii< v3::longitudeM_type >( ),
                            "Detector vertex longitude, geographical "
                            "coordinates: Integer minutes; 60 > MM >= 0" ) );
                        v3_description( FrSE(
                            "longitudeS",
                            Description::Ascii< v3::longitudeS_type >( ),
                            "Detector vertex longitude, geographical "
                            "coordinates: Decimal seconds; 60.0 > SS >= 0" ) );
                        v3_description(
                            FrSE( "latitudeD",
                                  Description::Ascii< v3::latitudeD_type >( ),
                                  "Detector vertex latitude, geographical "
                                  "coordinates: Integer degrees; DDD>0 => E of "
                                  "Greenwich" ) );
                        v3_description( FrSE(
                            "latitudeM",
                            Description::Ascii< v3::latitudeM_type >( ),
                            "Detector vertex latitude, geographical "
                            "coordinates: Integer minutes; 60 > MM >= 0" ) );
                        v3_description( FrSE(
                            "latitudeS",
                            Description::Ascii< v3::latitudeS_type >( ),
                            "Detector vertex latitude, geographical "
                            "coordinates: Decimal seconds; 60.0 > SS >= 0" ) );
                        v3_description(
                            FrSE( "elevation",
                                  Description::Ascii< v3::elevation_type >( ),
                                  "Vertex elevation, meters. relative to WGS84 "
                                  "ellipsoid." ) );
                        v3_description(
                            FrSE( "armXazimuth",
                                  Description::Ascii< v3::armXazimuth_type >( ),
                                  "Orientation of X arm, measured in radians "
                                  "East of North" ) );
                        v3_description(
                            FrSE( "armYazimuth",
                                  Description::Ascii< v3::armYazimuth_type >( ),
                                  "Orientation of Y arm, measured in radians "
                                  "East of North " ) );
                        v3_description( FrSE(
                            "armLength",
                            Description::Ascii< v3::armLength_type >( ),
                            "Length of interferometer arm, measured between "
                            "arm cavity mirror surface, in meters" ) );

                        v3_description(
                            FrSE( "more",
                                  PTR_STRUCT::Desc( FrVect::StructName( ) ),
                                  "Identifier for user-provided (presently "
                                  "undefined) structure for additional "
                                  "detector data." ) );
                    }
                    description = &v3_description;
                }
                return ( description );
            }
        } // namespace FrDetectorImpl
    } // namespace Universal
} // namespace FrameCPP
