/*
 *
 *  Copyright (C) 2008-2020, 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 definitions for accessing DICOM dataset structures (items,
 *           sequences and leaf elements via string-based path access.
 *
 */

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

#include "dcmtk/dcmdata/dcpath.h"
#include "dcmtk/dcmdata/dcsequen.h"

#define INCLUDE_CINTTYPES
#include "dcmtk/ofstd/ofstdinc.h"

/*******************************************************************/
/*              Implementation of class DcmPath                    */
/*******************************************************************/

// Constructor
DcmPath::DcmPath()
    : m_path()
{
}

// Construct from existing path (kind of copy constructor)
DcmPath::DcmPath(const OFList<DcmPathNode*>& currentPath)
    : m_path()
{
    OFListConstIterator(DcmPathNode*) it        = currentPath.begin();
    OFListConstIterator(DcmPathNode*) endOfPath = currentPath.end();
    while (it != endOfPath)
    {
        m_path.push_back(new DcmPathNode((*it)->m_obj, (*it)->m_itemNo));
        it++;
    }
}

// Append a node to the path
void DcmPath::append(DcmPathNode* node)
{
    if (node != NULL)
        m_path.push_back(node); // do any validity checking?
}

// Deletes last node from the path and frees corresponding memory
void DcmPath::deleteBackNode()
{
    DcmPathNode* node = m_path.back();
    m_path.pop_back();
    if (node)
    {
        delete node;
        node = NULL;
    }
}

// Returns iterator to first element of the path
OFListIterator(DcmPathNode*) DcmPath::begin()
{
    return m_path.begin();
}

// Returns iterator to last element of the path
DcmPathNode* DcmPath::back()
{
    return m_path.back();
}

// Returns iterator to the end of path, i.e. dummy after actual last element
OFListIterator(DcmPathNode*) DcmPath::end()
{
    return m_path.end();
}

// Returns number of path nodes in the path
Uint32 DcmPath::size() const
{
    return OFstatic_cast(Uint32, m_path.size());
}

// Returns true if path is empty, i.e. number of path nodes is zero
OFBool DcmPath::empty() const
{
    return (m_path.size() == 0);
}

// Returns string representation of the path
OFString DcmPath::toString() const
{
    OFListConstIterator(DcmPathNode*) it        = m_path.begin();
    OFListConstIterator(DcmPathNode*) endOfList = m_path.end();
    OFString pathStr;
    DcmEVR vr;
    DcmObject* obj;
    char buf[500];
    while (it != endOfList)
    {
        if (((*it) == NULL) || ((*it)->m_obj == NULL))
            return "Invalid search result";
        obj = (*it)->m_obj;
        vr  = obj->ident();
        if ((vr == EVR_SQ) || (obj->isLeaf()))
        {
            pathStr.append(OFconst_cast(DcmTag*, &obj->getTag())->getTagName());
            it++;
        }
        else if ((vr == EVR_item) || (vr == EVR_dataset))
        {
#ifdef PRIu32
            sprintf(buf, "[%" PRIu32 "]", (*it)->m_itemNo);
#elif SIZEOF_LONG == 8
            sprintf(buf, "[%u]", (*it)->m_itemNo);
#else
            sprintf(buf, "[%lu]", (*it)->m_itemNo);
#endif
            pathStr.append(buf);
            it++;
            if (it != endOfList)
                pathStr.append(".");
        }
        else
        {
            pathStr.append("<UNKNOWN>");
            it++;
        }
    }
    return pathStr;
}

// Checks whether a specific group number is used in the path's path nodes
OFBool DcmPath::containsGroup(const Uint16 groupNo) const
{
    OFListConstIterator(DcmPathNode*) it        = m_path.begin();
    OFListConstIterator(DcmPathNode*) endOfList = m_path.end();
    while (it != endOfList)
    {
        DcmPathNode* node = *it;
        if ((node == NULL) || (node->m_obj == NULL))
            return OFFalse;
        if (node->m_obj->getGTag() == groupNo)
            return OFTrue;
        it++;
    }
    return OFFalse;
}

