#ifndef INCLUDED_BOBCAT_FSWAP_
#define INCLUDED_BOBCAT_FSWAP_

#include <cstring>
#include <algorithm>
#include <stdexcept>

namespace FBB
{

struct SwapMode
{
    enum Enum
    {
        STDSWAP,
        SWAPMEMBER
    };
};

template <SwapMode::Enum swapMode>
struct ModeType
{};

    // fswap1.f
    //
template <SwapMode::Enum mode = SwapMode::SWAPMEMBER,
          typename First, typename Type, typename ...MemberList>
void fswap(First *firstAddr, Type &lhs, Type &rhs, MemberList &&...swapList);

    // fswap2.f
template <SwapMode::Enum mode = SwapMode::SWAPMEMBER,
          typename Type, typename ...MemberList>
inline void fswap(Type &lhs, Type &rhs, MemberList &&...swapList);
                                    // arguments

template <typename Type>
class FSwapPOD
{
    friend struct FSwapBase;

    char *d_buffer;
    char *d_from;
    Type &d_lhs;
    Type &d_rhs;

    FSwapPOD(char *buffer, char *from, Type &lhs, Type &rhs);

        // FSwapPOD constructing function
        //
    template <typename Tp>
    friend FSwapPOD<Tp> PODfactory(char *buffer, char *from,
                                   Tp &lhs, Tp &rhs);

    template <typename Mode, typename Tp, typename ...SwapModes>
    friend struct FSwap;
};


template <typename Type>
FSwapPOD<Type>::FSwapPOD(char *buffer, char *from, Type &lhs, Type &rhs)
:
    d_buffer(buffer),
    d_from(from),
    d_lhs(lhs),
    d_rhs(rhs)
{}

template <typename Type>
inline FSwapPOD<Type> PODfactory(char *buffer, char *from,
                                 Type &lhs, Type &rhs)
{
    return FSwapPOD<Type>(buffer, from, lhs, rhs);
}

template <typename Type>
class FSwapMode
{
    SwapMode::Enum d_mode;
    Type &d_member;

    public:
        using MemberType =  Type;

        FSwapMode(SwapMode::Enum mode, Type &member);
        SwapMode::Enum mode() const;
        Type &member() const;
};

    // FSwapMode constructing functions:

    // for std::swap
    //
template <typename Type>
inline FSwapMode<Type> stdswap(Type &member);

    // for .swap() members
    //
template <typename Type>
inline FSwapMode<Type> swapmember(Type &member);


template <typename Type>
FSwapMode<Type>::FSwapMode(SwapMode::Enum mode, Type &member)
:
    d_mode(mode),
    d_member(member)
{}

template <typename Type>
inline SwapMode::Enum FSwapMode<Type>::mode() const
{
    return d_mode;
}

template <typename Type>
inline Type &FSwapMode<Type>::member() const
{
    return d_member;
}

template <typename Type>
inline FSwapMode<Type> stdswap(Type &member)
{
    return typename FSwapMode<Type>::FSwapMode(SwapMode::STDSWAP, member);
}


template <typename Type>
inline FSwapMode<Type> swapmember(Type &member)
{
    return typename FSwapMode<Type>::FSwapMode(SwapMode::SWAPMEMBER, member);
}
                                    // (stdswap(member), swapmember(member))

class FSwapBase
{
        // fswap1.f
        //
    template <SwapMode::Enum mode, typename First, typename Tp,
              typename ...Lst>
    friend void fswap(First *addr, Tp &lhs, Tp &rhs, Lst &&...list);

        // fswap2.f
        //
    template <SwapMode::Enum mode, typename Tp, typename ...Lst>
    friend void fswap(Tp &lhs, Tp &rhs, Lst &&...list);

        // fswap3.f
        //
    template <typename Tp>
    friend void fswap(Tp &lhs, Tp &rhs);

    protected:
        template <typename Type>                // swap using memcpy
        static void rawswap(FSwapPOD<Type> &pod, char *two, int size);

        template <typename Type>
        static constexpr char *addr(Type &);    // does reinterpret_cast

        template <typename Type>
        static constexpr char *addr(Type *);    // does reinterpret_cast

                                                // use specified swap method
        template <typename Type, typename FSwapMode>
        static void explicitSwap(FSwapPOD<Type> &pod, FSwapMode &&member);

