/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/

#ifndef itkDOMNode_h
#define itkDOMNode_h

#include "itkObject.h"
#include "itkObjectFactory.h"
#include "ITKIOXMLExport.h"

#include <string>
#include <vector>
#include <list>
#include <map>

namespace itk
{

class DOMTextNode; // forward declaration

/**
 * \class DOMNode
 * \brief Class to represent a node in a Document Object Model (DOM) tree structure.
 *
 * A DOM is a structured document representation, in our case parsed from an XML stream (i.e. a file or a string).
 * It is a tree structure in which each node has links to its children nodes and
 * to its parent node (if it is not the root). This class serves as a base node corresponding to an
 * XML tag that only contains attributes (no content for the tag itself).
 *
 * \note We reserve the use of the attribute "id" (all combinations of upper and lower
 *       case) for uniquely identifying an XML structure among its siblings. That is, we assume
 *       that this tag is unique among all siblings in an XML tree structure. If it is not unique,
 *       the user may not be able to correctly retrieve a node by function GetChildByID(), or to search for
 *       a node using Find() with a query string that is based on "id".
 *
 * \ingroup ITKIOXML
 */
class ITKIOXML_EXPORT DOMNode : public Object
{
public:
  ITK_DISALLOW_COPY_AND_MOVE(DOMNode);

  /** Standard class type aliases. */
  using Self = DOMNode;
  using Superclass = Object;
  using Pointer = SmartPointer<Self>;
  using ConstPointer = SmartPointer<const Self>;

  /** Method for creation through the object factory. */
  itkNewMacro(Self);

  /** \see LightObject::GetNameOfClass() */
  itkOverrideGetNameOfClassMacro(DOMNode);

  /** Container to return all or a subset of the children of a DOM node. */
  using ChildrenListType = std::vector<DOMNode *>;
  using ConstChildrenListType = std::vector<const DOMNode *>;

  /** An attribute is a pair of <key,value>, both key and value are strings. */
  using AttributeKeyType = std::string;
  using AttributeValueType = std::string;

  using AttributeItemType = std::pair<const AttributeKeyType, AttributeValueType>;

  /** Container to return the attributes of a DOM node. */
  using AttributesListType = std::list<AttributeItemType>;

  using SizeType = size_t;
  using IdentifierType = int;
  using OffsetType = int;

  /** Retrieve the parent node. */
  virtual void
  SetParent(DOMNode * node);
  DOMNode *
  GetParent();
  const DOMNode *
  GetParent() const;

  /** Retrieve the tag name of this node. */
  itkSetMacro(Name, const std::string &);
  itkGetConstReferenceMacro(Name, std::string);

  /** Retrieve the special attribute "id" of this node. */
  itkSetMacro(ID, const std::string &);
  itkGetConstReferenceMacro(ID, std::string);

  /** Retrieve an attribute by key (return an empty string if not found). */
  virtual std::string
  GetAttribute(const std::string & key) const;
  /** Check whether has an attribute. */
  virtual bool
  HasAttribute(const std::string & key) const;
  /** Add or replace an attribute. */
  virtual void
  SetAttribute(const std::string & key, const std::string & value);
  /** Remove an attribute by key (throw exception if not found). */
  virtual void
  RemoveAttribute(const std::string & key);

  /**
   * Return all attributes, in the order of being parsed or added (default),
   * or alphabetic order of the attribute keys (keepOriginalOrder = false).
   */
  virtual void
  GetAllAttributes(AttributesListType & output, bool keepOriginalOrder = true) const;
  /** Remove all attributes. */
  virtual void
  RemoveAllAttributes();

  /** Get number of children. */
  virtual SizeType
  GetNumberOfChildren() const;

  /** Return all children. */
  virtual void
  GetAllChildren(ChildrenListType & output);
  /** Return all children for read-only access. */
  virtual void
  GetAllChildren(ConstChildrenListType & output) const;

  /** Return all children of a same tag name. */
  virtual void
  GetChildren(const std::string & tag, ChildrenListType & output);
  /** Return all children of a same tag name for read-only access. */
  virtual void
  GetChildren(const std::string & tag, ConstChildrenListType & output) const;

  /** Remove all children. */
  virtual void
  RemoveAllChildren();

  /** Add a child in front of another child (throw exception if not able to add). */
  virtual void
  AddChild(DOMNode * node, IdentifierType i = 0);
  /** Add a child in front of the children list (throw exception if not able to add). */
  virtual void
  AddChildAtBegin(DOMNode * node);
  /** Add a child at the end of the children list (throw exception if not able to add). */
  virtual void
  AddChildAtEnd(DOMNode * node);