// Helper function for findOrCreatePath(). Parses item no from start of string
OFCondition DcmPath::parseItemNoFromPath(OFString& path,      // inout
                                         Uint32& itemNo,      // out
                                         OFBool& wasWildcard) // out
{
    wasWildcard = OFFalse;
    itemNo      = 0;
    // check whether there is an item to parse
    size_t closePos = path.find_first_of(']', 0);
    if ((closePos != OFString_npos) && (path[0] == '['))
    {
        long int parsedNo;
        // try parsing item number; parsing for %lu would cause overflows in case of negative numbers
        int parsed = sscanf(path.c_str(), "[%ld]", &parsedNo);
        if (parsed == 1)
        {
            if (parsedNo < 0)
            {
                OFString errMsg = "Negative item number (not permitted) at beginning of path: ";
                errMsg += path;
                return makeOFCondition(OFM_dcmdata, 25, OF_error, errMsg.c_str());
            }
            itemNo = OFstatic_cast(Uint32, parsedNo);
            if (closePos + 1 < path.length()) // if end of path not reached, cut off "."
                closePos++;
            path.erase(0, closePos + 1); // remove item from path
            return EC_Normal;
        }
        char aChar;
        parsed = sscanf(path.c_str(), "[%c]", &aChar);
        if ((parsed == 1) && (aChar == '*'))
        {
            wasWildcard = OFTrue;
            if (closePos + 1 < path.length()) // if end of path not reached, cut off "."
                closePos++;
            path.erase(0, closePos + 1); // remove item from path
            return EC_Normal;
        }
    }
    OFString errMsg = "Unable to parse item number at beginning of path: ";
    errMsg += path;
    return makeOFCondition(OFM_dcmdata, 25, OF_error, errMsg.c_str());
}

// Function that parses a tag from the beginning of a path string.
OFCondition DcmPath::parseTagFromPath(OFString& path, // inout
                                      DcmTag& tag)    // out
{
    OFCondition result;
    size_t pos = OFString_npos;

    // In case we have a tag "(gggg,xxxx)"
    if (path[0] == '(')
    {
        pos = path.find_first_of(')', 0);
        if (pos != OFString_npos)
            result = DcmTag::findTagFromName(path.substr(1, pos - 1).c_str() /* "gggg,eeee" */, tag);
        else
        {
            OFString errMsg("Unable to parse tag at beginning of path: ");
            errMsg += path;
            return makeOFCondition(OFM_dcmdata, 25, OF_error, errMsg.c_str());
        }
        pos++; // also cut off closing bracket
    }
    // otherwise we could have a dictionary name
    else
    {
        // maybe an item follows
        pos = path.find_first_of('[', 0);
        if (pos == OFString_npos)
            result = DcmTag::findTagFromName(path.c_str(), tag); // check full path
        else
            result = DcmTag::findTagFromName(path.substr(0, pos).c_str(), tag); // parse path up to "[" char
    }
    // construct error message if necessary and return
    if (result.bad())
    {
        OFString errMsg("Unable to parse tag/dictionary name at beginning of path: ");
        errMsg += path;
        return makeOFCondition(OFM_dcmdata, 25, OF_error, errMsg.c_str());
    }
    // else remove parsed tag from path and return success
    else
    {
        path.erase(0, pos);
    }

    return EC_Normal;
}

// Destructor, frees memory of path nodes (but not of underlying DICOM objects)
DcmPath::~DcmPath()
{
    // free dynamically allocated memory
    while (m_path.size() != 0)
    {
        DcmPathNode* node = m_path.front();
        delete node;
        node = NULL;
        m_path.pop_front();
    }
}

// Separate a string path into the different nodes
OFCondition DcmPath::separatePathNodes(const OFString& path, OFList<OFString>& result)
{
    OFString pathStr(path);
    OFCondition status = EC_Normal;
    OFBool nextIsItem  = OFTrue;
    Uint32 itemNo      = 0;
    OFBool isWildcard  = OFFalse;

    // initialize parsing loop
    if (!pathStr.empty())
    {
        if (pathStr[0] != '[')
            nextIsItem = OFFalse;
    }

    char buf[100];
    // parse node for node and only stop if error occurs or parsing completes
    while (!pathStr.empty())
    {
        if (nextIsItem)
        {
            status = parseItemNoFromPath(pathStr, itemNo, isWildcard);
            if (status.bad())
                return status;
            if (isWildcard)
                result.push_back("[*]");
            else
            {
#ifdef PRIu32
                if (sprintf(buf, "[%" PRIu32 "]", itemNo) < 2)
                    return EC_IllegalParameter;
#elif SIZEOF_LONG == 8
                if (sprintf(buf, "[%u]", itemNo) < 2)
                    return EC_IllegalParameter;
#else
                if (sprintf(buf, "[%lu]", itemNo) < 2)
                    return EC_IllegalParameter;
#endif
                result.push_back(buf);
            }
            nextIsItem = OFFalse;
        }
        else
        {
            DcmTag tag;
            status = parseTagFromPath(pathStr, tag);
            if (status.bad())
                return status;
            if (sprintf(buf, "(%04X,%04X)", tag.getGroup(), tag.getElement()) != 11)
                return EC_IllegalParameter;
            result.push_back(buf);
            nextIsItem = OFTrue;
        }
    }
    return status;
}

