/*
 *
 *  Copyright (C) 2008-2024, 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:  dcmdata
 *
 *  Author:  Michael Onken
 *
 *  Purpose: Class declarations for accessing DICOM dataset structures (items,
 *           sequences and leaf elements via string-based path access.
 *
 */

#ifndef DCPATH_H
#define DCPATH_H

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

#include "dcmtk/dcmdata/dcdatset.h"

/** Class representing a node in DcmPath. A node contains just
 * a pointer to a DcmObject (e.g. a sequence or an item). Additionally
 * an item number is stored which also makes sense in case that the
 * object pointed to is an item. The item number is necessary because
 * when having only a pointer to a DcmItem there is no way to find out
 * at which position the item actually has in the surrounding sequence.
 */
class DCMTK_DCMDATA_EXPORT DcmPathNode
{

public:
    /** Constructor. Creates empty path node.
     */
    DcmPathNode()
        : m_obj(NULL)
        , m_itemNo(0)
    {
    }

    /** Constructor. Creates search node from object pointer and item number.
     *  @param obj [in] The object the search node points to. The memory of the
     *             given DICOM object is not handled by the node itself but
     *             must be handled (i.e. freed) from outside.
     *  @param itemNo [in] The item number that should be set. Only relevant
     *                if obj parameter contains an item
     */
    DcmPathNode(DcmObject* obj, Uint32 itemNo)
        : m_obj(obj)
        , m_itemNo(itemNo)
    {
    }

    /** Destructor. Nothing to do, the DICOM object is not freed automatically!
     */
    ~DcmPathNode()
    {
    }

    /// Pointer to object this search node points to
    DcmObject* m_obj;

    /// The item number of the item in m_obj; only useful if m_obj is an item
    Uint32 m_itemNo;

private:
    /** Private undefined copy constructor
     *  @param rhs Object to copy from
     */
    DcmPathNode(const DcmPathNode& rhs);

    /** Private undefined assignment operator
     *  @param arg Object to copy from
     *  @return Reference to this object
     */
    DcmPathNode& operator=(const DcmPathNode& arg);
};

/** Class representing a path of DICOM objects. A path is a "way" through
 *  a DICOM dataset, i.e. it starts e.g. with an item, followed by a sequence
 *  originally contained in this item. The sequence then could be followed by
 *  another item which is part of the sequence and so on. Thus the path
 *  should be well-formed, but if it is created manually (e.g. using append())
 *  there is no corresponding checking.
 */
class DCMTK_DCMDATA_EXPORT DcmPath
{

public:
    /** Constructor, creates an empty search result
     */
    DcmPath();

    /** Constructor, creates a search result from a list of search nodes.
     *  @param currentPath [in] The list of search nodes representing a
     *                     "path" through a DICOM dataset
     */
    DcmPath(const OFList<DcmPathNode*>& currentPath);

    /** Appends a search node at the end of the search result path. It is not
     *  checked whether the resulting path is still valid.
     *  @param node [in] The node to append.
     *
     */
    void append(DcmPathNode* node);

    /** Removes last path node from path. Also frees memory of path node
     *  but does _not_ free memory of the underlying DICOM item or element.
     *  @return none
     */
    void deleteBackNode();

    /** Returns iterator pointing to first path component.
     *  @return Iterator to first path node.
     */
    OFListIterator(DcmPathNode*) begin();

    /** Returns last path component.
     *  @return The last path component, NULL if none exists.
     */
    DcmPathNode* back();

    /** Returns iterator pointing to last path component.
     *  @return Iterator to last path node.
     */
    OFListIterator(DcmPathNode*) end();

    /** Returns number of path components.
     *  @return The number of path components
     */
    Uint32 size() const;

    /** Returns whether path is empty, ie does not contain any path nodes
     *  @return OFTrue, if path is empty, OFFalse otherwise
     */
    OFBool empty() const;

    /** Returns a string representation of the path,
     *  e.g.\ "SourceImageSequence[0].ReferencedSOPInstanceUID"
     *  @return String representing path
     */
    OFString toString() const;

    /** Returns whether the path contains tags of a given group.
     *  Might be useful for looking after (unwanted) meta header tags etc.
     *  @param groupNo [in] The group number to look for
     *  @return OFTrue if group number is found in path, OFFalse otherwise
     */
    OFBool containsGroup(const Uint16 groupNo) const;