  /** Replace a child (throw exception if not able to replace). */
  virtual void
  SetChild(DOMNode * node, IdentifierType i = 0);

  /** Remove a child by index (throw exception if not able to remove). */
  virtual void
  RemoveChild(IdentifierType i = 0);

  /** Remove all attributes and children. */
  virtual void
  RemoveAllAttributesAndChildren();

  /** Retrieve a child by index (return nullptr if i is out of range). */
  virtual DOMNode *
  GetChild(IdentifierType i = 0);
  virtual const DOMNode *
  GetChild(IdentifierType i = 0) const;

  /** Retrieve a child by tag name and an index (multiple children can have a same tag name, return nullptr if no such
   * child). */
  virtual DOMNode *
  GetChild(const std::string & tag, IdentifierType i = 0);
  virtual const DOMNode *
  GetChild(const std::string & tag, IdentifierType i = 0) const;

  /** Retrieve a child by its unique "id" attribute value (return nullptr if not found). */
  virtual DOMNode *
  GetChildByID(const std::string & value);
  virtual const DOMNode *
  GetChildByID(const std::string & value) const;

  /** Retrieve an older or younger sibling by distance (return nullptr if no such sibling). */
  virtual DOMNode *
  GetSibling(OffsetType i);
  virtual const DOMNode *
  GetSibling(OffsetType i) const;

  /** Return the root node. */
  virtual DOMNode *
  GetRoot();
  virtual const DOMNode *
  GetRoot() const;

  /** Test whether the input node and this node share the same root. */
  virtual bool
  ShareRoot(const DOMNode * node) const;

  /**
   * The following function finds a child or sibling or relative using a query string or path.
   * A path or QueryString consists of multiple following items that are separated by '/':
   * \verbatim
   *     -[n]           : an older sibling by distance 1 (when omitted) or n;
   *     +[n]           : a younger sibling by distance 1 (when omitted) or n;
   *     n              : a child at index n;
   *     <tag>[:n]      : a child at index 0 (when omitted) or n after filtering children with a tag name;
   *     ![:n]          : a child at index 0 (when omitted) or n within all text children;
   *     :<id>          : a child by id;
   *     .              : current node;
   *     ..             : parent node;
   *     /<rpath>       : absolute path (denote apath), search from the root.
   * \endverbatim
   *
   * The method returns nullptr if queried node does not exist.
   */
  virtual DOMNode *
  Find(const std::string & path);
  virtual const DOMNode *
  Find(const std::string & path) const;

  /** Return the path of this node within its root, in the form of a query string that uses only indices. */
  virtual std::string
  GetPath() const;

  /** Get a child and cast it to a text node (return nullptr if out of range or not a text node). */
  virtual DOMTextNode *
  GetTextChild(IdentifierType i = 0);
  virtual const DOMTextNode *
  GetTextChild(IdentifierType i = 0) const;

  /** Generate a text node from a string and add/insert it as a child (see AddChild(node,i)). */
  virtual void
  AddTextChild(const std::string & text, IdentifierType i = 0);
  /** Generate a text node from a string and add/insert in front of all children. */
  virtual void
  AddTextChildAtBegin(const std::string & text);
  /** Generate a text node from a string and add/insert at the end of all children. */
  virtual void
  AddTextChildAtEnd(const std::string & text);

  /** Generate a text node from a string and replace a child with it (see SetChild(node,i). */
  virtual void
  SetTextChild(const std::string & text, IdentifierType i = 0);

protected:
  DOMNode();

private:
  /** The parent node that this node was placed into. */
  DOMNode * m_Parent{ nullptr };

  /** The XML tag of this node. */
  std::string m_Name{};

  /** The special attribute "id" of this node. */
  std::string m_ID{};

  /** Internally the children are stored in a vector. */
  using ChildrenContainer = std::vector<Pointer>;
  ChildrenContainer m_Children{};

  /** Internally the attributes are stored in a map. */
  using AttributesContainer = std::map<AttributeKeyType, AttributeValueType>;
  AttributesContainer m_Attributes{};

  /** Container to keep the inserting orders of the attributes. */
  using OrderedAttributesContainer = std::list<AttributeItemType *>;
  OrderedAttributesContainer m_OrderedAttributes{};
};

} // namespace itk

#include "itkDOMTextNode.h"

#include "itkStringTools.h"
#include "itkFancyString.h"

#endif // itkDOMNode_h
