/*
 * Copyright (c) 2009 University of Washington
 *
 * SPDX-License-Identifier: GPL-2.0-only
 */

#include "names.h"

#include "abort.h"
#include "assert.h"
#include "log.h"
#include "object.h"
#include "singleton.h"

#include <map>

/**
 * @file
 * @ingroup config
 * ns3::Names, ns3::NamesNode and ns3::NamePriv implementations.
 */

namespace ns3
{

NS_LOG_COMPONENT_DEFINE("Names");

/**
 * @ingroup config
 *  Node in the naming tree.
 */
class NameNode
{
  public:
    /** Default constructor. */
    NameNode();
    /**
     * Copy constructor.
     *
     * @param [in] nameNode The NameNode to copy from.
     */
    NameNode(const NameNode& nameNode);
    /**
     * Constructor.
     *
     * @param [in] parent The parent NameNode.
     * @param [in] name The name of this NameNode
     * @param [in] object The object corresponding to this NameNode.
     */
    NameNode(NameNode* parent, std::string name, Ptr<Object> object);
    /**
     * Assignment operator.
     *
     * @param [in] rhs The NameNode to copy from.
     * @returns The lhs NameNode.
     */
    NameNode& operator=(const NameNode& rhs);

    /** Destructor. */
    ~NameNode();

    /** The parent NameNode. */
    NameNode* m_parent;
    /** The name of this NameNode. */
    std::string m_name;
    /** The object corresponding to this NameNode. */
    Ptr<Object> m_object;

    /** Children of this NameNode. */
    std::map<std::string, NameNode*> m_nameMap;
};

NameNode::NameNode()
    : m_parent(nullptr),
      m_name(""),
      m_object(nullptr)
{
}

NameNode::NameNode(const NameNode& nameNode)
{
    m_parent = nameNode.m_parent;
    m_name = nameNode.m_name;
    m_object = nameNode.m_object;
    m_nameMap = nameNode.m_nameMap;
}

NameNode&
NameNode::operator=(const NameNode& rhs)
{
    m_parent = rhs.m_parent;
    m_name = rhs.m_name;
    m_object = rhs.m_object;
    m_nameMap = rhs.m_nameMap;
    return *this;
}

NameNode::NameNode(NameNode* parent, std::string name, Ptr<Object> object)
    : m_parent(parent),
      m_name(name),
      m_object(object)
{
    NS_LOG_FUNCTION(this << parent << name << object);
}

NameNode::~NameNode()
{
    NS_LOG_FUNCTION(this);
}

/**
 * @ingroup config
 * The singleton root Names object.
 */
class NamesPriv : public Singleton<NamesPriv>
{
  public:
    /** Constructor. */
    NamesPriv();
    /** Destructor. */
    ~NamesPriv() override;

    // Doxygen \copydoc bug: won't copy these docs, so we repeat them.

    /**
     * Internal implementation for Names::Add(std::string,Ptr<Object>)
     *
     * @param [in] name The name of the object you want to associate;
     *             which may be prepended with a path to that object.
     * @param [in] object A smart pointer to the object itself.
     * @return \c true if the object was named successfully.
     */
    bool Add(std::string name, Ptr<Object> object);
    /**
     * Internal implementation for Names::Add(std::string,std::string,Ptr<Object>)
     *
     * @param [in] path A path name describing a previously named object
     *             under which you want this new name to be defined.
     * @param [in] name The name of the object you want to associate.
     * @param [in] object A smart pointer to the object itself.
     * @return \c true if the object was named successfully.
     */
    bool Add(std::string path, std::string name, Ptr<Object> object);
    /**
     * Internal implementation for Names::Add(Ptr<Object>,std::string,Ptr<Object>)
     *
     * @param [in] context A smart pointer to an object that is used
     *             in place of the path under which you want this new
     *             name to be defined.
     * @param [in] name The name of the object you want to associate.
     * @param [in] object A smart pointer to the object itself.
     * @return \c true if the object was named successfully.
     */
    bool Add(Ptr<Object> context, std::string name, Ptr<Object> object);