/*******************************************************************/
/*          Implementation of class DcmPathProcessor               */
/*******************************************************************/

// Constructor, constructs an empty path processor
DcmPathProcessor::DcmPathProcessor()
    : m_currentPath()
    , m_results()
    , m_createIfNecessary(OFFalse)
    , m_checkPrivateReservations(OFTrue)
    , m_itemWildcardsEnabled(OFTrue)
{
}

// enables (class default:enabled) or disables checking of private reservations
void DcmPathProcessor::checkPrivateReservations(const OFBool doChecking)
{
    m_checkPrivateReservations = doChecking;
}

// enables (class default:enabled) or disables support for item wildcards
void DcmPathProcessor::setItemWildcardSupport(const OFBool supported)
{
    m_itemWildcardsEnabled = supported;
}

// Permits finding and creating DICOM object hierarchies based on a path string
OFCondition DcmPathProcessor::findOrCreatePath(DcmObject* obj, const OFString& path, OFBool createIfNecessary)
{
    // check input parameters
    if ((obj == NULL) || path.empty())
        return EC_IllegalParameter;

    if (!m_itemWildcardsEnabled)
    {
        if (path.find('*') != OFString_npos)
        {
            return makeOFCondition(OFM_dcmdata, 25, OF_error, "Item wildcard '*' found in path but wildcards disabled");
        }
    }
    clear();
    m_createIfNecessary = createIfNecessary;

    // do real work in private member functions
    OFString pathCopy = path;
    if ((obj->ident() == EVR_item) || (obj->ident() == EVR_dataset))
        return findOrCreateItemPath(OFstatic_cast(DcmItem*, obj), pathCopy);
    else if (obj->ident() == EVR_SQ)
        return findOrCreateSequencePath(OFstatic_cast(DcmSequenceOfItems*, obj), pathCopy);
    else
        return EC_IllegalParameter;
}

// Permits deleting DICOM object hierarchies based on a path string
OFCondition DcmPathProcessor::findOrDeletePath(DcmObject* obj, const OFString& path, Uint32& numDeleted)
{
    // check input parameters
    if ((obj == NULL) || path.empty())
        return EC_IllegalParameter;
    numDeleted = 0;
    if (!m_itemWildcardsEnabled)
    {
        if (path.find('*') != OFString_npos)
        {
            return makeOFCondition(OFM_dcmdata, 25, OF_error, "Item wildcard '*' found in path but wildcards disabled");
        }
    }

    // search
    m_createIfNecessary = OFFalse;
    OFString pathCopy   = path;
    OFCondition result;
    clear();
    if ((obj->ident() == EVR_item) || (obj->ident() == EVR_dataset))
        result = findOrCreateItemPath(OFstatic_cast(DcmItem*, obj), pathCopy);
    else if (obj->ident() == EVR_SQ)
        result = findOrCreateSequencePath(OFstatic_cast(DcmSequenceOfItems*, obj), pathCopy);
    else
        return EC_IllegalParameter;
    if (result.bad())
        return result;

    // check results
    OFList<DcmPath*> resultPaths;
    Uint32 numPaths = getResults(resultPaths);
    if (numPaths == 0)
        return EC_IllegalCall; // should never happen at this point
    OFListIterator(DcmPath*) pathIt = resultPaths.begin();
    OFListIterator(DcmPath*) endIt  = resultPaths.end();
    while (pathIt != endIt)
    {
        // get last item/element from path which should be deleted
        DcmPathNode* nodeToDelete = (*pathIt)->back();
        if ((nodeToDelete == NULL) || (nodeToDelete->m_obj == NULL))
            return EC_IllegalCall;

        // if it's not an item, delete element from item.
        // deletes DICOM content of node but not node itself (done later)
        if (nodeToDelete->m_obj->ident() != EVR_item)
        {
            result = deleteLastElemFromPath(obj, *pathIt, nodeToDelete);
        }
        // otherwise we need to delete an item from a sequence
        else
        {
            result = deleteLastItemFromPath(obj, *pathIt, nodeToDelete);
        }
        if (result.bad())
            return result;
        // if success, remove node from path and clear node memory
        (*pathIt)->deleteBackNode();
        numDeleted++;
        pathIt++;
    }
    return result;
}

