/*
 * Copyright (c) 2007-2009 Strasbourg University
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author: Sebastien Vincent <vincent@clarinet.u-strasbg.fr>
 */

#ifndef NDISC_CACHE_H
#define NDISC_CACHE_H

#include "ns3/ipv6-address.h"
#include "ns3/net-device.h"
#include "ns3/nstime.h"
#include "ns3/output-stream-wrapper.h"
#include "ns3/packet.h"
#include "ns3/ptr.h"
#include "ns3/timer.h"

#include <list>
#include <map>
#include <stdint.h>

namespace ns3
{

class NetDevice;
class Ipv6Interface;
class Ipv6Header;
class Icmpv6L4Protocol;

/**
 * @ingroup ipv6
 *
 * @brief IPv6 Neighbor Discovery cache.
 */
class NdiscCache : public Object
{
  public:
    class Entry;

    /**
     * @brief Get the type ID
     * @return type ID
     */
    static TypeId GetTypeId();

    /**
     * @brief Default value for unres qlen.
     */
    static const uint32_t DEFAULT_UNRES_QLEN = 3;

    /**
     * @brief Constructor.
     */
    NdiscCache();

    /**
     * @brief Destructor.
     */
    ~NdiscCache() override;

    // Delete default and copy constructor, and assignment operator to avoid misuse
    NdiscCache(const NdiscCache&) = delete;
    NdiscCache& operator=(const NdiscCache&) = delete;

    /**
     * @brief Get the NetDevice associated with this cache.
     * @return NetDevice
     */
    Ptr<NetDevice> GetDevice() const;

    /**
     * @brief Get the Ipv6Interface associated with this cache.
     * @returns The Ipv6Interface.
     */
    Ptr<Ipv6Interface> GetInterface() const;

    /**
     * @brief Lookup in the cache.
     * @param dst destination address.
     * @return the entry if found, 0 otherwise.
     */
    virtual NdiscCache::Entry* Lookup(Ipv6Address dst);

    /**
     * @brief Lookup in the cache for a MAC address.
     * @param dst destination MAC address.
     * @return a list of matching entries.
     */
    std::list<NdiscCache::Entry*> LookupInverse(Address dst);

    /**
     * @brief Add an entry.
     * @param to address to add
     * @return an new Entry
     */
    virtual NdiscCache::Entry* Add(Ipv6Address to);

    /**
     * @brief Delete an entry.
     * @param entry pointer to delete from the list.
     */
    void Remove(NdiscCache::Entry* entry);

    /**
     * @brief Clear the cache of all entries except auto-generated entries
     *
     * If you wish to remove all entries, including auto-generated entries,
     * call this method and then also call RemoveAutoGeneratedEntries().
     *
     * @see RemoveAutoGenerated
     */
    void Flush();

    /**
     * @brief Set the max number of waiting packet.
     * @param unresQlen value to set
     */
    void SetUnresQlen(uint32_t unresQlen);

    /**
     * @brief Get the max number of waiting packet.
     * @return max number
     */
    uint32_t GetUnresQlen();

    /**
     * @brief Set the device and interface.
     * @param device the device
     * @param interface the IPv6 interface
     * @param icmpv6 the ICMPv6 protocol
     */
    void SetDevice(Ptr<NetDevice> device,
                   Ptr<Ipv6Interface> interface,
                   Ptr<Icmpv6L4Protocol> icmpv6);

    /**
     * @brief Print the NDISC cache entries
     *
     * @param stream the ostream the NDISC cache entries is printed to
     */
    void PrintNdiscCache(Ptr<OutputStreamWrapper> stream);

    /**
     * @brief Clear the NDISC cache of all Auto-Generated entries
     */
    void RemoveAutoGeneratedEntries();

    /**
     * @brief Pair of a packet and an Ipv4 header.
     */
    typedef std::pair<Ptr<Packet>, Ipv6Header> Ipv6PayloadHeaderPair;

