/*
 *
 *  Copyright (C) 2000-2022, 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: DSRBasicCodedEntry, DSRCodedEntryValue
 *
 */


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

#include "dcmtk/dcmsr/dsrtypes.h"
#include "dcmtk/dcmsr/dsrcodvl.h"
#include "dcmtk/dcmsr/dsrxmld.h"

#include "dcmtk/dcmdata/dcdeftag.h"
#include "dcmtk/dcmdata/dcvrcs.h"
#include "dcmtk/dcmdata/dcvrdt.h"
#include "dcmtk/dcmdata/dcvrlo.h"
#include "dcmtk/dcmdata/dcvrsh.h"
#include "dcmtk/dcmdata/dcvruc.h"
#include "dcmtk/dcmdata/dcvrui.h"
#include "dcmtk/dcmdata/dcvrur.h"


// implementation of class DSRBasicCodedEntry

DSRBasicCodedEntry::DSRBasicCodedEntry(const OFString &codeValue,
                                       const OFString &codingSchemeDesignator,
                                       const OFString &codeMeaning,
                                       const DSRTypes::E_CodeValueType codeValueType)
  : CodeValueType(codeValueType),
    CodeValue(codeValue),
    CodingSchemeDesignator(codingSchemeDesignator),
    CodingSchemeVersion(),
    CodeMeaning(codeMeaning)
{
}


DSRBasicCodedEntry::DSRBasicCodedEntry(const OFString &codeValue,
                                       const OFString &codingSchemeDesignator,
                                       const OFString &codingSchemeVersion,
                                       const OFString &codeMeaning,
                                       const DSRTypes::E_CodeValueType codeValueType)
  : CodeValueType(codeValueType),
    CodeValue(codeValue),
    CodingSchemeDesignator(codingSchemeDesignator),
    CodingSchemeVersion(codingSchemeVersion),
    CodeMeaning(codeMeaning)
{
}


// implementation of class DSRCodedEntryValue

DSRCodedEntryValue::DSRCodedEntryValue()
  : CodeValueType(DSRTypes::CVT_Short /* should not be "auto" */),
    CodeValue(),
    CodingSchemeDesignator(),
    CodingSchemeVersion(),
    CodeMeaning(),
    ContextIdentifier(),
    ContextUID(),
    MappingResource(),
    ContextGroupVersion(),
    ContextGroupLocalVersion(),
    ContextGroupExtensionCreatorUID()
{
    /* empty coded entry */
}


DSRCodedEntryValue::DSRCodedEntryValue(const DSRBasicCodedEntry &basicCodedEntry,
                                       const OFBool check)
  : CodeValueType(DSRTypes::CVT_auto),
    CodeValue(),
    CodingSchemeDesignator(),
    CodingSchemeVersion(),
    CodeMeaning(),
    ContextIdentifier(),
    ContextUID(),
    MappingResource(),
    ContextGroupVersion(),
    ContextGroupLocalVersion(),
    ContextGroupExtensionCreatorUID()
{
    /* check code (if not disabled) */
    setCode(basicCodedEntry, check);
}


DSRCodedEntryValue::DSRCodedEntryValue(const OFString &codeValue,
                                       const OFString &codingSchemeDesignator,
                                       const OFString &codeMeaning,
                                       const DSRTypes::E_CodeValueType codeValueType,
                                       const OFExplicitBool check)
  : CodeValueType(DSRTypes::CVT_auto),
    CodeValue(),
    CodingSchemeDesignator(),
    CodingSchemeVersion(),
    CodeMeaning(),
    ContextIdentifier(),
    ContextUID(),
    MappingResource(),
    ContextGroupVersion(),
    ContextGroupLocalVersion(),
    ContextGroupExtensionCreatorUID()
{
    /* check code (if not disabled) */
    setCode(codeValue, codingSchemeDesignator, codeMeaning, codeValueType, check);
}


DSRCodedEntryValue::DSRCodedEntryValue(const OFString &codeValue,
                                       const OFString &codingSchemeDesignator,
                                       const OFString &codingSchemeVersion,
                                       const OFString &codeMeaning,
                                       const DSRTypes::E_CodeValueType codeValueType,
                                       const OFBool check)
  : CodeValueType(DSRTypes::CVT_auto),
    CodeValue(),
    CodingSchemeDesignator(),
    CodingSchemeVersion(),
    CodeMeaning(),
    ContextIdentifier(),
    ContextUID(),
    MappingResource(),
    ContextGroupVersion(),
    ContextGroupLocalVersion(),
    ContextGroupExtensionCreatorUID()
{
    /* check code (if not disabled) */
    setCode(codeValue, codingSchemeDesignator, codingSchemeVersion, codeMeaning, codeValueType, check);
}


