#pragma once
/**
 * @file
 * @brief This header provides the StrongId class.
 *
 * It is template which can be used to create strong Id's
 * which avoid accidental type conversions (generating compiler errors when they occur).
 *
 * Motivation
 * ==========
 * It is common to use an Id (typically an integer) to identify and represent a component.
 * A basic example (poor style):
 *
 *      size_t count_net_terminals(int net_id);
 *
 * Where a plain int is used to represent the net identifier.
 * Using a plain basic type is poor style since it makes it unclear that the parameter is
 * an Id.
 *
 * A better example is to use a typedef:
 *
 *      typedef int NetId;
 *
 *      size_t count_net_teriminals(NetId net_id);
 *
 * It is now clear that the parameter is expecting an Id.
 *
 * However this approach has some limitations. In particular, typedef's only create type
 * aliases, and still allow conversions. This is problematic if there are multiple types
 * of Ids. For example:
 *
 *      typedef int NetId;
 *      typedef int BlkId;
 *
 *      size_t count_net_teriminals(NetId net_id);
 *
 *      BlkId blk_id = 10;
 *      NetId net_id = 42;
 *
 *      count_net_teriminals(net_id); //OK
 *      count_net_teriminals(blk_id); //Bug: passed a BlkId as a NetId
 *
 * Since typdefs are aliases the compiler issues no errors or warnings, and silently passes
 * the BlkId where a NetId is expected. This results in hard to diagnose bugs.
 *
 * We can avoid this issue by using a StrongId:
 *
 *      struct net_id_tag; //Phantom tag for NetId
 *      struct blk_id_tag; //Phantom tag for BlkId
 *
 *      typedef StrongId<net_id_tag> NetId;
 *      typedef StrongId<blk_id_tag> BlkId;
 *
 *      size_t count_net_teriminals(NetId net_id);
 *
 *      BlkId blk_id = 10;
 *      NetId net_id = 42;
 *
 *      count_net_teriminals(net_id); //OK
 *      count_net_teriminals(blk_id); //Compiler Error: NetId expected!
 *
 * StrongId is a template which implements the basic features of an Id, but disallows silent conversions
 * between different types of Ids. It uses another 'tag' type (passed as the first template parameter)
 * to uniquely identify the type of the Id (preventing conversions between different types of Ids).
 *
 * Usage
 * =====
 *
 * The StrongId template class takes one required and three optional template parameters:
 *
 *    1. Tag        - the unique type used to identify this type of Ids [Required]
 *    2. T          - the underlying integral id type (default: int) [Optional]
 *    3. T sentinel - a value representing an invalid Id (default: -1) [Optional]
 *
 * If no value is supplied during construction the StrongId is initialized to the invalid/sentinel value.
 *
 * Example 1: default definition
 *
 *      struct net_id_tag;
 *      typedef StrongId<net_id_tag> NetId; //Internally stores an integer Id, -1 represents invalid
 *
 * Example 2: definition with custom underlying type
 *
 *      struct blk_id_tag;
 *      typedef StrongId<net_id_tag,size_t> BlkId; //Internally stores a size_t Id, -1 represents invalid
 *
 * Example 3: definition with custom underlying type and custom sentinel value
 *
 *      struct pin_id_tag;
 *      typedef StrongId<net_id_tag,size_t,0> PinId; //Internally stores a size_t Id, 0 represents invalid
 *
 * Example 4: Creating Ids
 *
 *      struct net_id_tag;
 *      typedef StrongId<net_id_tag> MyId; //Internally stores an integer Id, -1 represents invalid
 *
 *      MyId my_id;           //Defaults to the sentinel value (-1 by default)
 *      MyId my_other_id = 5; //Explicit construction
 *      MyId my_thrid_id(25); //Explicit construction
 *
 * Example 5: Comparing Ids
 *
 *      struct net_id_tag;
 *      typedef StrongId<net_id_tag> MyId; //Internally stores an integer Id, -1 represents invalid
 *
 *      MyId my_id;           //Defaults to the sentinel value (-1 by default)
 *      MyId my_id_one = 1;
 *      MyId my_id_two = 2;
 *      MyId my_id_also_one = 1;
 *
 *      my_id_one == my_id_also_one; //True
 *      my_id_one == my_id; //False
 *      my_id_one == my_id_two; //False
 *      my_id_one != my_id_two; //True
 *
 * Example 5: Checking for invalid Ids
 *
 *      struct net_id_tag;
 *      typedef StrongId<net_id_tag> MyId; //Internally stores an integer Id, -1 represents invalid
 *
 *      MyId my_id;           //Defaults to the sentinel value
 *      MyId my_id_one = 1;
 *
 *      //Comparison against a constructed invalid id
 *      my_id == MyId::INVALID(); //True
 *      my_id_one == MyId::INVALID(); //False
 *      my_id_one != MyId::INVALID(); //True
 *
 *      //The Id can also be evaluated in a boolean context against the sentinel value
 *      if(my_id) //False, my_id is invalid
 *      if(!my_id) //True my_id is valid
 *      if(my_id_one) //True my_id_one is valid
 *
 * Example 6: Indexing data structures
 *
 *      struct my_id_tag;
 *      typedef StrongId<net_id_tag> MyId; //Internally stores an integer Id, -1 represents invalid
 *
 *      std::vector<int> my_vec = {0, 1, 2, 3, 4, 5};
 *
 *      MyId my_id = 2;
 *
 *      my_vec[size_t(my_id)]; //Access the third element via explicit conversion
 */
