#ifndef fr_msg_hh
#define fr_msg_hh

#include <typeinfo>
#include <type_traits>

#include <boost/test/included/unit_test.hpp>

#include "ldastoolsal/ldas_types.h"

#include "framecpp/FrMsg.hh"

using FrmaeCPP::GPSTime;

namespace testing
{
    namespace fr_msg
    {
        INSTANTIATE_STRUCT_UNIVERSAL( FrMsg, FR_MSG_TYPE );

        template < int T_FRAME_SPEC_VERSION >
        using fr_msg_type =
            typename fr_object_impl< T_FRAME_SPEC_VERSION, FR_MSG_TYPE >::type;
        template < int T_FRAME_SPEC_VERSION >
        using fr_msg_previous_type =
            typename fr_object_previous_impl< T_FRAME_SPEC_VERSION,
                                              FR_MSG_TYPE >::type;

        //======================================
        // F U N C T I O N:  version
        //======================================
        // -----
        /// \brief Track where the transitions take place
        // -----
        typedef base_version_constant< 3 > version_root_type;

        template < int T_FRAME_SPEC_VERSION >
        constexpr bool
        is_root( )
        {
            return ( T_FRAME_SPEC_VERSION == version_root_type::value );
        }

        // ---------------
        // version_changes
        //
        // Track where changes to this data structure appear in the frame
        // specification
        // ---------------
        template < int T_FRAME_SPEC_VERSION >
        constexpr int
        version_changes( )
        {
            return ( std::conditional<
                     T_FRAME_SPEC_VERSION >= 8,
                     base_version_constant< 8 >::type,
                     typename std::conditional<
                         T_FRAME_SPEC_VERSION >= 6,
                         base_version_constant< 6 >::type,
                         typename std::enable_if< T_FRAME_SPEC_VERSION >=
                                                      version_root_type::value,
                                                  version_root_type::type >
                         // conditional - 6
                         >::type
                     // conditional - 8
                     >::type
                     // return value
                     ::type::value );
        }

        template < int T_FRAME_SPEC_VERSION >
        constexpr VERSION
                  version( )
        {
            return ( frame_spec_to_version_mapper<
                     version_changes< T_FRAME_SPEC_VERSION >( ) >( ) );
        }

        template < int T_FRAME_SPEC_VERSION >
        constexpr bool
        is_change_version( )
        {
            return ( testing::is_change_version<
                     T_FRAME_SPEC_VERSION,
                     version_changes< T_FRAME_SPEC_VERSION >( ) >( ) );
        }

        // ============
        // version_info
        //
        // Custumization point on how each change to the spec should be handled
        // ============
        template < int T_FRAME_SPEC_VERSION, VERSION T_VERSION >
        struct version_info;

        template < int T_FRAME_SPEC_VERSION >
        struct version_info< T_FRAME_SPEC_VERSION, VERSION::V3 >
        {
            constexpr static int frame_spec_version{ T_FRAME_SPEC_VERSION };

            typedef std::string alarm_type;
            typedef std::string message_type;
            typedef INT_4U      severity_type;

            struct expected_type
            {
                // -----------
                // Seed values
                // -----------
                // FrMsg
                alarm_type    alarm{ alarm_type( "alarm" ) };
                message_type  message{ message_type( "message" ) };
                severity_type severity{ 20 };

                bool
                compare( const fr_msg_type< T_FRAME_SPEC_VERSION >& Actual )
                {
                    return (
                        // FrMsg
                        ( Actual.GetAlarm( ).compare( alarm ) == 0 ) &&
                        ( Actual.GetMessage( ).compare( message ) == 0 ) &&
                        ( Actual.GetSeverity( ) == severity )
                        // FrMsg end
                    );
                }
                void
                set_default_constructor_values( )
                {
                    // local overrides for corner case of default constructor
                    alarm = "";
                    message = "";
                    severity = 0;
                }
            };

