/*
 * Copyright (c) 2018 Universita' degli Studi di Napoli Federico II
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author: Stefano Avallone <stavallo@unina.it>
 */

#ifndef QUEUE_SIZE_H
#define QUEUE_SIZE_H

#include "ns3/abort.h"
#include "ns3/attribute-helper.h"
#include "ns3/attribute.h"

#include <iostream>
#include <string>

namespace ns3
{

/**
 * @ingroup network
 * @defgroup queuesize Queue size
 */

/**
 * @ingroup queuesize
 * @brief Enumeration of the operating modes of queues.
 *
 */
enum QueueSizeUnit
{
    PACKETS, /**< Use number of packets for queue size */
    BYTES,   /**< Use number of bytes for queue size */
};

/**
 * @ingroup queuesize
 * @brief Class for representing queue sizes
 *
 * Allows for natural and familiar use of queue sizes.  Allows construction
 * from strings, natural sum e.g.:
 * @code
 *   QueueSize x("7kB");
 *   Ptr<Packet> p = Create<Packet> (1000);
 *   QueueSize y = x + p;    // 8kB
 * @endcode
 * This class also supports the regular comparison operators \c <, \c >,
 * \c <=, \c >=, \c ==, and \c !=
 *
 * Queue size specifiers consist of
 * * A numeric value,
 * * An optional multiplier prefix and
 * * A unit.
 *
 * Whitespace is allowed but not required between the numeric value and
 * multiplier or unit.
 *
 * Supported multiplier prefixes:
 *
 * | Prefix   | Value       |
 * | :------- | ----------: |
 * | "k", "K" | 1000        |
 * | "Ki"     | 1024        |
 * | "M"      | 1000000     |
 * | "Mi"     | 1024 Ki     |
 *
 * Supported unit strings:
 *
 * | Symbol   | Meaning     |
 * | :------- | :---------- |
 * | "B"      | 8-bit bytes |
 * | "p"      | packets     |
 *
 * Examples:
 * * "56kB" = 56,000 bytes
 * * "128 kB" = 128,000 bytes
 * * "8KiB" = 8,192 bytes
 * * "1000p" = 1,000 packets
 *
 * @see attribute_QueueSize
 */
class QueueSize
{
  public:
    QueueSize();
    /**
     * @brief Integer constructor
     *
     * Construct a queue size from a mode and a value.
     * @param unit whether the value is expressed in terms of packets or bytes
     * @param value the value
     */
    QueueSize(QueueSizeUnit unit, uint32_t value);
    /**
     * @brief String constructor
     *
     * Construct a queue size from a string.  Many different unit strings are supported
     * Supported unit strings:
     * B, p \n
     * kB, KB, KiB, kp, Kp, Kip \n
     * MB, MiB, Mp, Mip \n
     *
     * Examples:
     * "56kB" = 56,000 bytes
     * "128 kB" = 128,000 bytes
     * "8KiB" = 8,192 bytes
     * "1000p" = 1,000 packets
     *
     * @param size string representing the size
     */
    QueueSize(std::string size);

    /**
     * @brief Equality operator.
     * @param rhs the queue size to compare to this queue size
     * @returns true if this size is equal to rhs
     */
    inline bool operator==(const QueueSize& rhs) const
    {
        NS_ABORT_MSG_IF(m_unit != rhs.m_unit, "Cannot compare heterogeneous sizes");
        return m_value == rhs.m_value;
    }

    /**
     * @brief Three-way comparison operator.
     * @param rhs the queue size to compare to this queue size
     * @returns The result of the comparison.
     */
    inline std::strong_ordering operator<=>(const QueueSize& rhs) const
    {
        NS_ABORT_MSG_IF(m_unit != rhs.m_unit, "Cannot compare heterogeneous sizes");
        return m_value <=> rhs.m_value;
    }

    /**
     * Get the underlying unit
     * @return The underlying unit
     */
    QueueSizeUnit GetUnit() const;

    /**
     * Get the underlying value
     * @return The underlying value
     */
    uint32_t GetValue() const;

  private:
    /**
     * @brief Parse a string representing a QueueSize
     *
     * Allowed unit representations include all combinations of
     *
     * * An SI prefix: k, K, M
     * * Bytes (8 bits) or packets
     *
     * @param [in] s The string representation, including unit
     * @param [in,out] unit The location to put the unit.
     * @param [in,out] value The location to put the value, in bytes or packets.
     * @return true if parsing was successful.
     */
    static bool DoParse(const std::string s, QueueSizeUnit* unit, uint32_t* value);

