/*
 * SPDX-FileCopyrightText: NVIDIA CORPORATION & AFFILIATES
 * Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 * SPDX-License-Identifier: LicenseRef-NvidiaProprietary
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#pragma once

#include <unordered_map>

#include "agg_types.h"
#include "am_common.h"
#include "csv_parser.h"
#include "option_manager.h"
#include "port_data.h"

class Vnode;
class Vport;
class Port;

using ListOfNodes = std::list<class Node*>;

using VectorOfUniquePorts = std::vector<std::unique_ptr<Port>>;
using SetNodesPtr = std::set<class Node*>;
using MapPortKeyToAnPortPtr = std::map<port_key_t, class Port*>;

using MapGuidToNodePtr = std::map<uint64_t, std::unique_ptr<Node>>;
using MapPortKeyToPairNodeKeyAndPortIndex = std::map<hca_port_key_t, std::pair<port_key_t, std::size_t>>;

enum class PortStatus : uint8_t
{
    ISOLATED,
    VALID,
    UNREACHABLE
};

enum class SmdbPortState : uint8_t
{
    NOC,
    DOWN,
    INIT,
    ARM,
    ACTIVE,
    ACTIVE_DEFER
};

enum class DiscThroughState : uint8_t
{
    NORMAL,
    IGNORED
};

enum SpecialPortType
{
    SPECIAL_PORT_TYPE_AN = 1,
    SPECIAL_PORT_TYPE_ROUTER = 2,
    SPECIAL_PORT_TYPE_ETH_GW = 3,
    SPECIAL_PORT_TYPE_UNKNOWN = 0xFF,
};

class SmRecord
{
   public:
    string version;
    u_int64_t pid;
    string host_name;
    u_int64_t port_guid;
    u_int64_t subnet_prefix;
    u_int64_t lid;
    string routing_engine;
    u_int64_t sa_key;

    SmRecord() : version(""), pid(0), host_name(""), port_guid(0), subnet_prefix(0), lid(0), routing_engine(""), sa_key(0x1) {}

    static int Init(vector<ParseFieldInfo<class SmRecord>>& parse_section_info);

    bool SetSmVersion(const char* field_str) { return CsvParser::Parse(field_str, version); }

    bool SetSmPid(const char* field_str) { return CsvParser::Parse(field_str, pid, 10); }

    bool SetSmHostName(const char* field_str) { return CsvParser::Parse(field_str, host_name); }

    bool SetSmPortGuid(const char* field_str) { return CsvParser::Parse(field_str, port_guid, 16); }

    bool SetSmSubnetPrefix(const char* field_str) { return CsvParser::Parse(field_str, subnet_prefix, 0); }

    bool SetSmlid(const char* field_str) { return CsvParser::Parse(field_str, lid, 10); }

    bool SetSmRoutingEngine(const char* field_str) { return CsvParser::Parse(field_str, routing_engine); }

    bool SetSmSaKey(const char* field_str) { return CsvParser::Parse(field_str, sa_key, 16); }
};

class NodeRecord
{
   public:
    u_int64_t node_guid;
    string node_description;
    u_int16_t num_ports;
    u_int8_t node_type;
    //    u_int8_t            class_version
    //    u_int8_t            base_version
    //    u_int16_t           device_id
    //    u_int8_t            local_port_num

    NodeRecord() : node_guid(0), node_description(""), num_ports(0), node_type(0) {}

    static int Init(vector<ParseFieldInfo<class NodeRecord>>& parse_section_info);

    bool SetNodeGuid(const char* field_str) { return CsvParser::Parse(field_str, node_guid, 16); }

    bool SetNodeDescription(const char* field_str)
    {
        const auto parser_result = CsvParser::Parse(field_str, node_description);
        RemoveWhitespaceCharacters(node_description);
        return parser_result;
    }

    bool SetNumPorts(const char* field_str) { return CsvParser::Parse(field_str, num_ports, 10); }

    bool SetNodeType(const char* field_str) { return CsvParser::Parse(field_str, node_type, 10); }
};

class PortRecord
{
   public:
    u_int64_t node_guid;
    phys_port_t port_num;
    u_int64_t port_guid;
    u_int16_t lid;
    u_int8_t lmc;
    SmdbPortState state;
    u_int16_t link_width;
    u_int16_t link_speed;
    PortStatus status;
    DiscThroughState disc_through_state;
    string timestamp;
    u_int8_t special_port_type;
    string port_label;
    u_int16_t aport;
    u_int8_t plane_number;
    u_int8_t number_of_planes;

    PortRecord()
        : node_guid(0),
          port_num(0),
          port_guid(0),
          lid(0),
          lmc(0),
          state{SmdbPortState::ACTIVE},
          link_width(0),
          link_speed(0),
          status{PortStatus::VALID},
          disc_through_state{DiscThroughState::NORMAL},
          timestamp{},
          special_port_type(SPECIAL_PORT_TYPE_UNKNOWN),
          port_label(),
          aport(0),
          plane_number(0),
          number_of_planes(0)
    {}

    static int Init(vector<ParseFieldInfo<class PortRecord>>& parse_section_info);

    bool SetNodeGuid(const char* field_str) { return CsvParser::Parse(field_str, node_guid, 16); }

    bool SetPortNum(const char* field_str) { return CsvParser::Parse(field_str, port_num, 10); }

    bool SetPortGuid(const char* field_str) { return CsvParser::Parse(field_str, port_guid, 16); }

    bool SetLID(const char* field_str) { return CsvParser::Parse(field_str, lid, 10); }

    bool SetLMC(const char* field_str) { return CsvParser::Parse(field_str, lmc, 10); }

    bool SetLinkWidth(const char* field_str)
    {
        string str;
        int num = 0;
        CsvParser::Parse(field_str, str);
        int ret = std::sscanf(str.c_str(), "%dx", &num);
        link_width = uint16_t(num);
        return ret ? true : false;
    }

    bool SetLinkSpeed(const char* field_str) { return CsvParser::Parse(field_str, link_speed, 10); }

    bool SetPortState(const char* field_str);

    bool SetStatus(const char* field_str);

    bool SetDiscThroughState(const char* field_str);

    bool SetTimestamp(const char* field_str) { return CsvParser::Parse(field_str, timestamp); }

    bool SetSpecialPortType(const char* field_str) { return CsvParser::Parse(field_str, special_port_type, 0); }

    bool SetPortLabel(const char* field_str) { return CsvParser::Parse(field_str, port_label); }

    bool SetAPort(const char* field_str) { return CsvParser::ParseWithNA(field_str, aport, (uint16_t)-1); }

    bool SetPlaneNumber(const char* field_str) { return CsvParser::ParseWithNA(field_str, plane_number, (uint8_t)-1); }

    bool SetNumberOfPlanes(const char* field_str) { return CsvParser::ParseWithNA(field_str, number_of_planes, (uint8_t)-1); }

    const std::string GetPortStateString() const;
    const std::string GetStatusString() const;
    const std::string GetDiscThroughStateString() const;
};

class LinkRecord
{
   public:
    u_int64_t node_guid1;
    u_int64_t node_guid2;
    phys_port_t port_num1;
    phys_port_t port_num2;

    LinkRecord() : node_guid1(0), node_guid2(0), port_num1(0), port_num2(0) {}

    static int Init(vector<ParseFieldInfo<class LinkRecord>>& parse_section_info);

    bool SetNodeGuid1(const char* field_str) { return CsvParser::Parse(field_str, node_guid1, 16); }

    bool SetNodeGuid2(const char* field_str) { return CsvParser::Parse(field_str, node_guid2, 16); }

    bool SetPortNum1(const char* field_str) { return CsvParser::Parse(field_str, port_num1, 10); }

    bool SetPortNum2(const char* field_str) { return CsvParser::Parse(field_str, port_num2, 10); }
};

class SwitchRecord
{
   public:
    u_int64_t node_guid;
    u_int16_t num_ports;
    u_int8_t status;
    u_int8_t rank;
    u_int16_t coordinate;
    SwitchIssuState issu;
    u_int16_t mlid_start;
    u_int16_t mlid_end;

    SwitchRecord()
        : node_guid(0), num_ports(0), status(0), rank(0), coordinate(0), issu(SwitchIssuState::ISSU_STATE_IDLE), mlid_start(0), mlid_end(0)
    {}

    static int Init(vector<ParseFieldInfo<class SwitchRecord>>& parse_section_info);

    bool SetNodeGuid(const char* field_str) { return CsvParser::Parse(field_str, node_guid, 16); }

    bool SetNumPorts(const char* field_str) { return CsvParser::Parse(field_str, num_ports, 10); }

    bool SetStatus(const char* field_str) { return CsvParser::Parse(field_str, status, 10); }

    bool SetRank(const char* field_str) { return CsvParser::Parse(field_str, rank, 10); }

    bool SetCoordinate(const char* field_str) { return CsvParser::Parse(field_str, coordinate, 16); }

    bool SetIssu(const char* field_str);

    bool SetMlidStart(const char* field_str) { return CsvParser::Parse(field_str, mlid_start, 10); }

    bool SetMlidEnd(const char* field_str) { return CsvParser::Parse(field_str, mlid_end, 10); }
};

class AnToAnRecord
{
   public:
    u_int64_t node_guid1;
    u_int64_t node_guid2;
    phys_port_t port_num1;
    phys_port_t port_num2;
    string timestamp;

    AnToAnRecord() : node_guid1(0), node_guid2(0), port_num1(0), port_num2(0) {}

    static int Init(vector<ParseFieldInfo<class AnToAnRecord>>& parse_section_info);

    bool SetNodeGuid1(const char* field_str) { return CsvParser::Parse(field_str, node_guid1, 16); }

    bool SetNodeGuid2(const char* field_str) { return CsvParser::Parse(field_str, node_guid2, 16); }

    bool SetPortNum1(const char* field_str) { return CsvParser::Parse(field_str, port_num1, 10); }

    bool SetPortNum2(const char* field_str) { return CsvParser::Parse(field_str, port_num2, 10); }

    bool SetTimestamp(const char* field_str) { return CsvParser::Parse(field_str, timestamp); }
};

class VnodeRecord
{
   public:
    u_int64_t vnode_guid;
    string vnode_description;
    u_int16_t num_ports;

    VnodeRecord() : vnode_guid(0), vnode_description(""), num_ports(0) {}

    static int Init(vector<ParseFieldInfo<class VnodeRecord>>& parse_section_info);

    bool SetVnodeGuid(const char* field_str) { return CsvParser::Parse(field_str, vnode_guid, 16); }

    bool SetVnodeDescription(const char* field_str)
    {
        const auto parser_result = CsvParser::Parse(field_str, vnode_description);
        RemoveWhitespaceCharacters(vnode_description);
        return parser_result;
    }

    bool SetNumPorts(const char* field_str) { return CsvParser::Parse(field_str, num_ports, 10); }
};

class VportRecord
{
   public:
    u_int64_t vport_guid;
    u_int16_t index;
    u_int64_t vnode_guid;
    u_int64_t port_guid;
    phys_port_t vport_num;
    u_int8_t state;
    u_int8_t is_lid_required;
    u_int16_t vport_lid_index;
    u_int16_t vport_lid;
    u_int16_t active_lid;

    VportRecord()
        : vport_guid(0),
          index(0),
          vnode_guid(0),
          port_guid(0),
          vport_num(0),
          state(0),
          is_lid_required(0),
          vport_lid_index(0),
          vport_lid(0),
          active_lid(0)
    {}

    static int Init(vector<ParseFieldInfo<class VportRecord>>& parse_section_info);

    bool SetVportGuid(const char* field_str) { return CsvParser::Parse(field_str, vport_guid, 16); }

    bool SetIndex(const char* field_str) { return CsvParser::Parse(field_str, index, 10); }

    bool SetVnodeGuid(const char* field_str) { return CsvParser::Parse(field_str, vnode_guid, 16); }

    bool SetPortGuid(const char* field_str) { return CsvParser::Parse(field_str, port_guid, 16); }

    bool SetVportNum(const char* field_str) { return CsvParser::Parse(field_str, vport_num, 10); }

    bool SetState(const char* field_str) { return CsvParser::Parse(field_str, state, 10); }

    bool SetIsLidRequired(const char* field_str) { return CsvParser::Parse(field_str, is_lid_required, 10); }

    bool SetVportLidIndex(const char* field_str) { return CsvParser::Parse(field_str, vport_lid_index, 10); }

    bool SetVportLid(const char* field_str) { return CsvParser::Parse(field_str, vport_lid, 10); }

    bool SetActiveLid(const char* field_str) { return CsvParser::Parse(field_str, active_lid, 10); }
};

class SmPortsRecord
{
   public:
    u_int64_t port_guid;
    u_int8_t status;

    SmPortsRecord() : port_guid(0), status(0) {}

    static int Init(vector<ParseFieldInfo<class SmPortsRecord>>& parse_section_info);

    bool SetPortGuid(const char* field_str) { return CsvParser::Parse(field_str, port_guid, 16); }

    bool SetStatus(const char* field_str) { return CsvParser::Parse(field_str, status, 10); }
};

class SmsRecord
{
   public:
    u_int64_t port_guid;
    u_int16_t lid;
    u_int8_t priority;
    u_int8_t state;

    SmsRecord() : port_guid(0), lid(0), priority(0), state(0) {}

    static int Init(vector<ParseFieldInfo<class SmsRecord>>& parse_section_info);

    bool SetPortGuid(const char* field_str) { return CsvParser::Parse(field_str, port_guid, 16); }

    bool SetLID(const char* field_str) { return CsvParser::Parse(field_str, lid, 10); }

    bool SetPriority(const char* field_str) { return CsvParser::Parse(field_str, priority, 10); }

    bool SetState(const char* field_str) { return CsvParser::Parse(field_str, state, 10); }
};

class PortPkeyRecord
{
   public:
    u_int64_t port_guid;
    std::set<u_int16_t> pkeys;

    PortPkeyRecord() : port_guid(0), pkeys() {}

    static int Init(vector<ParseFieldInfo<class PortPkeyRecord>>& parse_section_info);

    bool SetPortGuid(const char* field_str) { return CsvParser::Parse(field_str, port_guid, 16); }

    bool SetPkeys(const char* field_str);
};

class VportPkeyRecord
{
   public:
    u_int64_t vport_guid;
    std::set<u_int16_t> pkeys;

    VportPkeyRecord() : vport_guid(0), pkeys() {}

    static int Init(vector<ParseFieldInfo<class VportPkeyRecord>>& parse_section_info);

    bool SetVportGuid(const char* field_str) { return CsvParser::Parse(field_str, vport_guid, 16); }

    bool SetPkeys(const char* field_str);
};

typedef std::map<AnToAnKey, AnToAnInfo> MapAnToAnInfo;
typedef MapAnToAnInfo::iterator MapAnToAnInfoIter;
typedef std::pair<MapAnToAnInfoIter, bool> MapAnToAnInfoInsertRes;

class CommandManager;

enum NodeState
{
    AGG_NODE_NEW = 0,
    AGG_NODE_DISCOVERY,
    AGG_NODE_REDISCOVERY,
    AGG_NODE_AMKEY_SET,
    AGG_NODE_AMKEY_RECOVERY,
    AGG_NODE_CLEAN,
    AGG_NODE_AN_INFO_CONFIGURE,
    AGG_NODE_PORT_CREDITS_CONFIGURE,
    AGG_NODE_READY_TO_BECOME_ACTIVE,
    AGG_NODE_ACTIVE,
    AGG_NODE_ERROR,
};

static inline const char* NodeStateToChar(NodeState state)
{
    switch (state) {
        case AGG_NODE_NEW:
            return ("New");
        case AGG_NODE_DISCOVERY:
            return ("Discovery");
        case AGG_NODE_REDISCOVERY:
            return ("Rediscovery");
        case AGG_NODE_AMKEY_SET:
            return ("AMKey set");
        case AGG_NODE_AMKEY_RECOVERY:
            return ("AMKey recovery");
        case AGG_NODE_CLEAN:
            return ("Clean");
        case AGG_NODE_AN_INFO_CONFIGURE:
            return ("ANInfo configure");
        case AGG_NODE_PORT_CREDITS_CONFIGURE:
            return ("Port credits configure");
        case AGG_NODE_READY_TO_BECOME_ACTIVE:
            return ("Ready to become active");
        case AGG_NODE_ACTIVE:
            return ("Active");
        case AGG_NODE_ERROR:
            return ("Error");
        default:
            return ("Unknown Aggregation Node state");
    }
}

///////////////////////////////////////////////////////////////////////////////
//
// Port class.
//

class Port
{
    uint64_t m_guid_;             // The port GUID (on SW only on Port0)
    class Port* m_remote_port_;   // Port connected on the other side of link
    class Node* m_node_;          // The node the port is part of.
    phys_port_t m_number_;        // Physical ports are identified by number.
    lid_t m_base_lid_;            // The base lid assigned to the port.
    uint8_t m_lmc_;
    PortTimestamp m_timestamp_;             // Port update timestamp reported by subnet manager
    SpecialPortType m_special_port_type_;   // Port type reported by subnet manager.
    uint16_t m_port_rate_;
    string m_port_label_;
    u_int16_t m_aport_;
    u_int8_t m_plane_number_;
    u_int8_t m_number_of_planes_;
    Pkeys_t m_pkeys_;

    Epoch m_port_epoch_;   // last update number

    std::unique_ptr<PortData> m_port_data_;

    uint8_t m_error_count;
    bool m_use_grh_;
    bool m_is_active_;

   public:
    // constructor
    Port(Node* p_node_ptr,
         phys_port_t number,
         uint64_t port_guid,
         lid_t lid,
         uint8_t lmc,
         const PortTimestamp& timestamp,
         SpecialPortType special_port_type,
         uint16_t port_rate,
         const string& port_label,
         u_int16_t aport,
         u_int8_t plane_number,
         u_int8_t number_of_planes)
        : m_guid_(port_guid),
          m_remote_port_(NULL),
          m_node_(p_node_ptr),
          m_number_(number),
          m_base_lid_(lid),
          m_lmc_(lmc),
          m_timestamp_(timestamp),
          m_special_port_type_(special_port_type),
          m_port_rate_(port_rate),
          m_port_label_(port_label),
          m_aport_(aport),
          m_plane_number_(plane_number),
          m_number_of_planes_(number_of_planes),
          m_port_epoch_(),
          m_port_data_(nullptr),
          m_error_count(0),
          m_use_grh_(false),
          m_is_active_{true}
    {}

    Port(const Port&) = delete;
    Port& operator=(const Port&) = delete;
    Port(Port&&) = default;
    Port& operator=(Port&&) = default;

    void SetEpoch(uint64_t epoch) { m_port_epoch_.SetEpoch(epoch); }

    void RevertEpoch(const uint64_t curr_epoch) { m_port_epoch_.Revert(curr_epoch); }

    uint64_t GetPortCurrEpoch() const { return m_port_epoch_.m_curr_epoch_; }
    uint64_t GetPortPrevEpoch() const { return m_port_epoch_.m_prev_epoch_; }
    bool GetUseGRH() const { return m_use_grh_; }
    bool IsActive() const { return m_is_active_; }
    void Activate() { m_is_active_ = true; }
    void Disable() { m_is_active_ = false; }
    inline bool IsStateChangeFromCurrentEpoch() const { return m_port_epoch_.m_curr_epoch_ == m_port_epoch_.m_prev_epoch_; }

    // get the port name
    string GetName() const;

    // connect the port to another node port
    // call separately on each port of the connection
    void Connect(Port* p_other_port, uint64_t epoch);

    void UpdatePortInfo(const PortInfo& port_info);

    uint64_t GetGuid() const { return m_guid_; }

    Port* GetRemotePort() const { return m_remote_port_; }

    Node* GetNode() { return m_node_; }

    phys_port_t GetNum() const { return m_number_; }
    u_int8_t GetPlaneNumber() const { return m_plane_number_; }
    u_int8_t GetMaxPlane() const { return m_number_of_planes_; }
    u_int8_t GetAport() const { return m_aport_; }

    void SetBaseLid(lid_t lid) { m_base_lid_ = lid; }
    lid_t GetBaseLid() { return m_base_lid_; }
    void SetLmc(const uint8_t lmc) { m_lmc_ = lmc; }
    lid_t GetLmc() const { return m_lmc_; }

    SpecialPortType GetSpecialPortType() const { return m_special_port_type_; }
    uint16_t GetPortRate() const { return m_port_rate_; }

    PortData* CreatePortData(const PortInfo& port_info);
    PortData* GetPortData() const { return m_port_data_.get(); }

    const PortTimestamp& GetTimestamp() const { return m_timestamp_; }
    void SetTimestamp(const PortTimestamp& timestamp) { m_timestamp_ = timestamp; }

    uint8_t GetErrorCount() { return m_error_count; }
    void IncMadErrorCount() { m_error_count++; }
    void ResetMadErrorCount() { m_error_count = 0; }

    inline void SetPkeys(const Pkeys_t& pkeys) { m_pkeys_ = pkeys; }
    inline const Pkeys_t& GetPkeys() const { return m_pkeys_; }

   private:
    // disconnect the port and remote port.
    void Disconnect();
    void DisconnectRemote();
};

///////////////////////////////////////////////////////////////////////////////
//
// Vport class.
//

class Vport
{
    uint64_t m_guid_;        // The port VGUID (on CA only)
    class Vnode* m_vnode_;   // The vnode the vport is part of.
    class Port* m_port_;     // The physical port the vport is part of.
    phys_port_t m_number_;   // vports are identified by number.
    lid_t m_vlid_;           // The base vlid assigned to the vport.
    Epoch m_vport_epoch_;    // last update number
    std::unique_ptr<VportData> m_vport_data_;
    bool m_use_grh_;   // indicates whether use GRH or not.

   public:
    // constructor
    Vport(const phys_port_t number,
          const uint64_t vport_guid,
          const lid_t vlid,
          Vnode* const p_vnode,
          Port* const p_port,
          const bool use_grh = false)
    {
        Reset(number, vport_guid, vlid, p_vnode, p_port, use_grh);
    }

    void Reset(const phys_port_t number,
               const uint64_t vport_guid,
               const lid_t vlid,
               Vnode* const p_vnode,
               Port* const p_port,
               const bool use_grh = false);

    void SetEpoch(uint64_t epoch) { m_vport_epoch_.SetEpoch(epoch); }

    void RevertEpoch(uint64_t curr_epoch) { m_vport_epoch_.Revert(curr_epoch); }

    uint64_t GetCurrEpoch() const { return m_vport_epoch_.m_curr_epoch_; }
    uint64_t GetPrevEpoch() const { return m_vport_epoch_.m_prev_epoch_; }
    bool GetUseGRH() const { return m_use_grh_; }

    // get the port name
    string GetName() const;

    uint64_t GetGuid() const { return m_guid_; }

    lid_t GetVlid() { return m_vlid_; }

    VportData* GetVportData() { return m_vport_data_.get(); }

    Vnode* GetVnode() { return m_vnode_; }
    Vnode const* GetVnode() const { return m_vnode_; }
    Port* GetPhysPort() { return m_port_; }

    phys_port_t GetNum() { return m_number_; }

    VportData* CreateVportData(const PortInfo& vport_info, const std::string& host_name);

    void Dump(ostream& sout) const;
};

///////////////////////////////////////////////////////////////////////////////
//
// Vnode class.
//

class Vnode
{
    uint64_t m_guid_;          // The virtual node GUID
    string m_description_;     // Description of the vnode
    uint64_t m_vnode_epoch_;   // last update number
    Pkeys_t m_pkeys_;

   public:
    // constructor
    Vnode(const uint64_t vport_guid, const string& vnode_description)
        : m_guid_(vport_guid), m_description_(vnode_description), m_vnode_epoch_()
    {}

    inline void SetEpoch(uint64_t epoch) { m_vnode_epoch_ = epoch; }
    inline uint64_t GetCurrEpoch() { return m_vnode_epoch_; }
    inline const string& GetDescription() const { return m_description_; }
    inline void UpdateDescription(const std::string& new_description) { m_description_ = new_description; }
    inline uint64_t GetGuid() const { return m_guid_; }
    inline void SetPkeys(const Pkeys_t& pkeys) { m_pkeys_ = pkeys; }
    inline const Pkeys_t& GetPkeys() const { return m_pkeys_; }
};

enum UpDownMinhopDirection
{
    UPDN_MINHOP_DIRECTION_UP = 0,
    UPDN_MINHOP_DIRECTION_DOWN = 1
};

//
// Node class
//
class Node
{
    VectorOfUniquePorts m_ports_;   // Vector of all the ports (in index 0 we will put port0 if exist)
    uint64_t m_guid_;               // Node Guild
    lid_t m_switch_lid_;            // Lid of management port (valid only for IB_SW_NODE)
    NodeType m_type_;               // Either a CA or SW
    string m_description_;          // Description of the node
    bool m_is_active_ = true;
    uint8_t m_first_plane_;
    uint8_t m_last_plane_;
    uint8_t m_max_plane_;
    SwitchIssuState m_issu_;
    uint16_t m_mlid_start_;
    uint16_t m_mlid_end_;

    NodeTopologyInfo m_topology_info_;   // Topology information for this node.

    Port* m_agg_peer_;   // port connected to agg_node;

    // Non planarized table are located at m_min_hops_table_[0]
    // Planarized tables are located according to plane index ( index = plane - 1)
    MinHopsTableByPlane m_min_hops_table_;   // minimal hops number to sw node, key=LID value=hops [0xFF, 0xFF, 1, 0, 0xFF, 2]
    MinHopsIndexTableByPlane
        m_min_hops_index_table_;   // contain all keys(LIDs) of min hops table [2,3,5] that have a valid range (not MAX_NUM_HOPS)

    PrivateAppData m_app_data_1;

    uint64_t m_node_epoch_;           // last update number
    uint64_t m_state_change_epoch_;   // epoch when state was last changed

    // temp protocol variables
    mutable SetNodesPtr m_adjacent_switch_nodes_;
    mutable uint16_t m_cycle_number_;   // used to check if visited

    NodeState m_state_;            // Indicates state of Node. Used to determine which action is needed next on this node.
    NodeState m_previous_state_;   // Indicates previous state of the Node.

   public:
    // Constructor
    Node(const NodeType node_type,
         const phys_port_t number_of_ports,
         const uint64_t node_guid,
         const string& description,
         const TopologyType topology_type);
    Node(const Node&) = delete;
    Node& operator=(const Node&) = delete;
    Node(Node&&) = default;
    Node& operator=(Node&&) = default;

    inline NodeState GetState() const { return m_state_; }
    inline NodeState GetPreviousState() const { return m_previous_state_; }
    inline void SetEpoch(uint64_t epoch) { m_node_epoch_ = epoch; }
    inline uint64_t GetCurrEpoch() const { return m_node_epoch_; }
    inline uint64_t GetStateChangeEpoch() const { return m_state_change_epoch_; }
    inline void SetStateChangeEpoch(uint64_t epoch) { m_state_change_epoch_ = epoch; }
    inline bool IsStateChangeFromCurrentEpoch() const { return m_node_epoch_ == m_state_change_epoch_; }
    // get the node name
    inline const string& GetDescription() const { return m_description_; }
    inline NodeType GetType() const { return m_type_; }
    void SetSwitchLid(lid_t lid) { m_switch_lid_ = lid; }
    lid_t GetSwitchLid() const { return m_switch_lid_; }
    NodeTopologyInfo& GetTopologyInfo() { return m_topology_info_; }
    inline phys_port_t GetTotalNumberOfPorts() const { return m_ports_.size() - 1; }
    inline phys_port_t GetNumberOfAllocatedPorts() const
    {
        return std::count_if(m_ports_.begin(), m_ports_.end(), [](const std::unique_ptr<Port>& port) { return (nullptr != port); });
    }
    inline uint64_t GetGuid() const { return m_guid_; }
    inline void SetAggPeer(Port* p_agg_peer) { m_agg_peer_ = p_agg_peer; }
    inline void SetAppData1(const uint64_t val) { m_app_data_1.val = val; }
    inline Port* GetAggPeer() { return m_agg_peer_; }
    inline const MinHopsTableByPlane* GetMinHopsTable() const { return &m_min_hops_table_; }
    inline const PrivateAppData& GetAppData1() const { return m_app_data_1; }
    inline bool IsVisited(uint16_t cycle_number) const { return (cycle_number == m_cycle_number_); }
    inline bool SetVisited(uint16_t cycle_number) const { return (m_cycle_number_ = cycle_number); }
    bool IsActive() const { return m_is_active_; }
    void Activate() { m_is_active_ = true; }
    void Disable() { m_is_active_ = false; }
    void AddPlane(const uint8_t plane);
    uint8_t GetPlaneFirst() const { return m_first_plane_; }
    uint8_t GetPlaneLast() const { return m_last_plane_; }
    void SetMaxPlane(const uint8_t max_planes) { m_max_plane_ = max_planes; }
    uint8_t GetMaxPlane() const { return m_max_plane_; }
    bool IsPlanarized() const
    {   // Planarized with single plane like black mamba switch with AN per plane
        return m_first_plane_ && m_last_plane_;
    }
    bool IsPlanarizedMultiPlane() const
    {   // Planarized with multiple planes like crocodile switch with single AN for all planes
        return m_first_plane_ && m_last_plane_ && m_first_plane_ != m_last_plane_;
    }
    bool IsOnPlane(uint8_t plane) { return m_first_plane_ <= plane && plane <= m_last_plane_; }
    std::string PlaneToString() const;
    void SetIssuState(SwitchIssuState issu) { m_issu_ = issu; };
    SwitchIssuState GetIssuState() { return m_issu_; }
    void SetNodeMlidStart(uint16_t mlid_start) { m_mlid_start_ = mlid_start; }
    void SetNodeMlidEnd(uint16_t mlid_end) { m_mlid_end_ = mlid_end; }
    uint16_t GetNodeMlidStart() { return m_mlid_start_; }
    uint16_t GetNodeMlidEnd() { return m_mlid_end_; }
    void SetState(const NodeState new_state);
    string GetName() const;

    // get or create port, add it to ca_port_by_guid if the node is IB_CA_NODE
    Port* GetOrCreatePort(const phys_port_t port_index,
                          const uint64_t port_guid,
                          const lid_t lid,
                          const uint8_t lmc,
                          const PortTimestamp& timestamp,
                          const SpecialPortType special_port_type,
                          const uint16_t port_rate,
                          const string& port_label,
                          const uint16_t aport,
                          const uint8_t plane_number,
                          const uint8_t number_of_planes,
                          MapPortKeyToPairNodeKeyAndPortIndex& ca_port_by_guid);
    // get a port by number num = 1..N:
    Port* GetPort(const phys_port_t num);

    bool IsAnyPort(const std::function<bool(const std::unique_ptr<Port>&)>& condition_callback);
    bool HasAtLeastOneActivePort(const uint64_t epoch);
    bool IsAggregationNode();
    void ExecuteOnPorts(const std::function<void(std::unique_ptr<Port>&)>& execute_callback);

    Port* GetAggValidPeer();
    // Return pointer to aggregation node object connected to this node.
    AggNode* GetValidAggNode();

    void UpdateDownNodesRank(ListOfNodes& nodes_queue);

    void CalculateUpDownNodesMinHopsTable(UpDownMinhopDirection direction);
    bool UpdateAdjacentNodesMinhopsTable();
    void SetNumHops(uint64_t sw_lid, uint8_t hops, uint8_t plane_index);
    void ClearMinhopsTable();
    uint8_t GetNumHops(uint64_t to_sw_guid, uint8_t plane);

    void Dump(ostream& sout, const MapGuidToNodePtr& node_by_guid) const;
    void DumpMinHopsTable(ostream& sout, const MapGuidToNodePtr& node_by_guid) const;

    // temp protocol variables
    void ResetProtocolVariables();

    const SetNodesPtr& GetAdjacentSwitchNodes() const;

    void LogNodePlaneMismatch(std::set<std::tuple<std::string, std::string, int, int>>& unique_pairs);

   private:
    void DumpDescLine(ostream& sout) const;
    bool UpdatesMinHopsTable(MinHopsTable& adjacent_min_hops_table, MinHopsIndexTable& adjacent_min_hops_index_table, uint8_t plane_index);

};   // Class Node

//------------------------
// Static methods, not related to any class
//------------------------

void LogPortsWithNewState(const MapPortKeyToAnPortPtr& p_ports,
                          const NodeState expected_previous_state,
                          std::function<std::pair<std::string, std::string>(Port*)> additional_msg_cb = {});