    /**
     * @ingroup ipv6
     *
     * @brief A record that holds information about a NdiscCache entry.
     */
    class Entry
    {
      public:
        /**
         * @brief Constructor.
         * @param nd The NdiscCache this entry belongs to.
         */
        Entry(NdiscCache* nd);

        virtual ~Entry() = default;

        /**
         * @brief The Entry state enumeration.
         */
        enum NdiscCacheEntryState_e
        {
            INCOMPLETE,          /**< No mapping between IPv6 and L2 addresses */
            REACHABLE,           /**< Mapping exists between IPv6 and L2 addresses */
            STALE,               /**< Mapping is stale */
            DELAY,               /**< Try to wait contact from remote host */
            PROBE,               /**< Try to contact IPv6 address to know again its L2 address */
            PERMANENT,           /**< Permanent Mapping exists between IPv6 and L2 addresses */
            STATIC_AUTOGENERATED /**< Permanent entries generate by NeighborCacheHelper*/
        };

        /**
         * @brief The state of the entry.
         */
        NdiscCacheEntryState_e m_state;

        /**
         * @brief Changes the state to this entry to INCOMPLETE.
         * @param p packet that wait to be sent
         */
        void MarkIncomplete(Ipv6PayloadHeaderPair p);

        /**
         * @brief Changes the state to this entry to REACHABLE.
         * @param mac MAC address
         * @return the list of packet waiting
         */
        std::list<Ipv6PayloadHeaderPair> MarkReachable(Address mac);

        /**
         * @brief Changes the state to this entry to PROBE.
         */
        void MarkProbe();

        /**
         * @brief Changes the state to this entry to STALE.
         * @param mac L2 address
         * @return the list of packet waiting
         */
        std::list<Ipv6PayloadHeaderPair> MarkStale(Address mac);

        /**
         * @brief Changes the state to this entry to STALE.
         */
        void MarkStale();

        /**
         * @brief Changes the state to this entry to REACHABLE.
         */
        void MarkReachable();

        /**
         * @brief Change the state to this entry to DELAY.
         */
        void MarkDelay();

        /**
         * @brief Change the state to this entry to PERMANENT.
         */
        void MarkPermanent();

        /**
         * @brief Changes the state of this entry to auto-generated.
         *
         * The entry must have a valid MacAddress.
         */
        void MarkAutoGenerated();

        /**
         * @brief Add a packet (or replace old value) in the queue.
         * @param p packet to add
         */
        void AddWaitingPacket(Ipv6PayloadHeaderPair p);

        /**
         * @brief Clear the waiting packet list.
         */
        void ClearWaitingPacket();

        /**
         * @brief Is the entry STALE
         * @return true if the entry is in STALE state, false otherwise
         */
        bool IsStale() const;

        /**
         * @brief Is the entry REACHABLE
         * @return true if the entry is in REACHABLE state, false otherwise
         */
        bool IsReachable() const;

        /**
         * @brief Is the entry DELAY
         * @return true if the entry is in DELAY state, false otherwise
         */
        bool IsDelay() const;

        /**
         * @brief Is the entry INCOMPLETE
         * @return true if the entry is in INCOMPLETE state, false otherwise
         */
        bool IsIncomplete() const;

        /**
         * @brief Is the entry PROBE
         * @return true if the entry is in PROBE state, false otherwise
         */
        bool IsProbe() const;

        /**
         * @brief Is the entry PERMANENT
         * @return true if the entry is in PERMANENT state, false otherwise
         */
        bool IsPermanent() const;

        /**
         * @brief Is the entry STATIC_AUTOGENERATED
         * @return True if the state of this entry is auto-generated; false otherwise.
         */
        bool IsAutoGenerated() const;

        /**
         * @brief Get the MAC address of this entry.
         * @return the L2 address
         */
        Address GetMacAddress() const;

        /**
         * @brief Set the MAC address of this entry.
         * @param mac the MAC address to set
         */
        void SetMacAddress(Address mac);

        /**
         * @brief If the entry is a host or a router.
         * @return true if the node is a router, 0 if it is a host
         */
        bool IsRouter() const;