    /** Returns whether the path contains tags with invalid group numbers
     *  (groups 1, 3, 5, 7 and 0xFFFF are illegal in DICOM).
     *  @return OFTrue if illegal group number is found in path, OFFalse otherwise
     */
    OFBool containsInvalidGroup() const;

    /** Returns a string representation of each path node separately.
     *  Tags are represented as numbers surrounded by braces "(gggg,eeee)",
     *  not dictionary names. Items are represented by a number or wildcard
     *  in square brackets, eg. "[12]" or "[*]".
     *  @param path The path to parse into different nodes
     *  @param result [out] List containing the resulting strings
     *  @return none
     */
    static OFCondition separatePathNodes(const OFString& path, OFList<OFString>& result);

    /** Helper function for findOrCreatePath(). Parses an item number from
     *  the beginning of the path string. The item number must be positive,
     *  starting with 0.
     *  The path must start like "[itemnumber]...".
     *  @param path [in/out] The path starting with the item number in square
     *              brackets, e.g.\ "[3]". The parsed item number and a
     *              potentially following "." are removed from the path
     *  @param itemNo [out] The parsed item number. If a wildcard was parsed,
     *                this output parameter is not set at all.
     *  @param wasWildcard [out] Is set to OFTrue, if wildcard was parsed
     *                     (instead of concrete item number).
     *  @return EC_Normal, if concrete item number or wildcard was parsed
     */
    static OFCondition parseItemNoFromPath(OFString& path,       // inout
                                           Uint32& itemNo,       // out
                                           OFBool& wasWildcard); // out

    /** Function that parses a tag from the beginning of a path string.
     *  The tag has to be either in numeric format, e.g. "(0010,0010)" or
     *  a dictionary name, e.g. "PatientName". If successful, the
     *  parsed tag is removed from the path string.
     *  @param path [in/out] The path string, starting with the attribute
     *              to parse
     *  @param tag [out] The tag parsed
     *  @return EC_Normal if successful, error code otherwise
     */
    static OFCondition parseTagFromPath(OFString& path, // inout
                                        DcmTag& tag);   // out

    /** Destructor, cleans up memory of path nodes. Does not delete
     *  the DcmObjects the nodes point to (this is also not done
     *  by the destructor of the path nodes, so the caller is responsible
     *  for freeing any related DICOM objects.
     */
    ~DcmPath();

private:
    /// Internal list representing the nodes in the path.
    OFList<DcmPathNode*> m_path;

    /** Private undefined copy constructor
     *  @param rhs Path to copy from
     */
    DcmPath(const DcmPath& rhs);

    /** Private undefined assignment operator
     *  @param arg Path to copy from
     *  @return Reference to this object
     */
    DcmPath& operator=(const DcmPath& arg);
};

/// Class that operates on DICOM datasets using a (string) path syntax.
class DCMTK_DCMDATA_EXPORT DcmPathProcessor
{

public:
    /** Constructor, creates an empty search object.
     */
    DcmPathProcessor();

    /** Sets whether searching/creating paths will support wildcard for
     *  items. If set to false, any path operation with item wildcard characters
     *  will return an error.
     *  @param supported [in] If true, wildcard are enabled (default)
     *                   If false, item wildcard support is disabled.
     */
    void setItemWildcardSupport(const OFBool supported);

    /** Enables (class default: enabled) or disables checking of private
     *  reservations when inserting private tags. If enabled and a private
     *  tag is created that has no private reservation, an error is returned.
     *  If disabled, it is possible to insert private tags that do not have
     *  a reservation in the corresponding item/dataset.
     *  @param doChecking [in] OFTrue enables reservation checking,
     *                    OFFalse disables it.
     */
    void checkPrivateReservations(const OFBool doChecking);

    /** Returns private creator identifier for given tag
     *  @param item   [in] The item to search in
     *  @param tagKey [in] The tag key for which a reservation should be looked up
     *  @param privateCreator [out] The private creator identifier
     *  @return Return EC_Normal if reservation was found (can be empty, though),
     *          EC_TagNotFound if no private creator tag exists, error otherwise
     */
    static OFCondition getPrivateCreator(DcmItem* item, const DcmTagKey& tagKey, OFString& privateCreator);

