/*
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author: George F. Riley<riley@ece.gatech.edu>
 * Modified by: John Abraham <john.abraham@gatech.edu>
 * Contributions: Eugene Kalishenko <ydginster@gmail.com> (Open Source and Linux Laboratory
 * http://wiki.osll.ru/doku.php/start) Tommaso Pecorella <tommaso.pecorella@unifi.it> Pavel Vasilyev
 * <pavel.vasilyev@sredasolutions.com>
 */

// Interface between ns-3 and the network animator

#include <cstdio>
#ifndef WIN32
#include <unistd.h>
#endif
#include <fstream>
#include <iomanip>
#include <map>
#include <sstream>
#include <string>

// ns3 includes
#ifdef __WIN32__
#include "ns3/csma-net-device.h"
#endif
#include "animation-interface.h"

#include "ns3/channel.h"
#include "ns3/config.h"
#include "ns3/constant-position-mobility-model.h"
#include "ns3/double.h"
#include "ns3/energy-source-container.h"
#include "ns3/ipv4-routing-protocol.h"
#include "ns3/ipv4.h"
#include "ns3/ipv6.h"
#include "ns3/lr-wpan-mac-header.h"
#include "ns3/lr-wpan-net-device.h"
#include "ns3/lte-enb-phy.h"
#include "ns3/lte-ue-phy.h"
#include "ns3/mobility-model.h"
#include "ns3/node.h"
#include "ns3/packet.h"
#include "ns3/simulator.h"
#include "ns3/uan-mac.h"
#include "ns3/uan-net-device.h"
#include "ns3/wifi-mac-header.h"
#include "ns3/wifi-mac.h"
#include "ns3/wifi-net-device.h"
#include "ns3/wifi-psdu.h"

