//
// Copyright (c) 2008-2010 INESC Porto
//
// SPDX-License-Identifier: GPL-2.0-only
//
// Author: Gustavo J. A. M. Carneiro  <gjc@inescporto.pt> <gjcarneiro@gmail.com>
//

#ifndef NS3_SEQ_NUM_H
#define NS3_SEQ_NUM_H

#include "ns3/type-name.h"

#include <compare>
#include <iostream>
#include <limits>
#include <stdint.h>

namespace ns3
{

/**
 * @ingroup network
 * @defgroup seq-counters Sequence Counter
 * @brief "sequence number" classes
 */

/**
 * @ingroup seq-counters
 * @brief Generic "sequence number" class
 *
 * This class can be used to handle sequence numbers.  In networking
 * protocols, sequence numbers are fixed precision integer numbers
 * that are used to order events relative to each other.  A sequence
 * number is expected to increase over time but, since it has a
 * limited number of bits, the number will "wrap around" from the
 * maximum value that can represented with the given number of bits
 * back to zero.  For this reason, comparison of two sequence numbers,
 * and subtraction, is non-trivial.  The SequenceNumber class behaves
 * like a number, with the usual arithmetic operators implemented, but
 * knows how to correctly compare and subtract sequence numbers.
 *
 * This is a templated class.  To use it you need to supply two
 * fundamental types as template parameters: NUMERIC_TYPE and
 * SIGNED_TYPE.  For instance, SequenceNumber<uint32_t, int32_t> gives
 * you a 32-bit sequence number, while SequenceNumber<uint16_t,
 * int16_t> is a 16-bit one.  For your convenience, these are
 * typedef'ed as SequenceNumber32 and SequenceNumber16, respectively.
 */
template <typename NUMERIC_TYPE, typename SIGNED_TYPE>
class SequenceNumber
{
  public:
    SequenceNumber()
        : m_value(0)
    {
    }

    /**
     * @brief Constructs a SequenceNumber with the given value
     * @param value the sequence number value
     */
    explicit SequenceNumber(NUMERIC_TYPE value)
        : m_value(value)
    {
    }

    /**
     * @brief Constructs a SequenceNumber from a copy
     * @param value sequence number to copy
     */
    SequenceNumber(const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>& value)
        : m_value(value.m_value)
    {
    }

    /**
     * @brief Constructs a SequenceNumber from an assignment of given value
     * @param value sequence number to copy
     * @returns reference to the assignee
     */
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>& operator=(NUMERIC_TYPE value)
    {
        m_value = value;
        return *this;
    }

    /**
     * @brief Constructs a SequenceNumber from an assignment of another sequence number
     * @param value sequence number to copy
     * @returns reference to the assignee
     */
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>& operator=(
        const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>& value)
    {
        m_value = value.m_value;
        return *this;
    }

#if 0
  // a SequenceNumber implicitly converts to a plain number, but not the other way around
  operator NUMERIC_TYPE () const
  {
    return m_value;
  }
#endif

    /**
     * @brief Extracts the numeric value of the sequence number
     * @returns the sequence number value
     */
    NUMERIC_TYPE GetValue() const
    {
        return m_value;
    }

    /**
     * @brief Prefix increment operator
     * @returns incremented sequence number
     */
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> operator++()
    {
        m_value++;
        return *this;
    }

    /**
     * @brief Postfix increment operator
     * @returns incremented sequence number
     */
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> operator++(int)
    {
        SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> retval(m_value);
        m_value++;
        return retval;
    }

    /**
     * @brief Prefix decrement operator
     * @returns decremented sequence number
     */
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> operator--()
    {
        m_value--;
        return *this;
    }

    /**
     * @brief Postfix decrement operator
     * @returns decremented sequence number
     */
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> operator--(int)
    {
        SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> retval(m_value);
        m_value--;
        return retval;
    }

    /**
     * @brief Plus equals operator
     * @param value value to add to sequence number
     * @returns incremented sequence number
     */
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>& operator+=(SIGNED_TYPE value)
    {
        m_value += value;
        return *this;
    }

    /**
     * @brief Minus equals operator
     * @param value value to subtract from sequence number
     * @returns decremented sequence number
     */
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>& operator-=(SIGNED_TYPE value)
    {
        m_value -= value;
        return *this;
    }

    /**
     * @brief Operator defining addition of two sequence numbers
     * @param other sequence number added to this
     * @returns sequence number representing sum
     */
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> operator+(
        const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>& other) const
    {
        return SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>(m_value + other.m_value);
    }

    /**
     * @brief Addition operator for adding numeric value to sequence number
     * @param delta value to add to sequence number
     * @returns sequence number representing sum
     */
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> operator+(SIGNED_TYPE delta) const
    {
        return SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>(m_value + delta);
    }

    /**
     * @brief Subtraction operator for subtracting numeric value from sequence number
     * @param delta value to subtract from sequence number
     * @returns sequence number representing difference
     */
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> operator-(SIGNED_TYPE delta) const
    {
        return SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>(m_value - delta);
    }