    /**
     * Internal implementation for Names::Rename(std::string,std::string)
     *
     * @param [in] oldpath The current path name to the object you want
     *             to change.
     * @param [in] newname The new name of the object you want to change.
     * @return \c true if the object was renamed successfully.
     */
    bool Rename(std::string oldpath, std::string newname);
    /**
     * Internal implementation for
     * Names::Rename(std::string,std::string,std::string)
     *
     * @param [in] path A path name describing a previously named object
     *             under which you want this name change to occur
     *             (cf. directory).
     * @param [in] oldname The currently defined name of the object.
     * @param [in] newname The new name you want the object to have.
     * @return \c true if the object was renamed successfully.
     */
    bool Rename(std::string path, std::string oldname, std::string newname);
    /**
     * Internal implementation for
     * Names::Rename(Ptr<Object>,std::string,std::string)
     *
     * @param [in] context A smart pointer to an object that is used
     *             in place of the path under which you want this
     *             new name to be defined.
     * @param [in] oldname The current shortname of the object you want
     *             to change.
     * @param [in] newname The new shortname of the object you want
     *             to change.
     * @return \c true if the object was renamed successfully.
     */
    bool Rename(Ptr<Object> context, std::string oldname, std::string newname);

    /**
     * Internal implementation for Names::FindName()
     *
     * @param [in] object A smart pointer to an object for which you want
     *             to find its name.
     * @returns A string containing the name of the object if found,
     *          otherwise the empty string.
     */
    std::string FindName(Ptr<Object> object);
    /**
     * Internal implementation of Names::FindPath()
     *
     * @param [in] object A smart pointer to an object for which you
     *             want to find its fullname.
     * @returns A string containing the name path of the object,
     *          otherwise the empty string.
     */
    std::string FindPath(Ptr<Object> object);

    /**
     * Internal implementation for Names::Clear()
     */
    void Clear();

    /**
     * Internal implementation for ns3::Names::Find(std::string)
     *
     * @param [in] path A string containing a name space path used
     *             to locate the object.
     * @returns A smart pointer to the named object converted to
     *          the requested type.
     */
    Ptr<Object> Find(std::string path);
    /**
     * Internal implementation for ns3::Names::Find(std::string,std::string)
     *
     * @param [in] path A path name describing a previously named object
     *             under which you want to look for the specified name.
     * @param [in] name A string containing a name to search for.
     * @returns A smart pointer to the named object converted to
     *          the requested type.
     */
    Ptr<Object> Find(std::string path, std::string name);
    /**
     * Internal implementation for ns3::Names::Find(Ptr<Object>,std::string)
     *
     * @param [in] context A smart pointer to an object that is used
     *             in place of the path under which you want this
     *             new name to be defined.
     * @param [in] name A string containing a name to search for.
     * @returns A smart pointer to the named object converted to
     *          the requested type.
     */
    Ptr<Object> Find(Ptr<Object> context, std::string name);

  private:
    /**
     * Check if an object has a name.
     *
     * @param [in] object The object to check.
     * @returns The corresponding NameNode, if it exists.
     */
    NameNode* IsNamed(Ptr<Object> object);
    /**
     * Check if a name already exists as a child of a NameNode.
     *
     * @param [in] node The node to search.
     * @param [in] name The name to search for.
     * @returns \c true if \c name already exists as a child of \c node.
     */
    bool IsDuplicateName(NameNode* node, std::string name);

    /** The root NameNode. */
    NameNode m_root;