                                                // actions before using a
                                                // specific swap method
        template <typename ReturnType, typename Type, typename Member>
        static ReturnType &preRawSwap(FSwapPOD<Type> &pod, Member &&member);
};

    // Raw memory swapping of a block of size bytes
    //
template <typename Type>
void FSwapBase::rawswap(FSwapPOD<Type> &pod, char *two, int size)
{
    memcpy(pod.d_buffer, pod.d_from, size);
    memcpy(pod.d_from, two,  size);
    memcpy(two, pod.d_buffer, size);
}

    // shortcuts for reinterpret_casts
    //
template <typename Type>
constexpr char *FSwapBase::addr(Type &type)
{
    return reinterpret_cast<char *>(&type);
}

template <typename Type>
constexpr char *FSwapBase::addr(Type *type)
{
    return reinterpret_cast<char *>(type);
}

    // use specified swap method
    //
template <typename Type, typename FSwapMode>
void FSwapBase::explicitSwap(FSwapPOD<Type> &pod, FSwapMode &&member)
{
    using MemberType =  typename FSwapMode::MemberType;

    MemberType &rhsSwap =     // stdSwap's address in the rhs object
            *reinterpret_cast<MemberType *>
            (
                addr(member.member()) - addr(pod.d_lhs) // shift wrt lhs, and
                +                                       // add to
                addr(pod.d_rhs)                         // &rhs
            );

    int const shift = pod.d_from - addr(pod.d_lhs); // #bytes from lhs to from

        // size: #bytes to rawswap
        // size == 0  happens if the last data member uses std::swap and
        // also if two subsequent data members usestd::swap
    int const size = addr(member.member()) - pod.d_from;
    if (size > 0)
        rawswap(pod, addr(pod.d_rhs) + shift, size);
    else if (size < 0)
        throw std::runtime_error(
            "fswap: members must be specified in order of declaration");

    if (member.mode() == SwapMode::STDSWAP)
        std::swap(member.member(), rhsSwap); // MemberTypes do std::swap
    else
        member.member().swap(rhsSwap); // otherwise: use a swap member

    pod.d_from = addr(member.member()) + sizeof(MemberType);
}

    // raw swapping up to a specified member
    //
template <typename ReturnType, typename Type, typename Member>
ReturnType &FSwapBase::preRawSwap(FSwapPOD<Type> &pod, Member &&member)
{
    ReturnType *ret =

            reinterpret_cast<ReturnType *>
            (
                addr(&member) - addr(pod.d_lhs)     // shift wrt lhs, and
                +                                   // add to
                addr(pod.d_rhs)                     // &rhs
            );

    int const shift = pod.d_from - addr(pod.d_lhs); // #bytes from lhs to from

        // size: #bytes to rawswap
        // size == 0  happens if the last data member uses std::swap and
        // also if two subsequent data members usestd::swap
    int const size = addr(&member) - pod.d_from;
    if (size > 0)
        rawswap(pod, addr(pod.d_rhs) + shift, size);
    else if (size < 0)
        throw std::runtime_error(
            "fswap: members must be specified in order of declaration");


    pod.d_from = addr(&member) + sizeof(ReturnType);

    return *ret;
}

    // declaration of the the class FastSwap: elements of SwapModes types
    // are swapped using an available swap-function.
    //
template <typename Enum, typename Type, typename ...SwapModes>
class FSwap;
    // End-specialization: nothing more to swap using swap functions
    // but rawswap anything that's left
    //
template <typename Enum, typename Type>
class FSwap<Enum, Type>: private FSwapBase
{
    static void swap(FSwapPOD<Type> &pod);

        // fswap1.f
        //
    template <SwapMode::Enum mode, typename First, typename Tp,
              typename ...Lst>
    friend void fswap(First *addr, Tp &lhs, Tp &rhs, Lst &&...list);

        // fswap2.f
        //
    template <SwapMode::Enum mode, typename Tp, typename ...Lst>
    friend void fswap(Tp &lhs, Tp &rhs, Lst &&...list);

        // fswap3.f
        //
    template <typename Tp>
    friend void fswap(Tp &lhs, Tp &rhs);

        // class FSwap
        //
    template <typename Mode, typename Tp, typename ...Lst>
    friend class  FSwap;
};