            static bool
            validate_constructors( )
            {
                bool retval{ true };

                try
                {
                    // -------------------
                    // Default constructor
                    // -------------------
                    fr_msg_type< T_FRAME_SPEC_VERSION > msg;

                    expected_type expected;
                    expected.set_default_constructor_values( );

                    std::cerr << "DEBUG: Default constructor "
                              << expected.compare( msg ) << std::endl;
                    retval = retval && expected.compare( msg );
                }
                catch ( ... )
                {
                    retval = false;
                }
                // -----
                // Explicit constructor
                // -----
                try
                {
                    expected_type                       expected;
                    fr_msg_type< T_FRAME_SPEC_VERSION > msg(
                        expected.alarm, expected.message, expected.severity );

                    std::cerr << "DEBUG: Explicit constructor "
                              << expected.compare( msg ) << std::endl;
                    retval = retval && expected.compare( msg );
                }
                catch ( ... )
                {
                    retval = false;
                }
                // -----
                // Copy constructor
                // -----
                try
                {
                    expected_type                       expected;
                    fr_msg_type< T_FRAME_SPEC_VERSION > source_msg(
                        expected.alarm, expected.message, expected.severity );
                    fr_msg_type< T_FRAME_SPEC_VERSION > msg( source_msg );

                    std::cerr << "DEBUG: Copy constructor "
                              << expected.compare( msg ) << std::endl;
                    retval = retval && expected.compare( msg );
                }
                catch ( ... )
                {
                    retval = false;
                }

                return ( retval );
            }

            static bool
            validate_data_types( )
            {
                fr_msg_type< frame_spec_version >    msg;
                dimension_type< frame_spec_version > dims;

                return ( // FrMsg
                    ( check_data_type_string< frame_spec_version >(
                        msg.GetAlarm( ) ) ) &&
                    ( check_data_type_string< frame_spec_version >(
                        msg.GetMessage( ) ) ) &&
                    ( check_data_type< severity_type >( msg.GetSeverity( ) ) )
                    // FrMsg - end
                );
            }
        };

        template < int T_FRAME_SPEC_VERSION >
        struct version_info< T_FRAME_SPEC_VERSION, VERSION::V6 >
        {
            constexpr static int frame_spec_version{ T_FRAME_SPEC_VERSION };

            using current_type = fr_msg_type< frame_spec_version >;
            using previous_type = fr_msg_previous_type< frame_spec_version >;

            constexpr static INT_4U current{ frame_spec_version };
            constexpr static auto   previous = previous_version( current );

            typedef std::string alarm_type;
            typedef std::string message_type;
            typedef INT_4U      severity_type;
            typedef GPSTime     gtime_type; // new

            inline static gtime_type
            DEFAULT_G_TIME( )
            {
                static gtime_type value;
                return ( value );
            }

            struct expected_type
            {
                // -----------
                // Seed values
                // -----------
                // FrMsg
                alarm_type    alarm{ alarm_type( "alarm" ) };
                message_type  message{ message_type( "message" ) };
                severity_type severity{ 20 };
                gtime_type    gtime{ gtime_type( 5, 7 ) };

                bool
                compare( const fr_msg_type< T_FRAME_SPEC_VERSION >& Actual )
                {
                    return (
                        // FrMsg
                        ( Actual.GetAlarm( ).compare( alarm ) == 0 ) &&
                        ( Actual.GetMessage( ).compare( message ) == 0 ) &&
                        ( Actual.GetSeverity( ) == severity )
                        // FrMsg end

                    );
                }
                void
                set_default_constructor_values( )
                {
                    // local overrides for corner case of default constructor
                    alarm = "";
                    message = "";
                    severity = 0;
                    gtime = DEFAULT_G_TIME( );
                }
            };

