//
// immer: immutable data structures for C++
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
//
// This software is distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
//

#pragma once

#include <cstddef>
#include <type_traits>

#if defined(__GNUC__) && __GNUC__ == 7 && __GNUC_MINOR__ == 1
#define IMMER_BROKEN_STANDARD_LAYOUT_DETECTION 1
#define immer_offsetof(st, m) ((std::size_t) & (((st*) 0)->m))
#else
#define IMMER_BROKEN_STANDARD_LAYOUT_DETECTION 0
#define immer_offsetof offsetof
#endif

namespace immer {
namespace detail {

//
// Metafunction that returns a standard layout struct that combines
// all the standard layout types in `Ts...`, while making sure that
// empty base optimizations are used.
//
// To query a part of the type do `get<my_part>(x)`;
//
// This is useful when putting together a type that merges various
// types coming from different policies.  Some of them might be empty,
// so we shall enable empty base optimizations.  But if we just
// inherit from all of them, we would break the "standard layout"
// rules, preventing us from using `offseof(...)`.  So metafunction
// will generate the type by sometimes inheriting, sometimes adding as
// member.
//
// Note that the types are added to the combined type from right to
// left!
//
template <typename... Ts>
struct combine_standard_layout;

template <typename... Ts>
using combine_standard_layout_t = typename combine_standard_layout<Ts...>::type;

namespace csl {

template <typename T>
struct type_t
{};

template <typename U, typename T>
U& get(T& x);

template <typename U, typename T>
const U& get(const T& x);

template <typename T, typename Next = void>
struct inherit
{
    struct type
        : T
        , Next
    {
        using Next::get_;

        template <typename U>
        friend decltype(auto) get(type& x)
        {
            return x.get_(type_t<U>{});
        }
        template <typename U>
        friend decltype(auto) get(const type& x)
        {
            return x.get_(type_t<U>{});
        }

        T& get_(type_t<T>) { return *this; }
        const T& get_(type_t<T>) const { return *this; }
    };
};

template <typename T>
struct inherit<T, void>
{
    struct type : T
    {
        template <typename U>
        friend decltype(auto) get(type& x)
        {
            return x.get_(type_t<U>{});
        }
        template <typename U>
        friend decltype(auto) get(const type& x)
        {
            return x.get_(type_t<U>{});
        }

        T& get_(type_t<T>) { return *this; }
        const T& get_(type_t<T>) const { return *this; }
    };
};

template <typename T, typename Next = void>
struct member
{
    struct type : Next
    {
        T d;

        using Next::get_;

        template <typename U>
        friend decltype(auto) get(type& x)
        {
            return x.get_(type_t<U>{});
        }
        template <typename U>
        friend decltype(auto) get(const type& x)
        {
            return x.get_(type_t<U>{});
        }

        T& get_(type_t<T>) { return d; }
        const T& get_(type_t<T>) const { return d; }
    };
};

template <typename T>
struct member<T, void>
{
    struct type
    {
        T d;

        template <typename U>
        friend decltype(auto) get(type& x)
        {
            return x.get_(type_t<U>{});
        }
        template <typename U>
        friend decltype(auto) get(const type& x)
        {
            return x.get_(type_t<U>{});
        }

        T& get_(type_t<T>) { return d; }
        const T& get_(type_t<T>) const { return d; }
    };
};

template <typename T, typename Next>
struct member_two
{
    struct type
    {
        Next n;
        T d;

        template <typename U>
        friend decltype(auto) get(type& x)
        {
            return x.get_(type_t<U>{});
        }
        template <typename U>
        friend decltype(auto) get(const type& x)
        {
            return x.get_(type_t<U>{});
        }

        T& get_(type_t<T>) { return d; }
        const T& get_(type_t<T>) const { return d; }

        template <typename U>
        auto get_(type_t<U> t) -> decltype(auto)
        {
            return n.get_(t);
        }
        template <typename U>
        auto get_(type_t<U> t) const -> decltype(auto)
        {
            return n.get_(t);
        }
    };
};

template <typename... Ts>
struct combine_standard_layout_aux;

template <typename T>
struct combine_standard_layout_aux<T>
{
    static_assert(std::is_standard_layout<T>::value, "");

    using type = typename std::conditional_t<std::is_empty<T>::value,
                                             csl::inherit<T>,
                                             csl::member<T>>::type;
};

template <typename T, typename... Ts>
struct combine_standard_layout_aux<T, Ts...>
{
    static_assert(std::is_standard_layout<T>::value, "");

    using this_t = T;
    using next_t = typename combine_standard_layout_aux<Ts...>::type;

    static constexpr auto empty_this = std::is_empty<this_t>::value;
    static constexpr auto empty_next = std::is_empty<next_t>::value;

    using type = typename std::conditional_t<
        empty_this,
        inherit<this_t, next_t>,
        std::conditional_t<empty_next,
                           member<this_t, next_t>,
                           member_two<this_t, next_t>>>::type;
};

} // namespace csl

using csl::get;

template <typename... Ts>
struct combine_standard_layout
{
    using type = typename csl::combine_standard_layout_aux<Ts...>::type;
#if !IMMER_BROKEN_STANDARD_LAYOUT_DETECTION
    static_assert(std::is_standard_layout<type>::value, "");
#endif
};

} // namespace detail
} // namespace immer
