/*
 *
 *  Copyright (C) 1994-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:  dcmdata
 *
 *  Author:  Gerd Ehlers, Andreas Barth
 *
 *  Purpose: Interface of class DcmByteString
 *
 */


#ifndef DCBYTSTR_H
#define DCBYTSTR_H

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

#include "dcmtk/dcmdata/dctypes.h"
#include "dcmtk/dcmdata/dcelem.h"
#include "dcmtk/ofstd/ofstring.h"

// forward declarations
class DcmJsonFormat;

// include this file in doxygen documentation

/** @file dcbytstr.h
 *  @brief base class for all DICOM value representations storing a character string
 */

/** This flag defines whether the VR checker is actually used by the various
 *  checkStringValue() methods.  Since this checker is currently limited to
 *  ASCII and Latin-1, and the detection of the character set might fail for
 *  incorrectly encoded DICOM datasets, this check can be disabled globally.
 *  Please note, however, that other checks (i.e. VM and max. value length)
 *  are still performed if the optional parameters are set accordingly.
 *  By default, the VR checker is enabled (value: "OFTrue").
 */
extern DCMTK_DCMDATA_EXPORT OFGlobal<OFBool> dcmEnableVRCheckerForStringValues;  /* default: OFTrue */


/** base class for all DICOM value representations storing a character string
 */
class DCMTK_DCMDATA_EXPORT DcmByteString: public DcmElement
{

 public:

    // Make friend with DcmItem which requires access to protected
    // constructor allowing construction using an explicit value length.
    friend class DcmItem;

    /** constructor.
     *  Create new element from given tag.
     *  @param tag DICOM tag for the new element
     */
    DcmByteString(const DcmTag &tag);

    /** copy constructor
     *  @param old element to be copied
     */
    DcmByteString(const DcmByteString &old);

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

    /** assignment operator
     *  @param obj element to be assigned/copied
     *  @return reference to this object
     */
    DcmByteString& operator=(const DcmByteString& obj);

    /** comparison operator that compares the normalized value of this object
     *  with a given object of the same type. The tag of the element is also
     *  considered as the first component that is compared, followed by the
     *  object types (VR, i.e. DCMTK'S EVR) and the comparison of all value
     *  components of the object, preferably in the order declared in the
     *  object (if applicable).
     *  @param  rhs the right hand side of the comparison
     *  @return 0 if the object values are equal.
     *    -1 if this element has fewer components than the rhs element.
     *    Also -1 if the value of the first component that does not match
     *    is lower in this object than in rhs. Also returned if rhs
     *    cannot be casted to this object type or both objects are of
     *    different VR (i.e. the DcmEVR returned by the element's ident()
     *    call are different).
     *    1 if either this element has more components than the rhs element, or
     *    if the first component that does not match is greater in this object
     *    than in rhs object.
     */
    virtual int compare(const DcmElement& rhs) const;


    /** clone method
     *  @return deep copy of this object
     */
    virtual DcmObject *clone() const
    {
      return new DcmByteString(*this);
    }

    /** Virtual object copying. This method can be used for DcmObject
     *  and derived classes to get a deep copy of an object. Internally
     *  the assignment operator is called if the given DcmObject parameter
     *  is of the same type as "this" object instance. If not, an error
     *  is returned. This function permits copying an object by value
     *  in a virtual way which therefore is different to just calling the
     *  assignment operator of DcmElement which could result in slicing
     *  the object.
     *  @param rhs - [in] The instance to copy from. Has to be of the same
     *                class type as "this" object
     *  @return EC_Normal if copying was successful, error otherwise
     */
    virtual OFCondition copyFrom(const DcmObject& rhs);

    /** get element type identifier
     *  @return type identifier of this class (EVR_UNKNOWN)
     */
    virtual DcmEVR ident() const;

    /** clear the currently stored value
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition clear();

    /** get value multiplicity
     *  @return number of string components (separated by a backslash)
     */
    virtual unsigned long getVM();

    /** get number of values stored in this element.
     *  The result is the same as getVM() unless overwritten in a derived class.
     *  @return number of values in this element
     */
    virtual unsigned long getNumberOfValues();