            static bool
            validate_constructors( )
            {
                bool retval{ true };

                try
                {
                    // -------------------
                    // Default constructor
                    // -------------------
                    fr_msg_type< T_FRAME_SPEC_VERSION > msg;

                    expected_type expected;
                    expected.set_default_constructor_values( );

                    std::cerr << "DEBUG: Default constructor "
                              << expected.compare( msg ) << std::endl;
                    retval = retval && expected.compare( msg );
                }
                catch ( ... )
                {
                    retval = false;
                }
                // -----
                // Explicit constructor
                // -----
                try
                {
                    expected_type                       expected;
                    fr_msg_type< T_FRAME_SPEC_VERSION > msg( expected.alarm,
                                                             expected.message,
                                                             expected.severity,
                                                             expected.gtime );

                    std::cerr << "DEBUG: Explicit constructor "
                              << expected.compare( msg ) << std::endl;
                    retval = retval && expected.compare( msg );
                }
                catch ( ... )
                {
                    retval = false;
                }
                // -----
                // Copy constructor
                // -----
                try
                {
                    expected_type                       expected;
                    fr_msg_type< T_FRAME_SPEC_VERSION > source_msg(
                        expected.alarm,
                        expected.message,
                        expected.severity,
                        expected.gtime );
                    fr_msg_type< T_FRAME_SPEC_VERSION > msg( source_msg );

                    std::cerr << "DEBUG: Copy constructor "
                              << expected.compare( msg ) << std::endl;
                    retval = retval && expected.compare( msg );
                }
                catch ( ... )
                {
                    retval = false;
                }

                return ( retval );
            }

            static bool
            validate_data_types( )
            {
                fr_msg_type< frame_spec_version >    msg;
                dimension_type< frame_spec_version > dims;

                return ( // FrMsg
                    ( check_data_type_string< frame_spec_version >(
                        msg.GetAlarm( ) ) ) &&
                    ( check_data_type_string< frame_spec_version >(
                        msg.GetMessage( ) ) ) &&
                    ( check_data_type< severity_type >( msg.GetSeverity( ) ) )
                    // FrMsg - end
                );
            }

            static bool
            compare_down_convert( object_type< frame_spec_version > Source,
                                  object_type< frame_spec_version > Derived )
            {
                bool retval{ true };

                try
                {
                    // -----
                    // Cast to concreate type
                    // -----
                    auto source_msg =
                        boost::dynamic_pointer_cast< current_type >( Source );
                    auto derived_msg =
                        boost::dynamic_pointer_cast< previous_type >( Derived );
                    // -----
                    // Do comparison
                    // -----
                    return (
                        // Must be different addresses
                        ( static_cast< const void* >( source_msg.get( ) ) !=
                          static_cast< const void* >( derived_msg.get( ) ) ) &&
                        // FrMsg
                        ( source_msg->GetAlarm( ).compare(
                              derived_msg->GetAlarm( ) ) == 0 ) &&
                        ( source_msg->GetMessage( ).compare(
                              derived_msg->GetMessage( ) ) == 0 ) &&
                        ( source_msg->GetSeverity( ) ==
                          derived_msg->GetSeverity( ) )
                        // FrMsg end
                    );
                }
                catch ( ... )
                {
                    retval = false;
                }
                return ( retval );
            }

            static bool
            compare_up_convert( object_type< frame_spec_version > Source,
                                object_type< frame_spec_version > Derived )
            {
                bool retval{ true };

                try
                {
                    // -----
                    // Cast to concreate type
                    // -----
                    auto derived_msg =
                        boost::dynamic_pointer_cast< current_type >( Derived );

                    return (
                        compare_down_convert( Derived, Source ) &&
                        ( derived_msg->GetGTime( ) == DEFAULT_G_TIME( ) ) );
                }
                catch ( ... )
                {
                    retval = false;
                }
                return ( retval );
            }
        };

        template < int T_FRAME_SPEC_VERSION >
        struct version_info< T_FRAME_SPEC_VERSION, VERSION::V8 >
        {
            constexpr static int frame_spec_version{ T_FRAME_SPEC_VERSION };

            using current_type = fr_msg_type< frame_spec_version >;
            using previous_type = fr_msg_previous_type< frame_spec_version >;

            constexpr static INT_4U current{ frame_spec_version };
            constexpr static auto   previous = previous_version( current );

            typedef std::string alarm_type;
            typedef std::string message_type;
            typedef INT_4U      severity_type;
            typedef GPSTime     gtime_type;

            inline static gtime_type
            DEFAULT_G_TIME( )
            {
                static gtime_type value;
                return ( value );
            }