DSRCodedEntryValue::DSRCodedEntryValue(const DSRCodedEntryValue &codedEntryValue)
  : CodeValueType(codedEntryValue.CodeValueType),
    CodeValue(codedEntryValue.CodeValue),
    CodingSchemeDesignator(codedEntryValue.CodingSchemeDesignator),
    CodingSchemeVersion(codedEntryValue.CodingSchemeVersion),
    CodeMeaning(codedEntryValue.CodeMeaning),
    ContextIdentifier(codedEntryValue.ContextIdentifier),
    ContextUID(codedEntryValue.ContextUID),
    MappingResource(codedEntryValue.MappingResource),
    ContextGroupVersion(codedEntryValue.ContextGroupVersion),
    ContextGroupLocalVersion(codedEntryValue.ContextGroupLocalVersion),
    ContextGroupExtensionCreatorUID(codedEntryValue.ContextGroupExtensionCreatorUID)
{
    /* do not check since this would be unexpected to the user */
}


DSRCodedEntryValue::~DSRCodedEntryValue()
{
}


DSRCodedEntryValue &DSRCodedEntryValue::operator=(const DSRCodedEntryValue &codedEntryValue)
{
    /* do not check since this would be unexpected to the user */
    CodeValueType = codedEntryValue.CodeValueType;
    CodeValue = codedEntryValue.CodeValue;
    CodingSchemeDesignator = codedEntryValue.CodingSchemeDesignator;
    CodingSchemeVersion = codedEntryValue.CodingSchemeVersion;
    CodeMeaning = codedEntryValue.CodeMeaning;
    ContextIdentifier = codedEntryValue.ContextIdentifier;
    ContextUID = codedEntryValue.ContextUID;
    MappingResource = codedEntryValue.MappingResource;
    ContextGroupVersion = codedEntryValue.ContextGroupVersion;
    ContextGroupLocalVersion = codedEntryValue.ContextGroupLocalVersion;
    ContextGroupExtensionCreatorUID = codedEntryValue.ContextGroupExtensionCreatorUID;
    return *this;
}


OFBool DSRCodedEntryValue::operator==(const DSRCodedEntryValue &codedEntryValue) const
{
    /* Code Meaning is not used for comparing the two codes, also the "Enhanced Encoding Mode" is not taken into account */
    return (CodeValue == codedEntryValue.CodeValue) &&
           (CodingSchemeDesignator == codedEntryValue.CodingSchemeDesignator) &&
           (CodingSchemeVersion == codedEntryValue.CodingSchemeVersion);
}


OFBool DSRCodedEntryValue::operator!=(const DSRCodedEntryValue &codedEntryValue) const
{
    /* Code Meaning is not used for comparing the two codes, also the "Enhanced Encoding Mode" is not taken into account */
    return (CodeValue != codedEntryValue.CodeValue) ||
           (CodingSchemeDesignator != codedEntryValue.CodingSchemeDesignator) ||
           (CodingSchemeVersion != codedEntryValue.CodingSchemeVersion);
}


OFBool DSRCodedEntryValue::operator==(const DSRBasicCodedEntry &basicCodedEntry) const
{
    /* Code Meaning is not used for comparing the two codes */
    return (CodeValue == basicCodedEntry.CodeValue) &&
           (CodingSchemeDesignator == basicCodedEntry.CodingSchemeDesignator) &&
           (CodingSchemeVersion == basicCodedEntry.CodingSchemeVersion);
}


OFBool DSRCodedEntryValue::operator!=(const DSRBasicCodedEntry &basicCodedEntry) const
{
    /* Code Meaning is not used for comparing the two codes */
    return (CodeValue != basicCodedEntry.CodeValue) ||
           (CodingSchemeDesignator != basicCodedEntry.CodingSchemeDesignator) ||
           (CodingSchemeVersion != basicCodedEntry.CodingSchemeVersion);
}


void DSRCodedEntryValue::clear()
{
    CodeValueType = DSRTypes::CVT_Short /* should not be "auto" */;
    CodeValue.clear();
    CodingSchemeDesignator.clear();
    CodingSchemeVersion.clear();
    CodeMeaning.clear();
    ContextIdentifier.clear();
    ContextUID.clear();
    MappingResource.clear();
    ContextGroupVersion.clear();
    ContextGroupLocalVersion.clear();
    ContextGroupExtensionCreatorUID.clear();
}


