#ifndef BOOST_SERIALIZATION_STD_VARIANT_HPP
#define BOOST_SERIALIZATION_STD_VARIANT_HPP

// MS compatible compilers support #pragma once
#if defined(_MSC_VER)
# pragma once
#endif

/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// variant.hpp - non-intrusive serialization of variant types
//
// copyright (c) 2019 Samuel Debionne, ESRF
//
// Use, modification and distribution is subject to the Boost Software
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
// See http://www.boost.org for updates, documentation, and revision history.
//
// Widely inspired form boost::variant serialization
//

#include <boost/serialization/throw_exception.hpp>

#include <variant>

#include <boost/archive/archive_exception.hpp>

#include <boost/serialization/split_free.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/nvp.hpp>

namespace boost {
namespace serialization {

template<class Archive>
struct std_variant_save_visitor
{
    std_variant_save_visitor(Archive& ar) :
        m_ar(ar)
    {}
    template<class T>
    void operator()(T const & value) const
    {
        m_ar << BOOST_SERIALIZATION_NVP(value);
    }
private:
    Archive & m_ar;
};


template<class Archive>
struct std_variant_load_visitor
{
    std_variant_load_visitor(Archive& ar) :
        m_ar(ar)
    {}
    template<class T>
    void operator()(T & value) const
    {
        m_ar >> BOOST_SERIALIZATION_NVP(value);
    }
private:
    Archive & m_ar;
};

template<class Archive, class ...Types>
void save(
    Archive & ar,
    std::variant<Types...> const & v,
    unsigned int /*version*/
){
    const std::size_t which = v.index();
    ar << BOOST_SERIALIZATION_NVP(which);
    std_variant_save_visitor<Archive> visitor(ar);
    std::visit(visitor, v);
}

// Minimalist metaprogramming for handling parameter pack
namespace mp {
    namespace detail {
    template <typename Seq>
    struct front_impl;

    template <template <typename...> class Seq, typename T, typename... Ts>
    struct front_impl<Seq<T, Ts...>> {
        using type = T;
    };

    template <typename Seq>
    struct pop_front_impl;

    template <template <typename...> class Seq, typename T, typename... Ts>
    struct pop_front_impl<Seq<T, Ts...>> {
        using type = Seq<Ts...>;
    };
    } //namespace detail

    template <typename... Ts>
    struct typelist {};

    template <typename Seq>
    using front = typename detail::front_impl<Seq>::type;

    template <typename Seq>
    using pop_front = typename detail::pop_front_impl<Seq>::type;
}  // namespace mp

template<std::size_t N, class Seq>
struct variant_impl
{
    template<class Archive, class V>
    static void load (
        Archive & ar,
        std::size_t which,
        V & v,
        const unsigned int version
    ){
        if(which == 0){
            // note: A non-intrusive implementation (such as this one)
            // necessary has to copy the value.  This wouldn't be necessary
            // with an implementation that de-serialized to the address of the
            // aligned storage included in the variant.
            using type = mp::front<Seq>;
            type value;
            ar >> BOOST_SERIALIZATION_NVP(value);
            v = std::move(value);
            type * new_address = & std::get<type>(v);
            ar.reset_object_address(new_address, & value);
            return;
        }
        //typedef typename mpl::pop_front<S>::type type;
        using types = mp::pop_front<Seq>;
        variant_impl<N - 1, types>::load(ar, which - 1, v, version);
    }
};

template<class Seq>
struct variant_impl<0, Seq>
{
    template<class Archive, class V>
    static void load (
        Archive & /*ar*/,
        std::size_t /*which*/,
        V & /*v*/,
        const unsigned int /*version*/
    ){}
};

template<class Archive, class... Types>
void load(
    Archive & ar,
    std::variant<Types...>& v,
    const unsigned int version
){
    std::size_t which;
    ar >> BOOST_SERIALIZATION_NVP(which);
    if(which >=  sizeof...(Types))
        // this might happen if a type was removed from the list of variant types
        boost::serialization::throw_exception(
            boost::archive::archive_exception(
                boost::archive::archive_exception::unsupported_version
            )
        );
    variant_impl<sizeof...(Types), mp::typelist<Types...>>::load(ar, which, v, version);
}

template<class Archive,class... Types>
inline void serialize(
    Archive & ar,
    std::variant<Types...> & v,
    const unsigned int file_version
){
    split_free(ar,v,file_version);
}

// Specialization for std::monostate
template<class Archive>
void serialize(Archive &ar, std::monostate &, const unsigned int /*version*/)
{}

} // namespace serialization
} // namespace boost

//template<typename T0_, BOOST_VARIANT_ENUM_SHIFTED_PARAMS(typename T)>

#include <boost/serialization/tracking.hpp>

namespace boost {
    namespace serialization {

template<class... Types>
struct tracking_level<
    std::variant<Types...>
>{
    typedef mpl::integral_c_tag tag;
    typedef mpl::int_< ::boost::serialization::track_always> type;
    BOOST_STATIC_CONSTANT(int, value = type::value);
};

} // namespace serialization
} // namespace boost

#endif //BOOST_SERIALIZATION_VARIANT_HPP
