#ifndef INCLUDED_BOBCAT_FIELD_
#define INCLUDED_BOBCAT_FIELD_

#include <cstddef>
#include <cstdint>

namespace FBB
{

template <size_t base, size_t end, size_t begin>
struct Field
{
    static_assert(base >= 2,
                "Field<base, end, begin>: 'base' must be >= 2");
    static_assert(begin < end,
                "Field<base, end, begin>: 'end' must be > 'begin'");

    static constexpr uint64_t set(uint64_t src, uint64_t value);
    static constexpr uint64_t get(uint64_t value);
};


template <int power2, size_t base, size_t end, size_t begin>
struct FieldType
{
    static constexpr uint64_t set(uint64_t src, uint64_t value);    // 0.f
    static constexpr uint64_t get(uint64_t value);                  // 0.f
};

    // handle get/set if base is a pure power of 2:
    //
template <size_t base, size_t end, size_t begin>
struct FieldType<1, base, end, begin>
{
    static constexpr uint64_t set(uint64_t src, uint64_t value);    // 1.f
    static constexpr uint64_t get(uint64_t value);                  // 1.f
};

template <size_t base, size_t end, size_t begin>
inline uint64_t constexpr Field<base, end, begin>::get(uint64_t value)
{
    return FieldType<((base - 1) & base) == 0, base, end, begin>::get(value);
}
template <size_t base, size_t end, size_t begin>
inline uint64_t constexpr Field<base, end, begin>::set(uint64_t src,
                                                       uint64_t value)
{
    return FieldType<((base - 1) & base) == 0, base, end, begin>::set(src,
                                                                      value);
}

template <size_t base, size_t pwr>
struct exp1
{
    enum
    {
        value = base * exp1<base, pwr - 1>::value
    };
};

template <size_t base>
struct exp1<base, 0>
{
    enum
    {
        value = 1
    };
};
template <size_t base>
struct exp2
{
    enum
    {
        value = 1 + exp2<base / 2>::value
    };
};

template <>
struct exp2<1>
{
    enum
    {
        value = 0
    };
};

template <int power2, size_t base, size_t end, size_t begin>
inline uint64_t constexpr
    FieldType<power2, base, end, begin>::get(uint64_t value)
{
    return value / exp1<base, begin>::value
                 % exp1<base, end - begin>::value;
}
template <int power2, size_t base, size_t end, size_t begin>
inline uint64_t constexpr
    FieldType<power2, base, end, begin>::set(uint64_t src, uint64_t value)
{
    // value is:    |   A   |   B   |   C
    //                     e       b

    return src / exp1<base, end>::value            // src: A ->
                * exp1<base, end>::value +          //  A   |  ...  |   ...

           value % exp1<base, end - begin>::value   // value % b..e -> B
                 * exp1<base, begin>::value +       //  ... |   B   |   ...

           src % exp1<base, begin>::value;         //  ... |  ...  |    C
}

template <size_t base, size_t end, size_t begin>
inline uint64_t constexpr FieldType<1, base, end, begin>::get(uint64_t value)
{
    return (value & ((1 << end * exp2<base>::value) - 1))
           >> (begin * exp2<base>::value);
}
template <size_t base, size_t end, size_t begin>
inline uint64_t constexpr
    FieldType<1, base, end, begin>::set(uint64_t src, uint64_t value)
{
        // consider src as |   C   |   B   |   A   |
        //                         e       b

    return
        (
            src &
            (
                ~(                                          // all C bits
                    (
                        (1 << end * exp2<base>::value) - 1  // (all B, A bits)
                    )
                )
                |                                           // and also:
                (
                    (1 << begin * exp2<base>::value) - 1    // all A bits
                )
            )
        )
        |                                                   // and also:
        (
            (
                value
                &
                (                                       // # bits from b..e
                    (1 << (end - begin) * exp2<base>::value) - 1
                )
            )                                   // value's at most b..e bits
            << (begin * exp2<base>::value)      // moved into b's offset
        );
}


} // FBB

#endif