OFBool DSRCodedEntryValue::isValid() const
{
    return checkCurrentValue().good();
}


OFBool DSRCodedEntryValue::isEmpty() const
{
    return CodeValue.empty() && CodingSchemeDesignator.empty() && CodingSchemeVersion.empty() && CodeMeaning.empty();
}


OFBool DSRCodedEntryValue::isComplete() const
{
    return !CodeValue.empty() && (!CodingSchemeDesignator.empty() || (CodeValueType == DSRTypes::CVT_URN)) && !CodeMeaning.empty();
}


void DSRCodedEntryValue::print(STD_NAMESPACE ostream &stream,
                               const OFBool printCodeValue,
                               const size_t flags) const
{
    if ((flags & DSRTypes::PF_printInvalidCodes) || isValid())
    {
        OFString printString;
        stream << "(";
        if (printCodeValue)
        {
            stream << DSRTypes::convertToPrintString(CodeValue, printString) << ",";
            stream << DSRTypes::convertToPrintString(CodingSchemeDesignator, printString);
            if (!CodingSchemeVersion.empty())
                stream << "[" << DSRTypes::convertToPrintString(CodingSchemeVersion, printString) << "]";
        } else
            stream << ",";
        stream << ",\"" << DSRTypes::convertToPrintString(CodeMeaning, printString) << "\")";
        if ((flags & DSRTypes::PF_indicateEnhancedEncodingMode) && usesEnhancedEncodingMode())
            stream << "*";
    }
    else if ((flags & DSRTypes::PF_printEmptyCodes) && isEmpty())
        stream << "empty code";
    else
        stream << "invalid code";
}


OFCondition DSRCodedEntryValue::readItem(DcmItem &dataset,
                                         const char *moduleName,
                                         const size_t flags)
{
    const OFBool acceptViolation = (flags & DSRTypes::RF_acceptInvalidContentItemValue) > 0;
    /* read "Basic Coded Entry Attributes" */
    OFCondition result = DSRTypes::getAndCheckStringValueFromDataset(dataset, DCM_LongCodeValue, CodeValue, "1", "1C", moduleName, acceptViolation);
    /* tbc: should we distinguish "tag not found" from other errors? */
    if (result.bad())                                               /* different types of code value */
    {
        result = DSRTypes::getAndCheckStringValueFromDataset(dataset, DCM_URNCodeValue, CodeValue, "1", "1C", moduleName, acceptViolation);
        if (result.bad())
        {
            result = DSRTypes::getAndCheckStringValueFromDataset(dataset, DCM_CodeValue, CodeValue, "1", "1", moduleName, acceptViolation);
            CodeValueType = DSRTypes::CVT_Short;
        } else
            CodeValueType = DSRTypes::CVT_URN;
    } else
        CodeValueType = DSRTypes::CVT_Long;
    if (result.good())
    {
        if (CodeValueType == DSRTypes::CVT_URN)                     /* conditional or mandatory */
            result = DSRTypes::getAndCheckStringValueFromDataset(dataset, DCM_CodingSchemeDesignator, CodingSchemeDesignator, "1", "1C", moduleName, acceptViolation);
        else
            result = DSRTypes::getAndCheckStringValueFromDataset(dataset, DCM_CodingSchemeDesignator, CodingSchemeDesignator, "1", "1", moduleName, acceptViolation);
    }
    if (result.good())                                              /* conditional (type 1C) */
        DSRTypes::getAndCheckStringValueFromDataset(dataset, DCM_CodingSchemeVersion, CodingSchemeVersion, "1", "1C", moduleName);
    if (result.good())
        result = DSRTypes::getAndCheckStringValueFromDataset(dataset, DCM_CodeMeaning, CodeMeaning, "1", "1", moduleName, acceptViolation);
    /* read "Enhanced Encoding Mode" */
    if (result.good())                                              /* optional or conditional */
    {
        DSRTypes::getAndCheckStringValueFromDataset(dataset, DCM_ContextIdentifier, ContextIdentifier, "1", "3", moduleName);
        if (!ContextIdentifier.empty())
        {
            OFString extensionFlag;
            /* check for a common error: Context Group Identifier includes "CID" prefix */
            if ((ContextIdentifier.find_first_not_of("0123456789") != OFString_npos) || (ContextIdentifier.at(0) == '0'))
            {
                DCMSR_DEBUG("Reading invalid Context Identifier (" << ContextIdentifier << ")");
                DCMSR_WARN("Context Identifier shall be a string of digits without leading zeros");
            }
            DSRTypes::getAndCheckStringValueFromDataset(dataset, DCM_MappingResource, MappingResource, "1", "1" /* was 1C */, moduleName);
            DSRTypes::getAndCheckStringValueFromDataset(dataset, DCM_ContextGroupVersion, ContextGroupVersion, "1", "1" /* was 1C */, moduleName);
            DSRTypes::getAndCheckStringValueFromDataset(dataset, DCM_ContextGroupExtensionFlag, extensionFlag, "1", "3", moduleName);
            if (extensionFlag == "Y")
            {
                DSRTypes::getAndCheckStringValueFromDataset(dataset, DCM_ContextGroupLocalVersion, ContextGroupLocalVersion, "1", "1" /* was 1C */, moduleName);
                DSRTypes::getAndCheckStringValueFromDataset(dataset, DCM_ContextGroupExtensionCreatorUID, ContextGroupExtensionCreatorUID, "1", "1" /* was 1C */, moduleName);
            } else {
                /* ignore Context Group Local Version and/or Context Group Extension Creator UID */
            }
        }
        DSRTypes::getAndCheckStringValueFromDataset(dataset, DCM_ContextUID, ContextUID, "1", "3", moduleName);
    }
    /* tbd: might add check for correct code */
    return result;
}