// Get results of a an operation started before (e.g. findOrCreatePath())
Uint32 DcmPathProcessor::getResults(OFList<DcmPath*>& searchResults)
{
    if (m_results.size() > 0)
    {
        // explicitly copy (shallow)
        OFListIterator(DcmPath*) it = m_results.begin();
        while (it != m_results.end())
        {
            searchResults.push_back(*it);
            it++;
        }
    }
    return OFstatic_cast(Uint32, m_results.size());
}

// applies a string path (optionally with value) to a dataset
OFCondition DcmPathProcessor::applyPathWithValue(DcmDataset* dataset, const OFString& overrideKey)
{
    if (dataset == NULL)
        return EC_IllegalCall;
    if (overrideKey.empty())
        return EC_Normal;
    OFString path = overrideKey;
    OFString value;
    size_t pos = path.find('=');
    // separate tag from value if there is one
    if (pos != OFString_npos)
    {
        value = path.substr(pos + 1); // value now contains value
        path.erase(pos);              // pure path without value
    }
    clear();

    // create path
    OFCondition result = findOrCreatePath(dataset, path, OFTrue /* create if necessary */);
    if (result.bad())
        return result;

    // prepare for value insertion
    OFListConstIterator(DcmPath*) it      = m_results.begin();
    OFListConstIterator(DcmPath*) endList = m_results.end();
    DcmPathNode* last                     = (*it)->back();
    if (last == NULL)
        return EC_IllegalCall;
    // if value is specified, be sure path does not end with item
    if (!last->m_obj->isLeaf())
    {
        if (value.empty())
            return EC_Normal;
        else
            return makeOFCondition(
                OFM_dcmdata, 25, OF_error, "Cannot insert value into path ending with item or sequence");
    }
    // insert value into each element affected by path
    while (it != endList)
    {
        last = (*it)->back();
        if (last == NULL)
            return EC_IllegalCall;
        DcmElement* elem = OFstatic_cast(DcmElement*, last->m_obj);
        if (elem == NULL)
            return EC_IllegalCall;
        result = elem->putString(value.c_str());
        if (result.bad())
            break;
        it++;
    }
    return result;
}

// Resets status (including results) of DcmPathProcessor and frees corresponding memory
void DcmPathProcessor::clear()
{
    while (m_results.size() != 0)
    {
        DcmPath* result = m_results.front();
        if (result != NULL)
        {
            delete result;
            result = NULL;
        }
        m_results.pop_front();
    }

    while (m_currentPath.size() != 0)
    {
        DcmPathNode* node = m_currentPath.front();
        if (node != NULL)
        {
            delete node;
            node = NULL;
        }
        m_currentPath.pop_front();
    }
}

// Destructor, frees memory by calling clear()
DcmPathProcessor::~DcmPathProcessor()
{
    clear();
}

/* protected helper functions */

// Helper function that deletes last DICOM element from a path from the DICOM hierarchy
OFCondition DcmPathProcessor::deleteLastElemFromPath(DcmObject* objSearchedIn, DcmPath* path, DcmPathNode* toDelete)
{
    // item containing the element to delete
    DcmItem* containingItem = NULL;
    if (path->size() == 1)
    {
        // if we have only a single elem in path, given object must be cont. item
        if ((objSearchedIn->ident() != EVR_item) && (objSearchedIn->ident() != EVR_dataset))
            return makeOFCondition(OFM_dcmdata, 25, OF_error, "Cannot search leaf element in object being not an item");
        containingItem = OFstatic_cast(DcmItem*, objSearchedIn);
    }
    else
    {
        // get containing item from path which is the penultimate in the path
        OFListIterator(DcmPathNode*) temp = path->end();
        temp--;
        temp--;
        if (*temp == NULL)
            return EC_IllegalCall; // never happens here...
        if ((*temp)->m_obj == NULL)
            return EC_IllegalCall;
        if ((*temp)->m_obj->ident() != EVR_item) // (no test for dataset needed)
            return makeOFCondition(OFM_dcmdata, 25, OF_error, "Cannot search leaf element in object being not an item");
        containingItem = OFstatic_cast(DcmItem*, (*temp)->m_obj);
    }
    if (containingItem == NULL)
        return EC_IllegalCall;
    OFCondition result = containingItem->findAndDeleteElement(toDelete->m_obj->getTag(), OFFalse, OFFalse);
    return result;
}