#include <type_traits> //for std::is_integral
#include <cstddef>     //for std::size_t
#include <functional>  //for std::hash
#include <ostream>     //for std::ostream

namespace vtr {

// Forward declare the class (needed for operator declarations)
template<typename tag, typename T, T sentinel>
class StrongId;

/*
 * Forward declare the equality/inequality operators
 *
 * We need to do this before the class definition so the class can
 * friend them
 */
template<typename tag, typename T, T sentinel>
constexpr bool operator==(const StrongId<tag, T, sentinel>& lhs, const StrongId<tag, T, sentinel>& rhs) noexcept;

template<typename tag, typename T, T sentinel>
constexpr bool operator!=(const StrongId<tag, T, sentinel>& lhs, const StrongId<tag, T, sentinel>& rhs) noexcept;

template<typename tag, typename T, T sentinel>
constexpr bool operator<(const StrongId<tag, T, sentinel>& lhs, const StrongId<tag, T, sentinel>& rhs) noexcept;

template<typename tag, typename T, T sentinel>
std::ostream& operator<<(std::ostream& out, const StrongId<tag, T, sentinel>& rhs);

///@brief Class template definition with default template parameters
template<typename tag, typename T = int, T sentinel = T(-1)>
class StrongId {
    static_assert(std::is_integral<T>::value, "T must be integral");

  public:
    ///@brief Gets the invalid Id
    static constexpr StrongId INVALID() noexcept { return StrongId(); }

    ///@brief Default to the sentinel value
    constexpr StrongId() noexcept
        : id_(sentinel) {}

    ///@brief Only allow explicit constructions from a raw Id (no automatic conversions)
    explicit constexpr StrongId(T id) noexcept
        : id_(id) {}

    // Allow some explicit conversion to useful types:

    /// @brief Allow explicit conversion to bool (e.g. if(id))
    explicit constexpr operator bool() const { return id_ != sentinel; }

    /// @brief Another name for the bool cast
    constexpr bool is_valid() const { return id_ != sentinel; }

    /// @brief Allow explicit conversion to size_t (e.g. my_vector[size_t(strong_id)])
    explicit constexpr operator std::size_t() const { return static_cast<std::size_t>(id_); }

    /// @brief Allow explicit conversion to int (int(RRNodeId::INVALID()) == -1)
    explicit constexpr operator int() const { return static_cast<int>(id_); }

    /// @brief To enable hashing Ids
    friend std::hash<StrongId<tag, T, sentinel>>;

    /**
     * @brief To enable comparisons between Ids
     *
     * Note that since these are templated functions we provide an empty set of template parameters
     * after the function name (i.e. <>)
     */
    friend constexpr bool operator== <>(const StrongId<tag, T, sentinel>& lhs, const StrongId<tag, T, sentinel>& rhs) noexcept;
    ///@brief != operator
    friend constexpr bool operator!= <>(const StrongId<tag, T, sentinel>& lhs, const StrongId<tag, T, sentinel>& rhs) noexcept;
    ///@brief < operator
    friend constexpr bool operator< <>(const StrongId<tag, T, sentinel>& lhs, const StrongId<tag, T, sentinel>& rhs) noexcept;

    /**
     * @brief to be able to print them out
     */
    friend std::ostream& operator<< <>(std::ostream& out, const StrongId<tag, T, sentinel>& rhs);

  private:
    T id_;
};

///@brief == operator
template<typename tag, typename T, T sentinel>
constexpr bool operator==(const StrongId<tag, T, sentinel>& lhs, const StrongId<tag, T, sentinel>& rhs) noexcept {
    return lhs.id_ == rhs.id_;
}

///@brief != operator
template<typename tag, typename T, T sentinel>
constexpr bool operator!=(const StrongId<tag, T, sentinel>& lhs, const StrongId<tag, T, sentinel>& rhs) noexcept {
    return !(lhs == rhs);
}

///@brief operator < Needed for std::map-like containers
template<typename tag, typename T, T sentinel>
constexpr bool operator<(const StrongId<tag, T, sentinel>& lhs, const StrongId<tag, T, sentinel>& rhs) noexcept {
    return lhs.id_ < rhs.id_;
}

///@brief operator << Needed for print-debugging
template<typename tag, typename T, T sentinel>
std::ostream& operator<<(std::ostream& out, const StrongId<tag, T, sentinel>& rhs) {
    out << rhs.id_;
    return out;
}
} //namespace vtr

///@brief Specialize std::hash for StrongId's (needed for std::unordered_map-like containers)
namespace std {
template<typename tag, typename T, T sentinel>
struct hash<vtr::StrongId<tag, T, sentinel>> {
    std::size_t operator()(const vtr::StrongId<tag, T, sentinel> k) const noexcept {
        return std::hash<T>()(k.id_); //Hash with the underlying type
    }
};
} //namespace std
