#ifndef MS_UTILS_UNWRAPPED_SEQUENCE_NUMBER_HPP
#define MS_UTILS_UNWRAPPED_SEQUENCE_NUMBER_HPP
#include "common.hpp"
#include <limits>
#include <ostream>
#include <typeinfo>
namespace Utils
{
template<typename T>
class UnwrappedSequenceNumber
{
public:
static_assert(!std::numeric_limits<T>::is_signed, "the wrapped type must be unsigned");
static_assert(
std::numeric_limits<T>::max() < std::numeric_limits<int64_t>::max(),
"the wrapped type must be less than the int64_t value space");
class Unwrapper
{
public:
Unwrapper() = default;
Unwrapper(const Unwrapper&) = default;
Unwrapper& operator=(const Unwrapper&) = default;
public:
UnwrappedSequenceNumber<T> Unwrap(T value)
{
if (!this->lastValue)
{
this->lastUnwrapped = value;
}
else
{
const uint64_t prev = this->lastValue->GetValue();
const uint64_t curr = value;
auto delta = static_cast<int64_t>(curr - prev);
const auto half = static_cast<int64_t>(1) << (sizeof(T) * 8 - 1);
if (delta < -half)
{
delta += static_cast<int64_t>(1) << (sizeof(T) * 8);
}
else if (delta > half)
{
delta -= static_cast<int64_t>(1) << (sizeof(T) * 8);
}
this->lastUnwrapped += delta;
}
this->lastValue = UnwrappedSequenceNumber<T>(value);
return UnwrappedSequenceNumber<T>(this->lastUnwrapped);
}
UnwrappedSequenceNumber<T> PeekUnwrap(T value) const
{
if (!this->lastValue)
{
return UnwrappedSequenceNumber<T>(value);
}
const uint64_t prev = this->lastValue->GetValue();
const uint64_t curr = value;
auto delta = static_cast<int64_t>(curr - prev);
const auto half = static_cast<int64_t>(1) << (sizeof(T) * 8 - 1);
if (delta < -half)
{
delta += static_cast<int64_t>(1) << (sizeof(T) * 8);
}
else if (delta > half)
{
delta -= static_cast<int64_t>(1) << (sizeof(T) * 8);
}
return UnwrappedSequenceNumber<T>(this->lastUnwrapped + delta);
}
void Reset()
{
this->lastUnwrapped = 0;
this->lastValue.reset();
}
private:
int64_t lastUnwrapped{ 0 };
std::optional<UnwrappedSequenceNumber<T>> lastValue;
};
public:
static UnwrappedSequenceNumber<T> AddTo(UnwrappedSequenceNumber<T> value, int64_t delta)
{
return UnwrappedSequenceNumber<T>(value.value + delta);
}
static T Difference(UnwrappedSequenceNumber<T> lhs, UnwrappedSequenceNumber<T> rhs)
{
return (lhs.value > rhs.value) ? (lhs.value - rhs.value) : (rhs.value - lhs.value);
}
private:
static constexpr auto ValueLimit = static_cast<int64_t>(1) << std::numeric_limits<T>::digits;
public:
explicit UnwrappedSequenceNumber(int64_t value) : value(value)
{
}
public:
T Wrap() const
{
return static_cast<T>(this->value % UnwrappedSequenceNumber::ValueLimit);
}
bool operator==(const UnwrappedSequenceNumber<T>& other) const
{
return this->value == other.value;
}
bool operator!=(const UnwrappedSequenceNumber<T>& other) const
{
return this->value != other.value;
}
bool operator<(const UnwrappedSequenceNumber<T>& other) const
{
return this->value < other.value;
}
bool operator>(const UnwrappedSequenceNumber<T>& other) const
{
return this->value > other.value;
}
bool operator>=(const UnwrappedSequenceNumber<T>& other) const
{
return this->value >= other.value;
}
bool operator<=(const UnwrappedSequenceNumber<T>& other) const
{
return this->value <= other.value;
}
constexpr const int64_t* operator->() const
{
return std::addressof(this->value);
}
constexpr const int64_t& operator*() const&
{
return this->value;
}
constexpr const int64_t&& operator*() const&&
{
return std::move(this->value);
}
constexpr const int64_t& GetValue() const&
{
return this->value;
}
constexpr const int64_t&& GetValue() const&&
{
return std::move(this->value);
}
constexpr explicit operator const int64_t&() const&
{
return this->value;
}
void Increment()
{
++this->value;
}
UnwrappedSequenceNumber<T> GetNextValue() const
{
return UnwrappedSequenceNumber<T>(this->value + 1);
}
private:
int64_t value{ 0 };
};
template<typename T>
inline std::ostream& operator<<(std::ostream& os, const UnwrappedSequenceNumber<T>& s)
{
return os << "{T:" << typeid(T).name() << ", wrapped:" << s.Wrap();
}
template<>
inline std::ostream& operator<<(std::ostream& os, const UnwrappedSequenceNumber<uint8_t>& s)
{
return os << "{T:uint8_t, wrapped:" << s.Wrap() << "}";
}
template<>
inline std::ostream& operator<<(std::ostream& os, const UnwrappedSequenceNumber<uint16_t>& s)
{
return os << "{T:uint16_t, wrapped:" << s.Wrap() << "}";
}
template<>
inline std::ostream& operator<<(std::ostream& os, const UnwrappedSequenceNumber<uint32_t>& s)
{
return os << "{T:uint32_t, wrapped:" << s.Wrap() << "}";
}
}
#endif