    /** Checks in item, whether a private reservation for a given
     *  tag key exists.
     *  @param item   [in] The item to search in
     *  @param tagKey [in/out] The tag to be checked.
     *  @param privateCreator [in] The private creator identifier to check for
     *                        (if known, can be left empty)
     *  @return Return EC_Normal if reservation checking was successful.
     *          Otherwise an error code is returned.
     */
    static OFCondition
    checkPrivateTagReservation(DcmItem* item, const DcmTagKey& tagKey, const OFString& privateCreator = "");

    /** Checks in item, whether a private reservation for a given
     *  tag key. If so, a dictionary lookup is performed and the VR and private
     *  creator of the tag is updated correspondingly.
     *  @param item [in] The item to search in
     *  @param tag [in/out] The tag to be checked. Will be updated with VR and
     *                      private creator.
     *  @param privateCreator [in] The private creator identifier to check for
     *                        (if known, can be left empty)
     *  @return Return EC_Normal if reservation checking and updating the
     *          tag was successful. Otherwise an error code is returned.
     */
    static OFCondition checkPrivateTagReservation(DcmItem* item, DcmTag& tag, const OFString& privateCreator = "");

    /** Function that allows for finding and/or inserting a hierarchy of items
     *  and attributes as defined by a path string; also returns a list of
     *  pointers for each successfully found or inserted paths. Every list
     *  contains pointers pointing to all the objects along the path
     *  starting from the object given as parameter. The pointer to the
     *  starting object will not be part of the result.
     *
     *  In principle, the path string must have the following format (in
     *  arbitrary depth). Note that for searching a sequence, the example
     *  below would start with [ITEMNO] instead:
     *  SEQUENCE[ITEMNO].SEQUENCE[ITEMNO].ATTRIBUTE
     *  . ITEMNO must be a positive integer starting with 0.
     *  SEQUENCE and ATTRIBUTE must be a tag, written e.g.
     *  "(0010,0010)" or as a dictionary name, e.g. "PatientName". If the
     *  path cannot be fully created (see option createIfNecessary), any
     *  possibly object changes are reverted. So a path is either fully created
     *  or no path component is created at all. The result can be obtained
     *  by calling the getResults() function.
     *
     *  Example: The path
     *  "ContentSequence[4].(0040,a043)[0].CodeValue" selects the Content
     *  Sequence in the given object, therein the 5th item, therein the "Concept
     *  Name Code Sequence" denoted by (0040,a043), therein the first item
     *  and finally therein the tag "Code Value".
     *  The resulting object list should (if success is returned) contain
     *  1 result, consisting of a list with 5 pointers to 5 objects in the order
     *  in their logical order as they occur in the path string
     *  (in total 2 sequences, 2 items, and one leaf attribute).
     *  @param obj [in] The object to search (or create) a path in
     *  @param path [in] The path either starting with an attribute (either a
     *              sequence or a leaf attribute as a dictionary name or tag) or
     *              starting with an item
     *  @param createIfNecessary [in] If set, all missing objects found
     *                           in the path string are created. If not set,
     *                           only existing paths can be accessed and
     *                           no new attribute or item is created.
     *  @return EC_Normal if successful, error code otherwise.
     */
    OFCondition findOrCreatePath(DcmObject* obj, const OFString& path, OFBool createIfNecessary = OFFalse);

    /** Function that allows for deleting elements and items from
     *  a DICOM object tree.
     *  In principle, the path string must have the following format (in
     *  arbitrary depth). Note that for searching in a sequence, the example
     *  below would start with [ITEMNO] instead:
     *  SEQUENCE[ITEMNO].SEQUENCE[ITEMNO].ATTRIBUTE
     *  . ITEMNO must be a positive integer starting with 0.
     *  SEQUENCE and ATTRIBUTE must be a tag, written e.g.
     *  "(0010,0010)" or as a dictionary name, e.g. "PatientName".
     *
     *  Example: The path
     *  "ContentSequence[4].(0040,a043)[0].CodeValue" selects the Content
     *  Sequence in the given object, therein the 5th item, therein the "Concept
     *  Name Code Sequence" denoted by (0040,a043), therein the first item
     *  and finally therein the tag "Code Value". Only "Code Value" will be
     *  deleted by the function.
     *  @param obj [in] The object to delete attribute or item from
     *  @param path [in] The path either starting with an attribute (either a
     *              sequence or a leaf attribute as a dictionary name or tag) or
     *              starting with an item
     *  @param numDeleted [out] Number of deleted attributes/items
     *  @return EC_Normal if successful, error code otherwise. If the desired
     *  attribute/item was not found, EC_TagNotFound is returned.
     */
    OFCondition findOrDeletePath(DcmObject* obj, const OFString& path, Uint32& numDeleted);