// Helper function that deletes last DICOM item from a path from the DICOM hierarchy
OFCondition DcmPathProcessor::deleteLastItemFromPath(DcmObject* objSearchedIn, DcmPath* path, DcmPathNode* toDelete)
{
    DcmSequenceOfItems* containingSeq = NULL;
    if (path->size() == 1)
    {
        // if we have only a single elem in path, given object must be cont. item
        if (objSearchedIn->ident() != EVR_SQ)
            return makeOFCondition(OFM_dcmdata, 25, OF_error, "Cannot search item in object being not a sequence");
        containingSeq = OFstatic_cast(DcmSequenceOfItems*, objSearchedIn);
    }
    else
    {
        // get containing item from path which is the penultimate in the path
        OFListIterator(DcmPathNode*) temp = path->end();
        temp--;
        temp--;
        if (*temp == NULL)
            return EC_IllegalCall; // never happens here...
        if ((*temp)->m_obj == NULL)
            return EC_IllegalCall;
        if ((*temp)->m_obj->ident() != EVR_SQ)
            return makeOFCondition(OFM_dcmdata, 25, OF_error, "Cannot search item in object being not a sequence");
        containingSeq = OFstatic_cast(DcmSequenceOfItems*, (*temp)->m_obj);
    }
    if (containingSeq == NULL)
        return EC_IllegalCall;
    DcmItem* item2BDeleted = containingSeq->remove(OFstatic_cast(DcmItem*, toDelete->m_obj));
    if (item2BDeleted == NULL)
        return EC_IllegalCall; // should not happen here...
    delete item2BDeleted;
    item2BDeleted = NULL;
    return EC_Normal;
}

// Helper function that does work for findOrCreatePath()
OFCondition DcmPathProcessor::findOrCreateItemPath(DcmItem* item, OFString& path)
{
    if (item == NULL)
        return EC_IllegalParameter;

    if (path.empty())
        return EC_IllegalParameter;

    OFString restPath(path);
    OFCondition status = EC_Normal;
    DcmTag tag;
    OFString privCreator;
    OFBool newlyCreated    = OFFalse; // denotes whether an element was created
    DcmElement* elem       = NULL;
    DcmPath* currentResult = NULL;

    // parse tag
    status = DcmPath::parseTagFromPath(restPath, tag);
    if (status.bad())
        return status;

    // insert element or sequence
    if (!(item->tagExists(tag))) // do not to overwrite existing tags
    {
        if (m_createIfNecessary)
        {
            // private tags needs special handling, e.g. checking reservation
            if (tag.isPrivate())
            {
                if (!tag.isPrivateReservation())
                {
                    // if this is no reservation, find the private creator and update tag
                    if (getPrivateCreator(item, tag, privCreator).good() && !privCreator.empty())
                    {
                        tag.setPrivateCreator(privCreator.c_str());
                    }
                    // now that private creator is hopefully set, lookup vr
                    tag.lookupVRinDictionary();
                    // check private reservation if desired
                    if (m_checkPrivateReservations)
                    {
                        status = checkPrivateTagReservation(item, tag.getXTag());
                        if (status.bad())
                            return status;
                    }
                }
            }
            // create element for insertion
            elem = DcmItem::newDicomElement(tag, privCreator.empty() ? NULL : privCreator.c_str());
            if (elem == NULL)
                return EC_IllegalCall;
            status = item->insert(elem, OFTrue);
            if (status.bad())
            {
                delete elem;
                elem = NULL;
                return status;
            }
            newlyCreated = OFTrue;
        }
        else
            return EC_TagNotFound;
    }

    // get element
    status = item->findAndGetElement(tag, elem);
    if (status.bad())
        return EC_CorruptedData; // should not happen

    // start recursion if element was a sequence
    if (tag.getEVR() == EVR_SQ)
    {
        DcmSequenceOfItems* seq = NULL;
        seq                     = OFstatic_cast(DcmSequenceOfItems*, elem);
        if (!seq)
            status = EC_IllegalCall; // should not happen
        else
        {
            // if sequence could be inserted and there is nothing more to do: add current path to results and return
            // success
            if (restPath.empty())
            {
                currentResult = new DcmPath(m_currentPath);
                currentResult->append(new DcmPathNode(elem, 0));
                m_results.push_back(currentResult);
                return EC_Normal;
            }
            // start recursion if there is path left
            DcmPathNode* node = new DcmPathNode(seq, 0);
            m_currentPath.push_back(node);
            status = findOrCreateSequencePath(seq, restPath);
            m_currentPath.pop_back(); // avoid side effects
            delete node;
        }
    }
    else if (restPath.empty()) // we inserted a leaf element: path must be completed
    {
        // add element and add current path to overall results; then return success
        currentResult = new DcmPath(m_currentPath);
        currentResult->append(new DcmPathNode(elem, 0));
        m_results.push_back(currentResult);
        return EC_Normal;
    }
    else // we inserted a leaf element but there is path left -> error
        status = makeOFCondition(
            OFM_dcmdata, 25, OF_error, "Invalid Path: Non-sequence tag found with rest path following");

    // in case of errors: delete result path copy and delete DICOM element if it was newly created
    if (status.bad() && (elem != NULL))
    {
        m_results.remove(currentResult); // remove from search result
        if (currentResult)
        {
            delete currentResult;
            currentResult = NULL;
        }
        if (newlyCreated) // only delete from this dataset and memory if newly created ("undo")
        {
            if (item->findAndDeleteElement(tag).bad())
                delete elem; // delete manually if not found in dataset
        }
        elem = NULL;
    }
    return status;
}

