/*
 *
 *  Copyright (C) 2002-2016, 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: DSRSOPInstanceReferenceList
 *             - InstanceStruct, SeriesStruct, StudyStruct
 *
 */


#ifndef DSRSOPRF_H
#define DSRSOPRF_H

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

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


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

/** Class for SOP instance reference lists
 */
class DCMTK_DCMSR_EXPORT DSRSOPInstanceReferenceList
  : public DSRTypes
{

  public:

    /** constructor
     ** @param  sequence  DICOM tag specifying the attribute (sequence) of the reference list
     */
    DSRSOPInstanceReferenceList(const DcmTagKey &sequence);

    /** destructor
     */
    ~DSRSOPInstanceReferenceList();

    /** clear list of references
     */
    void clear();

    /** check whether list of references is empty
     ** @return OFTrue if list is empty, OFFalse otherwise
     */
    OFBool isEmpty() const;

    /** get number of instance stored in the list of references
     ** @return number of instances
     */
    size_t getNumberOfInstances() const;

    /** read list of referenced SOP instances.
     *  The hierarchical structure is automatically reorganized in a way that each study,
     *  each series (within a study) and each instance (within a series) only exist once,
     *  i.e. the structure might look different when written back to a dataset.  However,
     *  the content is identical and this way of storing information saves storage space.
     ** @param  dataset  DICOM dataset from which the data should be read
     *  @param  flags    flag used to customize the reading process (see DSRTypes::RF_xxx)
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition read(DcmItem &dataset,
                     const size_t flags);

    /** write list of referenced SOP instances.
     *  Does nothing if list is empty.
     ** @param  dataset  DICOM dataset to which the data should be written
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition write(DcmItem &dataset) const;

    /** read list of references from XML document.
     *  The hierarchical structure is automatically reorganized in a way that each study,
     *  each series (within a study) and each instance (within a series) only exist once,
     *  i.e. the structure might look different when written back to a dataset.  However,
     *  the content is identical and this way of storing information saves storage space.
     ** @param  doc     document containing the XML file content
     *  @param  cursor  cursor pointing to the starting node
     *  @param  flags   flag used to customize the reading process (see DSRTypes::XF_xxx)
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition readXML(const DSRXMLDocument &doc,
                        DSRXMLCursor cursor,
                        const size_t flags);

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

    /** set specific character set, which is used for checking the affected element values.
     *  Please note that this method does not return an error if the given 'value' is not
     *  defined by the DICOM standard or not supported by this implementation.
     ** @param  value  value to be set (single or multiple values) or "" for no value
     *  @param  check  check 'value' for conformance with VR (CS) and VM (1-n) if enabled
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition setSpecificCharacterSet(const OFString &value,
                                        const OFBool check = OFTrue);

    /** add the specified item to the list of references.
     *  Before adding the item, the given UID values are usually checked.  If one of the
     *  values is invalid, nothing is done but an error is returned.  If successful, the
     *  item is inserted into the hierarchical structure of studies, series and instances,
     *  if not already contained in the list. In any case, the specified item is selected
     *  as the current one.
     ** @param  studyUID     study instance UID of the entry to be added
     *  @param  seriesUID    series instance UID of the entry to be added
     *  @param  sopClassUID  SOP class UID of the entry to be added
     *  @param  instanceUID  SOP instance UID of the entry to be added
     *  @param  check        if enabled, check values for validity before adding them.
     *                       See checkSOPInstance() method for details.  Empty values are
     *                       never accepted.
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition addItem(const OFString &studyUID,
                        const OFString &seriesUID,
                        const OFString &sopClassUID,
                        const OFString &instanceUID,
                        const OFBool check = OFTrue);

    /** add item from specified DICOM dataset to the list of references.
     *  Internally, an item representing the given dataset is inserted into the hierarchical
     *  structure of studies, series and instances, if not already contained in the list.
     *  In any case, the specified item is selected as the current one.
     ** @param  dataset  reference to DICOM dataset from which the relevant UIDs are retrieved
     *  @param  check    if enabled, check values for validity before adding them.  See
     *                   checkSOPInstance() method for details.  An empty value is never
     *                   accepted.
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition addItem(DcmItem &dataset,
                        const OFBool check = OFTrue);

    /** remove the current item from the list of referenced SOP instances.
     *  After successful removal the cursor is set to the next valid position.
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition removeItem();

    /** remove the specified item from the list of references.
     *  After successful removal the cursor is set to the next valid position.
     ** @param  sopClassUID  SOP class UID of the item to be removed
     *  @param  instanceUID  SOP instance UID of the item to be removed
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition removeItem(const OFString &sopClassUID,
                           const OFString &instanceUID);

    /** remove the specified item from the list of references.
     *  After successful removal the cursor is set to the next valid position.
     ** @param  studyUID     study instance UID of the item to be removed
     *  @param  seriesUID    series instance UID of the item to be removed
     *  @param  instanceUID  SOP instance UID of the item to be removed
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition removeItem(const OFString &studyUID,
                           const OFString &seriesUID,
                           const OFString &instanceUID);

    /** select the specified item as the current one
     ** @param  sopClassUID  SOP class UID of the item to be selected
     *  @param  instanceUID  SOP instance UID of the item to be selected
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition gotoItem(const OFString &sopClassUID,
                         const OFString &instanceUID);

    /** select the specified item as the current one
     ** @param  studyUID     study instance UID of the item to be selected
     *  @param  seriesUID    series instance UID of the item to be selected
     *  @param  instanceUID  SOP instance UID of the item to be selected
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition gotoItem(const OFString &studyUID,
                         const OFString &seriesUID,
                         const OFString &instanceUID);

    /** select the first item in the list.
     *  That means the first instance in the first series of the first study
     *  is selected (if available).
     ** @return status, EC_Normal if successful, an error code otherwise.
     *    (e.g. if the list is empty)
     */
    OFCondition gotoFirstItem();

    /** select the next item in the list.
     *  That means the next instance in the current series, or the first instance
     *  in the next series, or the first instance in the first series of the next
     *  study is selected (if available).  The combination of this function and
     *  gotoFirstItem() allows to iterate over all referenced SOP instances.
     ** @return status, EC_Normal if successful, an error code otherwise.
     *    (e.g. if the end of the list has been reached)
     */
    OFCondition gotoNextItem();

    /** get the study instance UID of the currently selected entry
     ** @param  stringValue  reference to string variable in which the result is stored
     ** @return reference to the resulting string (might be empty)
     */
    const OFString &getStudyInstanceUID(OFString &stringValue) const;

    /** get the series instance UID of the currently selected entry
     ** @param  stringValue  reference to string variable in which the result is stored
     ** @return reference to the resulting string (might be empty)
     */
    const OFString &getSeriesInstanceUID(OFString &stringValue) const;

    /** get the SOP instance UID of the currently selected entry
     ** @param  stringValue  reference to string variable in which the result is stored
     ** @return reference to the resulting string (might be empty)
     */
    const OFString &getSOPInstanceUID(OFString &stringValue) const;

    /** get the SOP class UID of the currently selected entry
     ** @param  stringValue  reference to string variable in which the result is stored
     ** @return reference to the resulting string (might be empty)
     */
    const OFString &getSOPClassUID(OFString &stringValue) const;

    /** get name associated with the SOP class UID of the currently selected entry
     ** @param  stringValue  reference to string variable in which the result is stored
     *  @param  defaultName  string value that is returned if the SOP class UID is unknown
     ** @return reference to the resulting string (might be empty even if 'defaultName' is
     *          non-empty, e.g. in case no entry is currently selected)
     */
    const OFString &getSOPClassName(OFString &stringValue,
                                    const OFString &defaultName = "unknown SOP Class UID") const;

    /** get the retrieve application entity title of the currently selected entry (optional).
     *  The resulting string may contain multiple values separated by a backslash ("\").
     ** @param  stringValue  reference to string variable in which the result is stored
     ** @return reference to the resulting string (might be empty)
     */
    const OFString &getRetrieveAETitle(OFString &stringValue) const;

    /** get the retrieve location UID of the currently selected entry (optional)
     ** @param  stringValue  reference to string variable in which the result is stored
     ** @return reference to the resulting string (might be empty)
     */
    const OFString &getRetrieveLocationUID(OFString &stringValue) const;

    /** get the storage media file set ID of the currently selected entry (optional)
     ** @param  stringValue  reference to string variable in which the result is stored
     ** @return reference to the resulting string (might be empty)
     */
    const OFString &getStorageMediaFileSetID(OFString &stringValue) const;

    /** get the storage media file set UID of the currently selected entry (optional)
     ** @param  stringValue  reference to string variable in which the result is stored
     ** @return reference to the resulting string (might be empty)
     */
    const OFString &getStorageMediaFileSetUID(OFString &stringValue) const;

    /** get purpose of reference code of the currently selected entry (optional)
     ** @param  codeValue  variable where a copy of the code should be stored
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition getPurposeOfReference(DSRCodedEntryValue &codeValue) const;

    /** set the retrieve application entity title of the currently selected entry
     ** @param  value  string value to be set (possibly multi-valued) or "" for no value
     *  @param  check  check 'value' for conformance with VR (AE) and VM (1-n) if enabled
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition setRetrieveAETitle(const OFString &value,
                                   const OFBool check = OFTrue);

    /** set the retrieve location UID of the currently selected entry
     ** @param  value  string value to be set (single value only) or "" for no value
     *  @param  check  check 'value' for conformance with VR (UI) and VM (1) if enabled
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition setRetrieveLocationUID(const OFString &value,
                                       const OFBool check = OFTrue);

    /** set the storage media file set ID of the currently selected entry
     ** @param  value  string value to be set (single value only) or "" for no value
     *  @param  check  check 'value' for conformance with VR (SH) and VM (1) if enabled
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition setStorageMediaFileSetID(const OFString &value,
                                         const OFBool check = OFTrue);

    /** set the storage media file set UID of the currently selected entry
     ** @param  value  string value to be set (single value only) or "" for no value
     *  @param  check  check 'value' for conformance with VR (UI) and VM (1) if enabled
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition setStorageMediaFileSetUID(const OFString &value,
                                          const OFBool check = OFTrue);

    /** set purpose of reference code of the currently selected entry.
     *  Before setting the value, it is usually checked.  If the value is invalid, the
     *  current value is not replaced and remains unchanged.
     ** @param  codeValue  value to be set (optional).  Use an empty code to remove the
     *                     current value.
     *  @param  check      if enabled, check value for validity before setting it.  See
     *                     checkPurposeOfReference() method for details.
     ** @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition setPurposeOfReference(const DSRCodedEntryValue &codeValue,
                                      const OFBool check = OFTrue);


  protected:

    /** Internal structure defining the instance list items
     */
    struct DCMTK_DCMSR_EXPORT InstanceStruct
    {
        /** constructor
         ** @param  sopClassUID  SOP class UID
         ** @param  instanceUID  SOP instance UID
         */
        InstanceStruct(const OFString &sopClassUID,
                       const OFString &instanceUID);

        /** clear additional information
         */
        void clear();

        /// SOP Class UID (VR=UI, type 1)
        const OFString SOPClassUID;
        /// SOP Instance UID (VR=UI, type 1)
        const OFString InstanceUID;
        /// Purpose of Reference Code Sequence (VR=SQ, type 3).
        /// NB: According to the DICOM standard, "One or more Items are permitted in this
        ///     sequence."  However, the current implementation only supports a single
        ///     item.  Also see documentation of DSRCodedEntryValue::readSequence().
        DSRCodedEntryValue PurposeOfReference;
    };

    /** Internal structure defining the series list items
     */
    struct DCMTK_DCMSR_EXPORT SeriesStruct
    {
        /** constructor
         ** @param  seriesUID  series instance UID
         */
        SeriesStruct(const OFString &seriesUID);

        /** destructor
         */
        ~SeriesStruct();

        /** get number of instance stored in the list of instances
         ** @return number of instances
         */
        size_t getNumberOfInstances() const;

        /** read instance level attributes from dataset
         ** @param  dataset  DICOM dataset from which the list should be read
         *  @param  flags    flag used to customize the reading process (see DSRTypes::RF_xxx)
         ** @return status, EC_Normal if successful, an error code otherwise
         */
        OFCondition read(DcmItem &dataset,
                         const size_t flags);

        /** write series and instance level attributes to dataset
         ** @param  dataset  DICOM dataset to which the list should be written
         ** @return status, EC_Normal if successful, an error code otherwise
         */
        OFCondition write(DcmItem &dataset) const;

        /** read series and instance level attributes from XML document
         ** @param  doc     document containing the XML file content
         *  @param  cursor  cursor pointing to the starting node
         *  @param  flags   flag used to customize the reading process (see DSRTypes::XF_xxx)
         ** @return status, EC_Normal if successful, an error code otherwise
         */
        OFCondition readXML(const DSRXMLDocument &doc,
                            DSRXMLCursor cursor,
                            const size_t flags);

        /** write series and instance level attributes in XML format
         ** @param  stream  output stream to which the XML document is written
         *  @param  flags   flag used to customize the output (see DSRTypes::XF_xxx)
         ** @return status, EC_Normal if successful, an error code otherwise
         */
        OFCondition writeXML(STD_NAMESPACE ostream &stream,
                             const size_t flags) const;

        /** set cursor to the specified instance (if existent)
         ** @param  instanceUID  SOP instance UID of the entry to be searched for
         ** @return pointer to the instance structure if successful, NULL otherwise
         */
        InstanceStruct *gotoInstance(const OFString &instanceUID);

        /** select the first item in the list.
         *  That means the first instance in the current series.
         ** @return status, EC_Normal if successful, an error code otherwise
         */
        OFCondition gotoFirstItem();

        /** select the next item in the list.
         *  That means the next instance in the current series (if available).
         ** @return status, EC_Normal if successful, an error code otherwise
         */
        OFCondition gotoNextItem();

        /** add new entry to the list of instances (if not already existent).
         *  Finally, the specified item is selected as the current one.
         ** @param  sopClassUID  SOP class UID of the entry to be added
         *  @param  instanceUID  SOP instance UID of the entry to be added
         ** @return status, EC_Normal if successful, an error code otherwise
         */
        OFCondition addItem(const OFString &sopClassUID,
                            const OFString &instanceUID);

        /** remove the current item from the list of instances.
         *  After successful removal the cursor is set to the next valid position.
         ** @return status, EC_Normal if successful, an error code otherwise
         */
        OFCondition removeItem();

        /// Series Instance UID (VR=UI, VM=1, type 1)
        const OFString SeriesUID;
        /// Retrieve Application Entity Title (VR=AE, VM=1-n, type 3)
        OFString RetrieveAETitle;
        /// Retrieve Location UID (VR=UI, VM=1, type 3)
        OFString RetrieveLocationUID;
        /// Storage Media File Set ID (VR=SH, VM=1, type 3)
        OFString StorageMediaFileSetID;
        /// Storage Media File Set UID (VR=UI, VM=1, type 3)
        OFString StorageMediaFileSetUID;

        /// list of referenced instances
        OFList<InstanceStruct *> InstanceList;
        /// currently selected instance (cursor)
        OFListIterator(InstanceStruct *) Iterator;
    };

    /** To make VC6 happy.
     */
    friend struct SeriesStruct;

    /** Internal structure defining the study list items
     */
    struct DCMTK_DCMSR_EXPORT StudyStruct
    {
        /** constructor
         ** @param  studyUID  study instance UID
         */
        StudyStruct(const OFString &studyUID);

        /** destructor
         */
        ~StudyStruct();

        /** get number of instance stored in the list of series
         ** @return number of instances
         */
        size_t getNumberOfInstances() const;

        /** read series and instance level from dataset
         ** @param  dataset  DICOM dataset from which the list should be read
         *  @param  flags    flag used to customize the reading process (see DSRTypes::RF_xxx)
         ** @return status, EC_Normal if successful, an error code otherwise
         */
        OFCondition read(DcmItem &dataset,
                         const size_t flags);

        /** write study, series and instance level attributes to dataset
         ** @param  dataset  DICOM dataset to which the list should be written
         ** @return status, EC_Normal if successful, an error code otherwise
         */
        OFCondition write(DcmItem &dataset) const;

        /** read study, series and instance level attributes from XML document
         ** @param  doc     document containing the XML file content
         *  @param  cursor  cursor pointing to the starting node
         *  @param  flags   flag used to customize the reading process (see DSRTypes::XF_xxx)
         ** @return status, EC_Normal if successful, an error code otherwise
         */
        OFCondition readXML(const DSRXMLDocument &doc,
                            DSRXMLCursor cursor,
                            const size_t flags);

        /** write study, series and instance level attributes in XML format
         ** @param  stream  output stream to which the XML document is written
         *  @param  flags   flag used to customize the output (see DSRTypes::XF_xxx)
         ** @return status, EC_Normal if successful, an error code otherwise
         */
        OFCondition writeXML(STD_NAMESPACE ostream &stream,
                             const size_t flags) const;

        /** set cursor to the specified series entry (if existent)
         ** @param  seriesUID  series instance UID of the entry to be searched for
         ** @return pointer to the series structure if successful, NULL otherwise
         */
        SeriesStruct *gotoSeries(const OFString &seriesUID);

        /** set cursor to the specified instance entry (if existent)
         ** @param  instanceUID  SOP instance UID of the entry to be searched for
         ** @return pointer to the instance structure if successful, NULL otherwise
         */
        InstanceStruct *gotoInstance(const OFString &instanceUID);

        /** select the first item in the list.
         *  That means the first instance in the first series of the current study.
         ** @return status, EC_Normal if successful, an error code otherwise
         */
        OFCondition gotoFirstItem();

        /** select the next item in the list.
         *  That means the next instance in the current series, or the first instance
         *  in the next series (if available).
         ** @return status, EC_Normal if successful, an error code otherwise
         */
        OFCondition gotoNextItem();

        /** add new entry to the list of series and instances (if not already existent).
         *  Finally, the specified items are selected as the current one.
         ** @param  seriesUID    series instance UID of the entry to be added
         *  @param  sopClassUID  SOP class UID of the entry to be added
         *  @param  instanceUID  SOP instance UID of the entry to be added
         ** @return status, EC_Normal if successful, an error code otherwise
         */
        OFCondition addItem(const OFString &seriesUID,
                            const OFString &sopClassUID,
                            const OFString &instanceUID);

        /** remove the current item from the list of series and instances.
         *  After successful removal the cursors are set to the next valid position.
         ** @return status, EC_Normal if successful, an error code otherwise
         */
        OFCondition removeItem();

        /** remove empty/incomplete items from the list.
         *  (e.g. series with no instances)
         *  Please note that this function modifies the value of 'Iterator'.
         */
        void removeIncompleteItems();

        /// Study Instance UID (VR=UI, type 1)
        const OFString StudyUID;

        /// list of referenced series
        OFList<SeriesStruct *> SeriesList;
        /// currently selected series (cursor)
        OFListIterator(SeriesStruct *) Iterator;
    };

    /** To make VC6 happy.
     */
    friend struct StudyStruct;

    /** set cursor to the specified study entry (if existent)
     ** @param  studyUID  study instance UID of the entry to be searched for
     ** @return pointer to the study structure if successful, NULL otherwise
     */
    StudyStruct *gotoStudy(const OFString &studyUID);

    /** get pointer to currently selected study structure (if any)
     ** @return pointer to the study structure if successful, NULL otherwise
     */
    StudyStruct *getCurrentStudy() const;

    /** get pointer to currently selected series structure (if any)
     ** @return pointer to the series structure if successful, NULL otherwise
     */
    SeriesStruct *getCurrentSeries() const;

    /** get pointer to currently selected instance structure (if any)
     ** @return pointer to the instance structure if successful, NULL otherwise
     */
    InstanceStruct *getCurrentInstance() const;

    /** remove empty/incomplete items from the list.
     *  (e.g. series with no instances or studies with no series)
     *  Please note that this function modifies the value of 'Iterator'.
     */
    void removeIncompleteItems();

    /** check the four specified UID values for validity.
     *  Currently, the only checks performed are that the strings are non-empty and that
     *  the given values conform to the corresponding VR (UI) and VM (1).
     ** @param  studyUID     study instance UID to be checked
     *  @param  seriesUID    series instance UID to be checked
     *  @param  sopClassUID  SOP class UID to be checked
     *  @param  instanceUID  SOP instance UID to be checked
     ** @return status, EC_Normal if all values are valid, an error code otherwise
     */
    OFCondition checkSOPInstance(const OFString &studyUID,
                                 const OFString &seriesUID,
                                 const OFString &sopClassUID,
                                 const OFString &instanceUID) const;

    /** check the specified purpose of reference code for validity.
     *  Internally, the method DSRCodedEntryValue::checkCurrentValue()
     *  is used for this purpose (if the code is not empty).
     ** @param  purposeOfReference  purpose of reference code to be checked
     ** @return status, EC_Normal if code is valid, an error code otherwise
     */
    OFCondition checkPurposeOfReference(const DSRCodedEntryValue &purposeOfReference) const;


  private:

    /// DICOM tag specifying the attribute (sequence) of the reference list
    const DcmTagKey SequenceTag;

    /// list of studies
    OFList<StudyStruct *> StudyList;
    /// internal cursor to current (selected) list item
    OFListIterator(StudyStruct *) Iterator;
    /// specific character set used for checking purposes
    OFString SpecificCharacterSet;

    // default constructor - not implemented!
    DSRSOPInstanceReferenceList();
    // copy constructor - not implemented!
    DSRSOPInstanceReferenceList(const DSRSOPInstanceReferenceList &);
    // assignment operator - not implemented!
    DSRSOPInstanceReferenceList &operator=(const DSRSOPInstanceReferenceList &);
};


#endif
