//
// 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__FrObjectMetaData_HH
#define FrameCPP_UNIVERSAL__IMPL__FrObjectMetaData_HH

#include <map>

#include "ldastoolsal/ldas_types.h"

namespace FrameCPP
{
    namespace Universal
    {
        namespace Impl
        {
            template < typename Object_T >
            class FrObjectMetaData
            {
            public:
                struct v3
                {
                    using struct_name_type = const char* const;
                    using struct_desc_type = const char* const;
                    using struct_id_type = INT_2U const;
                };

                using v4 = v3;
                using v5 = v4;
                using v6 = v5;
                using v7 = v6;
                using v8 = v7;
                using v9 = v8;

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

                using current_version = v9;

                using struct_name_type =
                    typename current_version::struct_name_type;
                using struct_desc_type =
                    typename current_version::struct_desc_type;
                using struct_id_type = typename current_version::struct_id_type;

                //-----------------------------------------------------------------
                /// @brief Structure name according to the frame specification
                //-----------------------------------------------------------------
                static struct_name_type
                FrameSpecName( )
                {
                    return ( versioned_info.begin( )->second.name );
                }

                //-----------------------------------------------------------------
                /// @brief Structure name according to the frame specification
                //-----------------------------------------------------------------
                static struct_name_type
                FrameSpecName( int Version )
                {
                    return findVersionInfo( Version ).name;
                }

                //-----------------------------------------------------------------
                /// @brief Structure name according to the frame specification
                ///
                /// @deprecated
                ///   This method is deprecated.
                ///   Use FrameSpecName instead.
                //-----------------------------------------------------------------
                static struct_name_type
                StructName( )
                {
                    return ( FrameSpecName( ) );
                }

                //-----------------------------------------------------------------
                /// @brief Structure name according to the frame specification
                ///
                /// @deprecated
                ///   This method is deprecated.
                ///   Use FrameSpecName instead.
                //-----------------------------------------------------------------
                static struct_name_type
                StructName( int Version )
                {
                    return ( FrameSpecName( Version ) );
                }

                //-----------------------------------------------------------------
                /// @brief Return the dictionary name of the structure.
                //-----------------------------------------------------------------
                static struct_desc_type
                FrameSpecDescription( )
                {
                    return ( versioned_info.begin( )->second.description );
                }

                //-----------------------------------------------------------------
                /// @brief Return the dictionary name of the structure.
                //-----------------------------------------------------------------
                static struct_desc_type
                FrameSpecDescription( int Version )
                {
                    return findVersionInfo( Version ).description;
                }

                //-----------------------------------------------------------------
                /// @brief Return the dictionary name of the structure.
                //-----------------------------------------------------------------
                static struct_id_type
                FrameSpecId( )
                {
                    return ( struct_id );
                }

                static struct_id_type struct_id;

            protected:
                struct Info
                {
                    struct_name_type name;
                    struct_desc_type description;
                };
                using versioned_info_type = std::map< int, Info >;
                static versioned_info_type const versioned_info;

                /**
                 * @brief Finds the version information for the provided version
                 * number.
                 *
                 * This function returns a reference to the Info corresponding
                 * to the highest version number that is less than or equal to
                 * the provided version.
                 *
                 * @param version The version number for which to retrieve the
                 * information.
                 * @return const Info& A reference to the corresponding
                 * structure information.
                 * @throw std::runtime_error If the versionMap is empty or the
                 * version is too low.
                 */
                static const Info&
                findVersionInfo( int version )
                {
                    if ( versioned_info.empty( ) )
                    {
                        throw std::runtime_error(
                            "No version information available." );
                    }
                    auto firstEntry = versioned_info.begin( );
                    if ( version < firstEntry->first )
                    {
                        throw std::runtime_error(
                            "Version number is too low; no structure defined "
                            "for this version." );
                    }
                    auto it = versioned_info.upper_bound( version );
                    if ( it != versioned_info.begin( ) )
                    {
                        --it;
                    }
                    return it->second;
                }
            };

        } // namespace Impl

    } // namespace Universal
} // namespace FrameCPP

#define FR_OBJECT_META_DATA_DECLARE_UNIVERSAL( OBJECT_T )                      \
    namespace FrameCPP                                                         \
    {                                                                          \
        namespace Universal                                                    \
        {                                                                      \
            namespace Impl                                                     \
            {                                                                  \
                template <>                                                    \
                FrObjectMetaData< Universal::OBJECT_T ::Data >::               \
                    versioned_info_type const FrObjectMetaData<                \
                        Universal::OBJECT_T ::Data >::versioned_info;          \
                template <>                                                    \
                FrObjectMetaData< Universal::OBJECT_T ::Data >::struct_id_type \
                    FrObjectMetaData< Universal::OBJECT_T ::Data >::struct_id; \
            }                                                                  \
        }                                                                      \
    }

#define FR_OBJECT_META_DATA_DEFINE_UNIVERSAL( OBJECT_T, ID, NAME, DESC )       \
    namespace FrameCPP                                                         \
    {                                                                          \
        namespace Universal                                                    \
        {                                                                      \
            namespace Impl                                                     \
            {                                                                  \
                using FrameCPP::Universal::OBJECT_T ::Data;                    \
                                                                               \
                template <>                                                    \
                FrObjectMetaData< Data >::versioned_info_type const            \
                    FrObjectMetaData< Data >::versioned_info{                  \
                        { 3, { NAME, DESC } }                                  \
                    };                                                         \
                template <>                                                    \
                FrObjectMetaData< Data >::struct_id_type                       \
                    FrObjectMetaData< Data >::struct_id =                      \
                        Common::FrameSpec::Info::ID;                           \
            }                                                                  \
        }                                                                      \
    }

#define FR_OBJECT_META_DATA_DEFINE_MAP_UNIVERSAL( OBJECT_T, ID, ... )          \
    namespace FrameCPP                                                         \
    {                                                                          \
        namespace Universal                                                    \
        {                                                                      \
            namespace Impl                                                     \
            {                                                                  \
                using FrameCPP::Universal::OBJECT_T::Data;                     \
                                                                               \
                template <>                                                    \
                FrObjectMetaData< Data >::versioned_info_type const            \
                    FrObjectMetaData< Data >::versioned_info{ __VA_ARGS__ };   \
                template <>                                                    \
                FrObjectMetaData< Data >::struct_id_type                       \
                    FrObjectMetaData< Data >::struct_id =                      \
                        Common::FrameSpec::Info::ID;                           \
            }                                                                  \
        }                                                                      \
    }
#endif /* FrameCPP_UNIVERSAL__IMPL__FrObjectMetaData_HH */