    /** Returns the results from the search / creation call.
     *  @param searchResults [out] The resulting paths that were created/searched
     *                       Note that the memory of the search results is freed
     *                       automatically by the destructor and must not be freed
     *                       by the caller.
     *  @return Number of results returned
     */
    Uint32 getResults(OFList<DcmPath*>& searchResults);

    /** Helper function that applies a specified "override key" in path syntax
     *  to the given dataset. The name "override" indicates that these keys have
     *  higher precedence than identical keys in a request dataset that might
     *  possibly read from a DICOM query file.
     *  @param dataset [in/out] the dataset (e.g.\ query keys) the override key is
     *                 applied to. Must be non-NULL.
     *  @param overrideKey [in] the override key in path syntax (see class DcmPath).
     *                     Also the path can end with a value assignment, e.g.
     *                     "PatientName=Doe^John". An empty (or missing value) will
     *                     not be ignored but will be written as empty to the
     *                     attribute (if not a sequence or item).
     *  @return EC_Normal if adding was successful, error code otherwise
     */
    OFCondition applyPathWithValue(DcmDataset* dataset, const OFString& overrideKey);

    /** Destructor, cleans up memory that was allocated for any search results.
     */
    ~DcmPathProcessor();

protected:
    /** Function that allows for finding and/or inserting a hierarchy of items
     *  and attributes as defined by a path string; also returns a list of
     *  pointers for each successfully found or inserted paths. Every list
     *  contains pointers pointing to all the objects along the path
     *  starting from the object given as parameter. The pointer to the
     *  starting object will not be part of the result.
     *
     *  In principle, the path string must have the following format (in
     *  arbitrary depth).
     *  SEQUENCE[ITEMNO].SEQUENCE[ITEMNO].ATTRIBUTE
     *  . ITEMNO must be a positive integer starting with 0.
     *  SEQUENCE and ATTRIBUTE must be a tag, written e.g.
     *  "(0010,0010)" or as a dictionary name, e.g. "PatientName". If the
     *  path cannot be fully created (see option createIfNecessary), any
     *  possibly object changes are reverted. So a path is either fully created
     *  or no path component is created at all. The result can be obtained
     *  by calling the getResults() function.
     *
     *  Example: The path
     *  "ContentSequence[4].(0040,a043)[0].CodeValue" selects the Content
     *  Sequence in the given item, therein the 5th item, therein the "Concept
     *  Name Code Sequence" denoted by (0040,a043), therein the first item
     *  and finally therein the tag "Code Value".
     *  The resulting object list should (if success is returned) contain
     *  1 result, consisting of a list with 5 pointers to 5 objects in the order
     *  in their logical order as they occur in the path string
     *  (in total 2 sequences, 2 items, and one leaf attribute).
     *  @param item [in] The object to search (or create) a path in
     *  @param path [in] The path starting with an attribute (either a
     *              sequence or a leaf attribute) as a dictionary name or tag.
     *              The parsed attribute is removed from the path string.
     *  @return EC_Normal if successful, error code otherwise.
     */
    OFCondition findOrCreateItemPath(DcmItem* item, OFString& path);

