/*
 *
 *  Copyright (C) 2000-2019, OFFIS e.V.
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  This software and supporting documentation were developed by
 *
 *    OFFIS e.V.
 *    R&D Division Health
 *    Escherweg 2
 *    D-26121 Oldenburg, Germany
 *
 *
 *  Module: dcmsr
 *
 *  Author: Joerg Riesmeier
 *
 *  Purpose:
 *    classes: DSRDocumentSubTree
 *
 */


#ifndef DSRDOCST_H
#define DSRDOCST_H

#include "dcmtk/config/osconfig.h"   /* make sure OS specific configuration is included first */

#include "dcmtk/dcmsr/dsrtree.h"
#include "dcmtk/dcmsr/dsrdoctn.h"
#include "dcmtk/dcmsr/dsrdncsr.h"
#include "dcmtk/dcmsr/dsritcsr.h"
#include "dcmtk/dcmsr/dsrcitem.h"

#include "dcmtk/ofstd/ofmem.h"


/*------------------------*
 *  forward declarations  *
 *------------------------*/

class DSRIODConstraintChecker;
class DSRSubTemplate;


/*-------------------*
 *  type definition  *
 *-------------------*/

typedef OFshared_ptr<DSRSubTemplate> DSRSharedSubTemplate;


/*--------------------------*
 *  template instantiation  *
 *--------------------------*/

// the following is required in order to help older Clang compilers,
// e.g. Clang 3.0 and 3.1 on Mac OS X when building with shared libs
#if defined(__clang__)
template class DSRTree<DSRDocumentTreeNode>;
#endif


/*---------------------*
 *  class declaration  *
 *---------------------*/

/** Class managing a SR document subtree.
 *  A subtree represents an extract of an SR document tree.  Compared to the "SR Document
 *  Content Tree" that is defined in the DICOM standard, there are almost no restrictions
 *  regarding the value and relationship types.  It is also possible to have multiple nodes
 *  on the top-level, i.e. no dedicated root, or to use "unknown" relationship types.
 */
