#pragma once

#include <climits>
#include <cstdint>
#include <cstddef>
#include <type_traits>
#include <utility>

namespace v2 {

namespace BitField {

template<typename>
struct Fields;

namespace detail {

// TODO: Consider adding a policy parameter; a candidate for a policy is bounds-checking and wrapping/clamping the argument to Set()
template<std::size_t Position, std::size_t Bits, typename ParentClass, typename Storage, typename Exposure = Storage>
class Field {
    friend struct Fields<Storage>;

    ParentClass object;

    constexpr Field(ParentClass object) : object(object) {}

    // Store intermediate computations as integral_constants to make sure even the dumbest compiler can inline our member functions
    // NOTE: the double Storage cast in full_mask is intended: E.g. if Storage is uint64_t and assuming 0 is of type int32_t, ~0 wouldn't match the expected 0xffffffffffffffff. On the other hand, if Storage is unsigned char, ~(Storage)0 gets promoted to the int value -1.
    static constexpr auto full_mask = std::integral_constant<Storage, static_cast<Storage>(~Storage{0})>::value;
    static constexpr auto field_mask = std::integral_constant<Storage, ((full_mask >> (8 * sizeof(Storage) - Bits)) << Position)>::value;
    static constexpr auto neg_field_mask = std::integral_constant<Storage, static_cast<Storage>(~field_mask)>::value;

public:
    operator Exposure() const {
        return static_cast<Exposure>((object.storage & field_mask) >> Position);
    }

    // Setter: Copies the storage of the parent instance and returns the manipulated value (contained in the new parent instance)
    [[nodiscard]] ParentClass Set(Exposure value) const {
        //static_assert(std::is_base_of_v<BitsOnBase, ParentClass>, "Given ParentClass does not inherit BitsOnBase");
        static_assert(sizeof(ParentClass) == sizeof(Storage), "ParentClass has additional members other than storage");

        auto ret = object;
        ret.storage = (object.storage & neg_field_mask) | ((static_cast<Storage>(value) << Position) & field_mask);
        return ret;
    }

    /// Concise version of casting to Exposure
    Exposure operator ()() const {
        return static_cast<Exposure>(*this);
    }

    /// Concise version of Set(value)
    [[nodiscard]] ParentClass operator ()(Exposure value) const {
        return Set(value);
    }

    static constexpr Exposure MaxValue() {
        // TODO: This may overflow (e.g. Bits=32, Exposure=uint32_t)
        return static_cast<Exposure>((Storage{1} << Bits) - 1);
    }

    // TODO: SignedBitField
};

// Base class defining the Field substruct. Expects ParentClass to have a single (inherited or non-inherited) member called "storage" of unsigned type (custom arithmetic allowed, howver, e.g. to implement endianness conversion).
template<typename Storage, typename ParentClass>
struct BitsOnBase {
    template<std::size_t Pos, std::size_t Bits, typename Exposure = Storage>
    auto MakeField() const {
        return Fields<Storage>{}.Make();
    }
};

} // namespace detail

/// BitFields on a given Storage type
/// TODO: Consider refactoring MakeOn to be a static constexpr function object rather than a function
template<typename Storage>
struct Fields {
    template<std::size_t Pos, std::size_t Bits, typename ParentClass, typename Exposure = Storage>
    static auto MakeOn(ParentClass obj) {
        return detail::Field<Pos, Bits, ParentClass, Storage, Exposure>{ obj };
    }

    /// Convenience overload to allow directly passing in a this pointer
    template<std::size_t Pos, std::size_t Bits, typename ParentClass, typename Exposure = Storage>
    static auto MakeOn(const ParentClass* obj) {
        return detail::Field<Pos, Bits, ParentClass, Storage, Exposure>{ *obj };
    }