OFCondition DSRCodedEntryValue::writeItem(DcmItem &dataset) const
{
    /* write "Basic Coded Entry Attributes" */
    OFCondition result = EC_Normal;
    if (CodeValueType == DSRTypes::CVT_Long)                        /* different types of code value */
        result = DSRTypes::putStringValueToDataset(dataset, DCM_LongCodeValue, CodeValue);
    else if (CodeValueType == DSRTypes::CVT_URN)
        result = DSRTypes::putStringValueToDataset(dataset, DCM_URNCodeValue, CodeValue);
    else /* short or auto */
        result = DSRTypes::putStringValueToDataset(dataset, DCM_CodeValue, CodeValue);
    if (result.good())
        result = DSRTypes::putStringValueToDataset(dataset, DCM_CodingSchemeDesignator, CodingSchemeDesignator);
    if (result.good())                                              /* conditional (type 1C) */
        result = DSRTypes::putStringValueToDataset(dataset, DCM_CodingSchemeVersion, CodingSchemeVersion, OFFalse /*allowEmpty*/);
    if (result.good())
        result = DSRTypes::putStringValueToDataset(dataset, DCM_CodeMeaning, CodeMeaning);
    /* write "Enhanced Encoding Mode" */
    if (result.good())                                              /* optional or conditional */
    {
        if (!ContextIdentifier.empty())
        {
            DSRTypes::putStringValueToDataset(dataset, DCM_ContextIdentifier, ContextIdentifier);
            DSRTypes::putStringValueToDataset(dataset, DCM_ContextUID, ContextUID, OFFalse /*allowEmpty*/);
            DSRTypes::putStringValueToDataset(dataset, DCM_MappingResource, MappingResource, OFFalse /*allowEmpty*/);
            DSRTypes::putStringValueToDataset(dataset, DCM_ContextGroupVersion, ContextGroupVersion, OFFalse /*allowEmpty*/);
            if (!ContextGroupLocalVersion.empty() && !ContextGroupExtensionCreatorUID.empty())
            {
                DSRTypes::putStringValueToDataset(dataset, DCM_ContextGroupExtensionFlag, "Y");
                DSRTypes::putStringValueToDataset(dataset, DCM_ContextGroupLocalVersion, ContextGroupLocalVersion);
                DSRTypes::putStringValueToDataset(dataset, DCM_ContextGroupExtensionCreatorUID, ContextGroupExtensionCreatorUID);
            }
        }
        DSRTypes::putStringValueToDataset(dataset, DCM_ContextUID, ContextUID, OFFalse /*allowEmpty*/);
    }
    return result;
}


OFCondition DSRCodedEntryValue::readSequence(DcmItem &dataset,
                                             const DcmTagKey &tagKey,
                                             const OFString &type,
                                             const size_t flags,
                                             const OFString &vm)
{
    /* read CodeSequence */
    DcmSequenceOfItems *dseq = NULL;
    OFCondition result = dataset.findAndGetSequence(tagKey, dseq);
    DSRTypes::checkElementValue(dseq, tagKey, vm, type, result);
    if (result.good())
    {
        DcmItem *ditem = dseq->getItem(0);
        if (ditem != NULL)
        {
            /* read Code */
            result = readItem(*ditem, DcmTag(tagKey).getTagName(), flags);
        } else
            result = SR_EC_InvalidDocumentTree;
    }
    return result;
}