    /** get length of the stored value.
     *  Trailing spaces (padding characters) are ignored for the "real" length.
     *  @return number of characters stored for the string value
     */
    Uint32 getRealLength();

    /** get DICOM length of the stored value.
     *  The string value is padded if required.  Therefore, the returned length
     *  always has an even value.
     *  @param xfer not used
     *  @param enctype not used
     *  @return number of characters stored in DICOM representation
     */
    virtual Uint32 getLength(const E_TransferSyntax xfer = EXS_LittleEndianImplicit,
                             const E_EncodingType enctype = EET_UndefinedLength);

    /** print element to a stream.
     *  The output format of the value is a backslash separated sequence of string
     *  components (if any).
     *  @param out output stream
     *  @param flags optional flag used to customize the output (see DCMTypes::PF_xxx)
     *  @param level current level of nested items. Used for indentation.
     *  @param pixelFileName not used
     *  @param pixelCounter not used
     */
    virtual void print(STD_NAMESPACE ostream &out,
                       const size_t flags = 0,
                       const int level = 0,
                       const char *pixelFileName = NULL,
                       size_t *pixelCounter = NULL);

    /** write data element to a stream
     *  @param outStream output stream
     *  @param oxfer transfer syntax used to write the data
     *  @param enctype flag, specifying the encoding with undefined or explicit length
     *  @param wcache pointer to write cache object, may be NULL
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition write(DcmOutputStream &outStream,
                              const E_TransferSyntax oxfer,
                              const E_EncodingType enctype,
                              DcmWriteCache *wcache);

    /** write data element to a stream as required for the creation of digital signatures
     *  @param outStream output stream
     *  @param oxfer transfer syntax used to write the data
     *  @param enctype flag, specifying the encoding with undefined or explicit length
     *  @param wcache pointer to write cache object, may be NULL
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition writeSignatureFormat(DcmOutputStream &outStream,
                                             const E_TransferSyntax oxfer,
                                             const E_EncodingType enctype,
                                             DcmWriteCache *wcache);

    /** get a copy of a particular string component
     *  @param stringVal variable in which the result value is stored
     *  @param pos index of the value in case of multi-valued elements (0..vm-1)
     *  @param normalize not used since string normalization depends on value representation
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition getOFString(OFString &stringVal,
                                    const unsigned long pos,
                                    OFBool normalize = OFTrue);

    /** get entire element value as a character string.
     *  In case of VM > 1 the individual values are separated by a backslash ('\').
     *  @param stringVal variable in which the result value is stored
     *  @param normalize normalize each element value prior to concatenation
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition getOFStringArray(OFString &stringVal,
                                         OFBool normalize = OFTrue);

    /** get a pointer to the current string value.
     *  This includes all string components and separators.
     *  NB: This method does not copy the stored value.
     *  @param stringVal reference to the pointer variable
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition getString(char *&stringVal);

    /** get a pointer to the current string value.
     *  This includes all string components and separators. Since the length is returned
     *  separately, the string value can contain more than one NULL byte.
     *  NB: This method does not copy the stored value.
     *  @param stringVal reference to the pointer variable
     *  @param stringLen length of the string (number of characters without the trailing
     *    NULL byte)
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition getString(char *&stringVal,
                                  Uint32 &stringLen);

    /** set element value from the given character string.
     *  The length of the given string is determined automatically by searching for the
     *  first NULL byte.
     *  @param stringVal input character string (possibly multi-valued)
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition putString(const char *stringVal);

    /** set element value from the given character string.
     *  The length of the string has to be specified explicitly. The string can, therefore,
     *  also contain more than one NULL byte.
     *  @param stringVal input character string (possibly multi-valued)
     *  @param stringLen length of the string (number of characters without the trailing
     *    NULL byte)
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition putString(const char *stringVal,
                                  const Uint32 stringLen);

    /** check the currently stored string value.
     *  Checks every string component for the maximum length specified for the particular
     *  value representation.
     *  @param autocorrect correct value and value component length if OFTrue
     *  @return status, EC_Normal if value length is correct, an error code otherwise
     */
    virtual OFCondition verify(const OFBool autocorrect = OFFalse);