    /// TODO: Privatize
    template<std::size_t Pos, std::size_t Bits, typename Exposure>
    struct MakeOn2Type {
        template<typename ParentClass>
        constexpr auto operator()(ParentClass obj) const {
            return detail::Field<Pos, Bits, ParentClass, Storage, Exposure>{ obj };
        }
    };

    template<std::size_t Pos, std::size_t Bits, typename Exposure = Storage>
    static constexpr auto MakeOn2 = MakeOn2Type<Pos, Bits, Exposure>{};
};


struct OwnStoragePolicy {};

/**
 * Convenience class to allow for terser syntax and to automatially allocate storage.
 * Child classes only define the actual fields.
 */
template<typename Storage, typename ParentClass, typename Policy = OwnStoragePolicy>
struct BitsOn : detail::BitsOnBase<Storage, ParentClass> {
    // Rely on ParentClass providing a "storage" member unless ProvideStorage is true (see specialization below)
};

template<typename Storage, typename ParentClass>
struct BitsOn<Storage, ParentClass, OwnStoragePolicy> : detail::BitsOnBase<Storage, ParentClass> {
    // Automatically allocate the "storage" member
    Storage storage;
};

} // namespace BitField

} // namespace v2

namespace v3 {

namespace BitField {

namespace detail {

template<std::size_t Bits>
using uint_least_n_bits =
    std::conditional_t<Bits <= sizeof(std::uint_least8_t) * CHAR_BIT, std::uint_least8_t,
        std::conditional_t<Bits <= sizeof(std::uint_least16_t) * CHAR_BIT, std::uint_least16_t,
            std::conditional_t<Bits <= sizeof(std::uint_least32_t) * CHAR_BIT, std::uint_least32_t,
                std::conditional_t<Bits <= sizeof(std::uint_least64_t) * CHAR_BIT, std::uint_least64_t,
                    std::uintmax_t>>>>;

template<std::size_t Position, std::size_t Bits, typename ParentClass, typename Storage, typename Exposure = Storage>
class Field {
public:
    ParentClass object;

    constexpr Field(ParentClass object) : object(object) {}

    // Store intermediate computations as integral_constants to make sure even the dumbest compiler can inline our member functions
    // NOTE: the double Storage cast in full_mask is intended: E.g. if Storage is uint64_t and assuming 0 is of type int32_t, ~0 wouldn't match the expected 0xffffffffffffffff. On the other hand, if Storage is unsigned char, ~(Storage)0 gets promoted to the int value -1.
    static constexpr auto full_mask = std::integral_constant<Storage, static_cast<Storage>(~Storage{0})>::value;
    static constexpr auto field_mask = std::integral_constant<Storage, ((full_mask >> (8 * sizeof(Storage) - Bits)) << Position)>::value;
    static constexpr auto neg_field_mask = std::integral_constant<Storage, static_cast<Storage>(~field_mask)>::value;

    // TODO: static_assert the Exposure type can store up to Bits bits. Refer to "operator Exposure()" to see how to distinguish the various cases

public:
    constexpr operator Exposure() const {
        // Extract the bit field value and convert it to Exposure. The latter step is trivial for scalar Exposures, but can be tricky for class-types:
        // * For aggregates, assign through a structured binding (because brace-initialization could be narrowing)

        if constexpr (std::is_class_v<ParentClass>) {
            auto [storage] = object;
            if constexpr (std::is_scalar_v<Exposure>) {
                return static_cast<Exposure>((storage & field_mask) >> Position);
            } else {
                // * use aggregate-initialization for aggregate types, or
                // * call single-argument constructor for non-aggregate types
                //
                // In both cases, the result type of the bit field extraction
                // may be larger than the aggregate member or constructor
                // argument. To prevent narrowing warnings in this case, we
                // static_cast the intermediate value to the smallest integer
                // that can hold Bits bits

                // TODO: How well will this work with signed storages?

                // TODO: Consider adding a customization point for this conversion
                return Exposure { static_cast<uint_least_n_bits<Bits>>((storage & field_mask) >> Position) };
            }
        } else {
            auto storage = object;
//            return Exposure { (storage & field_mask) >> Position };
            return static_cast<Exposure>((storage & field_mask) >> Position);
        }
    }