class DCMTK_DCMSR_EXPORT DSRDocumentSubTree
  : public DSRTree<DSRDocumentTreeNode>
{

  public:

    /** default constructor
     */
    DSRDocumentSubTree();

    /** copy constructor.
     *  Please note that the internal cursor is not copied but reset, i.e. set to the root
     *  node.  Also the IOD constraint checker is not copied by this class but recreated
     *  by the derived class DSRDocumentTree (based on the corresponding document type).
     *  This constructor also updates any by-reference relationships, i.e. translates the
     *  references from the source 'tree' (based on the position string) to the IDs of the
     *  newly created nodes.
     ** @param  tree  subtree to be copied
     */
    DSRDocumentSubTree(const DSRDocumentSubTree &tree);

    /** destructor
     */
    virtual ~DSRDocumentSubTree();

    /** assignment operator.
     *  Please note that internally the copy constructor is used, so the same comments apply.
     ** @param  tree  subtree to be copied
     ** @return reference to this subtree after copying
     */
    DSRDocumentSubTree &operator=(DSRDocumentSubTree tree);

    /** clone this subtree.
     *  Internally, the copy constructor is used, so the corresponding comments apply.
     ** @return copy of this subtree
     */
    virtual DSRDocumentSubTree *clone() const;

    /** clear internal member variables
     */
    virtual void clear();

    /** check whether the current internal state is valid.
     *  A subtree is valid if it is not empty.
     ** @return OFTrue if valid, OFFalse otherwise
     */
    virtual OFBool isValid() const;

    /** check whether the internal cursor, which points to the current content item, is valid
     ** @return OFTrue if cursor is valid, OFFalse otherwise
     */
    virtual OFBool isCursorValid() const;

    /** check whether this subtree is a valid document tree.
     *  In order to be a valid document tree, there should be a single root node only, with
     *  the value type "CONTAINER", and the internal relationship type of this node should be
     *  DSRTypes::RT_isRoot.
     ** @param  defaultRelType  default relationship type that is used if the one of the
     *                          top-level node (root node) is "unknown"
     ** @return OFTrue if subtree is a valid document tree, OFFalse otherwise
     */
    virtual OFBool isValidDocumentTree(const E_RelationshipType defaultRelType = RT_unknown) const;

    /** check whether this subtree is an expanded document tree.
     *  Expanded means that no instances of DSRIncludedTemplateTreeNode exist in the document
     *  tree, i.e. no templates were included or all of them were replaced by their content
     *  (subtree).
     ** @return OFTrue if subtree is an expanded document tree, OFFalse otherwise
     */
    virtual OFBool isExpandedDocumentTree() const;

    /** check whether template identification is set
     ** @return OFTrue if template identification is set, OFFalse otherwise
     */
    virtual OFBool hasTemplateIdentification() const;

    /** check whether template identification is possible at all.
     *  According to the DICOM standard, it can be used if "the template consists of a single
     *  CONTAINER with nested content, and it is the outermost invocation of a set of nested
     *  templates that start with the same CONTAINER."
     *  With other words, the tree should have a single root node with value type "CONTAINER".
     ** @return OFTrue if template identification is possible, OFFalse otherwise
     */
    virtual OFBool canUseTemplateIdentification() const;

    /** print current SR document tree to specified output stream
     ** @param  stream      output stream
     *  @param  flags       optional flag used to customize the output (see DSRTypes::PF_xxx)
     *  @param  posCounter  optional pointer to position counter that should be used to
     *                      initialize the counter for line indentation or numbering of
     *                      nested content items
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition print(STD_NAMESPACE ostream &stream,
                              const size_t flags = 0,
                              const DSRPositionCounter *posCounter = NULL);

    /** write current SR document tree in XML format
     ** @param  stream  output stream to which the XML document is written
     *  @param  flags   optional flag used to customize the output (see DSRTypes::XF_xxx)
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition writeXML(STD_NAMESPACE ostream &stream,
                                 const size_t flags = 0);

    /** get reference to current content item.
     *  This mechanism allows to access all content items without using pointers.
     ** @return reference to current content item (might be invalid)
     */
    virtual DSRContentItem &getCurrentContentItem();

    /** get read-only access to current node (content item)
     ** @return pointer to current node (might be NULL)
     */
    virtual const DSRDocumentTreeNode *getCurrentNode() const;

    /** get a cursor to the root node of this document tree.
     *  This cursor can be used to iterate over the nodes of the document tree without
     *  changing the internal cursor.  Please note that the cursor might become invalid,
     *  e.g. by pointing to a non-existing node if the content of the document tree is
     *  modified after the cursor has been retrieved.
     *  Included sub-templates are not supported by this type of cursor, i.e. the subtree
     *  that is managed by an instance of DSRIncludedTemplateTreeNode is not iterated.
     ** @param  cursor  reference to variable where the cursor is stored
     ** @return OFTrue is the returned 'cursor' is valid, OFFalse otherwise
     */
    virtual OFBool getCursorToRootNode(DSRDocumentTreeNodeCursor &cursor) const;

    /** get a cursor to the root node of this document tree.
     *  This cursor can be used to iterate over the nodes of the document tree without
     *  changing the internal cursor.  Please note that the cursor might become invalid,
     *  e.g. by pointing to a non-existing node if the content of the document tree is
     *  modified after the cursor has been retrieved.
     *  This type of cursor also supports included sub-templates, i.e. the subtree that
     *  is managed by an instance of DSRIncludedTemplateTreeNode is also iterated.
     ** @param  cursor  reference to variable where the cursor is stored
     ** @return OFTrue is the returned 'cursor' is valid, OFFalse otherwise
     */
    virtual OFBool getCursorToRootNode(DSRIncludedTemplateNodeCursor &cursor) const;

    /** get a cursor to the current node of this document tree.
     *  This cursor can be used to iterate over the nodes of the document subtree that
     *  starts at the current node without changing the internal cursor.  Please note
     *  that the cursor might become invalid, e.g. by pointing to a non-existing node if
     *  the content of the document tree is modified after the cursor has been retrieved.
     *  Included sub-templates are not supported by this type of cursor, i.e. the subtree
     *  that is managed by an instance of DSRIncludedTemplateTreeNode is not iterated.
     ** @param  cursor  reference to variable where the cursor is stored
     ** @return OFTrue is the returned 'cursor' is valid, OFFalse otherwise
     */
    virtual OFBool getCursorToCurrentNode(DSRDocumentTreeNodeCursor &cursor) const;

    /** get a cursor to the subtree below the current node.
     *  This cursor can be used to iterate over the nodes of the selected subtree without
     *  changing the internal cursor.  Please note that the cursor might become invalid,
     *  e.g. by pointing to a non-existing node if the content of the document tree is
     *  modified after the cursor has been retrieved.
     *  Included sub-templates are not supported by this type of cursor, i.e. the subtree
     *  that is managed by an instance of DSRIncludedTemplateTreeNode is not iterated.
     ** @param  cursor  reference to variable where the cursor is stored
     ** @return OFTrue is the returned 'cursor' is valid, OFFalse otherwise
     */
    virtual OFBool getCursorToSubTree(DSRDocumentTreeNodeCursor &cursor) const;

    /** count number of content items (nodes) in the document tree.
     *  This method iterates over all nodes that are stored in the document tree.
     *  By default, included sub-templates are counted as a single node (see options).
     ** @param  searchIntoSubTemplates      optional flag indicating whether to also
     *                                      count the content of included sub-templates
     *                                      (i.e.\ the nodes of the managed subtrees)
     *  @param  countIncludedTemplateNodes  optional flag indicating whether to count
     *                                      the DSRIncludedTemplateTreeNode instances
     *                                      as nodes.  See includeTemplate() for details.
     ** @return number of nodes, 0 if document tree is empty
     */
    size_t countNodes(const OFBool searchIntoSubTemplates = OFFalse,
                      const OFBool countIncludedTemplateNodes = OFTrue) const;

    /** set internal cursor to a matching node.
     *  If more than one node exists matching the given filter, the first one will be
     *  selected.  Use gotoNextMatchingNode() in order to go to the next matching node.
     ** @param  filter         matching criterion based on a single or multiple filters
     *  @param  startFromRoot  flag indicating whether to start from the root node or
     *                         the current one
     *  @param  searchIntoSub  flag indicating whether to search into sub-trees
     *                         ("deep search") or on the current level only
     ** @return ID of the new current node if successful, 0 otherwise
     */
    virtual size_t gotoMatchingNode(const DSRDocumentTreeNodeFilter &filter,
                                    const OFBool startFromRoot = OFTrue,
                                    const OFBool searchIntoSub = OFTrue);

    /** set internal cursor to the next matching node.
     *  Starts from "next" node, i.e. either the first child of the current node or the
     *  first sibling following the current node.
     ** @param  filter         matching criterion based on a single or multiple filters
     *  @param  searchIntoSub  flag indicating whether to search into sub-trees
     *                         ("deep search") or on the current level only
     ** @return ID of the new current node if successful, 0 otherwise
     */
    virtual size_t gotoNextMatchingNode(const DSRDocumentTreeNodeFilter &filter,
                                        const OFBool searchIntoSub = OFTrue);

    /** set internal cursor to a named node.
     *  If more than one node exists with the given concept name, the first one will
     *  be selected.  Use gotoNextNamedNode() in order to go to the next matching node.
     ** @param  conceptName    concept name of the node to be searched for
     *  @param  startFromRoot  flag indicating whether to start from the root node or
     *                         the current one
     *  @param  searchIntoSub  flag indicating whether to search into sub-trees
     *                         ("deep search") or on the current level only
     ** @return ID of the new current node if successful, 0 otherwise
     */
    virtual size_t gotoNamedNode(const DSRCodedEntryValue &conceptName,
                                 const OFBool startFromRoot = OFTrue,
                                 const OFBool searchIntoSub = OFTrue);

    /** set internal cursor to a named node (starting from the first child of the
     *  current node and searching on this level only).
     *  This is just a shortcut for calling gotoChild() followed by gotoNamedNode()
     *  with 'searchIntoSub' being OFFalse, i.e. only the first sub-level is checked.
     *  If more than one node exists with the given concept name, the first one will
     *  be selected.
     ** @param  conceptName  concept name of the node to be searched for
     ** @return ID of the new current node if successful, 0 otherwise
     */
    virtual size_t gotoNamedChildNode(const DSRCodedEntryValue &conceptName);

    /** set internal cursor to a named node in the subtree below the current node.
     *  If more than one node exists with the given concept name, the first one will
     *  be selected.  If the current node has no children, the cursor is not moved.
     ** @param  conceptName    concept name of the node to be searched for
     *  @param  searchIntoSub  flag indicating whether to search into sub-trees
     *                         ("deep search") or on the first sub-level only
     ** @return ID of the new current node if successful, 0 otherwise
     */
    virtual size_t gotoNamedNodeInSubTree(const DSRCodedEntryValue &conceptName,
                                          const OFBool searchIntoSub = OFTrue);

    /** set internal cursor to the next named node.
     *  Starts from "next" node, i.e. either the first child of the current node
     *  or the first sibling following the current node.
     ** @param  conceptName    concept name of the node to be searched for
     *  @param  searchIntoSub  flag indicating whether to search into sub-trees
     *                         ("deep search") or on the current level only
     ** @return ID of the new current node if successful, 0 otherwise
     */
    virtual size_t gotoNextNamedNode(const DSRCodedEntryValue &conceptName,
                                     const OFBool searchIntoSub = OFTrue);

    /** set internal cursor to an annotated node.
     *  If more than one node exists with the given annotation text, the first one will
     *  be selected.  Use gotoNextAnnotatedNode() in order to go to the next matching
     *  node.  In contrast to gotoNamedNode(), a "deep search" is always performed.
     ** @param  annotationText  annotation text of the node to be searched for
     *  @param  startFromRoot   flag indicating whether to start from the root node or
     *                          the current one
     ** @return ID of the new current node if successful, 0 otherwise
     */
    virtual size_t gotoAnnotatedNode(const OFString &annotationText,
                                     const OFBool startFromRoot = OFTrue);

    /** set internal cursor to the next annotated node.
     *  Starts from "next" node, i.e. either the first child of the current node
     *  or the first sibling following the current node.
     ** @param  annotationText  annotation text of the node to be searched for
     ** @return ID of the new current node if successful, 0 otherwise
     */
    virtual size_t gotoNextAnnotatedNode(const OFString &annotationText);

    /** check whether specified content item can be added to the current one.
     *  This method can be used to decide which type of content items can be added prior
     *  to really doing so.  Please note that only by-value relationships are supported.
     *  Always returns true if no constraint checker is available but 'relationshipType'
     *  and 'valueType' have valid values.
     ** @param  relationshipType  relationship type of node to be checked with regard to
     *                            the current one
     *  @param  valueType         value type of node to be checked
     *  @param  addMode           flag specifying at which position the new node would
     *                            be added (e.g. after or below the current node)
     ** @return OFTrue if specified node can be added, OFFalse otherwise
     */
    virtual OFBool canAddContentItem(const E_RelationshipType relationshipType,
                                     const E_ValueType valueType,
                                     const E_AddMode addMode = AM_afterCurrent) const;

    /** check whether specified by-reference relationship can be added to the current
     *  content item.
     *  Always returns true if no constraint checker is available but 'relationshipType' and
     *  'targetValueType' have valid values.  The value type DSRTypes::VT_includedTemplate is
     *  never allowed for the target content item.
     ** @param  relationshipType  type of relationship between current and target node
     *  @param  targetValueType   value type of the referenced node (target content item)
     ** @return OFTrue if specified by-reference relationship can be added, OFFalse otherwise
     */
    virtual OFBool canAddByReferenceRelationship(const E_RelationshipType relationshipType,
                                                 const E_ValueType targetValueType) const;

    /** add specified content item to the current one.
     *  If possible, this method creates a new node as specified and adds it to the current
     *  one.  The method canAddContentItem() is called internally to check parameters first.
     *  If the node could be added successfully, the cursor is set to it automatically.
     ** @param  relationshipType  relationship type of node to be added with regard to
     *                            the current one
     *  @param  valueType         value type of node to be added
     *  @param  addMode           flag specifying at which position to add the new node
     *                            (e.g. after or below the current node)
     ** @return ID of new node if successful, 0 otherwise
     */
    virtual size_t addContentItem(const E_RelationshipType relationshipType,
                                  const E_ValueType valueType,
                                  const E_AddMode addMode = AM_afterCurrent);

    /** add specified content item to the current one.
     *  If possible, this method adds a given new node to the current one.  The method
     *  canAddContentItem() is called internally to check parameters first.  If the
     *  node could be added successfully, the cursor is set to it automatically.
     *  Please note that no copy of the given node is created.  Therefore, the node
     *  has to be created with new() or with DSRTypes::createDocumentTreeNode() - do
     *  not use a reference to a local variable and do not delete it a second time.
     ** @param  node          pointer to the new node to be added (should not be empty).
     *                        Reference remains valid after successful insertion.
     *  @param  addMode       flag specifying at which position to add the new node
     *                        (e.g. after or below the current node)
     *  @param  deleteIfFail  flag specifying whether to delete the given 'node' if
     *                        adding fails.  By default, the item is not deleted, i.e.
     *                        in case of error it has to be deleted by the caller.
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition addContentItem(DSRDocumentTreeNode *node,
                                       const E_AddMode addMode = AM_afterCurrent,
                                       const OFBool deleteIfFail = OFFalse);

    /** add specified content item after the current one.
     *  If possible, this method creates a new node as specified and adds it after the current
     *  one, i.e. on the same level.  Internally, the first variant of the addContentItem()
     *  method is called with the third parameter being DSRTypes::AM_afterCurrent.  If
     *  successful, the given concept name is set for the new node, and the cursor is updated.
     *  @note This is a convenience function that avoids calling several other functions.
     ** @param  relationshipType  relationship type of node to be added with regard to
     *                            the current one
     *  @param  valueType         value type of node to be added
     *  @param  conceptName       concept name of the node to be added
     *  @param  check             if enabled, check 'conceptName for validity before
     *                            setting it
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition addContentItem(const E_RelationshipType relationshipType,
                                       const E_ValueType valueType,
                                       const DSRCodedEntryValue &conceptName,
                                       const OFBool check = OFTrue);

    /** add specified content item below the current one.
     *  If possible, this method creates a new node as specified and adds it below the current
     *  one, i.e. as a child.  Internally, the first variant of the addContentItem() method
     *  is called with the third parameter being DSRTypes::AM_belowCurrent.  If successful,
     *  the given concept name is set for the new node, and the cursor is updated.
     *  @note This is a convenience function that avoids calling several other functions.
     ** @param  relationshipType  relationship type of node to be added with regard to
     *                            the current one
     *  @param  valueType         value type of node to be added
     *  @param  conceptName       concept name of the node to be added
     *  @param  check             if enabled, check 'conceptName for validity before
     *                            setting it
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition addChildContentItem(const E_RelationshipType relationshipType,
                                            const E_ValueType valueType,
                                            const DSRCodedEntryValue &conceptName,
                                            const OFBool check = OFTrue);

    /** add specified by-reference relationship to the current content item.
     *  If possible, this method creates a new pseudo-node (relationship) and adds it to the
     *  current one.  The method canAddByReferenceRelationship() is called internally to check
     *  parameters first.  The internal cursor is automatically re-set to the current node.
     ** @param  relationshipType  relationship type between current and referenced node
     *  @param  referencedNodeID  ID of the referenced node (target content item)
     ** @return ID of new pseudo-node if successful, 0 otherwise
     */
    virtual size_t addByReferenceRelationship(const E_RelationshipType relationshipType,
                                              const size_t referencedNodeID);

    /** update the position strings used to encode by-reference relationships (if any).
     *  Internally, this method calls checkByReferenceRelationships() with the 'mode'
     *  parameter being DSRTypes::CM_updatePositionString.  It should be called before
     *  this subtree is cloned in order to make sure that the by-reference relationships
     *  (if any) still work on the cloned subtree.  This method should also be called
     *  before accessing the position string of a referenced content item, see
     *  DSRByReferenceTreeNode::getReferencedContentItem().
     ** @param  updateIncludedTemplates  optional flag indicating whether to also update
     *                                   the subtrees managed by included sub-templates
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition updateByReferenceRelationships(const OFBool updateIncludedTemplates = OFFalse);

    /** check whether specified subtree can be inserted at the current position, i.e.\ added
     *  to the current content item.  Internally, the method canAddContentItem() is used for
     *  all top-level nodes of the document subtree.  In addition, if a constraint checker
     *  is available, the remaining nodes of the given subtree are also checked for their
     *  compliance with the relationship content constraints of the underlying SR IOD.
     ** @param  tree            pointer to new subtree to be inserted (should not be empty)
     *  @param  addMode         flag specifying at which position the new subtree would
     *                          be added (e.g. after or below the current node)
     *  @param  defaultRelType  default relationship type between the top-level nodes of
     *                          the given subtree and the current node.  This relationship
     *                          type is used if the one of a top-level node is "unknown".
     ** @return OFTrue if specified subtree can be inserted, OFFalse otherwise
     */
    virtual OFBool canInsertSubTree(const DSRDocumentSubTree *tree,
                                    const E_AddMode addMode = AM_belowCurrent,
                                    const E_RelationshipType defaultRelType = RT_unknown) const;

    /** insert specified subtree to this tree, i.e.\ add it to the current content item.
     *  If possible, this method adds a given new subtree to the current content item.
     *  The method canInsertSubTree() is called internally to check the parameters first.
     *  Please note that no copy of the given subtree is created.  Therefore, the subtree
     *  has to be created with new() or with cloneSubTree() - do not use a reference to a
     *  local variable and do not delete it a second time.
     ** @param  tree            pointer to new subtree to be inserted (should not be empty).
     *                          Reference becomes invalid after successful insertion!
     *  @param  addMode         flag specifying at which position to add the new subtree
     *                          (e.g. after or below the current node)
     *  @param  defaultRelType  default relationship type between the top-level nodes of
     *                          the given subtree and the current node.  This relationship
     *                          type is used if the one of a top-level node is "unknown".
     *  @param  deleteIfFail    flag specifying whether to delete the given 'tree' if
     *                          adding fails.  By default, the tree is not deleted, i.e.
     *                          in case of error it has to be deleted by the caller.
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition insertSubTree(DSRDocumentSubTree *tree,
                                      const E_AddMode addMode = AM_belowCurrent,
                                      const E_RelationshipType defaultRelType = RT_unknown,
                                      const OFBool deleteIfFail = OFFalse);

    /** extract a subtree, i.e.\ a fragment from this tree.
     *  The subtree is specified by the current node, which becomes the root of the subtree.
     *  In contrast to cloneSubTree(), this method also makes sure that the by-reference
     *  relationships are preserved (as long as both source and target node are contained
     *  in the same tree).  Please note that the returned subtree has to be deleted by the
     *  caller if it is not inserted into the document tree using insertSubTree().
     ** @return pointer to the extracted subtree, NULL in case of error
     */
    virtual DSRDocumentSubTree *extractSubTree();

    /** remove current content item from tree.
     *  Please note that not only the specified node but also all of its child nodes are
     *  removed from the tree and then deleted.  The internal cursor is set automatically
     *  to a new valid position.
     ** @return ID of the node which became the current one after deletion, 0 if an error
     *    occurred or the tree is now empty.
     */
    virtual size_t removeCurrentContentItem();

    /** remove a subtree from this tree.
     *  The subtree to be removed (and deleted) is either specified by the current node or by
     *  the node with the given ID.  Afterwards, the internal cursor is set automatically to
     *  a new valid position.  It would be an error to remove a subtree from an empty tree.
     ** @param  searchID  ID of the root node specifying the subtree to be removed.
     *                    By default (0), the current node is used.
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition removeSubTree(const size_t searchID = 0);

    /** clone the current tree node.
     *  Internally, the copy constructor of the respective tree node class is used, so the
     *  corresponding comments apply.  Please note that the returned tree node has to be
     *  deleted by the caller if it is not added to the document tree using addContentItem().
     ** @return pointer to a copy of the current tree node, NULL in case of error
     */
    virtual DSRDocumentTreeNode *cloneCurrentTreeNode() const;

    /** clone a subtree, i.e.\ a fragment of this tree.
     *  The cloning starts with the current node and ends with the given node.
     *  Please note that the returned subtree has to be deleted by the caller if it is not
     *  inserted into the document tree using insertSubTree().
     ** @param  stopAfterNodeID  ID of the node after which the cloning should stop.
     *                           By default (0), the process ends after cloning the
     *                           current node with all of its child nodes (if any).
     ** @return pointer to a copy of the specified subtree, NULL in case of error
     */
    virtual DSRDocumentSubTree *cloneSubTree(const size_t stopAfterNodeID = 0) const;

    /** created an expanded version of this (sub)tree.
     *  Expanded means that no instance of DSRIncludedTemplateTreeNode will exist in the new
     *  document tree, i.e. all of them are replaced by their content (subtree).  Please note
     *  that the returned subtree has to be deleted by the caller if it is not inserted into
     *  the document tree using insertSubTree().
     ** @param  tree  variable that will store the pointer to the new subtree
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition createExpandedSubTree(DSRDocumentSubTree *&tree) const;

    /** set observation date/time for all content items in the document tree.
     *  Calling this method might be useful when a (sub)tree is copied into a new document,
     *  since content date and time of the new document are likely to be different than the
     *  date and time of the original observation.
     ** @param  observationDateTime  value to be set (might be an empty string)
     *  @param  check                check 'observationDateTime' for conformance with VR (DT)
     *                               and VM (1) if enabled
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition setObservationDateTime(const OFString &observationDateTime,
                                               const OFBool check = OFTrue);

    /** compare template identification of the root node with given values.
     *  Please note that the comparison only takes place if there is a single node at the
     *  root of the tree and its value type is CONTAINER.
     ** @param  templateIdentifier  template identifier to compare with
     *  @param  mappingResource     mapping resource that defines the template
     *  @param  mappingResourceUID  uniquely identifies the mapping resource (optional).
     *                              Not used for comparison if the value is empty.
     ** @result OFTrue if template identification can be compared and the values are
     *          identical, OFFalse otherwise
     */
    virtual OFBool compareTemplateIdentification(const OFString &templateIdentifier,
                                                 const OFString &mappingResource,
                                                 const OFString &mappingResourceUID = "") const;

    /** get template identifier and mapping resource from the root node of this tree.
     *  See DSRDocumentTreeNode::getTemplateIdentification() for details on template
     *  identification.
     *  Please note that the template identification is only retrieved if there is a single
     *  node at the root of the tree and its value type is CONTAINER.
     ** @param  templateIdentifier  identifier of the template (might be empty)
     *  @param  mappingResource     mapping resource that defines the template (might be empty)
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition getTemplateIdentification(OFString &templateIdentifier,
                                                  OFString &mappingResource) const;

    /** get template identifier, mapping resource and optional mapping resource UID from the
     *  root node of this tree.  See DSRDocumentTreeNode::getTemplateIdentification() for
     *  details on template identification.
     *  Please note that the template identification is only retrieved if there is a single
     *  node at the root of the tree and its value type is CONTAINER.
     ** @param  templateIdentifier  identifier of the template (might be empty)
     *  @param  mappingResource     mapping resource that defines the template (might be empty)
     *  @param  mappingResourceUID  uniquely identifies the mapping resource (might be empty)
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition getTemplateIdentification(OFString &templateIdentifier,
                                                  OFString &mappingResource,
                                                  OFString &mappingResourceUID) const;

    /** set template identifier and mapping resource to the root node of this tree.
     *  The identification is valid if the first two values are either present (non-empty) or
     *  all three values are absent (empty).  See DSRDocumentTreeNode::getTemplateIdentification()
     *  for details.
     *  Please note that the template identification is only set if there is a single node at
     *  the root of the tree and its value type is CONTAINER.
     ** @param  templateIdentifier  identifier of the template to be set
     *  @param  mappingResource     mapping resource that defines the template
     *  @param  mappingResourceUID  uniquely identifies the mapping resource (optional)
     *  @param  check               check 'templateIdentifier', 'mappingResource' and
     *                              'mappingResourceUID' for conformance with VR (CS,UI) and
     *                              VM (1) if enabled
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition setTemplateIdentification(const OFString &templateIdentifier,
                                                  const OFString &mappingResource,
                                                  const OFString &mappingResourceUID = "",
                                                  const OFBool check = OFTrue);


  protected:

    /** special constructor that receives a pointer to the root node.
     *  Please note that the 'rootNode' and the associated tree is not copied!
     ** @param  rootNode  pointer to the root node of the "new" tree
     */
    DSRDocumentSubTree(DSRDocumentTreeNode *rootNode);

    /** special copy constructor that clones a particular subtree only
     ** @param  startCursor      cursor pointing to first node of the subtree to be copied
     *  @param  stopAfterNodeID  ID of the node after which the cloning should stop
     */
    DSRDocumentSubTree(const DSRDocumentTreeNodeCursor &startCursor,
                       size_t stopAfterNodeID);

    /** fast, non-throwing swap function.
     *  The time complexity of this function is constant.
     ** @param  tree  subtree to swap with
     */
    void swap(DSRDocumentSubTree &tree);

    /** get pointer to current node.
     *  Hide this inherited method from the public interface.
     ** @return pointer to current node (might be NULL)
     */
    virtual DSRDocumentTreeNode *getNode() const;

    /** add new node to the current one.
     *  Please note that no copy of the given node is created.  Therefore, the node
     *  has to be created with new() - do not use a reference to a local variable.
     *  If the node could be added successfully, the cursor is set to it automatically.
     ** @param  node     pointer to the new node to be added
     *  @param  addMode  flag specifying at which position to add the new node
     *                   (e.g. after or below the current node)
     ** @return ID of the new node if successful, 0 otherwise
     */
    virtual size_t addNode(DSRDocumentTreeNode *node,
                           const E_AddMode addMode = AM_afterCurrent);

    /** replace current node by the given one.
     *  Please note that no copy of the given node is created.  Therefore, the node
     *  should be created with new() - do not use a reference to a local variable.  If
     *  the node could be replaced successfully, the "old" node (and all of its child
     *  nodes) are deleted, and the cursor is set to the new one.
     ** @param  node  pointer to the new node to replace the current one
     ** @return ID of the new node if successful, 0 otherwise
     */
    virtual size_t replaceNode(DSRDocumentTreeNode *node);

    /** extract current node from tree.
     *  Please note that not only the specified node but also all of its child nodes are
     *  extracted from the tree.  The cursor is set automatically to a new valid position.
     ** @return pointer to extracted node, NULL in case of error (or the tree was empty)
     */
    virtual DSRDocumentTreeNode *extractNode();

    /** get pointer to root node and "forget" the internal reference to this node.
     *  In other words: after calling this method, the stored tree will be empty.
     *  This also means that the caller is responsible for deleting the allocated memory.
     ** @return pointer to root node, might be NULL (empty tree)
     */
    virtual DSRDocumentTreeNode *getAndRemoveRootNode();

    /** remove current node from tree.
     *  Please note that not only the specified node but also all of his child nodes are
     *  removed from the tree and deleted afterwards.  The cursor is set automatically to
     *  a new valid position.
     ** @return ID of the node which became the current one after deletion, 0 if an error
     *          occurred or the tree is now empty.
     */
    virtual size_t removeNode();

    /** include specified sub-template, i.e.\ add a new DSRIncludedTemplateTreeNode, which
     *  references this template, to the current content item.
     *  Please note that no checks are performed that would make sure that the template
     *  with its top-level nodes can actually be added, e.g. by using an IOD constraint
     *  checker.  This is also the reason why this method is "protected" and not "public",
     *  i.e. it can only be used by derived classes, e.g. DSRSubTemplate and its children.
     ** @param  subTemplate     shared pointer to a sub-template that should be included
     *                          into this tree (at the current position)
     *  @param  addMode         flag specifying at which position to add the 'subTemplate'
     *                          (e.g. after or below the current node)
     *  @param  defaultRelType  default relationship type that will be used when the
     *                          subtree that is managed by the 'subTemplate' is inserted
     *                          into this tree and the relationship type of one of the
     *                          top-level nodes is "unknown".  Also see documentation of
     *                          createExpandedSubTree().
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition includeTemplate(const DSRSharedSubTemplate &subTemplate,
                                        const E_AddMode addMode = AM_belowCurrent,
                                        const E_RelationshipType defaultRelType = RT_unknown);

    /** expand all "included template" content items in a given (sub)tree.
     *  Expanding means that all instances of DSRIncludedTemplateTreeNode are replaced by
     *  their content (subtree).
     *  Please note that the internal cursor of the given 'tree' is set to the root node
     *  if no error occurred.  Otherwise, the cursor points to the content item that
     *  caused the problem.
     ** @param  tree  pointer to the subtree that should be expanded
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition expandIncludedTemplates(DSRDocumentSubTree *tree) const;

    /** check the by-reference relationships (if any) for validity.
     *  This function checks whether all by-reference relationships possibly contained
     *  in the document tree are valid according to the following restrictions: source
     *  and target node are not identical and the target node is not an ancestor of the
     *  source node (requirement from the DICOM standard to prevent loops -> directed
     *  acyclic graph, though this is not 100% true - see "reportlp.dcm" example).
     *  In addition, the position strings (used to encode by-reference relationships
     *  according to the DICOM standard) OR the node IDs (used internally to uniquely
     *  identify nodes) can be updated.
     *  Please note that the checking modes DSRTypes::CM_updatePositionString and
     *  DSRTypes::CM_updateNodeID are mutually exclusive.
     ** @tparam  T_Cursor  template type used for the cursor iterating the document (sub)tree
     ** @param   mode      mode used to customize the checking process (see DSRTypes::CM_xxx)
     *  @param   flags     flag used to customize the reading (see DSRTypes::RF_xxx) and/or
     *                     printing process (see DSRTypes::PF_xxx).  Conflicting definitions
     *                     are avoided using the appropriate bit mask (see DSRTypes::CB_xxx).
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    template <typename T_Cursor>
    OFCondition checkByReferenceRelationships(const size_t mode = 0,
                                              const size_t flags = 0);

    /** reset flag for all content items whether they are target of a by-reference relationship.
     *  This function calls 'setReferenceTarget(OFFalse)' for all content items in the tree.
     */
    virtual void resetReferenceTargetFlag();

    /** update the tree for subsequent output, e.g.\ for being printed or added to an SR
     *  document.  By default, this virtual function does nothing but is called automatically
     *  by the affected output methods and should be overwritten in derived classes.
     */
    virtual void updateTreeForOutput();

    /** check whether the given subtree complies with the constraints of the given checker
     ** @param  tree     pointer to subtree that should be checked
     *  @param  checker  pointer to relationship content constraints checker to be used.
     *                   If NULL, no checks are performed by this method.
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition checkSubTreeConstraints(const DSRDocumentSubTree *tree,
                                                const DSRIODConstraintChecker *checker) const;


    // --- static helper function ---

    /** check whether a by-reference relationship from a given source to a target node
     *  (specified by the position strings) is valid, i.e.\ is allowed according to the
     *  DICOM standard.  This check should prevent loops (directed acyclic graphs).
     ** @param sourcePosition position string of the source content item to be checked
     *  @param targetPosition position string of the target content item to be checked
     ** @return OFTrue if by-reference relationship is allowed, OFFalse otherwise
     */
    virtual OFBool validByReferenceRelationship(const OFString &sourcePosition,
                                                const OFString &targetPosition);

    /** clone a particular subtree, i.e.\ a fragment of a tree
     ** @param  startCursor      cursor pointing to first node of the subtree to be copied
     *  @param  stopAfterNodeID  ID of the node after which the cloning should stop
     ** @return pointer to a copy of the specified subtree, NULL in case of error
     */
    static DSRDocumentSubTree *cloneSubTree(const DSRDocumentTreeNodeCursor &startCursor,
                                            const size_t stopAfterNodeID = 0);

    /// check relationship content constraints of the associated IOD
    DSRIODConstraintChecker *ConstraintChecker;


  private:

    /// current content item.  Introduced to avoid the external use of pointers.
    DSRContentItem CurrentContentItem;
};


#endif