// Helper function that does work for findOrCreatePath()
OFCondition DcmPathProcessor::findOrCreateSequencePath(DcmSequenceOfItems* seq, OFString& path)
{
    if (seq == NULL)
        return EC_IllegalParameter;

    // prepare variables
    OFString restPath(path);
    OFCondition status     = EC_Normal;
    DcmItem* resultItem    = NULL;
    Uint32 itemNo          = 0;
    Uint32 newlyCreated    = 0; // number of items created (appended) (only non-wildcard mode)
    Uint32 newPathsCreated = 0; // wildcard mode: number of paths found

    // parse item number
    OFBool isWildcard = OFFalse;
    status            = DcmPath::parseItemNoFromPath(restPath, itemNo, isWildcard);
    if (status.bad())
        return status;

    // wildcard code: add result path for every matching item
    if (isWildcard)
    {
        // if there are no items -> no results are found
        Uint32 numItems = OFstatic_cast(Uint32, seq->card());
        if (numItems == 0)
        {
            if (!m_createIfNecessary)
                return EC_TagNotFound;
            else
                return makeOFCondition(
                    OFM_dcmdata, 25, OF_error, "Cannot insert unspecified number (wildcard) of items into sequence");
        }

        // copy every item to result
        for (itemNo = 0; itemNo < numItems; itemNo++)
        {
            DcmItem* oneItem = seq->getItem(itemNo);
            /* if we found an item that matches, copy current result path, then
               add the item found and finally start recursive search for
               that item.
             */
            if (oneItem != NULL)
            {
                // if the item was the last thing to parse, add list to results and return
                if (restPath.empty())
                {
                    DcmPathNode* itemNode  = new DcmPathNode(oneItem, itemNo);
                    DcmPath* currentResult = new DcmPath(m_currentPath);
                    currentResult->append(itemNode);
                    m_results.push_back(currentResult);
                    newPathsCreated++;
                }
                // else there is path left: continue searching in the new item
                else
                {
                    DcmPathNode* itemNode = new DcmPathNode(oneItem, itemNo);
                    m_currentPath.push_back(itemNode);
                    status = findOrCreateItemPath(oneItem, restPath);
                    m_currentPath.pop_back(); // avoid side effects
                    delete itemNode;
                    itemNode = NULL;
                    if (status.bad()) // we did not find the path in that item
                    {
                        if (status != EC_TagNotFound)
                            return status;
                    }
                    else
                    {
                        newPathsCreated++;
                    }
                }
            }
            else // should be possible to get every item, however...
                return EC_IllegalCall;
        }
        // if there was at least one result, success can be returned
        if (newPathsCreated != 0)
        {
            return EC_Normal;
        }
        else
            return EC_TagNotFound;
    }

    /* no wildcard, just select single item or create it if necessary */

    // if item already exists, just grab a reference
    if (itemNo < seq->card())
        resultItem = seq->getItem(itemNo);
    // if item does not exist, create new if desired
    else if (m_createIfNecessary)
    {
        // create and insert items until desired item count is reached
        while ((seq->card() <= itemNo) || (status.bad()))
        {
            resultItem = new DcmItem();
            if (!resultItem)
                return EC_MemoryExhausted;
            status = seq->insert(resultItem);
            if (status.bad())
                delete resultItem;
            else
                newlyCreated++;
        }
    }
    // item does not exist and should not be created newly, return "path not found"
    else
        return EC_TagNotFound;

    // at this point, the item has been obtained and everything is fine so far

    // finding/creating the path was successful. now check whether there is more to do
    if (!restPath.empty())
    {
        // push new item to result path and continue
        DcmPathNode* itemNode = new DcmPathNode(resultItem, itemNo);
        m_currentPath.push_back(itemNode);
        status = findOrCreateItemPath(resultItem, restPath);
        m_currentPath.pop_back(); // avoid side effects to input parameter
        delete itemNode;
        itemNode = NULL;
        // in case of no success, delete any items that were newly created and return error
        if (status.bad())
        {
            for (Uint32 i = newlyCreated; i > 0; i--)
            {
                DcmItem* todelete = seq->remove(i - 1);
                if (todelete != NULL)
                {
                    delete todelete;
                    todelete = NULL;
                }
            }
            return status;
        }
    }
    else // finally everything was successful
    {
        DcmPathNode* itemNode = new DcmPathNode(resultItem, itemNo);
        m_currentPath.push_back(itemNode);
        m_results.push_back(new DcmPath(m_currentPath));
        m_currentPath.pop_back(); // avoid side effects
        delete itemNode;
        itemNode = NULL;
        status   = EC_Normal;
    }
    return status;
}