            struct expected_type
            {
                // -----------
                // Seed values
                // -----------
                // FrMsg
                alarm_type    alarm{ alarm_type( "alarm" ) };
                message_type  message{ message_type( "message" ) };
                severity_type severity{ 20 };
                gtime_type    gtime{ gtime_type( 5, 7 ) };

                bool
                compare( const fr_msg_type< T_FRAME_SPEC_VERSION >& Actual )
                {
                    return (
                        // FrMsg
                        ( Actual.GetAlarm( ).compare( alarm ) == 0 ) &&
                        ( Actual.GetMessage( ).compare( message ) == 0 ) &&
                        ( Actual.GetSeverity( ) == severity )
                        // FrMsg end

                    );
                }
                void
                set_default_constructor_values( )
                {
                    // local overrides for corner case of default constructor
                    alarm = "";
                    message = "";
                    severity = 0;
                    gtime = DEFAULT_G_TIME( );
                }
            };

            static bool
            validate_constructors( )
            {
                bool retval{ true };

                try
                {
                    // -------------------
                    // Default constructor
                    // -------------------
                    fr_msg_type< T_FRAME_SPEC_VERSION > msg;

                    expected_type expected;
                    expected.set_default_constructor_values( );

                    std::cerr << "DEBUG: Default constructor "
                              << expected.compare( msg ) << std::endl;
                    retval = retval && expected.compare( msg );
                }
                catch ( ... )
                {
                    retval = false;
                }
                // -----
                // Explicit constructor
                // -----
                try
                {
                    expected_type                       expected;
                    fr_msg_type< T_FRAME_SPEC_VERSION > msg( expected.alarm,
                                                             expected.message,
                                                             expected.severity,
                                                             expected.gtime );

                    std::cerr << "DEBUG: Explicit constructor "
                              << expected.compare( msg ) << std::endl;
                    retval = retval && expected.compare( msg );
                }
                catch ( ... )
                {
                    retval = false;
                }
                // -----
                // Copy constructor
                // -----
                try
                {
                    expected_type                       expected;
                    fr_msg_type< T_FRAME_SPEC_VERSION > source_msg(
                        expected.alarm,
                        expected.message,
                        expected.severity,
                        expected.gtime );
                    fr_msg_type< T_FRAME_SPEC_VERSION > msg( source_msg );

                    std::cerr << "DEBUG: Copy constructor "
                              << expected.compare( msg ) << std::endl;
                    retval = retval && expected.compare( msg );
                }
                catch ( ... )
                {
                    retval = false;
                }

                return ( retval );
            }

            static bool
            validate_data_types( )
            {
                fr_msg_type< frame_spec_version >    msg;
                dimension_type< frame_spec_version > dims;

                return ( // FrMsg
                    ( check_data_type_string< frame_spec_version >(
                        msg.GetAlarm( ) ) ) &&
                    ( check_data_type_string< frame_spec_version >(
                        msg.GetMessage( ) ) ) &&
                    ( check_data_type< severity_type >( msg.GetSeverity( ) ) )
                    // FrMsg - end
                );
            }

            static bool
            compare_down_convert( object_type< frame_spec_version > Source,
                                  object_type< frame_spec_version > Derived )
            {
                bool retval{ true };

                try
                {
                    // -----
                    // Cast to concreate type
                    // -----
                    auto source_msg =
                        boost::dynamic_pointer_cast< current_type >( Source );
                    auto derived_msg =
                        boost::dynamic_pointer_cast< previous_type >( Derived );
                    // -----
                    // Do comparison
                    // -----
                    return (
                        // Must be different addresses
                        ( static_cast< const void* >( source_msg.get( ) ) !=
                          static_cast< const void* >( derived_msg.get( ) ) ) &&
                        // FrMsg
                        ( source_msg->GetAlarm( ).compare(
                              derived_msg->GetAlarm( ) ) == 0 ) &&
                        ( source_msg->GetMessage( ).compare(
                              derived_msg->GetMessage( ) ) == 0 ) &&
                        ( source_msg->GetSeverity( ) ==
                          derived_msg->GetSeverity( ) ) &&
                        ( source_msg->GetGTime( ) == derived_msg->GetGTime( ) )
                        // FrMsg end
                    );
                }
                catch ( ... )
                {
                    retval = false;
                }
                return ( retval );
            }