OFCondition DSRCodedEntryValue::readSequenceItem(DcmItem &item,
                                                 const DcmTagKey &tagKey,
                                                 const size_t flags)
{
    /* call the real function, which is "protected" */
    return readItem(item, DcmTag(tagKey).getTagName(), flags);
}


OFCondition DSRCodedEntryValue::writeSequence(DcmItem &dataset,
                                              const DcmTagKey &tagKey) const
{
    OFCondition result = EC_MemoryExhausted;
    /* write CodeSequence */
    DcmSequenceOfItems *dseq = new DcmSequenceOfItems(tagKey);
    if (dseq != NULL)
    {
        /* check for empty value */
        if (isEmpty())
            result = EC_Normal;
        else
        {
            DcmItem *ditem = new DcmItem();
            if (ditem != NULL)
            {
                /* write Code */
                if (isValid())
                    result = writeItem(*ditem);
                if (result.good())
                    dseq->insert(ditem);
                else
                    delete ditem;
            } else
                result = EC_MemoryExhausted;
        }
        if (result.good())
            result= dataset.insert(dseq, OFTrue /*replaceOld*/);
        if (result.bad())
            delete dseq;
    }
    return result;
}


OFCondition DSRCodedEntryValue::writeSequenceItem(DcmItem &item,
                                                  const DcmTagKey & /*tagKey*/) const
{
    /* call the real function, which is "protected" */
    return writeItem(item);
}


OFCondition DSRCodedEntryValue::readXML(const DSRXMLDocument &doc,
                                        DSRXMLCursor cursor,
                                        const size_t /*flags*/)
{
    OFCondition result = SR_EC_CorruptedXMLStructure;
    if (cursor.valid())
    {
        /* check whether code is stored as XML attributes */
        if (doc.hasAttribute(cursor, "codValue"))
        {
            doc.getStringFromAttribute(cursor, CodeValue, "codValue", OFTrue /*encoding*/);
            doc.getStringFromAttribute(cursor, CodingSchemeDesignator, "codScheme", OFTrue /*encoding*/);
            doc.getStringFromAttribute(cursor, CodingSchemeVersion, "codVersion", OFTrue /*encoding*/, OFFalse /*required*/);
            doc.getStringFromNodeContent(cursor, CodeMeaning, NULL /*name*/, OFTrue /*encoding*/);
        } else {
            /* goto first child node */
            cursor.gotoChild();
            /* iterate over all content items */
            while (cursor.valid())
            {
                /* check for known element tags */
                if (doc.matchNode(cursor, "scheme"))
                {
                    doc.getStringFromNodeContent(doc.getNamedChildNode(cursor, "designator"), CodingSchemeDesignator, NULL /*name*/, OFTrue /*encoding*/, OFFalse /*clearString*/);
                    doc.getStringFromNodeContent(doc.getNamedChildNode(cursor, "version", OFFalse /*required*/), CodingSchemeVersion, NULL /*name*/, OFTrue /*encoding*/, OFFalse /*clearString*/);
                } else {
                    doc.getStringFromNodeContent(cursor, CodeValue, "value", OFTrue /*encoding*/, OFFalse /*clearString*/);
                    doc.getStringFromNodeContent(cursor, CodeMeaning, "meaning", OFTrue /*encoding*/, OFFalse /*clearString*/);
                }
                /* proceed with next node */
                cursor.gotoNext();
            }
        }
        CodeValueType = determineCodeValueType(CodeValue);
        /* check whether code is valid */
        result = isValid() ? EC_Normal : SR_EC_InvalidValue;
    }
    return result;
}