    /** Function that allows for finding and/or inserting a hierarchy of items
     *  and attributes as defined by a path string; also returns a list of
     *  pointers for each successfully found or inserted paths. Every list
     *  contains pointers pointing to all the objects along the path
     *  starting from the object given as parameter. The pointer to the
     *  starting object will not be part of the result.
     *
     *  In principle, the path string must have the following format (in
     *  arbitrary depth).
     *  [ITEMNO].SEQUENCE[ITEMNO].ATTRIBUTE
     *  . ITEMNO must be a positive integer starting with 0.
     *  SEQUENCE and ATTRIBUTE must be a tag, written e.g.
     *  "(0010,0010)" or as a dictionary name, e.g. "PatientName". If the
     *  path cannot be fully created (see option createIfNecessary), any
     *  possibly object changes are reverted. So a path is either fully created
     *  or no path component is created at all. The result can be obtained
     *  by calling the getResults() function.
     *
     *  Example: The path
     *  "[4].(0040,a043)[0].CodeValue" selects the 5th item in the given
     *  sequence, therein the "Concept Name Code Sequence" denoted by
     *  (0040,a043), therein the first item and finally therein the
     *  tag "Code Value".
     *  The resulting object list should (if success is returned) contain
     *  1 result, consisting of a list with 4 pointers to 4 objects in the order
     *  in their logical order as they occur in the path string
     *  (in total 1 sequence, 2 items, and one leaf element).
     *  @param seq  [in] The object to search (or create) a path in
     *  @param path [in] The path starting with an item. The parsed item number
     *  (e.g. "[0]") is removed from the path string.
     *  @return EC_Normal if successful, error code otherwise.
     */
    OFCondition findOrCreateSequencePath(DcmSequenceOfItems* seq, OFString& path);

    /** Helper function that looks at the last node in a given path and deletes
     *  the corresponding DICOM object. Does not delete the path node itself:
     *  That is done by the calling function, findOrCreateItemPath().
     *  @param objSearchedIn [in/out] The object the given path starts in.
     *  @param path [in/out] The complete path to the DICOM object to delete
     *  @param toDelete [in/out] The path node to delete. This node must be
     *                  identical to the last node in the path parameter. Also
     *                  the node must represent a DICOM sequence or leaf element,
     *                  not an item. However, because it is isolated already by
     *                  the calling function, it is provided here for convenience.
     *  @return EC_Normal if successful, error otherwise
     */
    static OFCondition deleteLastElemFromPath(DcmObject* objSearchedIn, DcmPath* path, DcmPathNode* toDelete);

    /** Helper function that looks at the last node in a given path and deletes
     *  the corresponding DICOM object. Does not delete the path node itself:
     *  That is done by the calling function, findOrCreateItemPath().
     *  @param objSearchedIn [in/out] The object the given path starts in.
     *  @param path [in/out] The complete path to the DICOM object to delete
     *  @param toDelete [in/out] The path node to delete. This node must be
     *                  identical to the last node in the path parameter. Also
     *                  the node must represent a DICOM item, not a sequence
     *                  However, because it is isolated already by the calling
     *                  function, it is provided here for convenience.
     *  @return EC_Normal if successful, error otherwise
     */
    static OFCondition deleteLastItemFromPath(DcmObject* objSearchedIn, DcmPath* path, DcmPathNode* toDelete);

    /** Returns the private reservation tag key for a given private tag
     *  @param privateKey [in] The private key to calculate reservation tag for
     *  @return The reservation key. If given key is not private or an error,
     *          return DCM_UndefinedTagKey. If the given key is a reservation
     *          itself, it is directly returned.
     */
    static DcmTagKey calcPrivateReservationTag(const DcmTagKey& privateKey);

    /** Cleans up memory that was allocated for any search results.
     *  Called when a new search is started or during object destruction.
     *  The DICOM data all freed paths and path nodes point to, is not
     *  touched, i.e. all memory to the DICOM objects pointed to must be
     *  freed from outside. Processing options like checking for private
     *  reservations and so on are not reset to default values but
     *  keep valid.
     */
    void clear();

private:
    /// Internal list that is during search for keeping track of current path
    OFList<DcmPathNode*> m_currentPath;

    /// Internal list that represents the search results found
    OFList<DcmPath*> m_results;

    /// Denotes whether missing items/sequences/attributes should be
    /// automatically inserted when using findAndCreate routines
    OFBool m_createIfNecessary;

    /// If enabled (default), any insertions of private tags will fail, if no
    /// corresponding reservation exists in the underlying item
    OFBool m_checkPrivateReservations;

    /// Denotes, whether a path is accepted that contains wildcards.
    /// If false, then any search containing wildcards will return an error.
    OFBool m_itemWildcardsEnabled;

    /** Private undefined copy constructor
     *  @param rhs Object to copy from
     */
    DcmPathProcessor(const DcmPathProcessor& rhs);

    /** Private undefined assignment operator
     *  @param arg Object to copy from
     *  @return Reference to this object
     */
    DcmPathProcessor& operator=(const DcmPathProcessor& arg);
};

#endif // DCPATH_H
