//
// 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
//

#ifndef FRAME_CPP__TEST__FR_STRUCT_HH
#define FRAME_CPP__TEST__FR_STRUCT_HH

#include <memory>

#include <boost/pointer_cast.hpp>
#include <boost/shared_ptr.hpp>

#include "framecpp/Common/FrameSpec.hh"

typedef FrameCPP::Common::FrameSpec::Object                   FrObject;
typedef FrameCPP::Common::FrameSpec::Info::frame_object_types FrameObjectTypes;
typedef boost::shared_ptr< FrObject >                         frame_object_type;
typedef frame_object_type mk_frame_object_ret_type;

static FrameCPP::Common::IStream* NULL_ISTREAM =
    (FrameCPP::Common::IStream*)NULL;

#define CHECK_STRING_4( P, PA, C, CA, N )                                      \
    BOOST_TEST_MESSAGE( Leader << "field: " #N );                              \
    BOOST_CHECK( C->CA( ).compare( P->PA( ) ) == 0 )

#define CHECK_STRING( P, C, A, N ) CHECK_STRING_4( P, A, C, A, N )

#define CHECK_NUMBER_CONST( V, VA, C, N )                                      \
    BOOST_TEST_MESSAGE( Leader << "field: " #N );                              \
    BOOST_CHECK( V->VA( ) == C )

#define CHECK_NUMBER_4( P, PA, C, CA, N )                                      \
    BOOST_TEST_MESSAGE( Leader << "field: " #N );                              \
    BOOST_CHECK( C->CA( ) == P->PA( ) )

#define CHECK_NUMBER( P, C, A, N ) CHECK_NUMBER_4( P, A, C, A, N )

inline void
AddressCheck( const void*        LHS,
              const void*        RHS,
              bool               Sameness,
              bool               Nullness,
              const std::string& Leader )
{
    //---------------------------------------------------------------------
    // Check if the addresses are the same
    //---------------------------------------------------------------------
    BOOST_TEST_MESSAGE( Leader << " Address Check" );
    BOOST_CHECK( ( LHS == RHS ) == Sameness );
    ;
    //---------------------------------------------------------------------
    // Check if the addresses is null
    //---------------------------------------------------------------------
    BOOST_TEST_MESSAGE( Leader << " Null Check" );
    BOOST_CHECK( ( LHS == (void*)NULL ) == Nullness );
}

mk_frame_object_ret_type
mk_frame_object( int SpecVersion,
                 FrameCPP::Common::FrameSpec::Info::frame_object_types Type );

template < int V >
mk_frame_object_ret_type mk_frame_object( FrameObjectTypes Type );

template < int V >
void verify_downconvert( frame_object_type  FrameObj,
                         const std::string& Leader );

template < int V >
void verify_upconvert( frame_object_type FrameObj, const std::string& Leader );

template < typename FrameObjCurType,
           typename FrameObjPrevType = FrameObjCurType >
class Demote
{
public:
    typedef typename boost::shared_ptr< FrameObjCurType >  current_type;
    typedef typename current_type::element_type            current_element_type;
    typedef typename boost::shared_ptr< FrameObjPrevType > previous_type;
    typedef typename previous_type::element_type previous_element_type;

    inline static void
    OutOfExistance( current_type       FrameObj,
                    int                PreviousSpecVersion,
                    const std::string& Leader )
    {
        current_type obj(
            boost::dynamic_pointer_cast< current_element_type >( FrameObj ) );

        if ( obj )
        {
            boost::shared_ptr< FrObject > previous(
                obj->DemoteObject( PreviousSpecVersion, obj, NULL_ISTREAM ) );
            AddressCheck( previous.get( ), obj.get( ), false, true, Leader );
        }
    }

    inline static previous_type
    Previous( current_type       Current,
              int                PreviousSpecVersion,
              const std::string& Leader )
    {
        previous_type previous;

        if ( Current )
        {
            boost::shared_ptr< FrObject > previous_object(
                Current->DemoteObject(
                    PreviousSpecVersion, Current, NULL_ISTREAM ) );
            previous = boost::dynamic_pointer_cast< previous_element_type >(
                previous_object );
            //-----------------------------------------------------------------
            // Address validation
            //-----------------------------------------------------------------
            AddressCheck(
                previous.get( ), Current.get( ), false, false, Leader );
        }
        return previous;
    };

    inline static void
    ToSame( current_type       FrameObj,
            int                PreviousSpecVersion,
            const std::string& Leader )
    {
        current_type obj(
            boost::dynamic_pointer_cast< current_element_type >( FrameObj ) );

        if ( obj )
        {
            boost::shared_ptr< FrObject > pobj(
                obj->DemoteObject( PreviousSpecVersion, obj, NULL_ISTREAM ) );
            previous_type previous(
                boost::dynamic_pointer_cast< previous_element_type >( pobj ) );
            AddressCheck( previous.get( ), obj.get( ), true, false, Leader );
        }
    }

private:
    inline static void
    demote( current_type                   Cur,
            previous_type&                 Previous,
            int                            PreviousSpecVersion,
            boost::shared_ptr< FrObject >& DemotedObj )
    {
        DemotedObj(
            Cur->DemoteObject( PreviousSpecVersion, Cur, NULL_ISTREAM ) );
        Previous =
            boost::dynamic_pointer_cast< previous_element_type >( DemotedObj );
    }
};

#define DEMOTE_TO_PREVIOUS_DIFF( CUR, PRE )                                    \
    typedef boost::shared_ptr< CUR > cast_type;                                \
    typedef CUR                      cur_type;                                 \
    typedef PREVIOUS_NAMESPACE::PRE  pre_type;                                 \
                                                                               \
    cast_type current(                                                         \
        boost::dynamic_pointer_cast< cast_type::element_type >( FrameObj ) );  \
                                                                               \
    Demote< cur_type, pre_type >::previous_type previous(                      \
        Demote< cur_type, pre_type >::Previous(                                \
            current, PREVIOUS_TEMPLATE_SPEC, Leader ) )

#define DEMOTE_TO_PREVIOUS( CUR ) DEMOTE_TO_PREVIOUS_DIFF( CUR, CUR )

#define DEMOTE_TO_SAME( CUR )                                                  \
    boost::shared_ptr< CUR > obj_(                                             \
        boost::dynamic_pointer_cast< CUR >( FrameObj ) );                      \
                                                                               \
    Demote< CUR, PREVIOUS_NAMESPACE::CUR >::ToSame(                            \
        obj_, PREVIOUS_TEMPLATE_SPEC, Leader )

#define DEMOTE_TO_NULL( CUR )                                                  \
    boost::shared_ptr< CUR > obj_(                                             \
        boost::dynamic_pointer_cast< CUR >( FrameObj ) );                      \
                                                                               \
    Demote< CUR, CUR >::OutOfExistance( obj_, PREVIOUS_TEMPLATE_SPEC, Leader )

template < typename FrameObjectTypes >
struct PreviousInfo
{
};

template <
    typename FrameObjCurType,
    typename FrameObjPreType = typename PreviousInfo< FrameObjCurType >::type >
void
DemoteToSame( frame_object_type  FrameObj,
              int                PreviousSpec,
              const std::string& Leader )
{
    boost::shared_ptr< FrameObjCurType > obj_(
        boost::dynamic_pointer_cast< FrameObjCurType >( FrameObj ) );

    Demote< FrameObjCurType, FrameObjPreType >::ToSame(
        obj_, PreviousSpec, Leader );
}

template < typename FrameObjCurType,
           typename FrameObjPrevType = FrameObjCurType >
class Promote
{
public:
    typedef typename boost::shared_ptr< FrameObjCurType >  current_type;
    typedef typename current_type::element_type            current_element_type;
    typedef typename boost::shared_ptr< FrameObjPrevType > previous_type;
    typedef typename previous_type::element_type previous_element_type;

    inline static void
    FromNothing( frame_object_type  FrameObj,
                 int                SpecVersion,
                 int                PreviousSpecVersion,
                 const std::string& Leader )
    {
        using FrameCPP::Common::FrameSpec;

        //---------------------------------------------------------------------
        //
        //---------------------------------------------------------------------
        current_type fr_object(
            boost::dynamic_pointer_cast< current_element_type >( FrameObj ) );

        //---------------------------------------------------------------------
        // Create instance of object to promote
        //---------------------------------------------------------------------
        const FrameSpec::Info::frame_object_types class_id(
            FrameSpec::Info::frame_object_types( FrameObj->GetClass( ) ) );

        previous_type previous(
            boost::dynamic_pointer_cast< previous_element_type >(
                mk_frame_object( PreviousSpecVersion, class_id ) ) );

        current_type promoted(
            boost::dynamic_pointer_cast< current_element_type >(
                fr_object->PromoteObject( SpecVersion,
                                          PreviousSpecVersion,
                                          previous,
                                          NULL_ISTREAM ) ) );

        AddressCheck( previous.get( ), promoted.get( ), true, true, Leader );
    }

    inline static void
    FromPrevious( frame_object_type  FrameObj,
                  int                SpecVersion,
                  current_type&      Current,
                  int                PreviousSpecVersion,
                  previous_type&     Previous,
                  const std::string& Leader )
    {
        if ( FrameObj )
        {
            //-------------------------------------------------------------------
            // Handle the promotion
            //-------------------------------------------------------------------
            promote(
                FrameObj, Current, Previous, PreviousSpecVersion, SpecVersion );
        }
        else
        {
            BOOST_TEST_MESSAGE(
                "WARNING: NULL FrameObj sent to PromoteFromPrevious" );
        }

        //-------------------------------------------------------------------
        // Address validation
        //-------------------------------------------------------------------
        AddressCheck( Current.get( ), Previous.get( ), false, false, Leader );
    }

    inline static void
    ToSame( frame_object_type  FrameObj,
            int                SpecVersion,
            int                PreviousSpecVersion,
            const std::string& Leader )
    {
        using FrameCPP::Common::FrameSpec;

        //---------------------------------------------------------------------
        //
        //---------------------------------------------------------------------
        current_type fr_object(
            boost::dynamic_pointer_cast< current_element_type >( FrameObj ) );

        //---------------------------------------------------------------------
        //---------------------------------------------------------------------
        const FrameSpec::Info::frame_object_types class_id(
            FrameSpec::Info::frame_object_types( FrameObj->GetClass( ) ) );

        previous_type previous(
            boost::dynamic_pointer_cast< previous_element_type >(
                mk_frame_object( PreviousSpecVersion, class_id ) ) );

        current_type promoted(
            boost::dynamic_pointer_cast< current_element_type >(
                fr_object->PromoteObject( SpecVersion,
                                          PreviousSpecVersion,
                                          previous,
                                          NULL_ISTREAM ) ) );

        AddressCheck( previous.get( ), promoted.get( ), true, false, Leader );
    }

private:
    inline static void
    promote( frame_object_type FrameObj,
             current_type&     Promoted,
             previous_type&    Previous,
             int               PreviousSpecVersion,
             int               SpecVersion )
    {
        current_type typed_obj(
            boost::dynamic_pointer_cast< current_element_type >( FrameObj ) );

        Previous = boost::dynamic_pointer_cast< previous_element_type >(
            mk_frame_object(
                PreviousSpecVersion,
                FrameCPP::Common::FrameSpec::Info::frame_object_types(
                    FrameObj->GetClass( ) ) ) );
        Promoted = boost::dynamic_pointer_cast< current_element_type >(
            typed_obj->PromoteObject(
                SpecVersion, PreviousSpecVersion, Previous, NULL_ISTREAM ) );
    }
};

#if 1
#define PROMOTE_FROM_NOTHING( CUR )                                            \
    Promote< CUR >::FromNothing(                                               \
        FrameObj, TEMPLATE_SPEC, PREVIOUS_TEMPLATE_SPEC, Leader )

#define PROMOTE_FROM_PREVIOUS_DIFF( CUR, PRE )                                 \
    typedef CUR                     cur_type;                                  \
    typedef PREVIOUS_NAMESPACE::PRE pre_type;                                  \
                                                                               \
    Promote< cur_type, pre_type >::previous_type previous;                     \
    Promote< cur_type, pre_type >::current_type  promoted;                     \
                                                                               \
    Promote< cur_type, pre_type >::FromPrevious( FrameObj,                     \
                                                 TEMPLATE_SPEC,                \
                                                 promoted,                     \
                                                 PREVIOUS_TEMPLATE_SPEC,       \
                                                 previous,                     \
                                                 Leader )

#define PROMOTE_FROM_PREVIOUS( CUR ) PROMOTE_FROM_PREVIOUS_DIFF( CUR, CUR )

#define PROMOTE_TO_SAME_DIFF( CUR, PRE )                                       \
    Promote< CUR, PREVIOUS_NAMESPACE::PRE >::ToSame(                           \
        FrameObj, TEMPLATE_SPEC, PREVIOUS_TEMPLATE_SPEC, Leader )

#define PROMOTE_TO_SAME( CUR ) PROMOTE_TO_SAME_DIFF( CUR, CUR )
#endif /* 0 */

template <
    typename FrameObjCurType,
    typename FrameObjPreType = typename PreviousInfo< FrameObjCurType >::type >
void
PromoteToSame( frame_object_type  FrameObj,
               int                CurrentSpec,
               int                PreviousSpec,
               const std::string& Leader )
{
    Promote< FrameObjCurType, FrameObjPreType >::ToSame(
        FrameObj, CurrentSpec, PreviousSpec, Leader );
}
#endif /* FRAME_CPP__TEST__FR_STRUCT_HH */