OFCondition DSRCodedEntryValue::writeXML(STD_NAMESPACE ostream &stream,
                                         const size_t flags) const
{
    OFString tmpString;
    if (flags & DSRTypes::XF_codeComponentsAsAttribute)
    {
        stream << " codValue=\"" << DSRTypes::convertToXMLString(CodeValue, tmpString) << "\"";
        stream << " codScheme=\"" << DSRTypes::convertToXMLString(CodingSchemeDesignator, tmpString) << "\"";
        if (!CodingSchemeVersion.empty() || (flags & DSRTypes::XF_writeEmptyTags))
            stream << " codVersion=\"" << DSRTypes::convertToXMLString(CodingSchemeVersion, tmpString) << "\"";
        stream << ">";      // close open bracket from calling routine
        stream << DSRTypes::convertToXMLString(CodeMeaning, tmpString);
    } else {
        DSRTypes::writeStringValueToXML(stream, CodeValue, "value", (flags & DSRTypes::XF_writeEmptyTags) > 0);
        stream << "<scheme>" << OFendl;
        DSRTypes::writeStringValueToXML(stream, CodingSchemeDesignator, "designator", (flags & DSRTypes::XF_writeEmptyTags) > 0);
        DSRTypes::writeStringValueToXML(stream, CodingSchemeVersion, "version", (flags & DSRTypes::XF_writeEmptyTags) > 0);
        stream << "</scheme>" << OFendl;
        DSRTypes::writeStringValueToXML(stream, CodeMeaning, "meaning", (flags & DSRTypes::XF_writeEmptyTags) > 0);
    }
    return EC_Normal;
}


OFCondition DSRCodedEntryValue::renderHTML(STD_NAMESPACE ostream &stream,
                                           const size_t flags,
                                           const OFBool fullCode,
                                           const OFBool valueFirst) const
{
    OFString htmlString;
    if (flags & DSRTypes::HF_useCodeDetailsTooltip)
    {
        /* render code details as a tooltip */
        stream << "<span title=\"(";
        stream << DSRTypes::convertToHTMLString(CodeValue, htmlString, flags) << ", ";
        stream << DSRTypes::convertToHTMLString(CodingSchemeDesignator, htmlString, flags);
        if (!CodingSchemeVersion.empty())
            stream << " [" << DSRTypes::convertToHTMLString(CodingSchemeVersion, htmlString, flags) << "]";
        stream << ", &quot;" << DSRTypes::convertToHTMLString(CodeMeaning, htmlString, flags) << "&quot;)\">";
        /* render value */
        if (valueFirst)
            stream << DSRTypes::convertToHTMLString(CodeValue, htmlString, flags);
        else
            stream << DSRTypes::convertToHTMLString(CodeMeaning, htmlString, flags);
        stream << "</span>";
    } else {
        /* render code in a conventional manner */
        if (valueFirst)
            stream << DSRTypes::convertToHTMLString(CodeValue, htmlString, flags);
        else
            stream << DSRTypes::convertToHTMLString(CodeMeaning, htmlString, flags);
        if (fullCode)
        {
            stream << " (";
            if (!valueFirst)
                stream << DSRTypes::convertToHTMLString(CodeValue, htmlString, flags) << ", ";
            stream << DSRTypes::convertToHTMLString(CodingSchemeDesignator, htmlString, flags);
            if (!CodingSchemeVersion.empty())
                stream << " [" << DSRTypes::convertToHTMLString(CodingSchemeVersion, htmlString, flags) << "]";
            if (valueFirst)
                stream << ", &quot;" << DSRTypes::convertToHTMLString(CodeMeaning, htmlString, flags) << "&quot;";
            stream << ")";
        }
    }
    return EC_Normal;
}


OFCondition DSRCodedEntryValue::getValue(DSRCodedEntryValue &codedEntryValue) const
{
    codedEntryValue = *this;
    return EC_Normal;
}


OFCondition DSRCodedEntryValue::setValue(const DSRCodedEntryValue &codedEntryValue,
                                         const OFBool check)
{
    /* first set "Basic Coded Entry Attributes" */
    OFCondition result = setCode(codedEntryValue.CodeValue,
                                 codedEntryValue.CodingSchemeDesignator,
                                 codedEntryValue.CodingSchemeVersion,
                                 codedEntryValue.CodeMeaning,
                                 codedEntryValue.CodeValueType,
                                 check);
    /* then handle "Enhanced Encoding Mode" (if present) */
    if (result.good())
    {
        if (!codedEntryValue.ContextIdentifier.empty())
        {
            /* specify details for a non-private context group */
            result = setEnhancedEncodingMode(codedEntryValue.ContextIdentifier,
                                             codedEntryValue.MappingResource,
                                             codedEntryValue.ContextGroupVersion,
                                             codedEntryValue.ContextUID,
                                             codedEntryValue.ContextGroupLocalVersion,
                                             codedEntryValue.ContextGroupExtensionCreatorUID,
                                             check);
        }
        else if (!codedEntryValue.ContextUID.empty())
        {
            /* specify details for a private context group */
            result = setEnhancedEncodingMode(codedEntryValue.ContextUID,
                                             check);
        }
    }
    return result;
}