    // Uses DoParse
    friend std::istream& operator>>(std::istream& is, QueueSize& size);

    QueueSizeUnit m_unit; //!< unit
    uint32_t m_value;     //!< queue size [bytes or packets]
};

/**
 * @brief Stream insertion operator.
 *
 * @param os the stream
 * @param size the queue size
 * @returns a reference to the stream
 */
std::ostream& operator<<(std::ostream& os, const QueueSize& size);

/**
 * @brief Stream extraction operator.
 *
 * @param is the stream
 * @param size the queue size
 * @returns a reference to the stream
 */
std::istream& operator>>(std::istream& is, QueueSize& size);

ATTRIBUTE_HELPER_HEADER(QueueSize);

/**
 * Increase the queue size by a packet size, if the queue size is in bytes,
 * or by one, otherwise.
 *
 * @param lhs queue size
 * @param rhs packet
 * @return the queue size increased by the packet size
 */
template <typename Item>
QueueSize operator+(const QueueSize& lhs, const Ptr<Item>& rhs);
/**
 * Increase the queue size by a packet size, if the queue size is in bytes,
 * or by one, otherwise.
 *
 * @param lhs packet
 * @param rhs queue size
 * @return the queue size increased by the packet size
 */
template <typename Item>
QueueSize operator+(const Ptr<Item>& lhs, const QueueSize& rhs);

/**
 * Decrease the queue size by a packet size, if the queue size is in bytes,
 * or by one, otherwise.
 *
 * @param lhs queue size
 * @param rhs packet
 * @return the queue size decreased by the packet size
 */
template <typename Item>
QueueSize operator-(const QueueSize& lhs, const Ptr<Item>& rhs);
/**
 * Decrease the queue size by a packet size, if the queue size is in bytes,
 * or by one, otherwise.
 *
 * @param lhs packet
 * @param rhs queue size
 * @return the queue size decreased by the packet size
 */
template <typename Item>
QueueSize operator-(const Ptr<Item>& lhs, const QueueSize& rhs);

/**
 * Implementation of the templates declared above.
 */

template <typename Item>
QueueSize
operator+(const QueueSize& lhs, const Ptr<Item>& rhs)
{
    if (lhs.GetUnit() == QueueSizeUnit::PACKETS)
    {
        return QueueSize(lhs.GetUnit(), lhs.GetValue() + 1);
    }
    if (lhs.GetUnit() == QueueSizeUnit::BYTES)
    {
        return QueueSize(lhs.GetUnit(), lhs.GetValue() + rhs->GetSize());
    }
    NS_FATAL_ERROR("Unknown queue size mode");
}

template <typename Item>
QueueSize
operator+(const Ptr<Item>& lhs, const QueueSize& rhs)
{
    if (rhs.GetUnit() == QueueSizeUnit::PACKETS)
    {
        return QueueSize(rhs.GetUnit(), rhs.GetValue() + 1);
    }
    if (rhs.GetUnit() == QueueSizeUnit::BYTES)
    {
        return QueueSize(rhs.GetUnit(), rhs.GetValue() + lhs->GetSize());
    }
    NS_FATAL_ERROR("Unknown queue size mode");
}

template <typename Item>
QueueSize
operator-(const QueueSize& lhs, const Ptr<Item>& rhs)
{
    if (lhs.GetUnit() == QueueSizeUnit::PACKETS)
    {
        NS_ABORT_IF(lhs.GetValue() < 1);
        return QueueSize(lhs.GetUnit(), lhs.GetValue() - 1);
    }
    if (lhs.GetUnit() == QueueSizeUnit::BYTES)
    {
        NS_ABORT_IF(lhs.GetValue() < rhs->GetSize());
        return QueueSize(lhs.GetUnit(), lhs.GetValue() - rhs->GetSize());
    }
    NS_FATAL_ERROR("Unknown queue size mode");
}

template <typename Item>
QueueSize
operator-(const Ptr<Item>& lhs, const QueueSize& rhs)
{
    if (rhs.GetUnit() == QueueSizeUnit::PACKETS)
    {
        NS_ABORT_IF(rhs.GetValue() < 1);
        return QueueSize(rhs.GetUnit(), rhs.GetValue() - 1);
    }
    if (rhs.GetUnit() == QueueSizeUnit::BYTES)
    {
        NS_ABORT_IF(rhs.GetValue() < lhs->GetSize());
        return QueueSize(rhs.GetUnit(), rhs.GetValue() - lhs->GetSize());
    }
    NS_FATAL_ERROR("Unknown queue size mode");
}

} // namespace ns3

#endif /* QUEUE_SIZE_H */