    /** Map from object pointers to their NameNodes. */
    std::map<Ptr<Object>, NameNode*> m_objectMap;
};

NamesPriv::NamesPriv()
{
    NS_LOG_FUNCTION(this);

    m_root.m_parent = nullptr;
    m_root.m_name = "Names";
    m_root.m_object = nullptr;
}

NamesPriv::~NamesPriv()
{
    NS_LOG_FUNCTION(this);
    Clear();
    m_root.m_name = "";
}

void
NamesPriv::Clear()
{
    NS_LOG_FUNCTION(this);
    //
    // Every name is associated with an object in the object map, so freeing the
    // NameNodes in this map will free all of the memory allocated for the NameNodes
    //
    for (auto i = m_objectMap.begin(); i != m_objectMap.end(); ++i)
    {
        delete i->second;
        i->second = nullptr;
    }

    m_objectMap.clear();

    m_root.m_parent = nullptr;
    m_root.m_name = "Names";
    m_root.m_object = nullptr;
    m_root.m_nameMap.clear();
}

bool
NamesPriv::Add(std::string name, Ptr<Object> object)
{
    NS_LOG_FUNCTION(this << name << object);
    //
    // This is the simple, easy to use version of Add, so we want it to be flexible.
    // We don't want to force a user to always type the fully qualified namespace
    // name, so we allow the namespace name to be omitted.  For example, calling
    // Add ("Client/ath0", obj) should result in exactly the same behavior as
    // Add ("/Names/Client/ath0", obj).  Calling Add ("Client", obj) should have
    // the same effect as Add ("Names/Client", obj)
    //
    // The first thing to do, then, is to "canonicalize" the input string to always
    // be a fully qualified name.
    //
    // If we are given a name that begins with "/Names/" we assume that this is a
    // fully qualified path name to the object we want to create.  We split the name
    // into a path string and a final segment (name) and then call the "Real" Add.
    //
    std::string namespaceName = "/Names";
    std::string::size_type offset = name.find(namespaceName);
    if (offset != 0)
    {
        //
        // This must be a name that has the "/Names" namespace prefix omitted.
        // Do some reasonableness checking on the rest of the name.
        //
        offset = name.find('/');
        if (offset == 0)
        {
            NS_ASSERT_MSG(false, "NamesPriv::Add(): Name begins with '/' but not \"/Names\"");
            return false;
        }

        name = "/Names/" + name;
    }

    //
    // There must now be a fully qualified path in the string.  All fully
    // qualified names begin with "/Names".  We have to split off the final
    // segment which will become the name of the object.  A '/' that
    // separates the path from the final segment had better be there since
    // we just made sure that at least the namespace name was there.
    //
    std::string::size_type i = name.rfind('/');
    NS_ASSERT_MSG(i != std::string::npos,
                  "NamesPriv::Add(): Internal error.  Can't find '/' in name");

    //
    // The slash we found cannot be the slash at the start of the namespaceName.
    // This would indicate there is no name in the path at all.  It can be
    // any other index.
    //
    NS_ASSERT_MSG(i != 0, "NamesPriv::Add(): Can't find a name in the path string");

    //
    // We now know where the path string starts and ends, and where the
    // name starts and ends.  All we have to do is to call our available
    // function for adding a name under a path string.
    //
    return Add(name.substr(0, i), name.substr(i + 1), object);
}

bool
NamesPriv::Add(std::string path, std::string name, Ptr<Object> object)
{
    NS_LOG_FUNCTION(this << path << name << object);
    if (path == "/Names")
    {
        return Add(Ptr<Object>(nullptr, false), name, object);
    }
    return Add(Find(path), name, object);
}

bool
NamesPriv::Add(Ptr<Object> context, std::string name, Ptr<Object> object)
{
    NS_LOG_FUNCTION(this << context << name << object);

    if (IsNamed(object))
    {
        NS_LOG_LOGIC("Object is already named");
        return false;
    }

    NameNode* node = nullptr;
    if (context)
    {
        node = IsNamed(context);
        NS_ASSERT_MSG(node, "NamesPriv::Name(): context must point to a previously named node");
    }
    else
    {
        node = &m_root;
    }

    if (IsDuplicateName(node, name))
    {
        NS_LOG_LOGIC("Name is already taken");
        return false;
    }

    auto newNode = new NameNode(node, name, object);
    node->m_nameMap[name] = newNode;
    m_objectMap[object] = newNode;

    return true;
}

bool
NamesPriv::Rename(std::string oldpath, std::string newname)
{
    NS_LOG_FUNCTION(this << oldpath << newname);
    //
    // This is the simple, easy to use version of Rename, so we want it to be
    // flexible.   We don't want to force a user to always type the fully
    // qualified namespace name, so we allow the namespace name to be omitted.
    // For example, calling Rename ("Client/ath0", "eth0") should result in
    // exactly the same behavior as Rename ("/Names/Client/ath0", "eth0").
    // Calling Rename ("Client", "Router") should have the same effect as
    // Rename ("Names/Client", "Router")
    //
    // The first thing to do, then, is to "canonicalize" the input string to always
    // be a fully qualified path.
    //
    // If we are given a name that begins with "/Names/" we assume that this is a
    // fully qualified path to the object we want to change.  We split the path into
    // path string (cf directory) and a final segment (cf filename) and then call
    // the "Real" Rename.
    //
    std::string namespaceName = "/Names";
    std::string::size_type offset = oldpath.find(namespaceName);
    if (offset != 0)
    {
        //
        // This must be a name that has the "/Names" namespace prefix omitted.
        // Do some reasonableness checking on the rest of the name.
        //
        offset = oldpath.find('/');
        if (offset == 0)
        {
            NS_ASSERT_MSG(false, "NamesPriv::Add(): Name begins with '/' but not \"/Names\"");
            return false;
        }

        oldpath = "/Names/" + oldpath;
    }

    //
    // There must now be a fully qualified path in the oldpath string.  All
    // fully qualified names begin with "/Names".  We have to split off the final
    // segment which will become the name we want to rename.  A '/' that
    // separates the path from the final segment (name) had better be there since
    // we just made sure that at least the namespace name was there.
    //
    std::string::size_type i = oldpath.rfind('/');
    NS_ASSERT_MSG(i != std::string::npos,
                  "NamesPriv::Add(): Internal error.  Can't find '/' in name");

    //
    // The slash we found cannot be the slash at the start of the namespaceName.
    // This would indicate there is no name in the path at all.  It can be
    // any other index.
    //
    NS_ASSERT_MSG(i != 0, "NamesPriv::Add(): Can't find a name in the path string");

    //
    // We now know where the path part of the string starts and ends, and where the
    // name part starts and ends.  All we have to do is to call our available
    // function for creating adding a name under a path string.
    //
    return Rename(oldpath.substr(0, i), oldpath.substr(i + 1), newname);
}

bool
NamesPriv::Rename(std::string path, std::string oldname, std::string newname)
{
    NS_LOG_FUNCTION(this << path << oldname << newname);
    if (path == "/Names")
    {
        return Rename(Ptr<Object>(nullptr, false), oldname, newname);
    }
    return Rename(Find(path), oldname, newname);
}

bool
NamesPriv::Rename(Ptr<Object> context, std::string oldname, std::string newname)
{
    NS_LOG_FUNCTION(this << context << oldname << newname);

    NameNode* node = nullptr;
    if (context)
    {
        node = IsNamed(context);
        NS_ASSERT_MSG(node, "NamesPriv::Name(): context must point to a previously named node");
    }
    else
    {
        node = &m_root;
    }

    if (IsDuplicateName(node, newname))
    {
        NS_LOG_LOGIC("New name is already taken");
        return false;
    }

    auto i = node->m_nameMap.find(oldname);
    if (i == node->m_nameMap.end())
    {
        NS_LOG_LOGIC("Old name does not exist in name map");
        return false;
    }
    else
    {
        NS_LOG_LOGIC("Old name exists in name map");

        //
        // The rename process consists of:
        // 1.  Getting the pointer to the name node from the map and remembering it;
        // 2.  Removing the map entry corresponding to oldname from the map;
        // 3.  Changing the name string in the name node;
        // 4.  Adding the name node back in the map under the newname.
        //
        NameNode* changeNode = i->second;
        node->m_nameMap.erase(i);
        changeNode->m_name = newname;
        node->m_nameMap[newname] = changeNode;
        return true;
    }
}

std::string
NamesPriv::FindName(Ptr<Object> object)
{
    NS_LOG_FUNCTION(this << object);

    auto i = m_objectMap.find(object);
    if (i == m_objectMap.end())
    {
        NS_LOG_LOGIC("Object does not exist in object map");
        return "";
    }
    else
    {
        NS_LOG_LOGIC("Object exists in object map");
        return i->second->m_name;
    }
}

std::string
NamesPriv::FindPath(Ptr<Object> object)
{
    NS_LOG_FUNCTION(this << object);

    auto i = m_objectMap.find(object);
    if (i == m_objectMap.end())
    {
        NS_LOG_LOGIC("Object does not exist in object map");
        return "";
    }

    NameNode* p = i->second;
    NS_ASSERT_MSG(p,
                  "NamesPriv::FindFullName(): Internal error: Invalid NameNode pointer from map");

    std::string path;

    do
    {
        path = "/" + p->m_name + path;
        NS_LOG_LOGIC("path is " << path);
    } while ((p = p->m_parent) != nullptr);

    return path;
}

Ptr<Object>
NamesPriv::Find(std::string path)
{
    //
    // This is hooked in from simple, easy to use version of Find, so we want it
    // to be flexible.
    //
    // If we are provided a path that doesn't begin with "/Names", we assume
    // that the caller has simply given us a path starting with a name that
    // is in the root namespace.  This allows people to omit the "/Names" prefix.
    // and simply do a Find ("Client/eth0") instead of having to always do a
    // Find ("/Names/Client/eth0");
    //
    // So, if we are given a name that begins with "/Names/" the upshot is that we
    // just remove that prefix and treat the rest of the string as starting with a
    // name in the root namespace.
    //

    NS_LOG_FUNCTION(this << path);
    std::string namespaceName = "/Names/";
    std::string remaining;

    std::string::size_type offset = path.find(namespaceName);
    if (offset == 0)
    {
        NS_LOG_LOGIC(path << " is a fully qualified name");
        remaining = path.substr(namespaceName.size());
    }
    else
    {
        NS_LOG_LOGIC(path << " begins with a relative name");
        remaining = path;
    }

    NameNode* node = &m_root;

    //
    // The string <remaining> is now composed entirely of path segments in
    // the /Names name space and we have eaten the leading slash. e.g.,
    // remaining = "ClientNode/eth0"
    //
    // The start of the search is always at the root of the name space.
    //
    for (;;)
    {
        NS_LOG_LOGIC("Looking for the object of name " << remaining);
        offset = remaining.find('/');
        if (offset == std::string::npos)
        {
            //
            // There are no remaining slashes so this is the last segment of the
            // specified name.  We're done when we find it
            //
            auto i = node->m_nameMap.find(remaining);
            if (i == node->m_nameMap.end())
            {
                NS_LOG_LOGIC("Name does not exist in name map");
                return nullptr;
            }
            else
            {
                NS_LOG_LOGIC("Name parsed, found object");
                return i->second->m_object;
            }
        }
        else
        {
            //
            // There are more slashes so this is an intermediate segment of the
            // specified name.  We need to "recurse" when we find this segment.
            //
            offset = remaining.find('/');
            std::string segment = remaining.substr(0, offset);

            auto i = node->m_nameMap.find(segment);
            if (i == node->m_nameMap.end())
            {
                NS_LOG_LOGIC("Name does not exist in name map");
                return nullptr;
            }
            else
            {
                node = i->second;
                remaining = remaining.substr(offset + 1);
                NS_LOG_LOGIC("Intermediate segment parsed");
                continue;
            }
        }
    }

    NS_ASSERT_MSG(node, "NamesPriv::Find(): Internal error:  this can't happen");
    return nullptr;
}

Ptr<Object>
NamesPriv::Find(std::string path, std::string name)
{
    NS_LOG_FUNCTION(this << path << name);

    if (path == "/Names")
    {
        return Find(Ptr<Object>(nullptr, false), name);
    }
    return Find(Find(path), name);
}

Ptr<Object>
NamesPriv::Find(Ptr<Object> context, std::string name)
{
    NS_LOG_FUNCTION(this << context << name);

    NameNode* node = nullptr;

    if (!context)
    {
        NS_LOG_LOGIC("Zero context implies root NameNode");
        node = &m_root;
    }
    else
    {
        node = IsNamed(context);
        if (node == nullptr)
        {
            NS_LOG_LOGIC("Context does not point to a previously named node");
            return nullptr;
        }
    }

    auto i = node->m_nameMap.find(name);
    if (i == node->m_nameMap.end())
    {
        NS_LOG_LOGIC("Name does not exist in name map");
        return nullptr;
    }
    else
    {
        NS_LOG_LOGIC("Name exists in name map");
        return i->second->m_object;
    }
}

NameNode*
NamesPriv::IsNamed(Ptr<Object> object)
{
    NS_LOG_FUNCTION(this << object);

    auto i = m_objectMap.find(object);
    if (i == m_objectMap.end())
    {
        NS_LOG_LOGIC("Object does not exist in object map, returning NameNode 0");
        return nullptr;
    }
    else
    {
        NS_LOG_LOGIC("Object exists in object map, returning NameNode " << &i->second);
        return i->second;
    }
}

bool
NamesPriv::IsDuplicateName(NameNode* node, std::string name)
{
    NS_LOG_FUNCTION(this << node << name);

    auto i = node->m_nameMap.find(name);
    if (i == node->m_nameMap.end())
    {
        NS_LOG_LOGIC("Name does not exist in name map");
        return false;
    }
    else
    {
        NS_LOG_LOGIC("Name exists in name map");
        return true;
    }
}

void
Names::Add(std::string name, Ptr<Object> object)
{
    NS_LOG_FUNCTION(name << object);
    bool result = NamesPriv::Get()->Add(name, object);
    NS_ABORT_MSG_UNLESS(result, "Names::Add(): Error adding name " << name);
}

void
Names::Rename(std::string oldpath, std::string newname)
{
    NS_LOG_FUNCTION(oldpath << newname);
    bool result = NamesPriv::Get()->Rename(oldpath, newname);
    NS_ABORT_MSG_UNLESS(result, "Names::Rename(): Error renaming " << oldpath << " to " << newname);
}

void
Names::Add(std::string path, std::string name, Ptr<Object> object)
{
    NS_LOG_FUNCTION(path << name << object);
    bool result = NamesPriv::Get()->Add(path, name, object);
    NS_ABORT_MSG_UNLESS(result, "Names::Add(): Error adding " << path << " " << name);
}

void
Names::Rename(std::string path, std::string oldname, std::string newname)
{
    NS_LOG_FUNCTION(path << oldname << newname);
    bool result = NamesPriv::Get()->Rename(path, oldname, newname);
    NS_ABORT_MSG_UNLESS(result,
                        "Names::Rename (): Error renaming " << path << " " << oldname << " to "
                                                            << newname);
}

void
Names::Add(Ptr<Object> context, std::string name, Ptr<Object> object)
{
    NS_LOG_FUNCTION(context << name << object);
    bool result = NamesPriv::Get()->Add(context, name, object);
    NS_ABORT_MSG_UNLESS(result,
                        "Names::Add(): Error adding name " << name << " under context "
                                                           << &context);
}

void
Names::Rename(Ptr<Object> context, std::string oldname, std::string newname)
{
    NS_LOG_FUNCTION(context << oldname << newname);
    bool result = NamesPriv::Get()->Rename(context, oldname, newname);
    NS_ABORT_MSG_UNLESS(result,
                        "Names::Rename (): Error renaming " << oldname << " to " << newname
                                                            << " under context " << &context);
}

std::string
Names::FindName(Ptr<Object> object)
{
    NS_LOG_FUNCTION(object);
    return NamesPriv::Get()->FindName(object);
}

std::string
Names::FindPath(Ptr<Object> object)
{
    NS_LOG_FUNCTION(object);
    return NamesPriv::Get()->FindPath(object);
}

void
Names::Clear()
{
    NS_LOG_FUNCTION_NOARGS();
    return NamesPriv::Get()->Clear();
}

Ptr<Object>
Names::FindInternal(std::string name)
{
    NS_LOG_FUNCTION(name);
    return NamesPriv::Get()->Find(name);
}

Ptr<Object>
Names::FindInternal(std::string path, std::string name)
{
    NS_LOG_FUNCTION(path << name);
    return NamesPriv::Get()->Find(path, name);
}

Ptr<Object>
Names::FindInternal(Ptr<Object> context, std::string name)
{
    NS_LOG_FUNCTION(context << name);
    return NamesPriv::Get()->Find(context, name);
}

} // namespace ns3