            static bool
            compare_up_convert( object_type< frame_spec_version > Source,
                                object_type< frame_spec_version > Derived )
            {
                bool retval{ true };

                try
                {
                    // -----
                    // Cast to concreate type
                    // -----
                    return ( compare_down_convert( Derived, Source ) );
                }
                catch ( ... )
                {
                    retval = false;
                }
                return ( retval );
            }
        };

        //======================================
        // F U N C T I O N:  mk_fr_msg
        //======================================

        template < int T_FRAME_SPEC_VERSION, VERSION T_VERSION >
        struct mk_fr_msg_helper;

        template < int T_FRAME_SPEC_VERSION >
        struct mk_fr_msg_helper< T_FRAME_SPEC_VERSION, VERSION::V3 >
        {
            using retval_type = fr_msg_type< T_FRAME_SPEC_VERSION >;

            static retval_type*
            create( )
            {
                static std::string ALARM( "alarming" );
                static std::string MESSAGE( "greeting" );
                static INT_4U      SEVERITY{ 20 };

                return ( new retval_type( ALARM, MESSAGE, SEVERITY ) );
            };
        };

        template < int T_FRAME_SPEC_VERSION >
        struct mk_fr_msg_helper< T_FRAME_SPEC_VERSION, VERSION::V6 >
        {
            using retval_type = fr_msg_type< T_FRAME_SPEC_VERSION >;

            static retval_type*
            create( )
            {
                typedef GPSTime gtime_type;

                static std::string ALARM( "alarming" );
                static std::string MESSAGE( "greeting" );
                static INT_4U      SEVERITY{ 20 };
                static gtime_type  GTIME{ gtime_type( 5, 7 ) };

                return ( new retval_type( ALARM, MESSAGE, SEVERITY, GTIME ) );
            };
        };

        template < int T_FRAME_SPEC_VERSION >
        struct mk_fr_msg_helper< T_FRAME_SPEC_VERSION, VERSION::V8 >
        {
            using retval_type = fr_msg_type< T_FRAME_SPEC_VERSION >;

            static retval_type*
            create( )
            {
                typedef GPSTime gtime_type;

                static std::string ALARM( "alarming" );
                static std::string MESSAGE( "greeting" );
                static INT_4U      SEVERITY{ 20 };
                static gtime_type  GTIME{ gtime_type( 5, 7 ) };

                return ( new retval_type( ALARM, MESSAGE, SEVERITY, GTIME ) );
            };
        };

        template < int T_FRAME_SPEC_VERSION >
        typename FR_MSG_TYPE< T_FRAME_SPEC_VERSION >::type*
        mk_fr_msg( )
        {
            return ( mk_fr_msg_helper<
                     T_FRAME_SPEC_VERSION,
                     version< T_FRAME_SPEC_VERSION >( ) >::create( ) );
        }

        //======================================
        // F U N C T I O N:  verify_data_types
        //======================================

        template < int T_FRAME_SPEC_VERSION >
        bool
        verify_data_types( )
        {
            return (
                version_info<
                    T_FRAME_SPEC_VERSION,
                    version< version_changes< T_FRAME_SPEC_VERSION >( ) >( ) >::
                    validate_data_types( ) );
        }

        //========================================
        // F U N C T I O N:   verify_constructors
        //========================================

        template < int T_FRAME_SPEC_VERSION >
        bool
        verify_constructors( )
        {
            return ( version_info< T_FRAME_SPEC_VERSION,
                                   version< T_FRAME_SPEC_VERSION >( ) >::
                         validate_constructors( ) );
        }

        //======================================
        // F U N C T I O N:   verify_convert
        //======================================

        // -----
        // -----
        template < int T_FRAME_SPEC_VERSION, CONVERT T_CONVERSION >
        struct verify_convert_helper;