    /** check if this element contains non-ASCII characters. Please note that this check
     *  is pretty simple and only works for single-byte character sets that do include
     *  the 7-bit ASCII codes, e.g. for the ISO 8859 family. In other words: All character
     *  codes below 128 are considered to be ASCII codes and all others are considered to
     *  be non-ASCII.
     *  @param checkAllStrings if true, also check elements with string values not affected
     *    by SpecificCharacterSet (0008,0005). By default, only check PN, LO, LT, SH, ST,
     *    UC and UT, i.e. none of the derived VR classes.
     *  @return true if element contains non-ASCII characters, false otherwise
     */
    virtual OFBool containsExtendedCharacters(const OFBool checkAllStrings = OFFalse);

    /** check if this element is affected by SpecificCharacterSet
     *  @return always returns false since none of the derived VR classes is affected by
     *    the SpecificCharacterSet (0008,0005) element
     */
    virtual OFBool isAffectedBySpecificCharacterSet() const;

    /** check if this object is empty
     *  @param normalize normalize value before checking (ignore non-significant characters)
     *  @return true if object is empty, i.e.\ has no value, false otherwise
     */
    virtual OFBool isEmpty(const OFBool normalize = OFTrue);

    /** write object in JSON format
     *  @param out output stream to which the JSON document is written
     *  @param format used to format and customize the output
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition writeJson(STD_NAMESPACE ostream &out,
                                  DcmJsonFormat &format);

    /// @copydoc DcmElement::matches()
    virtual OFBool matches(const DcmElement& candidate,
                           const OFBool enableWildCardMatching = OFTrue) const;

    /** perform attribute matching on a single pair of string values.
     *  Compare two single string values using the attribute matching function appropriate for
     *  this element's VR (Universal Matching, Single Value Matching, Wild Card Matching or
     *  Range Matching).
     *  @note This method is called by the other overload of matches() for each combination of
     *    values in the key and candidate element (implementing multi value matching for
     *    elements with VM>1).
     *  @param key the key value to match against the candidate.
     *  @param candidate the candidate value to match the key against.
     *  @param enableWildCardMatching enable or disable wild card matching. Defaults to OFTrue,
     *    which means wild card matching is performed if the element's VR supports it. Set to
     *    OFFalse to force single value matching instead.
     *  @return OFTrue if the candidate string matches the key string, OFFalse otherwise.
     */
    virtual OFBool matches(const OFString& key,
                           const OFString& candidate,
                           const OFBool enableWildCardMatching = OFTrue) const;

 protected:

    /** constructor. Create new element from given tag and length.
     *  Only reachable from friend classes since construction with
     *  length different from 0 leads to a state with length being set but
     *  the element's value still being uninitialized. This can lead to crashes
     *  when the value is read or written. Thus the method calling this
     *  constructor with length > 0 must ensure that the element's value is
     *  explicitly initialized, too.
     *  @param tag DICOM tag for the new element
     *  @param len value length for the new element
     */
    DcmByteString(const DcmTag &tag,
                  const Uint32 len);

    /// internal type used to specify the current string representation
    enum E_StringMode
    {
        /// string has internal representation (no padding)
        DCM_MachineString,
        /// string has DICOM representation (even length)
        DCM_DicomString,
        /// string has unknown representation (maybe multiple padding chars?)
        DCM_UnknownString
    };

    /** create a new value field (string buffer) of the previously defined size
     *  (member variable 'Length'). Also handles odd value length by allocating
     *  extra space for the padding character.
     *  This method is used by derived classes only.
     *  @return pointer to the newly created value field
     */
    virtual Uint8 *newValueField();

    /** method is called after the element value has been loaded.
     *  Can be used to correct the value before it is used for the first time.
     */
    virtual void postLoadValue();

    /** get current representation of the string value
     *  @return current representation of the string value
     */
    E_StringMode getStringMode() const { return fStringMode; }

    /** convert currently stored string value to internal representation.
     *  It removes any trailing space character and recomputes the string length.
     *  @param length number of characters of the string value (optional)
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition makeMachineByteString(const Uint32 length = 0);

    /** convert currently stored string value to DICOM representation.
     *  It removes trailing spaces apart from a possibly required single padding
     *  character (in case of odd string length).
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition makeDicomByteString();

    /** get a copy of the current string value.
     *  This includes all string components and separators.
     *  @param stringVal variable in which the result is stored
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition getStringValue(OFString &stringVal);

    /** get the maximum number of characters for each string component
     *  @return maximum number of characters for each string component
     */
    Uint32 getMaxLength() const { return maxLength; }