    // End-specialization: nothing more to swap using swap functions:
    // rawswap anything that's left
    //
template <typename Enum, typename Type>
void FSwap<Enum, Type>::swap(FSwapPOD<Type> &pod)
{
    int const shift = pod.d_from - addr(pod.d_lhs); // 'from' location in lhs
    int const size = sizeof(Type) - shift;          // # bytes beyond 'from'

    rawswap(pod, addr(pod.d_rhs) + shift, size);
}
    // STDSWAP by default: Use std::swap on the member at the head of the
    //  list.
    //
template <typename Type, typename Member, typename ...List>
class  FSwap<ModeType<SwapMode::STDSWAP>, Type, Member, List...>: private
                                                                FSwapBase
{
    static void swap(FSwapPOD<Type> &pod, Member &&member,
                                          List ...memberSpecs);
        // fswap1.f
        //
    template <SwapMode::Enum mode, typename First, typename Tp,
              typename ...Lst>
    friend void fswap(First *addr, Tp &lhs, Tp &rhs, Lst &&...list);

        // fswap2.f
        //
    template <SwapMode::Enum mode, typename Tp, typename ...Lst>
    friend void fswap(Tp &lhs, Tp &rhs, Lst &&...list);

        // fswap3.f
        //
    template <typename Tp>
    friend void fswap(Tp &lhs, Tp &rhs);

        // class FSwap
        //
    template <typename Mode, typename Tp, typename ...Lst>
    friend class  FSwap;
};

    // STDSWAP: Use std::swap on the member at the head of the list.
    //
template <typename Type, typename Member, typename ...List>
void FSwap<ModeType<SwapMode::STDSWAP>, Type, Member, List...>::
    swap(FSwapPOD<Type> &pod, Member &&member, List ...memberSpecs)
{
    using MemberType =  typename std::remove_reference<Member>::type;

    std::swap(member,                   // std swap `member' and the member
              preRawSwap<MemberType>(   // in the rhs object
                  pod, std::forward<Member>(member)
              )
    );

                                        // then do the tail...
    FSwap<ModeType<SwapMode::STDSWAP>, Type, List...>::
        swap(pod, std::forward<List>(memberSpecs) ...);
}
    // STDSWAP by default, but an explicit swaptype specification for a member
    //
template <typename Type, typename Member, typename ...List>
class FSwap<ModeType<SwapMode::STDSWAP>, Type, FSwapMode<Member>, List...>:
                                                            private FSwapBase
{
    static void swap(FSwapPOD<Type> &pod, FSwapMode<Member> &&member,
                                          List ...memberSpecs);
        // fswap1.f
        //
    template <SwapMode::Enum mode, typename First, typename Tp,
              typename ...Lst>
    friend void fswap(First *addr, Tp &lhs, Tp &rhs, Lst &&...list);

        // fswap2.f
        //
    template <SwapMode::Enum mode, typename Tp, typename ...Lst>
    friend void fswap(Tp &lhs, Tp &rhs, Lst &&...list);

        // fswap3.f
        //
    template <typename Tp>
    friend void fswap(Tp &lhs, Tp &rhs);

        // class FSwap
        //
    template <typename Mode, typename Tp, typename ...Lst>
    friend class  FSwap;
};

    // STDSWAP, but an explicit specification for the member at the head
    // of the list.
    //
template <typename Type, typename Member, typename ...List>
void FSwap<ModeType<SwapMode::STDSWAP>, Type, FSwapMode<Member>, List...>::
    swap(FSwapPOD<Type> &pod, FSwapMode<Member> &&member, List ...memberSpecs)
{
    explicitSwap(pod, std::forward<FSwapMode<Member>>(member));

    FSwap<ModeType<SwapMode::STDSWAP>, Type, List...>::   // do the tail
        swap(pod, std::forward<List>(memberSpecs) ...);
}
                                    // swap for the head of the list
    // SWAPMEMBER by default: Use member.swap at the head of the list.
    //
template <typename Type, typename Member, typename ...List>
class FSwap<ModeType<SwapMode::SWAPMEMBER>, Type, Member, List...>: private
                                                                    FSwapBase
{
    static void swap(FSwapPOD<Type> &pod, Member &&member,
                                          List ...memberSpecs);
        // fswap1.f
        //
    template <SwapMode::Enum mode, typename First, typename Tp,
              typename ...Lst>
    friend void fswap(First *addr, Tp &lhs, Tp &rhs, Lst &&...list);

        // fswap2.f
        //
    template <SwapMode::Enum mode, typename Tp, typename ...Lst>
    friend void fswap(Tp &lhs, Tp &rhs, Lst &&...list);

        // fswap3.f
        //
    template <typename Tp>
    friend void fswap(Tp &lhs, Tp &rhs);

        // class FSwap
        //
    template <typename Mode, typename Tp, typename ...Lst>
    friend class  FSwap;
};