        /**
         * @brief Set the node type.
         * @param router true is a router, false means a host
         */
        void SetRouter(bool router);

        /**
         * @brief Get the time of last reachability confirmation.
         * @return time
         */
        Time GetLastReachabilityConfirmation() const;

        /**
         * @brief Start the reachable timer.
         */
        void StartReachableTimer();

        /**
         * @brief Update the reachable timer.
         */
        void UpdateReachableTimer();

        /**
         * @brief Start retransmit timer.
         */
        void StartRetransmitTimer();

        /**
         * @brief Start probe timer.
         */
        void StartProbeTimer();

        /**
         * @brief Start delay timer.
         */
        void StartDelayTimer();

        /**
         * @brief Stop NUD timer and reset the NUD retransmission counter
         */
        void StopNudTimer();

        /**
         * @brief Function called when reachable timer timeout.
         */
        void FunctionReachableTimeout();

        /**
         * @brief Function called when retransmit timer timeout.
         * It verify that the NS retransmit has reached the max so discard the entry
         * otherwise it retransmit a NS.
         */
        void FunctionRetransmitTimeout();

        /**
         * @brief Function called when probe timer timeout.
         */
        void FunctionProbeTimeout();

        /**
         * @brief Function called when delay timer timeout.
         */
        void FunctionDelayTimeout();

        /**
         * @brief Set the IPv6 address.
         * @param ipv6Address IPv6 address
         */
        void SetIpv6Address(Ipv6Address ipv6Address);

        /**
         * @brief Get the IPv6 address.
         * @returns The IPv6 address
         */
        Ipv6Address GetIpv6Address() const;

        /**
         * @brief Get the state of the entry.
         * @returns The state of the entry
         */
        NdiscCacheEntryState_e GetEntryState() const;

        /**
         * @brief Print this entry to the given output stream.
         *
         * @param os the output stream to which this Ipv6Address is printed
         */
        void Print(std::ostream& os) const;

      protected:
        /**
         * @brief the NdiscCache associated.
         */
        NdiscCache* m_ndCache;

      private:
        /**
         * @brief The IPv6 address.
         */
        Ipv6Address m_ipv6Address;

        /**
         * @brief The MAC address.
         */
        Address m_macAddress;

        /**
         * @brief The list of packet waiting.
         */
        std::list<Ipv6PayloadHeaderPair> m_waiting;

        /**
         * @brief Type of node (router or host).
         */
        bool m_router;

        /**
         * @brief Timer (used for NUD).
         */
        Timer m_nudTimer;

        /**
         * @brief Last time we see a reachability confirmation.
         */
        Time m_lastReachabilityConfirmation;

        /**
         * @brief Number of NS retransmission.
         */
        uint8_t m_nsRetransmit;
    };

  protected:
    /**
     * @brief Dispose this object.
     */
    void DoDispose() override;

    /**
     * @brief Neighbor Discovery Cache container
     */
    typedef std::map<Ipv6Address, NdiscCache::Entry*> Cache;
    /**
     * @brief Neighbor Discovery Cache container iterator
     */
    typedef std::map<Ipv6Address, NdiscCache::Entry*>::iterator CacheI;

    /**
     * @brief A list of Entry.
     */
    Cache m_ndCache;

  private:
    /**
     * @brief The NetDevice.
     */
    Ptr<NetDevice> m_device;

    /**
     * @brief the interface.
     */
    Ptr<Ipv6Interface> m_interface;

    /**
     * @brief the icmpv6 L4 protocol for this cache.
     */
    Ptr<Icmpv6L4Protocol> m_icmpv6;

    /**
     * @brief Max number of packet stored in m_waiting.
     */
    uint32_t m_unresQlen;
};

/**
 * @brief Stream insertion operator.
 *
 * @param os the reference to the output stream
 * @param entry the NdiscCache::Entry
 * @returns the reference to the output stream
 */
std::ostream& operator<<(std::ostream& os, const NdiscCache::Entry& entry);

} /* namespace ns3 */

#endif /* NDISC_CACHE_H */