OFCondition DSRCodedEntryValue::setCode(const DSRBasicCodedEntry &basicCodedEntry,
                                        const OFBool check)
{
    /* set "Basic Coded Entry Attributes" */
    return setCode(basicCodedEntry.CodeValue, basicCodedEntry.CodingSchemeDesignator, basicCodedEntry.CodingSchemeVersion,
                   basicCodedEntry.CodeMeaning, basicCodedEntry.CodeValueType, check);
}


OFCondition DSRCodedEntryValue::setCode(const OFString &codeValue,
                                        const OFString &codingSchemeDesignator,
                                        const OFString &codeMeaning,
                                        const DSRTypes::E_CodeValueType codeValueType,
                                        const OFExplicitBool check)
{
    /* call the real function */
    return setCode(codeValue, codingSchemeDesignator, "" /*codingSchemeVersion*/, codeMeaning, codeValueType, check);
}


OFCondition DSRCodedEntryValue::setCode(const OFString &codeValue,
                                        const OFString &codingSchemeDesignator,
                                        const OFString &codingSchemeVersion,
                                        const OFString &codeMeaning,
                                        const DSRTypes::E_CodeValueType codeValueType,
                                        const OFBool check)
{
    OFCondition result = EC_Normal;
    const DSRTypes::E_CodeValueType actualCodeValueType = (codeValueType == DSRTypes::CVT_auto) ? determineCodeValueType(codeValue) : codeValueType;
    if (check)
    {
        /* check whether the passed values are valid */
        result = checkCode(codeValue, codingSchemeDesignator, codingSchemeVersion, codeMeaning, actualCodeValueType);
    } else {
        /* make sure that the mandatory values are non-empty */
        if (codeValue.empty() || (codingSchemeDesignator.empty() && (actualCodeValueType != DSRTypes::CVT_URN)) || codeMeaning.empty())
            result = EC_IllegalParameter;
    }
    if (result.good())
    {
        /* copy "Basic Coded Entry Attributes" */
        CodeValueType = actualCodeValueType;
        CodeValue = codeValue;
        CodingSchemeDesignator = codingSchemeDesignator;
        CodingSchemeVersion = codingSchemeVersion;
        CodeMeaning = codeMeaning;
        /* clear attributes from "Enhanced Encoding Mode" */
        removeEnhancedEncodingMode();
    }
    return result;
}


OFBool DSRCodedEntryValue::usesEnhancedEncodingMode() const
{
    /* this is currently a very simple check */
    return !ContextIdentifier.empty() || !ContextUID.empty();
}


void DSRCodedEntryValue::removeEnhancedEncodingMode()
{
    ContextIdentifier.clear();
    ContextUID.clear();
    MappingResource.clear();
    ContextGroupVersion.clear();
    ContextGroupLocalVersion.clear();
    ContextGroupExtensionCreatorUID.clear();
}


OFCondition DSRCodedEntryValue::setEnhancedEncodingMode(const OFString &contextUID,
                                                        const OFBool check)
{
    OFCondition result = EC_Normal;
    /* first, make sure that the mandatory value is non-empty */
    if (contextUID.empty())
        result = EC_IllegalParameter;
    else if (check)
    {
        /* check whether the passed value is valid */
        result = DcmUniqueIdentifier::checkStringValue(contextUID, "1");
    }
    if (result.good())
    {
        ContextUID = contextUID;
        /* clear all other attributes */
        ContextIdentifier.clear();
        MappingResource.clear();
        ContextGroupVersion.clear();
        ContextGroupLocalVersion.clear();
        ContextGroupExtensionCreatorUID.clear();
    }
    return result;
}