        // -----
        /// Specialization to handle T_FRAME_SPEC_VERSION 3 as this is the root
        /// of the frame specification
        // -----
        template < int T_FRAME_SPEC_VERSION >
        struct verify_convert_helper< T_FRAME_SPEC_VERSION, CONVERT::ROOT >
        {
            static bool
            check( )
            {
                return ( convert_check_root< T_FRAME_SPEC_VERSION >(
                    mk_fr_msg< T_FRAME_SPEC_VERSION > ) );
            }
        };

        // -----
        /// Specialization to handle no conversion as the base and derived
        /// versions are the same
        // -----
        template < int T_FRAME_SPEC_VERSION >
        struct verify_convert_helper< T_FRAME_SPEC_VERSION, CONVERT::SAME >
        {
            static bool
            check( )
            {
                return ( convert_check_same< T_FRAME_SPEC_VERSION >(
                    mk_fr_msg< T_FRAME_SPEC_VERSION >,
                    mk_fr_msg< previous_version( T_FRAME_SPEC_VERSION ) > ) );
            }
        };

        // -----
        /// Specialization to handle no conversion as the base and derived
        /// versions are the same
        // -----
        template < int T_FRAME_SPEC_VERSION, CONVERT T_CONVERSION >
        struct verify_convert_helper
        {
            static bool
            check( )
            {
                constexpr INT_4U current{ T_FRAME_SPEC_VERSION };
                constexpr auto   previous = previous_version( current );

                bool retval{ true };

                // -----
                // Down convert
                // -----
                try
                {
                    // With no conversion, the object returned should be at the
                    // same address
                    object_type< T_FRAME_SPEC_VERSION > source_obj{
                        mk_fr_msg< current >( )
                    };
                    auto derived_obj = source_obj->DemoteObject(
                        previous, source_obj, nullptr );
                    retval = retval &&
                        version_info< T_FRAME_SPEC_VERSION,
                                      frame_spec_to_version_mapper<
                                          convert_to_frame_spec_mapper<
                                              T_CONVERSION >( ) >( ) >::
                            compare_down_convert( source_obj, derived_obj );
                }
                catch ( ... )
                {
                    retval = false;
                }

                // -----
                // Up convert
                // -----
                try
                {
                    // With no conversion, the object returned should be at the
                    // same address
                    object_type< T_FRAME_SPEC_VERSION > source_obj{
                        mk_fr_msg< previous >( )
                    };
                    auto derived_obj = source_obj->PromoteObject(
                        current, previous, source_obj, nullptr );
                    retval = retval &&
                        version_info< T_FRAME_SPEC_VERSION,
                                      frame_spec_to_version_mapper<
                                          convert_to_frame_spec_mapper<
                                              T_CONVERSION >( ) >( ) >::
                            compare_up_convert( source_obj, derived_obj );
                }
                catch ( ... )
                {
                    retval = false;
                }

                return ( retval );
            }
        };

        template < int T_FRAME_SPEC_VERSION >
        bool
        verify_convert( )
        {
            return ( verify_convert_helper<
                     T_FRAME_SPEC_VERSION,
                     conversion<
                         T_FRAME_SPEC_VERSION,
                         is_root< T_FRAME_SPEC_VERSION >( ),
                         is_change_version< T_FRAME_SPEC_VERSION >( ) >( ) >::
                         check( ) );
        }

        //======================================
        // F U N C T I O N:   do_standard_tests
        //======================================
        template < int FrameSpecT >
        void
        do_standard_tests( )
        {
            BOOST_TEST_MESSAGE( "Checking basic correctness of frame structure "
                                "FrMsg at version "
                                << FrameSpecT );
            BOOST_CHECK( verify_data_types< FrameSpecT >( ) );
            BOOST_CHECK( verify_constructors< FrameSpecT >( ) );
            BOOST_CHECK( verify_convert< FrameSpecT >( ) );
        }
    } // namespace fr_msg
} // namespace testing

void
fr_msg( )
{
    testing::fr_msg::do_standard_tests< 3 >( );
    testing::fr_msg::do_standard_tests< 4 >( );
    testing::fr_msg::do_standard_tests< 6 >( );
    testing::fr_msg::do_standard_tests< 7 >( );
    testing::fr_msg::do_standard_tests< 8 >( );
}

#endif /* fr_msg_hh */