    // Setter: Copies the storage of the parent instance and returns the manipulated value (contained in the new parent instance)
    [[nodiscard]] constexpr ParentClass Set(Exposure value) const {
        //static_assert(std::is_base_of_v<BitsOnBase, ParentClass>, "Given ParentClass does not inherit BitsOnBase");
        static_assert(sizeof(ParentClass) == sizeof(Storage), "ParentClass has additional members other than storage");

        auto ret = object;
        if constexpr (std::is_class_v<ParentClass>) {
            auto& [storage] = ret;
            storage = (storage & neg_field_mask) | ((static_cast<Storage>(value) << Position) & field_mask);
        } else {
            auto storage = object;
            ret = (storage & neg_field_mask) | ((static_cast<Storage>(value) << Position) & field_mask);
        }
        return ret;
    }

    /// Concise version of casting to Exposure
    constexpr Exposure operator ()() const {
        return static_cast<Exposure>(*this);
    }

    /// Concise version of Set(value)
    [[nodiscard]] constexpr ParentClass operator ()(Exposure value) const {
        return Set(value);
    }

    static constexpr Exposure MaxValue() {
        return Exposure { (Storage{1} << Bits) - 1 };
    }

    static constexpr std::size_t NumBits() noexcept {
        return Bits;
    }
};

template<typename T>
constexpr auto GetStorage(const T& t) {
    auto& [storage] = t;
    return storage;
}

// TODO: Instead of using a PointerToMember here, a generic lens would allow for accessing e.g. arrays!
// TODO: Define generic Lens concept: Must be a callable with signature Class -> (Member -> Member) -> (Member, Class)
template<size_t Position, size_t Bits, typename ParentClass, typename MemberLens,
         typename Exposure = typename MemberLens::MemberType>
class FieldNew {
    using Storage = typename MemberLens::MemberType;

public:
    ParentClass object;

    constexpr FieldNew(ParentClass object) : object(object) {}

    // Store intermediate computations as integral_constants to make sure even the dumbest compiler can inline our member functions
    // NOTE: the double Storage cast in full_mask is intended: E.g. if Storage is uint64_t and assuming 0 is of type int32_t, ~0 wouldn't match the expected 0xffffffffffffffff. On the other hand, if Storage is unsigned char, ~(Storage)0 gets promoted to the int value -1.
    static constexpr auto full_mask = std::integral_constant<Storage, static_cast<Storage>(~Storage{0})>::value;
    static constexpr auto field_mask = std::integral_constant<Storage, ((full_mask >> (8 * sizeof(Storage) - Bits)) << Position)>::value;
    static constexpr auto neg_field_mask = std::integral_constant<Storage, static_cast<Storage>(~field_mask)>::value;

public:
    constexpr operator Exposure() const {
        return static_cast<Exposure>((MemberLens::get(object) & field_mask) >> Position);
    }

    // Setter: Copies the storage of the parent instance and returns the manipulated value (contained in the new parent instance)
    [[nodiscard]] ParentClass Set(Exposure value) const {
        return MemberLens::modify(   *this,
                                    [&](const auto& data) { return (data & neg_field_mask) | ((static_cast<Storage>(value) << Position) & field_mask); });
    }

    /// Concise version of casting to Exposure
    constexpr Exposure operator ()() const {
        return static_cast<Exposure>(*this);
    }

    /// Concise version of Set(value)
    [[nodiscard]] constexpr ParentClass operator ()(Exposure value) const {
        return Set(value);
    }

    static constexpr Exposure MaxValue() {
        // TODO: This may overflow (e.g. Bits=32, Exposure=uint32_t)
        return static_cast<Exposure>((Storage{1} << Bits) - 1);
    }