    /** get the end-of-string padding character
     *  @return end-of-string padding character
     */
    char getPaddingChar() const { return paddingChar; }

    /** set the end-of-string padding character
     *  @param c end-of-string padding character
     */
    void setPaddingChar(char c) { paddingChar = c; }

    /** set the maximum number of characters for each string component
     *  @param val maximum number of characters for each string component
     */
    void setMaxLength(Uint32 val) { maxLength = val; }

    /** set non-significant characters used to determine whether the value is empty
     *  @param characters non-significant characters used to determine whether the value is empty
     */
    void setNonSignificantChars(const OFString &characters) { nonSignificantChars = characters; }

    /* --- static helper functions --- */

    /** check if a given character string contains non-ASCII characters.
     *  Please note that this check is pretty simple and only works for single-byte character
     *  sets that do include the 7-bit ASCII codes, e.g. for the ISO 8859 family. In other
     *  words: All character codes below 128 are considered to be ASCII codes and all others
     *  are considered to be non-ASCII.
     *  @param stringVal character string to be checked
     *  @param stringLen length of the string (number of characters without the trailing
     *    NULL byte)
     * @return true if character string contains non-ASCII characters, false otherwise
     */
    static OFBool containsExtendedCharacters(const char *stringVal,
                                             const size_t stringLen);

    /** check whether given string value conforms to a certain VR and VM.
     *  @param value string value to be checked (possibly multi-valued)
     *  @param vm value multiplicity (according to the data dictionary) to be checked for.
     *    (See DcmElement::checkVM() for a list of valid values; "" for no check.)
     *  @param vr two-character identifier of the VR to be checked (lower case)
     *  @param vrID expected numeric identifier of the VR
     *  @param maxLen maximum number of characters allowed for a single value (0 = no check)
     *  @param charset character set (according to the value of the SpecificCharacterSet
     *    element) to be used for checking the string value. The default is ASCII (7-bit).
     *    Currently, the VR checker only supports ASCII (ISO_IR 6) and Latin-1 (ISO_IR 100).
     *    All other values disable the check of the value representation, e.g. "UNKNOWN".
     *  @return status of the check, EC_Normal if value is correct, an error code otherwise
     */
    static OFCondition checkStringValue(const OFString &value,
                                        const OFString &vm,
                                        const OFString &vr,
                                        const int vrID,
                                        const size_t maxLen = 0,
                                        const OFString &charset = "");

private:

    /// padding character used to adjust odd value length (e.g. a space)
    char paddingChar;

    /// maximum number of characters for each string component
    Uint32 maxLength;

    /// number of characters of the internal string representation
    Uint32 realLength;

    /// current representation of the string value
    E_StringMode fStringMode;

    /// non-significant characters used to determine whether the value is empty
    OFString nonSignificantChars;
};


/** @name string normalization flags.
 *  These flags can be used with normalizeString() to specify the extent of normalization.
 */
//@{

/// delete trailing spaces
const OFBool DELETE_TRAILING = OFTrue;
/// delete leading spaces
const OFBool DELETE_LEADING = OFTrue;
/// handle string as multi-valued (components separated by a backslash)
const OFBool MULTIPART = OFTrue;

//@}


/* Function to get part out of a String for VM > 1 */

/** normalize the given string value, i.e.\ remove leading and/or trailing padding
 *  @param string input and output string value to be normalized
 *  @param multiPart handle string as multi-valued if OFTrue
 *  @param leading delete leading spaces if OFTrue
 *  @param trailing delete trailing spaces if OFTrue
 *  @param paddingChar padding character to be removed (usually a space)
 */
DCMTK_DCMDATA_EXPORT void normalizeString(OFString &string,
                     const OFBool multiPart,
                     const OFBool leading,
                     const OFBool trailing,
                     const char paddingChar = ' ');


#endif // DCBYTSTR_H