    /**
     * @brief Subtraction operator for subtracting sequence number from sequence number
     * @param other sequence number to subtract from this sequence number
     * @returns numeric value representing the difference
     */
    SIGNED_TYPE operator-(const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>& other) const
    {
        static const NUMERIC_TYPE maxValue = std::numeric_limits<NUMERIC_TYPE>::max();
        static const NUMERIC_TYPE halfMaxValue = std::numeric_limits<NUMERIC_TYPE>::max() / 2;
        if (m_value > other.m_value)
        {
            NUMERIC_TYPE diff = m_value - other.m_value;
            if (diff < halfMaxValue)
            {
                return static_cast<SIGNED_TYPE>(diff);
            }
            else
            {
                //      |------------|------------|
                //       ====                  ===
                //          ^                  ^
                //       other.m_value      m_value
                return -(static_cast<SIGNED_TYPE>(maxValue - m_value + 1 + other.m_value));
            }
        }
        else
        {
            NUMERIC_TYPE diff = other.m_value - m_value;
            if (diff < halfMaxValue)
            {
                //      |------------|------------|
                //          ========
                //          ^      ^
                //     m_value   other.m_value
                return -(static_cast<SIGNED_TYPE>(diff));
            }
            else
            {
                //      |------------|------------|
                //       ====                  ===
                //          ^                  ^
                //       m_value      other.m_value
                return static_cast<SIGNED_TYPE>(maxValue - other.m_value + 1 + m_value);
            }
        }
    }

    /**
     * @brief Equality operator for comparing sequence number
     * @param other sequence number to compare to this sequence number
     * @returns true if the sequence numbers are equal
     */
    constexpr bool operator==(const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>& other) const
    {
        return m_value == other.m_value;
    }

    /**
     * Spaceship comparison operator. All the other comparison operators
     * are automatically generated from this one.
     *
     * Here is the critical part, how the comparison is made taking into
     * account wrap-around. From RFC 3626:
     *
     * The sequence number S1 is said to be "greater than" the sequence
     * number S2 if:
     *      S1 > S2 AND S1 - S2 <= MAXVALUE/2 OR
     *      S2 > S1 AND S2 - S1 > MAXVALUE/2
     *
     * @param other sequence number to compare to this one
     * @returns The result of the comparison.
     */
    constexpr std::strong_ordering operator<=>(
        const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>& other) const
    {
        constexpr auto halfMaxValue = std::numeric_limits<NUMERIC_TYPE>::max() / 2;

        if (m_value == other.m_value)
        {
            return std::strong_ordering::equivalent;
        }
        if (((m_value > other.m_value) && (m_value - other.m_value) <= halfMaxValue) ||
            ((other.m_value > m_value) && (other.m_value - m_value) > halfMaxValue))
        {
            return std::strong_ordering::greater;
        }
        return std::strong_ordering::less;
    }

    /**
     * @brief For printing sequence number
     * @param os output stream
     * @param val sequence number to display
     * @returns output stream os
     */
    template <typename NUMERIC_TYPE2, typename SIGNED_TYPE2>
    friend std::ostream& operator<<(std::ostream& os,
                                    const SequenceNumber<NUMERIC_TYPE2, SIGNED_TYPE2>& val);

    /**
     * @brief For loading sequence number from input streams
     * @param is input stream
     * @param val sequence number to load
     * @returns input stream is
     */
    template <typename NUMERIC_TYPE2, typename SIGNED_TYPE2>
    friend std::istream& operator>>(std::istream& is,
                                    const SequenceNumber<NUMERIC_TYPE2, SIGNED_TYPE2>& val);

  public:
    // Unimplemented operators
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>& operator+=(
        const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>&) = delete;
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>& operator-=(
        const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>&) = delete;
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> operator*(
        const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>&) const = delete;
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> operator/(
        const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>&) const = delete;
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> operator%(
        const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>&) const = delete;
    bool operator!() const = delete;
    bool operator&&(const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>&) const = delete;
    bool operator||(const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>&) const = delete;
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> operator~() const = delete;
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> operator&(
        const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>&) const = delete;
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> operator|(
        const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>&) const = delete;
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> operator^(
        const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>&) const = delete;
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> operator<<(
        const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>&) const = delete;
    SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE> operator>>(
        const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>&) const = delete;
    int operator*() = delete;
    // SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>* operator& () = delete;

  private:
    NUMERIC_TYPE m_value; //!< Sequence number value
};

/**
 * @brief Stream insertion operator.
 *
 * @param os the stream
 * @param val the value
 * @returns a reference to the stream
 */
template <typename NUMERIC_TYPE, typename SIGNED_TYPE>
std::ostream&
operator<<(std::ostream& os, const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>& val)
{
    os << val.m_value;
    return os;
}

/**
 * @brief Stream extraction operator.
 *
 * @param is the stream
 * @param val the value
 * @returns a reference to the stream
 */
template <typename NUMERIC_TYPE, typename SIGNED_TYPE>
std::istream&
operator>>(std::istream& is, const SequenceNumber<NUMERIC_TYPE, SIGNED_TYPE>& val)
{
    is >> val.m_value;
    return is;
}

/**
 * @ingroup seq-counters
 * 32 bit Sequence number.
 */
typedef SequenceNumber<uint32_t, int32_t> SequenceNumber32;
/**
 * @ingroup seq-counters
 * 16 bit Sequence number.
 */
typedef SequenceNumber<uint16_t, int16_t> SequenceNumber16;
/**
 * @ingroup seq-counters
 * 8 bit Sequence number.
 */
typedef SequenceNumber<uint8_t, int8_t> SequenceNumber8;

namespace TracedValueCallback
{

/**
 * @ingroup seq-counters
 * TracedValue callback signature for SequenceNumber32
 *
 * @param [in] oldValue original value of the traced variable
 * @param [in] newValue new value of the traced variable
 */
typedef void (*SequenceNumber32)(SequenceNumber32 oldValue, SequenceNumber32 newValue);

} // namespace TracedValueCallback

/**
 * @ingroup seq-counters
 *
 * ns3::TypeNameGet<SequenceNumber32>() specialization.
 * @returns The type name as a string.
 */
TYPENAMEGET_DEFINE(SequenceNumber32);

} // namespace ns3

#endif /* NS3_SEQ_NUM_H */