    // TODO: SignedBitField
};

template<auto PointerToMember>
struct PMDLens;

template<typename Member, typename Class, Member Class::*PointerToMember>
struct PMDLens<PointerToMember> {
    using MemberType = Member;

    template<typename Modifier>
    static Class modify(Class&& object, Modifier&& modifier) {
        // TODO: Should avoid copy for rvalue objects
        auto ret = std::forward<Class>(object);
        ret.*PointerToMember = std::forward<Modifier>(modifier)(ret.*PointerToMember);
        return ret;
    }

    template<typename Modifier>
    static Class modify(const Class& object, Modifier&& modifier) {
        auto ret = object;
        ret.*PointerToMember = std::forward<Modifier>(modifier)(ret.*PointerToMember);
        return ret;
    }

    static MemberType get(Class&& object) {
        return std::forward<Class>(object).*PointerToMember;
    }

    static MemberType get(const Class& object) {
        return object.*PointerToMember;
    }
};

} // namespace detail

template<std::size_t Position, std::size_t Bits, typename Exposure, typename ParentClass>
constexpr auto MakeFieldOn(const ParentClass* t) {
    if constexpr (std::is_class_v<ParentClass>) {
        using Storage = decltype(detail::GetStorage(std::declval<ParentClass>()));
        return detail::Field<Position, Bits, ParentClass, Storage, Exposure> { *t };
    } else {
        return detail::Field<Position, Bits, ParentClass, ParentClass, Exposure> { *t };
    }
}

template<std::size_t Position, std::size_t Bits, typename ParentClass>
constexpr auto MakeFieldOn(const ParentClass* t) {
    if constexpr (std::is_class_v<ParentClass>) {
        using Storage = decltype(detail::GetStorage(std::declval<ParentClass>()));
        return detail::Field<Position, Bits, ParentClass, Storage, Storage> { *t };
    } else {
        return detail::Field<Position, Bits, ParentClass, ParentClass, ParentClass> { *t };
    }
}

// Usage, e.g.: auto enabled() const { return BitField::v3::MakeFlagOn< 0, &StencilTest::raw1>(this); }
template<auto member, size_t Position, size_t Bits, typename Exposure = typename detail::PMDLens<member>::MemberType, typename ParentClass>
constexpr detail::FieldNew<Position, Bits, ParentClass, detail::PMDLens<member>, Exposure>
MakeFieldOn(const ParentClass* t) {
    static_assert(std::is_class_v<ParentClass>, "Must only call this overload on class types");
    static_assert(std::is_member_pointer_v<decltype(member)>, "Given member must be a pointer-to-member");
    static_assert(std::is_invocable_v<decltype(member), ParentClass>, "Given object type is not compatible with the pointer-to-member");

    return { *t };
}

template<auto member, size_t Position, typename ParentClass>
constexpr detail::FieldNew<Position, 1, ParentClass, detail::PMDLens<member>, bool>
MakeFlagOn(const ParentClass* t) {
    static_assert(std::is_class_v<ParentClass>, "Must only call this overload on class types");
    static_assert(std::is_member_pointer_v<decltype(member)>, "Given member must be a pointer-to-member");
    static_assert(std::is_invocable_v<decltype(member), ParentClass>, "Given object type is not compatible with the pointer-to-member");

    return { *t };
}


template<std::size_t Position, typename ParentClass>
constexpr auto MakeFlagOn(const ParentClass* t) {
    if constexpr (std::is_class_v<ParentClass>) {
        using Storage = decltype(detail::GetStorage(std::declval<ParentClass>()));
        return detail::Field<Position, 1, ParentClass, Storage, bool> { *t };
    } else {
        return detail::Field<Position, 1, ParentClass, ParentClass, bool> { *t };
    }
}

} // namespace BitField

} // namespace v3

namespace BitField {

namespace v2 {
using namespace ::v2::BitField;
}

namespace v3 {
using namespace ::v3::BitField;
}

}