    // SWAPMEMBER: Use member.swap() on the member at the head of the list.
    //
template <typename Type, typename Member, typename ...List>
void FSwap<ModeType<SwapMode::SWAPMEMBER>, Type, Member, List...>::
    swap(FSwapPOD<Type> &pod, Member &&member, List ...memberSpecs)
{
    using MemberType =  typename std::remove_reference<Member>::type;

    member.swap(                // use the swap member with the
                                // member in the rhs object
        preRawSwap<MemberType>(pod, std::forward<Member>(member))
    );

    FSwap<ModeType<SwapMode::SWAPMEMBER>, Type, List...>:: // then do the tail
        swap(pod, std::forward<List>(memberSpecs) ...);
}
    // SWAPMEMBER by default, but an explicit swaptype specification for
    // the member at the head of the list.
    //
template <typename Type, typename Member, typename ...List>
class FSwap<ModeType<SwapMode::SWAPMEMBER>,
            Type, FSwapMode<Member>, List...>: private FSwapBase
{
    static void swap(FSwapPOD<Type> &pod, FSwapMode<Member> &&member,
                                          List ...memberSpecs);

        // fswap1.f
        //
    template <SwapMode::Enum mode, typename First, typename Tp,
              typename ...Lst>
    friend void fswap(First *addr, Tp &lhs, Tp &rhs, Lst &&...list);

        // fswap2.f
        //
    template <SwapMode::Enum mode, typename Tp, typename ...Lst>
    friend void fswap(Tp &lhs, Tp &rhs, Lst &&...list);

        // fswap3.f
        //
    template <typename Tp>
    friend void fswap(Tp &lhs, Tp &rhs);

        // class FSwap
        //
    template <typename Mode, typename Tp, typename ...Lst>
    friend class  FSwap;
};

    // SWAPMEMBER, but an explicit specification for the member at the head
    // of the list.
    //
template <typename Type, typename Member, typename ...List>
void FSwap<ModeType<SwapMode::SWAPMEMBER>, Type, FSwapMode<Member>, List...>::
    swap(FSwapPOD<Type> &pod, FSwapMode<Member> &&member, List ...memberSpecs)
{
    explicitSwap(pod, std::forward<FSwapMode<Member>>(member));

    FSwap<ModeType<SwapMode::SWAPMEMBER>, Type, List...>::   // do the tail
        swap(pod, std::forward<List>(memberSpecs) ...);
}
                                    // swap for the head of the list


    // The full fswap function template, requiring an explicit first member
    // address
    //
template <SwapMode::Enum mode,
          typename First, typename Type, typename ...MemberList>
void fswap(First *firstAddr, Type &lhs, Type &rhs, MemberList &&...swapList)
{
    char buffer[sizeof(Type)];

    FSwapPOD<Type> pod{
        PODfactory(
            buffer, FSwapBase::addr(firstAddr), lhs, rhs
        )
    };

    FSwap<ModeType<mode>, Type, MemberList ...>::
        swap(pod, std::forward<MemberList>(swapList) ...);
}
    // the shorter overloaded version, calling fswap1.f with
    // firstAddr = &lhs, thus not using any shift to the address of the
    // first data member
    //
template <SwapMode::Enum mode,
          typename Type, typename ...MemberList>
inline void fswap(Type &lhs, Type &rhs, MemberList &&...swapList)
{
    fswap<mode>(FSwapBase::addr(lhs),
        lhs, rhs, std::forward<MemberList>(swapList) ...);
}
    // traditional fswap operation
    //
template <typename Type>
void fswap(Type &lhs, Type &rhs)
{
    char buffer[sizeof(Type)];

    FSwapPOD<Type> pod{
        PODfactory(buffer, FSwapBase::addr(lhs), lhs, rhs)};

    FSwapBase::rawswap(pod, FSwapBase::addr(&rhs), sizeof(Type));
}
    // overloaded fswap function swapping pointers
    //
template <typename Type>
inline void fswap(Type *&lhs, Type *&rhs)
{
    std::swap(lhs, rhs);
}

}   // namespace FBB

#endif