namespace ns3
{

NS_LOG_COMPONENT_DEFINE("AnimationInterface");

// Globals

static bool initialized = false; //!< Initialization flag

// Public methods

AnimationInterface::AnimationInterface(const std::string fn)
    : m_f(nullptr),
      m_routingF(nullptr),
      m_mobilityPollInterval(Seconds(0.25)),
      m_outputFileName(fn),
      gAnimUid(0),
      m_writeCallback(nullptr),
      m_started(false),
      m_enablePacketMetadata(false),
      m_startTime(),
      m_stopTime(Seconds(3600 * 1000)),
      m_maxPktsPerFile(MAX_PKTS_PER_TRACE_FILE),
      m_originalFileName(fn),
      m_routingStopTime(),
      m_routingFileName(""),
      m_routingPollInterval(Seconds(5)),
      m_trackPackets(true)
{
    initialized = true;
    StartAnimation();

#ifdef __WIN32__
    /**
     * Shared libraries are handled differently on Windows and
     * need to be explicitly loaded via LoadLibrary("library.dll").
     *
     * Otherwise, static import libraries .dll.a/.lib (MinGW/MSVC)
     * can be linked to the executables to perform the loading of
     * their respective .dll implicitly during static initialization.
     *
     * The .dll.a/.lib however, only gets linked if we instantiate at
     * least one symbol exported by the .dll.
     *
     * To ensure TypeIds from the Csma, Uan, and Wifi
     * modules are registered during runtime, we need to instantiate
     * at least one symbol exported by each of these module libraries.
     */
    static CsmaNetDevice c;
    static WifiNetDevice w;
    static UanNetDevice u;
#endif
}

AnimationInterface::~AnimationInterface()
{
    StopAnimation();
}

void
AnimationInterface::SkipPacketTracing()
{
    m_trackPackets = false;
}

void
AnimationInterface::EnableWifiPhyCounters(Time startTime, Time stopTime, Time pollInterval)
{
    m_wifiPhyCountersStopTime = stopTime;
    m_wifiPhyCountersPollInterval = pollInterval;
    m_wifiPhyTxDropCounterId = AddNodeCounter("WifiPhy TxDrop", AnimationInterface::DOUBLE_COUNTER);
    m_wifiPhyRxDropCounterId = AddNodeCounter("WifiPhy RxDrop", AnimationInterface::DOUBLE_COUNTER);
    for (auto i = NodeList::Begin(); i != NodeList::End(); ++i)
    {
        Ptr<Node> n = *i;
        m_nodeWifiPhyTxDrop[n->GetId()] = 0;
        m_nodeWifiPhyRxDrop[n->GetId()] = 0;
        UpdateNodeCounter(m_wifiPhyTxDropCounterId, n->GetId(), 0);
        UpdateNodeCounter(m_wifiPhyRxDropCounterId, n->GetId(), 0);
    }
    Simulator::Schedule(startTime, &AnimationInterface::TrackWifiPhyCounters, this);
}

void
AnimationInterface::EnableWifiMacCounters(Time startTime, Time stopTime, Time pollInterval)
{
    m_wifiMacCountersStopTime = stopTime;
    m_wifiMacCountersPollInterval = pollInterval;
    m_wifiMacTxCounterId = AddNodeCounter("WifiMac Tx", AnimationInterface::DOUBLE_COUNTER);
    m_wifiMacTxDropCounterId = AddNodeCounter("WifiMac TxDrop", AnimationInterface::DOUBLE_COUNTER);
    m_wifiMacRxCounterId = AddNodeCounter("WifiMac Rx", AnimationInterface::DOUBLE_COUNTER);
    m_wifiMacRxDropCounterId = AddNodeCounter("WifiMac RxDrop", AnimationInterface::DOUBLE_COUNTER);
    for (auto i = NodeList::Begin(); i != NodeList::End(); ++i)
    {
        Ptr<Node> n = *i;
        m_nodeWifiMacTx[n->GetId()] = 0;
        m_nodeWifiMacTxDrop[n->GetId()] = 0;
        m_nodeWifiMacRx[n->GetId()] = 0;
        m_nodeWifiMacRxDrop[n->GetId()] = 0;
        UpdateNodeCounter(m_wifiMacTxCounterId, n->GetId(), 0);
        UpdateNodeCounter(m_wifiMacTxDropCounterId, n->GetId(), 0);
        UpdateNodeCounter(m_wifiMacRxCounterId, n->GetId(), 0);
        UpdateNodeCounter(m_wifiMacRxDropCounterId, n->GetId(), 0);
    }
    Simulator::Schedule(startTime, &AnimationInterface::TrackWifiMacCounters, this);
}

void
AnimationInterface::EnableQueueCounters(Time startTime, Time stopTime, Time pollInterval)
{
    m_queueCountersStopTime = stopTime;
    m_queueCountersPollInterval = pollInterval;
    m_queueEnqueueCounterId = AddNodeCounter("Enqueue", AnimationInterface::DOUBLE_COUNTER);
    m_queueDequeueCounterId = AddNodeCounter("Dequeue", AnimationInterface::DOUBLE_COUNTER);
    m_queueDropCounterId = AddNodeCounter("Queue Drop", AnimationInterface::DOUBLE_COUNTER);
    for (auto i = NodeList::Begin(); i != NodeList::End(); ++i)
    {
        Ptr<Node> n = *i;
        m_nodeQueueEnqueue[n->GetId()] = 0;
        m_nodeQueueDequeue[n->GetId()] = 0;
        m_nodeQueueDrop[n->GetId()] = 0;
        UpdateNodeCounter(m_queueEnqueueCounterId, n->GetId(), 0);
        UpdateNodeCounter(m_queueDequeueCounterId, n->GetId(), 0);
        UpdateNodeCounter(m_queueDropCounterId, n->GetId(), 0);
    }
    Simulator::Schedule(startTime, &AnimationInterface::TrackQueueCounters, this);
}

void
AnimationInterface::EnableIpv4L3ProtocolCounters(Time startTime, Time stopTime, Time pollInterval)
{
    m_ipv4L3ProtocolCountersStopTime = stopTime;
    m_ipv4L3ProtocolCountersPollInterval = pollInterval;
    m_ipv4L3ProtocolTxCounterId = AddNodeCounter("Ipv4 Tx", AnimationInterface::DOUBLE_COUNTER);
    m_ipv4L3ProtocolRxCounterId = AddNodeCounter("Ipv4 Rx", AnimationInterface::DOUBLE_COUNTER);
    m_ipv4L3ProtocolDropCounterId = AddNodeCounter("Ipv4 Drop", AnimationInterface::DOUBLE_COUNTER);
    for (auto i = NodeList::Begin(); i != NodeList::End(); ++i)
    {
        Ptr<Node> n = *i;
        m_nodeIpv4Tx[n->GetId()] = 0;
        m_nodeIpv4Rx[n->GetId()] = 0;
        m_nodeIpv4Drop[n->GetId()] = 0;
        UpdateNodeCounter(m_ipv4L3ProtocolTxCounterId, n->GetId(), 0);
        UpdateNodeCounter(m_ipv4L3ProtocolRxCounterId, n->GetId(), 0);
        UpdateNodeCounter(m_ipv4L3ProtocolDropCounterId, n->GetId(), 0);
    }
    Simulator::Schedule(startTime, &AnimationInterface::TrackIpv4L3ProtocolCounters, this);
}

AnimationInterface&
AnimationInterface::EnableIpv4RouteTracking(std::string fileName,
                                            Time startTime,
                                            Time stopTime,
                                            Time pollInterval)
{
    SetOutputFile(fileName, true);
    m_routingStopTime = stopTime;
    m_routingPollInterval = pollInterval;
    WriteXmlAnim(true);
    Simulator::Schedule(startTime, &AnimationInterface::TrackIpv4Route, this);
    return *this;
}

AnimationInterface&
AnimationInterface::EnableIpv4RouteTracking(std::string fileName,
                                            Time startTime,
                                            Time stopTime,
                                            NodeContainer nc,
                                            Time pollInterval)
{
    m_routingNc = nc;
    return EnableIpv4RouteTracking(fileName, startTime, stopTime, pollInterval);
}

AnimationInterface&
AnimationInterface::AddSourceDestination(uint32_t fromNodeId, std::string ipv4Address)
{
    Ipv4RouteTrackElement element = {ipv4Address, fromNodeId};
    m_ipv4RouteTrackElements.push_back(element);
    return *this;
}

void
AnimationInterface::SetStartTime(Time t)
{
    m_startTime = t;
}

void
AnimationInterface::SetStopTime(Time t)
{
    m_stopTime = t;
}

void
AnimationInterface::SetMaxPktsPerTraceFile(uint64_t maxPacketsPerFile)
{
    m_maxPktsPerFile = maxPacketsPerFile;
}

uint32_t
AnimationInterface::AddNodeCounter(std::string counterName, CounterType counterType)
{
    m_nodeCounters.push_back(counterName);
    uint32_t counterId = m_nodeCounters.size() - 1; // counter ID is zero-indexed
    WriteXmlAddNodeCounter(counterId, counterName, counterType);
    return counterId;
}

uint32_t
AnimationInterface::AddResource(std::string resourcePath)
{
    m_resources.push_back(resourcePath);
    uint32_t resourceId = m_resources.size() - 1; // resource ID is zero-indexed
    WriteXmlAddResource(resourceId, resourcePath);
    return resourceId;
}

void
AnimationInterface::EnablePacketMetadata(bool enable)
{
    m_enablePacketMetadata = enable;
    if (enable)
    {
        Packet::EnablePrinting();
    }
}

bool
AnimationInterface::IsInitialized()
{
    return initialized;
}

bool
AnimationInterface::IsStarted() const
{
    return m_started;
}

void
AnimationInterface::SetAnimWriteCallback(AnimWriteCallback cb)
{
    m_writeCallback = cb;
}

void
AnimationInterface::ResetAnimWriteCallback()
{
    m_writeCallback = nullptr;
}

void
AnimationInterface::SetMobilityPollInterval(Time t)
{
    m_mobilityPollInterval = t;
}

void
AnimationInterface::SetConstantPosition(Ptr<Node> n, double x, double y, double z)
{
    NS_ASSERT(n);
    Ptr<ConstantPositionMobilityModel> loc = n->GetObject<ConstantPositionMobilityModel>();
    if (!loc)
    {
        loc = CreateObject<ConstantPositionMobilityModel>();
        n->AggregateObject(loc);
    }
    Vector hubVec(x, y, z);
    loc->SetPosition(hubVec);
    NS_LOG_INFO("Node:" << n->GetId() << " Position set to:(" << x << "," << y << "," << z << ")");
}

void
AnimationInterface::UpdateNodeImage(uint32_t nodeId, uint32_t resourceId)
{
    NS_LOG_INFO("Setting node image for Node Id:" << nodeId);
    if (resourceId > (m_resources.size() - 1))
    {
        NS_FATAL_ERROR("Resource Id:" << resourceId << " not found. Did you use AddResource?");
    }
    WriteXmlUpdateNodeImage(nodeId, resourceId);
}

void
AnimationInterface::UpdateNodeCounter(uint32_t nodeCounterId, uint32_t nodeId, double counter)
{
    if (nodeCounterId > (m_nodeCounters.size() - 1))
    {
        NS_FATAL_ERROR("NodeCounter Id:" << nodeCounterId
                                         << " not found. Did you use AddNodeCounter?");
    }
    WriteXmlUpdateNodeCounter(nodeCounterId, nodeId, counter);
}

void
AnimationInterface::SetBackgroundImage(std::string fileName,
                                       double x,
                                       double y,
                                       double scaleX,
                                       double scaleY,
                                       double opacity)
{
    if ((opacity < 0) || (opacity > 1))
    {
        NS_FATAL_ERROR("Opacity must be between 0.0 and 1.0");
    }
    WriteXmlUpdateBackground(fileName, x, y, scaleX, scaleY, opacity);
}

void
AnimationInterface::UpdateNodeSize(Ptr<Node> n, double width, double height)
{
    UpdateNodeSize(n->GetId(), width, height);
}

void
AnimationInterface::UpdateNodeSize(uint32_t nodeId, double width, double height)
{
    AnimationInterface::NodeSize s = {width, height};
    m_nodeSizes[nodeId] = s;
    WriteXmlUpdateNodeSize(nodeId, s.width, s.height);
}

void
AnimationInterface::UpdateNodeColor(Ptr<Node> n, uint8_t r, uint8_t g, uint8_t b)
{
    UpdateNodeColor(n->GetId(), r, g, b);
}

void
AnimationInterface::UpdateNodeColor(uint32_t nodeId, uint8_t r, uint8_t g, uint8_t b)
{
    NS_ASSERT(NodeList::GetNode(nodeId));
    NS_LOG_INFO("Setting node color for Node Id:" << nodeId);
    Rgb rgb = {r, g, b};
    m_nodeColors[nodeId] = rgb;
    WriteXmlUpdateNodeColor(nodeId, r, g, b);
}

void
AnimationInterface::UpdateLinkDescription(uint32_t fromNode,
                                          uint32_t toNode,
                                          std::string linkDescription)
{
    WriteXmlUpdateLink(fromNode, toNode, linkDescription);
}

void
AnimationInterface::UpdateLinkDescription(Ptr<Node> fromNode,
                                          Ptr<Node> toNode,
                                          std::string linkDescription)
{
    NS_ASSERT(fromNode);
    NS_ASSERT(toNode);
    WriteXmlUpdateLink(fromNode->GetId(), toNode->GetId(), linkDescription);
}

void
AnimationInterface::UpdateNodeDescription(Ptr<Node> n, std::string descr)
{
    UpdateNodeDescription(n->GetId(), descr);
}

void
AnimationInterface::UpdateNodeDescription(uint32_t nodeId, std::string descr)
{
    NS_ASSERT(NodeList::GetNode(nodeId));
    m_nodeDescriptions[nodeId] = descr;
    WriteXmlUpdateNodeDescription(nodeId);
}

// Private methods

double
AnimationInterface::GetNodeEnergyFraction(Ptr<const Node> node) const
{
    const auto fractionIter = m_nodeEnergyFraction.find(node->GetId());
    NS_ASSERT(fractionIter != m_nodeEnergyFraction.end());
    return fractionIter->second;
}

void
AnimationInterface::MobilityCourseChangeTrace(Ptr<const MobilityModel> mobility)
{
    CHECK_STARTED_INTIMEWINDOW;
    Ptr<Node> n = mobility->GetObject<Node>();
    NS_ASSERT(n);
    Vector v;
    if (!mobility)
    {
        v = GetPosition(n);
    }
    else
    {
        v = mobility->GetPosition();
    }
    UpdatePosition(n, v);
    WriteXmlUpdateNodePosition(n->GetId(), v.x, v.y);
}

bool
AnimationInterface::NodeHasMoved(Ptr<Node> n, Vector newLocation)
{
    Vector oldLocation = GetPosition(n);
    bool moved = !((ceil(oldLocation.x) == ceil(newLocation.x)) &&
                   (ceil(oldLocation.y) == ceil(newLocation.y)));
    return moved;
}

void
AnimationInterface::MobilityAutoCheck()
{
    CHECK_STARTED_INTIMEWINDOW;
    std::vector<Ptr<Node>> MovedNodes = GetMovedNodes();
    for (uint32_t i = 0; i < MovedNodes.size(); i++)
    {
        Ptr<Node> n = MovedNodes[i];
        NS_ASSERT(n);
        Vector v = GetPosition(n);
        WriteXmlUpdateNodePosition(n->GetId(), v.x, v.y);
    }
    if (!Simulator::IsFinished())
    {
        PurgePendingPackets(AnimationInterface::WIFI);
        PurgePendingPackets(AnimationInterface::LTE);
        PurgePendingPackets(AnimationInterface::CSMA);
        PurgePendingPackets(AnimationInterface::LRWPAN);
        Simulator::Schedule(m_mobilityPollInterval, &AnimationInterface::MobilityAutoCheck, this);
    }
}

std::vector<Ptr<Node>>
AnimationInterface::GetMovedNodes()
{
    std::vector<Ptr<Node>> movedNodes;
    for (auto i = NodeList::Begin(); i != NodeList::End(); ++i)
    {
        Ptr<Node> n = *i;
        NS_ASSERT(n);
        Ptr<MobilityModel> mobility = n->GetObject<MobilityModel>();
        Vector newLocation;
        if (!mobility)
        {
            newLocation = GetPosition(n);
        }
        else
        {
            newLocation = mobility->GetPosition();
        }
        if (!NodeHasMoved(n, newLocation))
        {
            continue; // Location has not changed
        }
        else
        {
            UpdatePosition(n, newLocation);
            movedNodes.push_back(n);
        }
    }
    return movedNodes;
}

int
AnimationInterface::WriteN(const std::string& st, FILE* f)
{
    if (!f)
    {
        return 0;
    }
    if (m_writeCallback)
    {
        m_writeCallback(st.c_str());
    }
    return WriteN(st.c_str(), st.length(), f);
}

int
AnimationInterface::WriteN(const char* data, uint32_t count, FILE* f)
{
    if (!f)
    {
        return 0;
    }
    // Write count bytes to h from data
    uint32_t nLeft = count;
    const char* p = data;
    uint32_t written = 0;
    while (nLeft)
    {
        int n = std::fwrite(p, 1, nLeft, f);
        if (n <= 0)
        {
            return written;
        }
        written += n;
        nLeft -= n;
        p += n;
    }
    return written;
}

void
AnimationInterface::WriteRoutePath(uint32_t nodeId,
                                   std::string destination,
                                   Ipv4RoutePathElements rpElements)
{
    NS_LOG_INFO("Writing Route Path From :" << nodeId << " To: " << destination);
    WriteXmlRp(nodeId, destination, rpElements);
    /*
    for (auto i = rpElements.begin (); i != rpElements.end (); ++i)
      {
        Ipv4RoutePathElement rpElement = *i;
        NS_LOG_INFO ("Node:" << rpElement.nodeId << "-->" << rpElement.nextHop.c_str ());
        WriteN (GetXmlRp (rpElement.node, GetIpv4RoutingTable (n)), m_routingF);

      }
    */
}

void
AnimationInterface::WriteNonP2pLinkProperties(uint32_t id,
                                              std::string ipv4Address,
                                              std::string channelType)
{
    WriteXmlNonP2pLinkProperties(id, ipv4Address, channelType);
}

const std::vector<std::string>
AnimationInterface::GetElementsFromContext(const std::string& context) const
{
    std::vector<std::string> elements;
    std::size_t pos1 = 0;
    std::size_t pos2;
    while (pos1 != std::string::npos)
    {
        pos1 = context.find('/', pos1);
        pos2 = context.find('/', pos1 + 1);
        elements.push_back(context.substr(pos1 + 1, pos2 - (pos1 + 1)));
        pos1 = pos2;
        pos2 = std::string::npos;
    }
    return elements;
}

Ptr<Node>
AnimationInterface::GetNodeFromContext(const std::string& context) const
{
    // Use "NodeList/*/ as reference
    // where element [1] is the Node Id

    std::vector<std::string> elements = GetElementsFromContext(context);
    Ptr<Node> n = NodeList::GetNode(std::stoi(elements.at(1)));
    NS_ASSERT(n);

    return n;
}

Ptr<NetDevice>
AnimationInterface::GetNetDeviceFromContext(std::string context)
{
    // Use "NodeList/*/DeviceList/*/ as reference
    // where element [1] is the Node Id
    // element [2] is the NetDevice Id

    std::vector<std::string> elements = GetElementsFromContext(context);
    Ptr<Node> n = GetNodeFromContext(context);

    return n->GetDevice(std::stoi(elements.at(3)));
}

uint64_t
AnimationInterface::GetAnimUidFromPacket(Ptr<const Packet> p)
{
    AnimByteTag tag;
    TypeId tid = tag.GetInstanceTypeId();
    ByteTagIterator i = p->GetByteTagIterator();
    bool found = false;
    while (i.HasNext())
    {
        ByteTagIterator::Item item = i.Next();
        if (tid == item.GetTypeId())
        {
            item.GetTag(tag);
            found = true;
        }
    }
    if (found)
    {
        return tag.Get();
    }
    else
    {
        return 0;
    }
}

void
AnimationInterface::AddByteTag(uint64_t animUid, Ptr<const Packet> p)
{
    AnimByteTag tag;
    tag.Set(animUid);
    p->AddByteTag(tag);
}

void
AnimationInterface::RemainingEnergyTrace(std::string context,
                                         double previousEnergy,
                                         double currentEnergy)
{
    CHECK_STARTED_INTIMEWINDOW;
    const Ptr<const Node> node = GetNodeFromContext(context);
    const uint32_t nodeId = node->GetId();

    NS_LOG_INFO("Remaining energy on one of sources on node " << nodeId << ": " << currentEnergy);

    const Ptr<energy::EnergySource> energySource = node->GetObject<energy::EnergySource>();

    NS_ASSERT(energySource);
    // Don't call GetEnergyFraction () because of recursion
    const double energyFraction = currentEnergy / energySource->GetInitialEnergy();

    NS_LOG_INFO("Total energy fraction on node " << nodeId << ": " << energyFraction);

    m_nodeEnergyFraction[nodeId] = energyFraction;
    UpdateNodeCounter(m_remainingEnergyCounterId, nodeId, energyFraction);
}

void
AnimationInterface::WifiPhyTxDropTrace(std::string context, Ptr<const Packet> p)
{
    const Ptr<const Node> node = GetNodeFromContext(context);
    ++m_nodeWifiPhyTxDrop[node->GetId()];
}

void
AnimationInterface::WifiPhyRxDropTrace(std::string context,
                                       Ptr<const Packet> p,
                                       WifiPhyRxfailureReason reason)
{
    const Ptr<const Node> node = GetNodeFromContext(context);
    ++m_nodeWifiPhyRxDrop[node->GetId()];
}

void
AnimationInterface::WifiMacTxTrace(std::string context, Ptr<const Packet> p)
{
    const Ptr<const Node> node = GetNodeFromContext(context);
    ++m_nodeWifiMacTx[node->GetId()];
}

void
AnimationInterface::WifiMacTxDropTrace(std::string context, Ptr<const Packet> p)
{
    const Ptr<const Node> node = GetNodeFromContext(context);
    ++m_nodeWifiMacTxDrop[node->GetId()];
}

void
AnimationInterface::WifiMacRxTrace(std::string context, Ptr<const Packet> p)
{
    const Ptr<const Node> node = GetNodeFromContext(context);
    ++m_nodeWifiMacRx[node->GetId()];
}

void
AnimationInterface::WifiMacRxDropTrace(std::string context, Ptr<const Packet> p)
{
    const Ptr<const Node> node = GetNodeFromContext(context);
    ++m_nodeWifiMacRxDrop[node->GetId()];
}

void
AnimationInterface::LrWpanMacTxTrace(std::string context, Ptr<const Packet> p)
{
    const Ptr<const Node> node = GetNodeFromContext(context);
    ++m_nodeLrWpanMacTx[node->GetId()];
}

void
AnimationInterface::LrWpanMacTxDropTrace(std::string context, Ptr<const Packet> p)
{
    const Ptr<const Node> node = GetNodeFromContext(context);
    ++m_nodeLrWpanMacTxDrop[node->GetId()];
}

void
AnimationInterface::LrWpanMacRxTrace(std::string context, Ptr<const Packet> p)
{
    const Ptr<const Node> node = GetNodeFromContext(context);
    ++m_nodeLrWpanMacRx[node->GetId()];
}

void
AnimationInterface::LrWpanMacRxDropTrace(std::string context, Ptr<const Packet> p)
{
    const Ptr<const Node> node = GetNodeFromContext(context);
    ++m_nodeLrWpanMacRxDrop[node->GetId()];
}

void
AnimationInterface::Ipv4TxTrace(std::string context,
                                Ptr<const Packet> p,
                                Ptr<Ipv4> ipv4,
                                uint32_t interfaceIndex)
{
    const Ptr<const Node> node = GetNodeFromContext(context);
    ++m_nodeIpv4Tx[node->GetId()];
}

void
AnimationInterface::Ipv4RxTrace(std::string context,
                                Ptr<const Packet> p,
                                Ptr<Ipv4> ipv4,
                                uint32_t interfaceIndex)
{
    const Ptr<const Node> node = GetNodeFromContext(context);
    ++m_nodeIpv4Rx[node->GetId()];
}

void
AnimationInterface::Ipv4DropTrace(std::string context,
                                  const Ipv4Header& ipv4Header,
                                  Ptr<const Packet> p,
                                  Ipv4L3Protocol::DropReason dropReason,
                                  Ptr<Ipv4> ipv4,
                                  uint32_t)
{
    const Ptr<const Node> node = GetNodeFromContext(context);
    ++m_nodeIpv4Drop[node->GetId()];
}

void
AnimationInterface::EnqueueTrace(std::string context, Ptr<const Packet> p)
{
    const Ptr<const Node> node = GetNodeFromContext(context);
    ++m_nodeQueueEnqueue[node->GetId()];
}

void
AnimationInterface::DequeueTrace(std::string context, Ptr<const Packet> p)
{
    const Ptr<const Node> node = GetNodeFromContext(context);
    ++m_nodeQueueDequeue[node->GetId()];
}

void
AnimationInterface::QueueDropTrace(std::string context, Ptr<const Packet> p)
{
    const Ptr<const Node> node = GetNodeFromContext(context);
    ++m_nodeQueueDrop[node->GetId()];
}

void
AnimationInterface::DevTxTrace(std::string context,
                               Ptr<const Packet> p,
                               Ptr<NetDevice> tx,
                               Ptr<NetDevice> rx,
                               Time txTime,
                               Time rxTime)
{
    NS_LOG_FUNCTION(this);
    CHECK_STARTED_INTIMEWINDOW_TRACKPACKETS;
    NS_ASSERT(tx);
    NS_ASSERT(rx);
    Time now = Simulator::Now();
    double fbTx = now.GetSeconds();
    double lbTx = (now + txTime).GetSeconds();
    double fbRx = (now + rxTime - txTime).GetSeconds();
    double lbRx = (now + rxTime).GetSeconds();
    CheckMaxPktsPerTraceFile();
    WriteXmlP("p",
              tx->GetNode()->GetId(),
              fbTx,
              lbTx,
              rx->GetNode()->GetId(),
              fbRx,
              lbRx,
              m_enablePacketMetadata ? GetPacketMetadata(p) : "");
}

void
AnimationInterface::GenericWirelessTxTrace(std::string context,
                                           Ptr<const Packet> p,
                                           ProtocolType protocolType)
{
    NS_LOG_FUNCTION(this);
    CHECK_STARTED_INTIMEWINDOW_TRACKPACKETS;
    Ptr<NetDevice> ndev = GetNetDeviceFromContext(context);
    NS_ASSERT(ndev);
    UpdatePosition(ndev);

    ++gAnimUid;
    NS_LOG_INFO(ProtocolTypeToString(protocolType)
                << " GenericWirelessTxTrace for packet:" << gAnimUid);
    AddByteTag(gAnimUid, p);
    AnimPacketInfo pktInfo(ndev, Simulator::Now());
    AddPendingPacket(protocolType, gAnimUid, pktInfo);

    Ptr<WifiNetDevice> netDevice = DynamicCast<WifiNetDevice>(ndev);
    if (netDevice)
    {
        Mac48Address nodeAddr = netDevice->GetMac()->GetAddress();
        std::ostringstream oss;
        oss << nodeAddr;
        Ptr<Node> n = netDevice->GetNode();
        NS_ASSERT(n);
        m_macToNodeIdMap[oss.str()] = n->GetId();
        NS_LOG_INFO("Added Mac" << oss.str() << " node:" << m_macToNodeIdMap[oss.str()]);
    }
    AnimUidPacketInfoMap* pendingPackets = ProtocolTypeToPendingPackets(protocolType);
    OutputWirelessPacketTxInfo(p, pendingPackets->at(gAnimUid), gAnimUid);
}

void
AnimationInterface::GenericWirelessRxTrace(std::string context,
                                           Ptr<const Packet> p,
                                           ProtocolType protocolType)
{
    NS_LOG_FUNCTION(this);
    CHECK_STARTED_INTIMEWINDOW_TRACKPACKETS;
    Ptr<NetDevice> ndev = GetNetDeviceFromContext(context);
    NS_ASSERT(ndev);
    UpdatePosition(ndev);
    uint64_t animUid = GetAnimUidFromPacket(p);
    NS_LOG_INFO(ProtocolTypeToString(protocolType) << " for packet:" << animUid);
    if (!IsPacketPending(animUid, protocolType))
    {
        NS_LOG_WARN(ProtocolTypeToString(protocolType) << " GenericWirelessRxTrace: unknown Uid");
        return;
    }
    AnimUidPacketInfoMap* pendingPackets = ProtocolTypeToPendingPackets(protocolType);
    pendingPackets->at(animUid).ProcessRxBegin(ndev, Simulator::Now().GetSeconds());
    OutputWirelessPacketRxInfo(p, pendingPackets->at(animUid), animUid);
}

void
AnimationInterface::UanPhyGenTxTrace(std::string context, Ptr<const Packet> p)
{
    NS_LOG_FUNCTION(this);
    return GenericWirelessTxTrace(context, p, AnimationInterface::UAN);
}

void
AnimationInterface::UanPhyGenRxTrace(std::string context, Ptr<const Packet> p)
{
    NS_LOG_FUNCTION(this);
    return GenericWirelessRxTrace(context, p, AnimationInterface::UAN);
}

void
AnimationInterface::WifiPhyTxBeginTrace(std::string context,
                                        WifiConstPsduMap psduMap,
                                        WifiTxVector /* txVector */,
                                        double /* txPowerW */)
{
    NS_LOG_FUNCTION(this);
    CHECK_STARTED_INTIMEWINDOW_TRACKPACKETS;
    Ptr<NetDevice> ndev = GetNetDeviceFromContext(context);
    NS_ASSERT(ndev);
    UpdatePosition(ndev);

    AnimPacketInfo pktInfo(ndev, Simulator::Now());
    AnimUidPacketInfoMap* pendingPackets = ProtocolTypeToPendingPackets(WIFI);
    for (auto& psdu : psduMap)
    {
        for (auto& mpdu : *PeekPointer(psdu.second))
        {
            ++gAnimUid;
            NS_LOG_INFO("WifiPhyTxTrace for MPDU:" << gAnimUid);
            AddByteTag(gAnimUid,
                       mpdu->GetPacket()); // the underlying MSDU/A-MSDU should be handed off
            AddPendingPacket(WIFI, gAnimUid, pktInfo);
            OutputWirelessPacketTxInfo(
                mpdu->GetProtocolDataUnit(),
                pendingPackets->at(gAnimUid),
                gAnimUid); // PDU should be considered in order to have header
        }
    }

    Ptr<WifiNetDevice> netDevice = DynamicCast<WifiNetDevice>(ndev);
    if (netDevice)
    {
        Mac48Address nodeAddr = netDevice->GetMac()->GetAddress();
        std::ostringstream oss;
        oss << nodeAddr;
        Ptr<Node> n = netDevice->GetNode();
        NS_ASSERT(n);
        m_macToNodeIdMap[oss.str()] = n->GetId();
        NS_LOG_INFO("Added Mac" << oss.str() << " node:" << m_macToNodeIdMap[oss.str()]);
    }
    else
    {
        NS_ABORT_MSG("This NetDevice should be a Wi-Fi network device");
    }
}

void
AnimationInterface::WifiPhyRxBeginTrace(std::string context,
                                        Ptr<const Packet> p,
                                        RxPowerWattPerChannelBand rxPowersW)
{
    NS_LOG_FUNCTION(this);
    CHECK_STARTED_INTIMEWINDOW_TRACKPACKETS;
    Ptr<NetDevice> ndev = GetNetDeviceFromContext(context);
    NS_ASSERT(ndev);
    UpdatePosition(ndev);
    uint64_t animUid = GetAnimUidFromPacket(p);
    NS_LOG_INFO("Wifi RxBeginTrace for packet: " << animUid);
    if (!IsPacketPending(animUid, AnimationInterface::WIFI))
    {
        NS_ASSERT_MSG(false, "WifiPhyRxBeginTrace: unknown Uid");
        std::ostringstream oss;
        WifiMacHeader hdr;
        if (!p->PeekHeader(hdr))
        {
            NS_LOG_WARN("WifiMacHeader not present");
            return;
        }
        oss << hdr.GetAddr2();
        if (m_macToNodeIdMap.find(oss.str()) == m_macToNodeIdMap.end())
        {
            NS_LOG_WARN("Transmitter Mac address " << oss.str() << " never seen before. Skipping");
            return;
        }
        Ptr<Node> txNode = NodeList::GetNode(m_macToNodeIdMap[oss.str()]);
        UpdatePosition(txNode);
        AnimPacketInfo pktInfo(nullptr, Simulator::Now(), m_macToNodeIdMap[oss.str()]);
        AddPendingPacket(AnimationInterface::WIFI, animUid, pktInfo);
        NS_LOG_WARN("WifiPhyRxBegin: unknown Uid, but we are adding a wifi packet");
    }
    /// @todo NS_ASSERT (WifiPacketIsPending (animUid) == true);
    m_pendingWifiPackets[animUid].ProcessRxBegin(ndev, Simulator::Now().GetSeconds());
    OutputWirelessPacketRxInfo(p, m_pendingWifiPackets[animUid], animUid);
}

void
AnimationInterface::LrWpanPhyTxBeginTrace(std::string context, Ptr<const Packet> p)
{
    NS_LOG_FUNCTION(this);
    CHECK_STARTED_INTIMEWINDOW_TRACKPACKETS;

    Ptr<NetDevice> ndev = GetNetDeviceFromContext(context);
    NS_ASSERT(ndev);
    Ptr<lrwpan::LrWpanNetDevice> netDevice = DynamicCast<lrwpan::LrWpanNetDevice>(ndev);

    Ptr<Node> n = ndev->GetNode();
    NS_ASSERT(n);

    UpdatePosition(n);

    lrwpan::LrWpanMacHeader hdr;
    if (!p->PeekHeader(hdr))
    {
        NS_LOG_WARN("LrWpanMacHeader not present");
        return;
    }

    std::ostringstream oss;
    if (hdr.GetSrcAddrMode() == 2)
    {
        Mac16Address nodeAddr = netDevice->GetMac()->GetShortAddress();
        oss << nodeAddr;
    }
    else if (hdr.GetSrcAddrMode() == 3)
    {
        Mac64Address nodeAddr = netDevice->GetMac()->GetExtendedAddress();
        oss << nodeAddr;
    }
    else
    {
        NS_LOG_WARN("LrWpanMacHeader without source address");
        return;
    }
    m_macToNodeIdMap[oss.str()] = n->GetId();
    NS_LOG_INFO("Added Mac" << oss.str() << " node:" << m_macToNodeIdMap[oss.str()]);

    ++gAnimUid;
    NS_LOG_INFO("LrWpan TxBeginTrace for packet:" << gAnimUid);
    AddByteTag(gAnimUid, p);

    AnimPacketInfo pktInfo(ndev, Simulator::Now());
    AddPendingPacket(AnimationInterface::LRWPAN, gAnimUid, pktInfo);

    OutputWirelessPacketTxInfo(p, m_pendingLrWpanPackets[gAnimUid], gAnimUid);
}

void
AnimationInterface::LrWpanPhyRxBeginTrace(std::string context, Ptr<const Packet> p)
{
    NS_LOG_FUNCTION(this);
    CHECK_STARTED_INTIMEWINDOW_TRACKPACKETS;
    Ptr<NetDevice> ndev = GetNetDeviceFromContext(context);
    NS_ASSERT(ndev);
    Ptr<Node> n = ndev->GetNode();
    NS_ASSERT(n);

    AnimByteTag tag;
    if (!p->FindFirstMatchingByteTag(tag))
    {
        return;
    }

    uint64_t animUid = GetAnimUidFromPacket(p);
    NS_LOG_INFO("LrWpan RxBeginTrace for packet:" << animUid);
    if (!IsPacketPending(animUid, AnimationInterface::LRWPAN))
    {
        NS_LOG_WARN("LrWpanPhyRxBeginTrace: unknown Uid - most probably it's an ACK.");
    }

    UpdatePosition(n);
    m_pendingLrWpanPackets[animUid].ProcessRxBegin(ndev, Simulator::Now().GetSeconds());
    OutputWirelessPacketRxInfo(p, m_pendingLrWpanPackets[animUid], animUid);
}

void
AnimationInterface::LteTxTrace(std::string context, Ptr<const Packet> p, const Mac48Address& m)
{
    NS_LOG_FUNCTION(this);
    return GenericWirelessTxTrace(context, p, AnimationInterface::LTE);
}

void
AnimationInterface::LteRxTrace(std::string context, Ptr<const Packet> p, const Mac48Address& m)
{
    NS_LOG_FUNCTION(this);
    return GenericWirelessRxTrace(context, p, AnimationInterface::LTE);
}

void
AnimationInterface::LteSpectrumPhyTxStart(std::string context, Ptr<const PacketBurst> pb)
{
    NS_LOG_FUNCTION(this);
    CHECK_STARTED_INTIMEWINDOW_TRACKPACKETS;
    if (!pb)
    {
        NS_LOG_WARN("pb == 0. Not yet supported");
        return;
    }
    context = "/" + context;
    Ptr<NetDevice> ndev = GetNetDeviceFromContext(context);
    NS_ASSERT(ndev);
    UpdatePosition(ndev);

    std::list<Ptr<Packet>> pbList = pb->GetPackets();
    for (auto i = pbList.begin(); i != pbList.end(); ++i)
    {
        Ptr<Packet> p = *i;
        ++gAnimUid;
        NS_LOG_INFO("LteSpectrumPhyTxTrace for packet:" << gAnimUid);
        AnimPacketInfo pktInfo(ndev, Simulator::Now());
        AddByteTag(gAnimUid, p);
        AddPendingPacket(AnimationInterface::LTE, gAnimUid, pktInfo);
        OutputWirelessPacketTxInfo(p, pktInfo, gAnimUid);
    }
}

void
AnimationInterface::LteSpectrumPhyRxStart(std::string context, Ptr<const PacketBurst> pb)
{
    NS_LOG_FUNCTION(this);
    CHECK_STARTED_INTIMEWINDOW_TRACKPACKETS;
    if (!pb)
    {
        NS_LOG_WARN("pb == 0. Not yet supported");
        return;
    }
    context = "/" + context;
    Ptr<NetDevice> ndev = GetNetDeviceFromContext(context);
    NS_ASSERT(ndev);
    UpdatePosition(ndev);

    std::list<Ptr<Packet>> pbList = pb->GetPackets();
    for (auto i = pbList.begin(); i != pbList.end(); ++i)
    {
        Ptr<Packet> p = *i;
        uint64_t animUid = GetAnimUidFromPacket(p);
        NS_LOG_INFO("LteSpectrumPhyRxTrace for packet:" << gAnimUid);
        if (!IsPacketPending(animUid, AnimationInterface::LTE))
        {
            NS_LOG_WARN("LteSpectrumPhyRxTrace: unknown Uid");
            return;
        }
        AnimPacketInfo& pktInfo = m_pendingLtePackets[animUid];
        pktInfo.ProcessRxBegin(ndev, Simulator::Now().GetSeconds());
        OutputWirelessPacketRxInfo(p, pktInfo, animUid);
    }
}

void
AnimationInterface::CsmaPhyTxBeginTrace(std::string context, Ptr<const Packet> p)
{
    NS_LOG_FUNCTION(this);
    CHECK_STARTED_INTIMEWINDOW_TRACKPACKETS;
    Ptr<NetDevice> ndev = GetNetDeviceFromContext(context);
    NS_ASSERT(ndev);
    UpdatePosition(ndev);
    ++gAnimUid;
    NS_LOG_INFO("CsmaPhyTxBeginTrace for packet:" << gAnimUid);
    AddByteTag(gAnimUid, p);
    UpdatePosition(ndev);
    AnimPacketInfo pktInfo(ndev, Simulator::Now());
    AddPendingPacket(AnimationInterface::CSMA, gAnimUid, pktInfo);
}

void
AnimationInterface::CsmaPhyTxEndTrace(std::string context, Ptr<const Packet> p)
{
    NS_LOG_FUNCTION(this);
    CHECK_STARTED_INTIMEWINDOW_TRACKPACKETS;
    Ptr<NetDevice> ndev = GetNetDeviceFromContext(context);
    NS_ASSERT(ndev);
    UpdatePosition(ndev);
    uint64_t animUid = GetAnimUidFromPacket(p);
    NS_LOG_INFO("CsmaPhyTxEndTrace for packet:" << animUid);
    if (!IsPacketPending(animUid, AnimationInterface::CSMA))
    {
        NS_LOG_WARN("CsmaPhyTxEndTrace: unknown Uid");
        NS_FATAL_ERROR("CsmaPhyTxEndTrace: unknown Uid");
        AnimPacketInfo pktInfo(ndev, Simulator::Now());
        AddPendingPacket(AnimationInterface::CSMA, animUid, pktInfo);
        NS_LOG_WARN("Unknown Uid, but adding Csma Packet anyway");
    }
    /// @todo NS_ASSERT (IsPacketPending (AnimUid) == true);
    AnimPacketInfo& pktInfo = m_pendingCsmaPackets[animUid];
    pktInfo.m_lbTx = Simulator::Now().GetSeconds();
}

void
AnimationInterface::CsmaPhyRxEndTrace(std::string context, Ptr<const Packet> p)
{
    NS_LOG_FUNCTION(this);
    CHECK_STARTED_INTIMEWINDOW_TRACKPACKETS;
    Ptr<NetDevice> ndev = GetNetDeviceFromContext(context);
    NS_ASSERT(ndev);
    UpdatePosition(ndev);
    uint64_t animUid = GetAnimUidFromPacket(p);
    if (!IsPacketPending(animUid, AnimationInterface::CSMA))
    {
        NS_LOG_WARN("CsmaPhyRxEndTrace: unknown Uid");
        return;
    }
    /// @todo NS_ASSERT (CsmaPacketIsPending (AnimUid) == true);
    AnimPacketInfo& pktInfo = m_pendingCsmaPackets[animUid];
    pktInfo.ProcessRxBegin(ndev, Simulator::Now().GetSeconds());
    NS_LOG_INFO("CsmaPhyRxEndTrace for packet:" << animUid);
    NS_LOG_INFO("CsmaPhyRxEndTrace for packet:" << animUid << " complete");
    OutputCsmaPacket(p, pktInfo);
}

void
AnimationInterface::CsmaMacRxTrace(std::string context, Ptr<const Packet> p)
{
    NS_LOG_FUNCTION(this);
    CHECK_STARTED_INTIMEWINDOW_TRACKPACKETS;
    Ptr<NetDevice> ndev = GetNetDeviceFromContext(context);
    NS_ASSERT(ndev);
    uint64_t animUid = GetAnimUidFromPacket(p);
    if (!IsPacketPending(animUid, AnimationInterface::CSMA))
    {
        NS_LOG_WARN("CsmaMacRxTrace: unknown Uid");
        return;
    }
    /// @todo NS_ASSERT (CsmaPacketIsPending (AnimUid) == true);
    AnimPacketInfo& pktInfo = m_pendingCsmaPackets[animUid];
    NS_LOG_INFO("MacRxTrace for packet:" << animUid << " complete");
    OutputCsmaPacket(p, pktInfo);
}

void
AnimationInterface::OutputWirelessPacketTxInfo(Ptr<const Packet> p,
                                               AnimPacketInfo& pktInfo,
                                               uint64_t animUid)
{
    CheckMaxPktsPerTraceFile();
    uint32_t nodeId = 0;
    if (pktInfo.m_txnd)
    {
        nodeId = pktInfo.m_txnd->GetNode()->GetId();
    }
    else
    {
        nodeId = pktInfo.m_txNodeId;
    }
    WriteXmlPRef(animUid,
                 nodeId,
                 pktInfo.m_fbTx,
                 m_enablePacketMetadata ? GetPacketMetadata(p) : "");
}

void
AnimationInterface::OutputWirelessPacketRxInfo(Ptr<const Packet> p,
                                               AnimPacketInfo& pktInfo,
                                               uint64_t animUid)
{
    CheckMaxPktsPerTraceFile();
    uint32_t rxId = pktInfo.m_rxnd->GetNode()->GetId();
    WriteXmlP(animUid, "wpr", rxId, pktInfo.m_fbRx, pktInfo.m_lbRx);
}

void
AnimationInterface::OutputCsmaPacket(Ptr<const Packet> p, AnimPacketInfo& pktInfo)
{
    CheckMaxPktsPerTraceFile();
    NS_ASSERT(pktInfo.m_txnd);
    uint32_t nodeId = pktInfo.m_txnd->GetNode()->GetId();
    uint32_t rxId = pktInfo.m_rxnd->GetNode()->GetId();

    WriteXmlP("p",
              nodeId,
              pktInfo.m_fbTx,
              pktInfo.m_lbTx,
              rxId,
              pktInfo.m_fbRx,
              pktInfo.m_lbRx,
              m_enablePacketMetadata ? GetPacketMetadata(p) : "");
}

void
AnimationInterface::AddPendingPacket(ProtocolType protocolType,
                                     uint64_t animUid,
                                     AnimPacketInfo pktInfo)
{
    AnimUidPacketInfoMap* pendingPackets = ProtocolTypeToPendingPackets(protocolType);
    NS_ASSERT(pendingPackets);
    pendingPackets->insert(AnimUidPacketInfoMap::value_type(animUid, pktInfo));
}

bool
AnimationInterface::IsPacketPending(uint64_t animUid, AnimationInterface::ProtocolType protocolType)
{
    AnimUidPacketInfoMap* pendingPackets = ProtocolTypeToPendingPackets(protocolType);
    NS_ASSERT(pendingPackets);
    return (pendingPackets->find(animUid) != pendingPackets->end());
}

void
AnimationInterface::PurgePendingPackets(AnimationInterface::ProtocolType protocolType)
{
    AnimUidPacketInfoMap* pendingPackets = ProtocolTypeToPendingPackets(protocolType);
    NS_ASSERT(pendingPackets);
    if (pendingPackets->empty())
    {
        return;
    }
    std::vector<uint64_t> purgeList;
    for (auto i = pendingPackets->begin(); i != pendingPackets->end(); ++i)
    {
        AnimPacketInfo pktInfo = i->second;
        double delta = (Simulator::Now().GetSeconds() - pktInfo.m_fbTx);
        if (delta > PURGE_INTERVAL)
        {
            purgeList.push_back(i->first);
        }
    }
    for (auto i = purgeList.begin(); i != purgeList.end(); ++i)
    {
        pendingPackets->erase(*i);
    }
}

AnimationInterface::AnimUidPacketInfoMap*
AnimationInterface::ProtocolTypeToPendingPackets(AnimationInterface::ProtocolType protocolType)
{
    AnimUidPacketInfoMap* pendingPackets = nullptr;
    switch (protocolType)
    {
    case AnimationInterface::WIFI: {
        pendingPackets = &m_pendingWifiPackets;
        break;
    }
    case AnimationInterface::UAN: {
        pendingPackets = &m_pendingUanPackets;
        break;
    }
    case AnimationInterface::CSMA: {
        pendingPackets = &m_pendingCsmaPackets;
        break;
    }
    case AnimationInterface::LTE: {
        pendingPackets = &m_pendingLtePackets;
        break;
    }
    case AnimationInterface::LRWPAN: {
        pendingPackets = &m_pendingLrWpanPackets;
        break;
    }
    }
    return pendingPackets;
}

std::string
AnimationInterface::ProtocolTypeToString(AnimationInterface::ProtocolType protocolType)
{
    std::string result = "Unknown";
    switch (protocolType)
    {
    case AnimationInterface::WIFI: {
        result = "WIFI";
        break;
    }
    case AnimationInterface::UAN: {
        result = "UAN";
        break;
    }
    case AnimationInterface::CSMA: {
        result = "CSMA";
        break;
    }
    case AnimationInterface::LTE: {
        result = "LTE";
        break;
    }
    case AnimationInterface::LRWPAN: {
        result = "LRWPAN";
        break;
    }
    }
    return result;
}

// Counters

std::string
AnimationInterface::CounterTypeToString(CounterType counterType)
{
    std::string typeString = "unknown";
    switch (counterType)
    {
    case UINT32_COUNTER: {
        typeString = "UINT32";
        break;
    }
    case DOUBLE_COUNTER: {
        typeString = "DOUBLE";
        break;
    }
    }
    return typeString;
}

// General

std::string
AnimationInterface::GetPacketMetadata(Ptr<const Packet> p)
{
    std::ostringstream oss;
    p->Print(oss);
    return oss.str();
}

uint64_t
AnimationInterface::GetTracePktCount() const
{
    return m_currentPktCount;
}

void
AnimationInterface::StopAnimation(bool onlyAnimation)
{
    m_started = false;
    NS_LOG_INFO("Stopping Animation");
    ResetAnimWriteCallback();
    if (m_f)
    {
        // Terminate the anim element
        WriteXmlClose("anim");
        std::fclose(m_f);
        m_f = nullptr;
    }
    if (onlyAnimation)
    {
        return;
    }
    if (m_routingF)
    {
        WriteXmlClose("anim", true);
        std::fclose(m_routingF);
        m_routingF = nullptr;
    }
}

void
AnimationInterface::StartAnimation(bool restart)
{
    m_currentPktCount = 0;
    m_started = true;
    SetOutputFile(m_outputFileName);
    WriteXmlAnim();
    WriteNodes();
    WriteNodeColors();
    WriteLinkProperties();
    WriteIpv4Addresses();
    WriteIpv6Addresses();
    WriteNodeSizes();
    WriteNodeEnergies();
    if (!restart)
    {
        Simulator::Schedule(m_mobilityPollInterval, &AnimationInterface::MobilityAutoCheck, this);
        ConnectCallbacks();
    }
}

void
AnimationInterface::AddToIpv4AddressNodeIdTable(std::string ipv4Address, uint32_t nodeId)
{
    m_ipv4ToNodeIdMap[ipv4Address] = nodeId;
    m_nodeIdIpv4Map.insert(NodeIdIpv4Pair(nodeId, ipv4Address));
}

void
AnimationInterface::AddToIpv4AddressNodeIdTable(std::vector<std::string> ipv4Addresses,
                                                uint32_t nodeId)
{
    for (auto i = ipv4Addresses.begin(); i != ipv4Addresses.end(); ++i)
    {
        AddToIpv4AddressNodeIdTable(*i, nodeId);
    }
}

void
AnimationInterface::AddToIpv6AddressNodeIdTable(std::string ipv6Address, uint32_t nodeId)
{
    m_ipv6ToNodeIdMap[ipv6Address] = nodeId;
    m_nodeIdIpv6Map.insert(NodeIdIpv6Pair(nodeId, ipv6Address));
}

void
AnimationInterface::AddToIpv6AddressNodeIdTable(std::vector<std::string> ipv6Addresses,
                                                uint32_t nodeId)
{
    for (auto i = ipv6Addresses.begin(); i != ipv6Addresses.end(); ++i)
    {
        AddToIpv6AddressNodeIdTable(*i, nodeId);
    }
}

// Callbacks
void
AnimationInterface::ConnectLteEnb(Ptr<Node> n, Ptr<LteEnbNetDevice> nd, uint32_t devIndex)
{
    Ptr<LteEnbPhy> lteEnbPhy = nd->GetPhy();
    Ptr<LteSpectrumPhy> dlPhy = lteEnbPhy->GetDownlinkSpectrumPhy();
    Ptr<LteSpectrumPhy> ulPhy = lteEnbPhy->GetUplinkSpectrumPhy();
    std::ostringstream oss;
    // NodeList/*/DeviceList/*/
    oss << "NodeList/" << n->GetId() << "/DeviceList/" << devIndex << "/";
    if (dlPhy)
    {
        dlPhy->TraceConnect("TxStart",
                            oss.str(),
                            MakeCallback(&AnimationInterface::LteSpectrumPhyTxStart, this));
        dlPhy->TraceConnect("RxStart",
                            oss.str(),
                            MakeCallback(&AnimationInterface::LteSpectrumPhyRxStart, this));
    }
    if (ulPhy)
    {
        ulPhy->TraceConnect("TxStart",
                            oss.str(),
                            MakeCallback(&AnimationInterface::LteSpectrumPhyTxStart, this));
        ulPhy->TraceConnect("RxStart",
                            oss.str(),
                            MakeCallback(&AnimationInterface::LteSpectrumPhyRxStart, this));
    }
}

void
AnimationInterface::ConnectLteUe(Ptr<Node> n, Ptr<LteUeNetDevice> nd, uint32_t devIndex)
{
    Ptr<LteUePhy> lteUePhy = nd->GetPhy();
    Ptr<LteSpectrumPhy> dlPhy = lteUePhy->GetDownlinkSpectrumPhy();
    Ptr<LteSpectrumPhy> ulPhy = lteUePhy->GetUplinkSpectrumPhy();
    std::ostringstream oss;
    // NodeList/*/DeviceList/*/
    oss << "NodeList/" << n->GetId() << "/DeviceList/" << devIndex << "/";
    if (dlPhy)
    {
        dlPhy->TraceConnect("TxStart",
                            oss.str(),
                            MakeCallback(&AnimationInterface::LteSpectrumPhyTxStart, this));
        dlPhy->TraceConnect("RxStart",
                            oss.str(),
                            MakeCallback(&AnimationInterface::LteSpectrumPhyRxStart, this));
    }
    if (ulPhy)
    {
        ulPhy->TraceConnect("TxStart",
                            oss.str(),
                            MakeCallback(&AnimationInterface::LteSpectrumPhyTxStart, this));
        ulPhy->TraceConnect("RxStart",
                            oss.str(),
                            MakeCallback(&AnimationInterface::LteSpectrumPhyRxStart, this));
    }
}

void
AnimationInterface::ConnectLte()
{
    for (auto i = NodeList::Begin(); i != NodeList::End(); ++i)
    {
        Ptr<Node> n = *i;
        NS_ASSERT(n);
        uint32_t nDevices = n->GetNDevices();
        for (uint32_t devIndex = 0; devIndex < nDevices; ++devIndex)
        {
            Ptr<NetDevice> nd = n->GetDevice(devIndex);
            if (!nd)
            {
                continue;
            }
            Ptr<LteUeNetDevice> lteUeNetDevice = DynamicCast<LteUeNetDevice>(nd);
            if (lteUeNetDevice)
            {
                ConnectLteUe(n, lteUeNetDevice, devIndex);
                continue;
            }
            Ptr<LteEnbNetDevice> lteEnbNetDevice = DynamicCast<LteEnbNetDevice>(nd);
            if (lteEnbNetDevice)
            {
                ConnectLteEnb(n, lteEnbNetDevice, devIndex);
            }
        }
    }
}

void
AnimationInterface::ConnectCallbacks()
{
    // Connect the callbacks
    Config::ConnectFailSafe("/ChannelList/*/TxRxPointToPoint",
                            MakeCallback(&AnimationInterface::DevTxTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::WifiNetDevice/Phy/PhyTxPsduBegin",
                            MakeCallback(&AnimationInterface::WifiPhyTxBeginTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::WifiNetDevice/Phy/PhyRxBegin",
                            MakeCallback(&AnimationInterface::WifiPhyRxBeginTrace, this));
    Config::ConnectWithoutContextFailSafe(
        "/NodeList/*/$ns3::MobilityModel/CourseChange",
        MakeCallback(&AnimationInterface::MobilityCourseChangeTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::LteNetDevice/Tx",
                            MakeCallback(&AnimationInterface::LteTxTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::LteNetDevice/Rx",
                            MakeCallback(&AnimationInterface::LteRxTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::CsmaNetDevice/PhyTxBegin",
                            MakeCallback(&AnimationInterface::CsmaPhyTxBeginTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::CsmaNetDevice/PhyTxEnd",
                            MakeCallback(&AnimationInterface::CsmaPhyTxEndTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::CsmaNetDevice/PhyRxEnd",
                            MakeCallback(&AnimationInterface::CsmaPhyRxEndTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::CsmaNetDevice/MacRx",
                            MakeCallback(&AnimationInterface::CsmaMacRxTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::UanNetDevice/Phy/PhyTxBegin",
                            MakeCallback(&AnimationInterface::UanPhyGenTxTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::UanNetDevice/Phy/PhyRxBegin",
                            MakeCallback(&AnimationInterface::UanPhyGenRxTrace, this));
    Config::ConnectFailSafe("/NodeList/*/$ns3::energy::BasicEnergySource/RemainingEnergy",
                            MakeCallback(&AnimationInterface::RemainingEnergyTrace, this));

    ConnectLte();

    Config::ConnectFailSafe("/NodeList/*/$ns3::Ipv4L3Protocol/Tx",
                            MakeCallback(&AnimationInterface::Ipv4TxTrace, this));
    Config::ConnectFailSafe("/NodeList/*/$ns3::Ipv4L3Protocol/Rx",
                            MakeCallback(&AnimationInterface::Ipv4RxTrace, this));
    Config::ConnectFailSafe("/NodeList/*/$ns3::Ipv4L3Protocol/Drop",
                            MakeCallback(&AnimationInterface::Ipv4DropTrace, this));

    // Queue Enqueues

    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::AlohaNoackNetDevice/Queue/Enqueue",
                            MakeCallback(&AnimationInterface::EnqueueTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::CsmaNetDevice/TxQueue/Enqueue",
                            MakeCallback(&AnimationInterface::EnqueueTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::PointToPointNetDevice/TxQueue/Enqueue",
                            MakeCallback(&AnimationInterface::EnqueueTrace, this));

    // Queue Dequeues

    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::AlohaNoackNetDevice/Queue/Dequeue",
                            MakeCallback(&AnimationInterface::DequeueTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::CsmaNetDevice/TxQueue/Dequeue",
                            MakeCallback(&AnimationInterface::DequeueTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::PointToPointNetDevice/TxQueue/Dequeue",
                            MakeCallback(&AnimationInterface::DequeueTrace, this));

    // Queue Drops

    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::AlohaNoackNetDevice/Queue/Drop",
                            MakeCallback(&AnimationInterface::QueueDropTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::CsmaNetDevice/TxQueue/Drop",
                            MakeCallback(&AnimationInterface::QueueDropTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::PointToPointNetDevice/TxQueue/Drop",
                            MakeCallback(&AnimationInterface::QueueDropTrace, this));

    // Wifi Mac
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::WifiNetDevice/Mac/MacTx",
                            MakeCallback(&AnimationInterface::WifiMacTxTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::WifiNetDevice/Mac/MacTxDrop",
                            MakeCallback(&AnimationInterface::WifiMacTxDropTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::WifiNetDevice/Mac/MacRx",
                            MakeCallback(&AnimationInterface::WifiMacRxTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::WifiNetDevice/Mac/MacRxDrop",
                            MakeCallback(&AnimationInterface::WifiMacRxDropTrace, this));

    // Wifi Phy
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::WifiNetDevice/Phy/PhyTxDrop",
                            MakeCallback(&AnimationInterface::WifiPhyTxDropTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::WifiNetDevice/Phy/PhyRxDrop",
                            MakeCallback(&AnimationInterface::WifiPhyRxDropTrace, this));

    // LrWpan
    Config::ConnectFailSafe("NodeList/*/DeviceList/*/$ns3::lrwpan::LrWpanNetDevice/Phy/PhyTxBegin",
                            MakeCallback(&AnimationInterface::LrWpanPhyTxBeginTrace, this));
    Config::ConnectFailSafe("NodeList/*/DeviceList/*/$ns3::lrwpan::LrWpanNetDevice/Phy/PhyRxBegin",
                            MakeCallback(&AnimationInterface::LrWpanPhyRxBeginTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::lrwpan::LrWpanNetDevice/Mac/MacTx",
                            MakeCallback(&AnimationInterface::LrWpanMacTxTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::lrwpan::LrWpanNetDevice/Mac/MacTxDrop",
                            MakeCallback(&AnimationInterface::LrWpanMacTxDropTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::lrwpan::LrWpanNetDevice/Mac/MacRx",
                            MakeCallback(&AnimationInterface::LrWpanMacRxTrace, this));
    Config::ConnectFailSafe("/NodeList/*/DeviceList/*/$ns3::lrwpan::LrWpanNetDevice/Mac/MacRxDrop",
                            MakeCallback(&AnimationInterface::LrWpanMacRxDropTrace, this));
}

Vector
AnimationInterface::UpdatePosition(Ptr<Node> n)
{
    Ptr<MobilityModel> loc = n->GetObject<MobilityModel>();
    if (loc)
    {
        m_nodeLocation[n->GetId()] = loc->GetPosition();
    }
    else
    {
        NS_LOG_UNCOND(
            "AnimationInterface WARNING:Node:"
            << n->GetId()
            << " Does not have a mobility model. Use SetConstantPosition if it is stationary");
        Ptr<UniformRandomVariable> x = CreateObject<UniformRandomVariable>();
        x->SetAttribute("Min", DoubleValue(0));
        x->SetAttribute("Max", DoubleValue(100));
        Ptr<UniformRandomVariable> y = CreateObject<UniformRandomVariable>();
        y->SetAttribute("Min", DoubleValue(0));
        y->SetAttribute("Max", DoubleValue(100));
        m_nodeLocation[n->GetId()] = Vector(int(x->GetValue()), int(y->GetValue()), 0);
    }
    return m_nodeLocation[n->GetId()];
}

Vector
AnimationInterface::UpdatePosition(Ptr<Node> n, Vector v)
{
    m_nodeLocation[n->GetId()] = v;
    return v;
}

Vector
AnimationInterface::UpdatePosition(Ptr<NetDevice> ndev)
{
    Ptr<Node> n = ndev->GetNode();
    NS_ASSERT(n);
    return UpdatePosition(n);
}

Vector
AnimationInterface::GetPosition(Ptr<Node> n)
{
    if (m_nodeLocation.find(n->GetId()) == m_nodeLocation.end())
    {
        NS_FATAL_ERROR("Node:" << n->GetId() << " not found in Location table");
    }
    return m_nodeLocation[n->GetId()];
}

std::string
AnimationInterface::GetMacAddress(Ptr<NetDevice> nd)
{
    Address nodeAddr = nd->GetAddress();
    std::ostringstream oss;
    oss << nodeAddr;
    return oss.str().substr(6); // Skip the first 6 chars to get the Mac
}

std::string
AnimationInterface::GetIpv4Address(Ptr<NetDevice> nd)
{
    Ptr<Ipv4> ipv4 = NodeList::GetNode(nd->GetNode()->GetId())->GetObject<Ipv4>();
    if (!ipv4)
    {
        NS_LOG_WARN("Node: " << nd->GetNode()->GetId() << " No ipv4 object found");
        return "0.0.0.0";
    }
    int32_t ifIndex = ipv4->GetInterfaceForDevice(nd);
    if (ifIndex == -1)
    {
        NS_LOG_WARN("Node :" << nd->GetNode()->GetId() << " Could not find index of NetDevice");
        return "0.0.0.0";
    }
    Ipv4InterfaceAddress addr = ipv4->GetAddress(ifIndex, 0);
    std::ostringstream oss;
    oss << addr.GetLocal();
    return oss.str();
}

std::string
AnimationInterface::GetIpv6Address(Ptr<NetDevice> nd)
{
    Ptr<Ipv6> ipv6 = NodeList::GetNode(nd->GetNode()->GetId())->GetObject<Ipv6>();
    if (!ipv6)
    {
        NS_LOG_WARN("Node: " << nd->GetNode()->GetId() << " No ipv4 object found");
        return "::";
    }
    int32_t ifIndex = ipv6->GetInterfaceForDevice(nd);
    if (ifIndex == -1)
    {
        NS_LOG_WARN("Node :" << nd->GetNode()->GetId() << " Could not find index of NetDevice");
        return "::";
    }
    bool nonLinkLocalFound = false;
    uint32_t nAddresses = ipv6->GetNAddresses(ifIndex);
    Ipv6InterfaceAddress addr;
    for (uint32_t addressIndex = 0; addressIndex < nAddresses; ++addressIndex)
    {
        addr = ipv6->GetAddress(ifIndex, addressIndex);
        if (!addr.GetAddress().IsLinkLocal())
        {
            nonLinkLocalFound = true;
            break;
        }
    }
    if (!nonLinkLocalFound)
    {
        addr = ipv6->GetAddress(ifIndex, 0);
    }
    std::ostringstream oss;
    oss << addr.GetAddress();
    return oss.str();
}

std::vector<std::string>
AnimationInterface::GetIpv4Addresses(Ptr<NetDevice> nd)
{
    std::vector<std::string> ipv4Addresses;
    Ptr<Ipv4> ipv4 = NodeList::GetNode(nd->GetNode()->GetId())->GetObject<Ipv4>();
    if (!ipv4)
    {
        NS_LOG_WARN("Node: " << nd->GetNode()->GetId() << " No ipv4 object found");
        return ipv4Addresses;
    }
    int32_t ifIndex = ipv4->GetInterfaceForDevice(nd);
    if (ifIndex == -1)
    {
        NS_LOG_WARN("Node :" << nd->GetNode()->GetId() << " Could not find index of NetDevice");
        return ipv4Addresses;
    }
    for (uint32_t index = 0; index < ipv4->GetNAddresses(ifIndex); ++index)
    {
        Ipv4InterfaceAddress addr = ipv4->GetAddress(ifIndex, index);
        std::ostringstream oss;
        oss << addr.GetLocal();
        ipv4Addresses.push_back(oss.str());
    }
    return ipv4Addresses;
}

std::vector<std::string>
AnimationInterface::GetIpv6Addresses(Ptr<NetDevice> nd)
{
    std::vector<std::string> ipv6Addresses;
    Ptr<Ipv6> ipv6 = NodeList::GetNode(nd->GetNode()->GetId())->GetObject<Ipv6>();
    if (!ipv6)
    {
        NS_LOG_WARN("Node: " << nd->GetNode()->GetId() << " No ipv6 object found");
        return ipv6Addresses;
    }
    int32_t ifIndex = ipv6->GetInterfaceForDevice(nd);
    if (ifIndex == -1)
    {
        NS_LOG_WARN("Node :" << nd->GetNode()->GetId() << " Could not find index of NetDevice");
        return ipv6Addresses;
    }
    for (uint32_t index = 0; index < ipv6->GetNAddresses(ifIndex); ++index)
    {
        Ipv6InterfaceAddress addr = ipv6->GetAddress(ifIndex, index);
        std::ostringstream oss;
        oss << addr.GetAddress();
        ipv6Addresses.push_back(oss.str());
    }
    return ipv6Addresses;
}

void
AnimationInterface::WriteIpv4Addresses()
{
    for (auto i = m_nodeIdIpv4Map.begin(); i != m_nodeIdIpv4Map.end(); ++i)
    {
        std::vector<std::string> ipv4Addresses;
        auto iterPair = m_nodeIdIpv4Map.equal_range(i->first);
        for (auto it = iterPair.first; it != iterPair.second; ++it)
        {
            ipv4Addresses.push_back(it->second);
        }
        WriteXmlIpv4Addresses(i->first, ipv4Addresses);
    }
}

void
AnimationInterface::WriteIpv6Addresses()
{
    for (auto i = m_nodeIdIpv6Map.begin(); i != m_nodeIdIpv6Map.end();
         i = m_nodeIdIpv6Map.upper_bound(i->first))
    {
        std::vector<std::string> ipv6Addresses;
        auto iterPair = m_nodeIdIpv6Map.equal_range(i->first);
        for (auto it = iterPair.first; it != iterPair.second; ++it)
        {
            ipv6Addresses.push_back(it->second);
        }
        WriteXmlIpv6Addresses(i->first, ipv6Addresses);
    }
}

void
AnimationInterface::WriteLinkProperties()
{
    for (auto i = NodeList::Begin(); i != NodeList::End(); ++i)
    {
        Ptr<Node> n = *i;
        UpdatePosition(n);
        uint32_t n1Id = n->GetId();
        uint32_t nDev = n->GetNDevices(); // Number of devices
        for (uint32_t i = 0; i < nDev; ++i)
        {
            Ptr<NetDevice> dev = n->GetDevice(i);
            NS_ASSERT(dev);
            Ptr<Channel> ch = dev->GetChannel();
            std::string channelType = "Unknown channel";
            if (ch)
            {
                channelType = ch->GetInstanceTypeId().GetName();
            }
            NS_LOG_DEBUG("Got ChannelType" << channelType);

            if (!ch || (channelType != "ns3::PointToPointChannel"))
            {
                NS_LOG_DEBUG("No channel can't be a p2p device");
                /*
                // Try to see if it is an LTE NetDevice, which does not return a channel
                if ((dev->GetInstanceTypeId ().GetName () == "ns3::LteUeNetDevice") ||
                    (dev->GetInstanceTypeId ().GetName () == "ns3::LteEnbNetDevice")||
                    (dev->GetInstanceTypeId ().GetName () == "ns3::VirtualNetDevice"))
                  {
                    WriteNonP2pLinkProperties (n->GetId (), GetIpv4Address (dev) + "~" +
                GetMacAddress (dev), channelType); AddToIpv4AddressNodeIdTable (GetIpv4Address
                (dev), n->GetId ());
                  }
                 */
                std::vector<std::string> ipv4Addresses = GetIpv4Addresses(dev);
                AddToIpv4AddressNodeIdTable(ipv4Addresses, n->GetId());
                std::vector<std::string> ipv6Addresses = GetIpv6Addresses(dev);
                AddToIpv6AddressNodeIdTable(ipv6Addresses, n->GetId());
                if (!ipv4Addresses.empty())
                {
                    NS_LOG_INFO("Writing Ipv4 link");
                    WriteNonP2pLinkProperties(n->GetId(),
                                              GetIpv4Address(dev) + "~" + GetMacAddress(dev),
                                              channelType);
                }
                else if (!ipv6Addresses.empty())
                {
                    NS_LOG_INFO("Writing Ipv6 link");
                    WriteNonP2pLinkProperties(n->GetId(),
                                              GetIpv6Address(dev) + "~" + GetMacAddress(dev),
                                              channelType);
                }
                continue;
            }

            else if (channelType == "ns3::PointToPointChannel")
            { // Since these are duplex links, we only need to dump
                // if srcid < dstid
                std::size_t nChDev = ch->GetNDevices();
                for (std::size_t j = 0; j < nChDev; ++j)
                {
                    Ptr<NetDevice> chDev = ch->GetDevice(j);
                    uint32_t n2Id = chDev->GetNode()->GetId();
                    if (n1Id < n2Id)
                    {
                        std::vector<std::string> ipv4Addresses = GetIpv4Addresses(dev);
                        AddToIpv4AddressNodeIdTable(ipv4Addresses, n1Id);
                        ipv4Addresses = GetIpv4Addresses(chDev);
                        AddToIpv4AddressNodeIdTable(ipv4Addresses, n2Id);
                        std::vector<std::string> ipv6Addresses = GetIpv6Addresses(dev);
                        AddToIpv6AddressNodeIdTable(ipv6Addresses, n1Id);
                        ipv6Addresses = GetIpv6Addresses(chDev);
                        AddToIpv6AddressNodeIdTable(ipv6Addresses, n2Id);

                        P2pLinkNodeIdPair p2pPair;
                        p2pPair.fromNode = n1Id;
                        p2pPair.toNode = n2Id;
                        if (!ipv4Addresses.empty())
                        {
                            LinkProperties lp = {GetIpv4Address(dev) + "~" + GetMacAddress(dev),
                                                 GetIpv4Address(chDev) + "~" + GetMacAddress(chDev),
                                                 ""};
                            m_linkProperties[p2pPair] = lp;
                        }
                        else if (!ipv6Addresses.empty())
                        {
                            LinkProperties lp = {GetIpv6Address(dev) + "~" + GetMacAddress(dev),
                                                 GetIpv6Address(chDev) + "~" + GetMacAddress(chDev),
                                                 ""};
                            m_linkProperties[p2pPair] = lp;
                        }
                        WriteXmlLink(n1Id, 0, n2Id);
                    }
                }
            }
        }
    }
    m_linkProperties.clear();
}

void
AnimationInterface::WriteNodes()
{
    for (auto i = NodeList::Begin(); i != NodeList::End(); ++i)
    {
        Ptr<Node> n = *i;
        NS_LOG_INFO("Update Position for Node: " << n->GetId());
        Vector v = UpdatePosition(n);
        WriteXmlNode(n->GetId(), n->GetSystemId(), v.x, v.y);
    }
}

void
AnimationInterface::WriteNodeColors()
{
    for (auto i = NodeList::Begin(); i != NodeList::End(); ++i)
    {
        Ptr<Node> n = *i;
        Rgb rgb = {255, 0, 0};
        if (m_nodeColors.find(n->GetId()) == m_nodeColors.end())
        {
            m_nodeColors[n->GetId()] = rgb;
        }
        UpdateNodeColor(n, rgb.r, rgb.g, rgb.b);
    }
}

void
AnimationInterface::WriteNodeSizes()
{
    for (auto i = NodeList::Begin(); i != NodeList::End(); ++i)
    {
        Ptr<Node> n = *i;
        NS_LOG_INFO("Update Size for Node: " << n->GetId());
        AnimationInterface::NodeSize s = {1, 1};
        m_nodeSizes[n->GetId()] = s;
        UpdateNodeSize(n->GetId(), s.width, s.height);
    }
}

void
AnimationInterface::WriteNodeEnergies()
{
    m_remainingEnergyCounterId =
        AddNodeCounter("RemainingEnergy", AnimationInterface::DOUBLE_COUNTER);
    for (auto i = NodeList::Begin(); i != NodeList::End(); ++i)
    {
        Ptr<Node> n = *i;
        if (NodeList::GetNode(n->GetId())->GetObject<energy::EnergySource>())
        {
            UpdateNodeCounter(m_remainingEnergyCounterId, n->GetId(), 1);
        }
    }
}

bool
AnimationInterface::IsInTimeWindow()
{
    return Simulator::Now() >= m_startTime && Simulator::Now() <= m_stopTime;
}

void
AnimationInterface::SetOutputFile(const std::string& fn, bool routing)
{
    if (!routing && m_f)
    {
        return;
    }
    if (routing && m_routingF)
    {
        NS_FATAL_ERROR("SetRoutingOutputFile already used once");
        return;
    }

    NS_LOG_INFO("Creating new trace file:" << fn);
    FILE* f = nullptr;
    f = std::fopen(fn.c_str(), "w");
    if (!f)
    {
        NS_FATAL_ERROR("Unable to open output file:" << fn);
        return; // Can't open output file
    }
    if (routing)
    {
        m_routingF = f;
        m_routingFileName = fn;
    }
    else
    {
        m_f = f;
        m_outputFileName = fn;
    }
}

void
AnimationInterface::CheckMaxPktsPerTraceFile()
{
    // Start a new trace file if the current packet count exceeded max packets per file
    ++m_currentPktCount;
    if (m_currentPktCount <= m_maxPktsPerFile)
    {
        return;
    }
    NS_LOG_UNCOND("Max Packets per trace file exceeded");
    StopAnimation(true);
}

std::string
AnimationInterface::GetNetAnimVersion()
{
    return NETANIM_VERSION;
}

void
AnimationInterface::TrackQueueCounters()
{
    if (Simulator::Now() > m_queueCountersStopTime)
    {
        NS_LOG_INFO("TrackQueueCounters Completed");
        return;
    }
    for (auto i = NodeList::Begin(); i != NodeList::End(); ++i)
    {
        uint32_t nodeId = Ptr<Node>(*i)->GetId();
        UpdateNodeCounter(m_queueEnqueueCounterId, nodeId, m_nodeQueueEnqueue[nodeId]);
        UpdateNodeCounter(m_queueDequeueCounterId, nodeId, m_nodeQueueDequeue[nodeId]);
        UpdateNodeCounter(m_queueDropCounterId, nodeId, m_nodeQueueDrop[nodeId]);
    }
    Simulator::Schedule(m_queueCountersPollInterval, &AnimationInterface::TrackQueueCounters, this);
}

void
AnimationInterface::TrackWifiMacCounters()
{
    if (Simulator::Now() > m_wifiMacCountersStopTime)
    {
        NS_LOG_INFO("TrackWifiMacCounters Completed");
        return;
    }
    for (auto i = NodeList::Begin(); i != NodeList::End(); ++i)
    {
        uint32_t nodeId = Ptr<Node>(*i)->GetId();
        UpdateNodeCounter(m_wifiMacTxCounterId, nodeId, m_nodeWifiMacTx[nodeId]);
        UpdateNodeCounter(m_wifiMacTxDropCounterId, nodeId, m_nodeWifiMacTxDrop[nodeId]);
        UpdateNodeCounter(m_wifiMacRxCounterId, nodeId, m_nodeWifiMacRx[nodeId]);
        UpdateNodeCounter(m_wifiMacRxDropCounterId, nodeId, m_nodeWifiMacRxDrop[nodeId]);
    }
    Simulator::Schedule(m_wifiMacCountersPollInterval,
                        &AnimationInterface::TrackWifiMacCounters,
                        this);
}

void
AnimationInterface::TrackWifiPhyCounters()
{
    if (Simulator::Now() > m_wifiPhyCountersStopTime)
    {
        NS_LOG_INFO("TrackWifiPhyCounters Completed");
        return;
    }
    for (auto i = NodeList::Begin(); i != NodeList::End(); ++i)
    {
        uint32_t nodeId = Ptr<Node>(*i)->GetId();
        UpdateNodeCounter(m_wifiPhyTxDropCounterId, nodeId, m_nodeWifiPhyTxDrop[nodeId]);
        UpdateNodeCounter(m_wifiPhyRxDropCounterId, nodeId, m_nodeWifiPhyRxDrop[nodeId]);
    }
    Simulator::Schedule(m_wifiPhyCountersPollInterval,
                        &AnimationInterface::TrackWifiPhyCounters,
                        this);
}

void
AnimationInterface::TrackIpv4L3ProtocolCounters()
{
    if (Simulator::Now() > m_ipv4L3ProtocolCountersStopTime)
    {
        NS_LOG_INFO("TrackIpv4L3ProtocolCounters Completed");
        return;
    }
    for (auto i = NodeList::Begin(); i != NodeList::End(); ++i)
    {
        uint32_t nodeId = Ptr<Node>(*i)->GetId();
        UpdateNodeCounter(m_ipv4L3ProtocolTxCounterId, nodeId, m_nodeIpv4Tx[nodeId]);
        UpdateNodeCounter(m_ipv4L3ProtocolRxCounterId, nodeId, m_nodeIpv4Rx[nodeId]);
        UpdateNodeCounter(m_ipv4L3ProtocolDropCounterId, nodeId, m_nodeIpv4Drop[nodeId]);
    }
    Simulator::Schedule(m_ipv4L3ProtocolCountersPollInterval,
                        &AnimationInterface::TrackIpv4L3ProtocolCounters,
                        this);
}

/***** Routing-related *****/

void
AnimationInterface::TrackIpv4RoutePaths()
{
    if (m_ipv4RouteTrackElements.empty())
    {
        return;
    }
    for (auto i = m_ipv4RouteTrackElements.begin(); i != m_ipv4RouteTrackElements.end(); ++i)
    {
        Ipv4RouteTrackElement trackElement = *i;
        Ptr<Node> fromNode = NodeList::GetNode(trackElement.fromNodeId);
        if (!fromNode)
        {
            NS_FATAL_ERROR("Node: " << trackElement.fromNodeId << " Not found");
            continue;
        }
        Ptr<ns3::Ipv4> ipv4 = fromNode->GetObject<ns3::Ipv4>();
        if (!ipv4)
        {
            NS_LOG_WARN("ipv4 object not found");
            continue;
        }
        Ptr<Ipv4RoutingProtocol> rp = ipv4->GetRoutingProtocol();
        if (!rp)
        {
            NS_LOG_WARN("Routing protocol object not found");
            continue;
        }
        NS_LOG_INFO("Begin Track Route for: " << trackElement.destination
                                              << " From:" << trackElement.fromNodeId);
        Ptr<Packet> pkt = Create<Packet>();
        Ipv4Header header;
        header.SetDestination(Ipv4Address(trackElement.destination.c_str()));
        Socket::SocketErrno sockerr;
        Ptr<Ipv4Route> rt = rp->RouteOutput(pkt, header, nullptr, sockerr);
        Ipv4RoutePathElements rpElements;
        if (!rt)
        {
            NS_LOG_INFO("No route to :" << trackElement.destination);
            Ipv4RoutePathElement elem = {trackElement.fromNodeId, "-1"};
            rpElements.push_back(elem);
            WriteRoutePath(trackElement.fromNodeId, trackElement.destination, rpElements);
            continue;
        }
        std::ostringstream oss;
        oss << rt->GetGateway();
        NS_LOG_INFO("Node:" << trackElement.fromNodeId << "-->" << rt->GetGateway());
        if (rt->GetGateway() == "0.0.0.0")
        {
            Ipv4RoutePathElement elem = {trackElement.fromNodeId, "C"};
            rpElements.push_back(elem);
            if (m_ipv4ToNodeIdMap.find(trackElement.destination) != m_ipv4ToNodeIdMap.end())
            {
                Ipv4RoutePathElement elem2 = {m_ipv4ToNodeIdMap[trackElement.destination], "L"};
                rpElements.push_back(elem2);
            }
        }
        else if (rt->GetGateway() == "127.0.0.1")
        {
            Ipv4RoutePathElement elem = {trackElement.fromNodeId, "-1"};
            rpElements.push_back(elem);
        }
        else
        {
            Ipv4RoutePathElement elem = {trackElement.fromNodeId, oss.str()};
            rpElements.push_back(elem);
        }
        RecursiveIpv4RoutePathSearch(oss.str(), trackElement.destination, rpElements);
        WriteRoutePath(trackElement.fromNodeId, trackElement.destination, rpElements);
    }
}

void
AnimationInterface::TrackIpv4Route()
{
    if (Simulator::Now() > m_routingStopTime)
    {
        NS_LOG_INFO("TrackIpv4Route completed");
        return;
    }
    if (m_routingNc.GetN())
    {
        for (auto i = m_routingNc.Begin(); i != m_routingNc.End(); ++i)
        {
            Ptr<Node> n = *i;
            WriteXmlRouting(n->GetId(), GetIpv4RoutingTable(n));
        }
    }
    else
    {
        for (auto i = NodeList::Begin(); i != NodeList::End(); ++i)
        {
            Ptr<Node> n = *i;
            WriteXmlRouting(n->GetId(), GetIpv4RoutingTable(n));
        }
    }
    TrackIpv4RoutePaths();
    Simulator::Schedule(m_routingPollInterval, &AnimationInterface::TrackIpv4Route, this);
}

std::string
AnimationInterface::GetIpv4RoutingTable(Ptr<Node> n)
{
    NS_ASSERT(n);
    Ptr<ns3::Ipv4> ipv4 = n->GetObject<ns3::Ipv4>();
    if (!ipv4)
    {
        NS_LOG_WARN("Node " << n->GetId() << " Does not have an Ipv4 object");
        return "";
    }
    std::stringstream stream;
    Ptr<OutputStreamWrapper> routingstream = Create<OutputStreamWrapper>(&stream);
    ipv4->GetRoutingProtocol()->PrintRoutingTable(routingstream);
    return stream.str();
}

void
AnimationInterface::RecursiveIpv4RoutePathSearch(std::string from,
                                                 std::string to,
                                                 Ipv4RoutePathElements& rpElements)
{
    NS_LOG_INFO("RecursiveIpv4RoutePathSearch from:" << from << " to:" << to);
    if (from == "0.0.0.0" || from == "127.0.0.1")
    {
        NS_LOG_INFO("Got " << from << " End recursion");
        return;
    }
    Ptr<Node> fromNode = NodeList::GetNode(m_ipv4ToNodeIdMap[from]);
    Ptr<Node> toNode = NodeList::GetNode(m_ipv4ToNodeIdMap[to]);
    if (fromNode->GetId() == toNode->GetId())
    {
        Ipv4RoutePathElement elem = {fromNode->GetId(), "L"};
        rpElements.push_back(elem);
        return;
    }
    if (!fromNode)
    {
        NS_FATAL_ERROR("Node: " << m_ipv4ToNodeIdMap[from] << " Not found");
        return;
    }
    if (!toNode)
    {
        NS_FATAL_ERROR("Node: " << m_ipv4ToNodeIdMap[to] << " Not found");
        return;
    }
    Ptr<ns3::Ipv4> ipv4 = fromNode->GetObject<ns3::Ipv4>();
    if (!ipv4)
    {
        NS_LOG_WARN("ipv4 object not found");
        return;
    }
    Ptr<Ipv4RoutingProtocol> rp = ipv4->GetRoutingProtocol();
    if (!rp)
    {
        NS_LOG_WARN("Routing protocol object not found");
        return;
    }
    Ptr<Packet> pkt = Create<Packet>();
    Ipv4Header header;
    header.SetDestination(Ipv4Address(to.c_str()));
    Socket::SocketErrno sockerr;
    Ptr<Ipv4Route> rt = rp->RouteOutput(pkt, header, nullptr, sockerr);
    if (!rt)
    {
        return;
    }
    NS_LOG_DEBUG("Node: " << fromNode->GetId() << " G:" << rt->GetGateway());
    std::ostringstream oss;
    oss << rt->GetGateway();
    if (oss.str() == "0.0.0.0" && (sockerr != Socket::ERROR_NOROUTETOHOST))
    {
        NS_LOG_INFO("Null gw");
        Ipv4RoutePathElement elem = {fromNode->GetId(), "C"};
        rpElements.push_back(elem);
        if (m_ipv4ToNodeIdMap.find(to) != m_ipv4ToNodeIdMap.end())
        {
            Ipv4RoutePathElement elem2 = {m_ipv4ToNodeIdMap[to], "L"};
            rpElements.push_back(elem2);
        }
        return;
    }
    NS_LOG_INFO("Node:" << fromNode->GetId() << "-->" << rt->GetGateway());
    Ipv4RoutePathElement elem = {fromNode->GetId(), oss.str()};
    rpElements.push_back(elem);
    RecursiveIpv4RoutePathSearch(oss.str(), to, rpElements);
}

/***** WriteXml *****/

void
AnimationInterface::WriteXmlAnim(bool routing)
{
    AnimXmlElement element("anim");
    element.AddAttribute("ver", GetNetAnimVersion());
    FILE* f = m_f;
    if (!routing)
    {
        element.AddAttribute("filetype", "animation");
    }
    else
    {
        element.AddAttribute("filetype", "routing");
        f = m_routingF;
    }
    WriteN(element.ToString(false) + ">\n", f);
}

void
AnimationInterface::WriteXmlClose(std::string name, bool routing)
{
    std::string closeString = "</" + name + ">\n";
    if (!routing)
    {
        WriteN(closeString, m_f);
    }
    else
    {
        WriteN(closeString, m_routingF);
    }
}

void
AnimationInterface::WriteXmlNode(uint32_t id, uint32_t sysId, double locX, double locY)
{
    AnimXmlElement element("node");
    element.AddAttribute("id", id);
    element.AddAttribute("sysId", sysId);
    element.AddAttribute("locX", locX);
    element.AddAttribute("locY", locY);
    WriteN(element.ToString(), m_f);
}

void
AnimationInterface::WriteXmlUpdateLink(uint32_t fromId, uint32_t toId, std::string linkDescription)
{
    AnimXmlElement element("linkupdate");
    element.AddAttribute("t", Simulator::Now().GetSeconds());
    element.AddAttribute("fromId", fromId);
    element.AddAttribute("toId", toId);
    element.AddAttribute("ld", linkDescription, true);
    WriteN(element.ToString(), m_f);
}

void
AnimationInterface::WriteXmlLink(uint32_t fromId, uint32_t toLp, uint32_t toId)
{
    AnimXmlElement element("link");
    element.AddAttribute("fromId", fromId);
    element.AddAttribute("toId", toId);

    LinkProperties lprop;
    lprop.fromNodeDescription = "";
    lprop.toNodeDescription = "";
    lprop.linkDescription = "";

    P2pLinkNodeIdPair p1 = {fromId, toId};
    P2pLinkNodeIdPair p2 = {toId, fromId};
    if (m_linkProperties.find(p1) != m_linkProperties.end())
    {
        lprop = m_linkProperties[p1];
    }
    else if (m_linkProperties.find(p2) != m_linkProperties.end())
    {
        lprop = m_linkProperties[p2];
    }

    element.AddAttribute("fd", lprop.fromNodeDescription, true);
    element.AddAttribute("td", lprop.toNodeDescription, true);
    element.AddAttribute("ld", lprop.linkDescription, true);
    WriteN(element.ToString(), m_f);
}

void
AnimationInterface::WriteXmlIpv4Addresses(uint32_t nodeId, std::vector<std::string> ipv4Addresses)
{
    AnimXmlElement element("ip");
    element.AddAttribute("n", nodeId);
    for (auto i = ipv4Addresses.begin(); i != ipv4Addresses.end(); ++i)
    {
        AnimXmlElement valueElement("address");
        valueElement.SetText(*i);
        element.AppendChild(valueElement);
    }
    WriteN(element.ToString(), m_f);
}

void
AnimationInterface::WriteXmlIpv6Addresses(uint32_t nodeId, std::vector<std::string> ipv6Addresses)
{
    AnimXmlElement element("ipv6");
    element.AddAttribute("n", nodeId);
    for (auto i = ipv6Addresses.begin(); i != ipv6Addresses.end(); ++i)
    {
        AnimXmlElement valueElement("address");
        valueElement.SetText(*i);
        element.AppendChild(valueElement);
    }
    WriteN(element.ToString(), m_f);
}

void
AnimationInterface::WriteXmlRouting(uint32_t nodeId, std::string routingInfo)
{
    AnimXmlElement element("rt");
    element.AddAttribute("t", Simulator::Now().GetSeconds());
    element.AddAttribute("id", nodeId);
    element.AddAttribute("info", routingInfo.c_str(), true);
    WriteN(element.ToString(), m_routingF);
}

void
AnimationInterface::WriteXmlRp(uint32_t nodeId,
                               std::string destination,
                               Ipv4RoutePathElements rpElements)
{
    std::string tagName = "rp";
    AnimXmlElement element(tagName, false);
    element.AddAttribute("t", Simulator::Now().GetSeconds());
    element.AddAttribute("id", nodeId);
    element.AddAttribute("d", destination.c_str());
    element.AddAttribute("c", rpElements.size());
    for (const auto& rpElement : rpElements)
    {
        AnimXmlElement rpeElement("rpe");
        rpeElement.AddAttribute("n", rpElement.nodeId);
        rpeElement.AddAttribute("nH", rpElement.nextHop.c_str());
        element.AppendChild(rpeElement);
    }
    WriteN(element.ToString(), m_routingF);
}

void
AnimationInterface::WriteXmlPRef(uint64_t animUid, uint32_t fId, double fbTx, std::string metaInfo)
{
    AnimXmlElement element("pr");
    element.AddAttribute("uId", animUid);
    element.AddAttribute("fId", fId);
    element.AddAttribute("fbTx", fbTx);
    if (!metaInfo.empty())
    {
        element.AddAttribute("meta-info", metaInfo.c_str(), true);
    }
    WriteN(element.ToString(), m_f);
}

void
AnimationInterface::WriteXmlP(uint64_t animUid,
                              std::string pktType,
                              uint32_t tId,
                              double fbRx,
                              double lbRx)
{
    AnimXmlElement element(pktType);
    element.AddAttribute("uId", animUid);
    element.AddAttribute("tId", tId);
    element.AddAttribute("fbRx", fbRx);
    element.AddAttribute("lbRx", lbRx);
    WriteN(element.ToString(), m_f);
}

void
AnimationInterface::WriteXmlP(std::string pktType,
                              uint32_t fId,
                              double fbTx,
                              double lbTx,
                              uint32_t tId,
                              double fbRx,
                              double lbRx,
                              std::string metaInfo)
{
    AnimXmlElement element(pktType);
    element.AddAttribute("fId", fId);
    element.AddAttribute("fbTx", fbTx);
    element.AddAttribute("lbTx", lbTx);
    if (!metaInfo.empty())
    {
        element.AddAttribute("meta-info", metaInfo.c_str(), true);
    }
    element.AddAttribute("tId", tId);
    element.AddAttribute("fbRx", fbRx);
    element.AddAttribute("lbRx", lbRx);
    WriteN(element.ToString(), m_f);
}

void
AnimationInterface::WriteXmlAddNodeCounter(uint32_t nodeCounterId,
                                           std::string counterName,
                                           CounterType counterType)
{
    AnimXmlElement element("ncs");
    element.AddAttribute("ncId", nodeCounterId);
    element.AddAttribute("n", counterName);
    element.AddAttribute("t", CounterTypeToString(counterType));
    WriteN(element.ToString(), m_f);
}

void
AnimationInterface::WriteXmlAddResource(uint32_t resourceId, std::string resourcePath)
{
    AnimXmlElement element("res");
    element.AddAttribute("rid", resourceId);
    element.AddAttribute("p", resourcePath);
    WriteN(element.ToString(), m_f);
}

void
AnimationInterface::WriteXmlUpdateNodeImage(uint32_t nodeId, uint32_t resourceId)
{
    AnimXmlElement element("nu");
    element.AddAttribute("p", "i");
    element.AddAttribute("t", Simulator::Now().GetSeconds());
    element.AddAttribute("id", nodeId);
    element.AddAttribute("rid", resourceId);
    WriteN(element.ToString(), m_f);
}

void
AnimationInterface::WriteXmlUpdateNodeSize(uint32_t nodeId, double width, double height)
{
    AnimXmlElement element("nu");
    element.AddAttribute("p", "s");
    element.AddAttribute("t", Simulator::Now().GetSeconds());
    element.AddAttribute("id", nodeId);
    element.AddAttribute("w", width);
    element.AddAttribute("h", height);
    WriteN(element.ToString(), m_f);
}

void
AnimationInterface::WriteXmlUpdateNodePosition(uint32_t nodeId, double x, double y)
{
    AnimXmlElement element("nu");
    element.AddAttribute("p", "p");
    element.AddAttribute("t", Simulator::Now().GetSeconds());
    element.AddAttribute("id", nodeId);
    element.AddAttribute("x", x);
    element.AddAttribute("y", y);
    WriteN(element.ToString(), m_f);
}

void
AnimationInterface::WriteXmlUpdateNodeColor(uint32_t nodeId, uint8_t r, uint8_t g, uint8_t b)
{
    AnimXmlElement element("nu");
    element.AddAttribute("p", "c");
    element.AddAttribute("t", Simulator::Now().GetSeconds());
    element.AddAttribute("id", nodeId);
    element.AddAttribute("r", (uint32_t)r);
    element.AddAttribute("g", (uint32_t)g);
    element.AddAttribute("b", (uint32_t)b);
    WriteN(element.ToString(), m_f);
}

void
AnimationInterface::WriteXmlUpdateNodeDescription(uint32_t nodeId)
{
    AnimXmlElement element("nu");
    element.AddAttribute("p", "d");
    element.AddAttribute("t", Simulator::Now().GetSeconds());
    element.AddAttribute("id", nodeId);
    if (m_nodeDescriptions.find(nodeId) != m_nodeDescriptions.end())
    {
        element.AddAttribute("descr", m_nodeDescriptions[nodeId], true);
    }
    WriteN(element.ToString(), m_f);
}

void
AnimationInterface::WriteXmlUpdateNodeCounter(uint32_t nodeCounterId,
                                              uint32_t nodeId,
                                              double counterValue)
{
    AnimXmlElement element("nc");
    element.AddAttribute("c", nodeCounterId);
    element.AddAttribute("i", nodeId);
    element.AddAttribute("t", Simulator::Now().GetSeconds());
    element.AddAttribute("v", counterValue);
    WriteN(element.ToString(), m_f);
}

void
AnimationInterface::WriteXmlUpdateBackground(std::string fileName,
                                             double x,
                                             double y,
                                             double scaleX,
                                             double scaleY,
                                             double opacity)
{
    AnimXmlElement element("bg");
    element.AddAttribute("f", fileName);
    element.AddAttribute("x", x);
    element.AddAttribute("y", y);
    element.AddAttribute("sx", scaleX);
    element.AddAttribute("sy", scaleY);
    element.AddAttribute("o", opacity);
    WriteN(element.ToString(), m_f);
}

void
AnimationInterface::WriteXmlNonP2pLinkProperties(uint32_t id,
                                                 std::string ipAddress,
                                                 std::string channelType)
{
    AnimXmlElement element("nonp2plinkproperties");
    element.AddAttribute("id", id);
    element.AddAttribute("ipAddress", ipAddress);
    element.AddAttribute("channelType", channelType);
    WriteN(element.ToString(), m_f);
}

/***** AnimXmlElement  *****/

AnimationInterface::AnimXmlElement::AnimXmlElement(std::string tagName, bool emptyElement)
    : m_tagName(tagName),
      m_text("")
{
}

template <typename T>
void
AnimationInterface::AnimXmlElement::AddAttribute(std::string attribute, T value, bool xmlEscape)
{
    std::ostringstream oss;
    oss << std::setprecision(10);
    oss << value;
    std::string attributeString = attribute;
    if (xmlEscape)
    {
        attributeString += "=\"";
        std::string valueStr = oss.str();
        for (auto it = valueStr.begin(); it != valueStr.end(); ++it)
        {
            switch (*it)
            {
            case '&':
                attributeString += "&amp;";
                break;
            case '\"':
                attributeString += "&quot;";
                break;
            case '\'':
                attributeString += "&apos;";
                break;
            case '<':
                attributeString += "&lt;";
                break;
            case '>':
                attributeString += "&gt;";
                break;
            default:
                attributeString += *it;
                break;
            }
        }
        attributeString += "\" ";
    }
    else
    {
        attributeString += "=\"" + oss.str() + "\" ";
    }
    m_attributes.push_back(attributeString);
}

void
AnimationInterface::AnimXmlElement::AppendChild(AnimXmlElement e)
{
    m_children.push_back(e.ToString());
}

void
AnimationInterface::AnimXmlElement::SetText(std::string text)
{
    m_text = text;
}

std::string
AnimationInterface::AnimXmlElement::ToString(bool autoClose)
{
    std::string elementString = "<" + m_tagName + " ";

    for (auto i = m_attributes.begin(); i != m_attributes.end(); ++i)
    {
        elementString += *i;
    }
    if (m_children.empty() && m_text.empty())
    {
        if (autoClose)
        {
            elementString += "/>";
        }
    }
    else
    {
        elementString += ">";
        if (!m_text.empty())
        {
            elementString += m_text;
        }
        if (!m_children.empty())
        {
            elementString += "\n";
            for (auto i = m_children.begin(); i != m_children.end(); ++i)
            {
                elementString += *i + "\n";
            }
        }
        if (autoClose)
        {
            elementString += "</" + m_tagName + ">";
        }
    }

    return elementString + ((autoClose) ? "\n" : "");
}

/***** AnimByteTag *****/

TypeId
AnimByteTag::GetTypeId()
{
    static TypeId tid = TypeId("ns3::AnimByteTag")
                            .SetParent<Tag>()
                            .SetGroupName("NetAnim")
                            .AddConstructor<AnimByteTag>();
    return tid;
}

TypeId
AnimByteTag::GetInstanceTypeId() const
{
    return GetTypeId();
}

uint32_t
AnimByteTag::GetSerializedSize() const
{
    return sizeof(uint64_t);
}

void
AnimByteTag::Serialize(TagBuffer i) const
{
    i.WriteU64(m_AnimUid);
}

void
AnimByteTag::Deserialize(TagBuffer i)
{
    m_AnimUid = i.ReadU64();
}

void
AnimByteTag::Print(std::ostream& os) const
{
    os << "AnimUid=" << m_AnimUid;
}

void
AnimByteTag::Set(uint64_t AnimUid)
{
    m_AnimUid = AnimUid;
}

uint64_t
AnimByteTag::Get() const
{
    return m_AnimUid;
}

AnimationInterface::AnimPacketInfo::AnimPacketInfo()
    : m_txnd(nullptr),
      m_txNodeId(0),
      m_fbTx(0),
      m_lbTx(0),
      m_lbRx(0)
{
}

AnimationInterface::AnimPacketInfo::AnimPacketInfo(const AnimPacketInfo& pInfo)
{
    m_txnd = pInfo.m_txnd;
    m_txNodeId = pInfo.m_txNodeId;
    m_fbTx = pInfo.m_fbTx;
    m_lbTx = pInfo.m_lbTx;
    m_lbRx = pInfo.m_lbRx;
}

AnimationInterface::AnimPacketInfo::AnimPacketInfo(Ptr<const NetDevice> txnd,
                                                   const Time fbTx,
                                                   uint32_t txNodeId)
    : m_txnd(txnd),
      m_txNodeId(0),
      m_fbTx(fbTx.GetSeconds()),
      m_lbTx(0),
      m_lbRx(0)
{
    if (!m_txnd)
    {
        m_txNodeId = txNodeId;
    }
}

void
AnimationInterface::AnimPacketInfo::ProcessRxBegin(Ptr<const NetDevice> nd, const double fbRx)
{
    Ptr<Node> n = nd->GetNode();
    m_fbRx = fbRx;
    m_rxnd = nd;
}

} // namespace ns3