DcmTagKey DcmPathProcessor::calcPrivateReservationTag(const DcmTagKey& privateKey)
{
    DcmTagKey reservationTag(0xFFFF, 0xFFFF);
    // if not a private key is given return "error"
    if (!privateKey.isPrivate())
        return reservationTag;
    // if the private key given is already a reservation key, return it
    if (privateKey.isPrivateReservation())
        return privateKey;

    // Calculate corresponding private creator element
    Uint16 elemNo = privateKey.getElement();
    // Get yz from given element number wxyz, groups stays the same
    elemNo = OFstatic_cast(Uint16, elemNo >> 8);
    reservationTag.setGroup(privateKey.getGroup());
    reservationTag.setElement(elemNo);
    return reservationTag;
}

OFCondition DcmPathProcessor::getPrivateCreator(DcmItem* item, const DcmTagKey& tagKey, OFString& privateCreator)
{
    DcmTagKey reservationKey;
    if (!tagKey.isPrivateReservation())
    {
        reservationKey = calcPrivateReservationTag(tagKey);
        if (reservationKey == DCM_UndefinedTagKey)
        {
            OFString msg("Unable to compute private reservation for tag: ");
            msg += tagKey.toString();
            return makeOFCondition(OFM_dcmdata, 25, OF_error, msg.c_str());
        }
        if (!item->tagExists(reservationKey))
        {
            return EC_TagNotFound;
        }
        return item->findAndGetOFString(reservationKey, privateCreator);
    }
    else
        return EC_IllegalCall;
}

OFCondition
DcmPathProcessor::checkPrivateTagReservation(DcmItem* item, const DcmTagKey& tagKey, const OFString& privateCreator)
{
    OFString actualPrivateCreator;
    OFCondition result = getPrivateCreator(item, tagKey, actualPrivateCreator);
    if (result.bad() || actualPrivateCreator.empty())
    {
        OFString msg = "Invalid or empty private creator tag: ";
        msg += calcPrivateReservationTag(tagKey).toString();
        return makeOFCondition(OFM_dcmdata, 25, OF_error, msg.c_str());
    }
    else if (!privateCreator.empty())
    {
        // check whether private creator is correct
        if (actualPrivateCreator != privateCreator)
        {
            OFString msg = "Private creator string (";
            msg += actualPrivateCreator;
            msg += ") other than expected ( ";
            msg += privateCreator;
            msg += privateCreator;
            return makeOFCondition(OFM_dcmdata, 25, OF_error, msg.c_str());
        }
    }
    return EC_Normal;
}