OFCondition DSRCodedEntryValue::setEnhancedEncodingMode(const OFString &contextIdentifier,
                                                        const OFString &mappingResource,
                                                        const OFString &contextGroupVersion,
                                                        const OFString &contextUID,
                                                        const OFString &localVersion,
                                                        const OFString &extensionCreatorUID,
                                                        const OFBool check)
{
    OFCondition result = EC_Normal;
    /* first, make sure that the mandatory values are non-empty */
    if (contextIdentifier.empty() || mappingResource.empty() || contextGroupVersion.empty())
        result = EC_IllegalParameter;
    /* both values need to be empty or non-empty */
    else if (localVersion.empty() != extensionCreatorUID.empty())
        result = SR_EC_InvalidValue;
    else if (check)
    {
        /* check whether the passed values are valid */
        result = DcmCodeString::checkStringValue(contextIdentifier, "1");
        if (result.good())
            result = DcmCodeString::checkStringValue(mappingResource, "1");
        if (result.good())
            result = DcmDateTime::checkStringValue(contextGroupVersion, "1");
        if (result.good())
            result = DcmUniqueIdentifier::checkStringValue(contextUID, "1");
        if (result.good())
            result = DcmDateTime::checkStringValue(localVersion, "1");
        if (result.good())
            result = DcmUniqueIdentifier::checkStringValue(extensionCreatorUID, "1");
    }
    if (result.good())
    {
        ContextIdentifier = contextIdentifier;
        MappingResource = mappingResource;
        ContextGroupVersion = contextGroupVersion;
        ContextUID = contextUID;
        ContextGroupLocalVersion = localVersion;
        ContextGroupExtensionCreatorUID = extensionCreatorUID;
    }
    return result;
}


OFCondition DSRCodedEntryValue::checkCurrentValue() const
{
    /* call the real function */
    return checkCode(CodeValue, CodingSchemeDesignator, CodingSchemeVersion, CodeMeaning, CodeValueType);
}


// static functions

OFCondition DSRCodedEntryValue::checkCode(const OFString &codeValue,
                                          const OFString &codingSchemeDesignator,
                                          const OFString &codingSchemeVersion,
                                          const OFString &codeMeaning,
                                          const DSRTypes::E_CodeValueType codeValueType)
{
    OFCondition result = EC_Normal;
    /* first, make sure that the mandatory values are non-empty and the type is valid */
    if (codeValueType == DSRTypes::CVT_URN)
    {
        /* CP-1913: Coding Scheme Version shall not be present if Coding Scheme Designator is absent */
        if (codeValue.empty() || (codingSchemeDesignator.empty() && !codingSchemeVersion.empty()) || codeMeaning.empty())
            result = SR_EC_InvalidValue;
    }
    else if (codeValueType == DSRTypes::CVT_auto)
    {
        DCMSR_DEBUG("INTERNAL ERROR: DSRCodedEntryValue::checkCode() called with DSRTypes::CVT_auto");
        result = EC_IllegalCall;
    } else {
        /* short or long code value */
        if (codeValue.empty() || codingSchemeDesignator.empty() || codeMeaning.empty())
            result = SR_EC_InvalidValue;
    }
    /* then, check whether the passed values are valid with regards to VR and VM.
     * tbd: unfortunately, we do not know the character set, so "UNKNOWN" is used. */
    if (result.good())
    {
        if (codeValueType == DSRTypes::CVT_Long)
            result = DcmUnlimitedCharacters::checkStringValue(codeValue, "1", "UNKNOWN");
        else if (codeValueType == DSRTypes::CVT_URN)
            result = DcmUniversalResourceIdentifierOrLocator::checkStringValue(codeValue);
        else /* short or auto */
            result = DcmShortString::checkStringValue(codeValue, "1", "UNKNOWN");
    }
    if (result.good())
        result = DcmShortString::checkStringValue(codingSchemeDesignator, "1", "UNKNOWN");
    if (result.good())
        result = DcmShortString::checkStringValue(codingSchemeVersion, "1", "UNKNOWN");
    if (result.good())
        result = DcmLongString::checkStringValue(codeMeaning, "1", "UNKNOWN");
    /* tbd: also need to check correctness of the code (requires code dictionary) */
    return result;
}


DSRTypes::E_CodeValueType DSRCodedEntryValue::determineCodeValueType(const OFString &codeValue)
{
    DSRTypes::E_CodeValueType codeValueType = DSRTypes::CVT_auto;
    /* first, check for URN or URL scheme (not perfect but should do in most cases) */
    if ((codeValue.compare(0, 4, "urn:") == 0) || (codeValue.find("://") != OFString_npos))
        codeValueType = DSRTypes::CVT_URN;
    /* then, check maximum value length (tbd: should be characters not bytes!) */
    else if (codeValue.length() > 16)
        codeValueType = DSRTypes::CVT_Long;
    else
        codeValueType = DSRTypes::CVT_Short;
    return codeValueType;
}


// output operators

STD_NAMESPACE ostream &operator<<(STD_NAMESPACE ostream &stream,
                                  const DSRCodedEntryValue& codedEntryValue)
{
    codedEntryValue.print(stream, OFTrue /*printCodeValue*/, DSRTypes::PF_printEmptyCodes /*flags*/);
    return stream;
}
