/*
 *
 *  Copyright (C) 2011-2018, 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:  dcmnet
 *
 *  Author:  Joerg Riesmeier
 *
 *  Purpose: DICOM Storage Service Class User (SCU)
 *
 */

#ifndef DSTORSCU_H
#define DSTORSCU_H

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

#include "dcmtk/dcmnet/scu.h"       /* for base class DcmSCU */


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

/** Interface class for a Storage Service Class User (SCU).
 *  This class supports C-STORE messages as an SCU.  In a first step, the SOP instances to be
 *  sent are added to a transfer list.  In a second step, the association negotiation takes
 *  place where the required presentation contexts are proposed, i.e. it is checked which SOP
 *  classes and transfer syntaxes are needed for transferring the SOP instances.  Finally, the
 *  SOP instances are sent to the SCP (if possible).
 *  \note
 *    \li The current implementation does not sort the transfer list according to the SOP
 *        Class UID and Transfer Syntax UID of the SOP instances and, therefore, might propose
 *        more presentation contexts than required for the transfer of all SOP instances.  A
 *        simple optimization that is performed internally is to check whether the current SOP
 *        instance can be sent using a presentation context that has previously been added for
 *        another SOP instance (of the same kind).  This approach also makes sure that studies
 *        and series are not mixed up, assuming that they have been added to the transfer list
 *        in the "correct" order.
 *    \li Another limitation of the current implementation is the handling of the "Default
 *        Transfer Syntax" in case of compression.  According to the DICOM standard, the
 *        default transfer syntax for "Lossless JPEG Compression", "Lossy JPEG Compression"
 *        and so on has to be proposed in at least one presentation context for the particular
 *        SOP class.  This is not (yet) implemented since the re-encoding of compressed
 *        datasets is not supported.  Nevertheless, depending on the options used, the default
 *        transfer syntax for the uncompressed case is always proposed (if possible).
 */
class DCMTK_DCMNET_EXPORT DcmStorageSCU
  : public DcmSCU
{

  public:

    /** dataset decompression modes
     */
    enum E_DecompressionMode
    {
        /// never decompress datasets
        DM_never,
        /// decompress lossless only
        DM_losslessOnly,
        /// default value: decompress lossless only
        DM_default = DM_losslessOnly,
        /// decompress both lossy and lossless
        DM_lossyAndLossless
    };

    /** dataset handling modes
     */
    enum E_HandlingMode
    {
        /// do nothing with the dataset
        HM_doNothing,
        /// compact the dataset after it has been sent
        HM_compactAfterSend,
        /// delete the dataset after it has been sent
        HM_deleteAfterSend,
        /// delete the dataset after it has been removed from the transfer list
        HM_deleteAfterRemove
    };

    /** default constructor
     */
    DcmStorageSCU();

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

    /** clear the internal member variables
     */
    virtual void clear();

    /** get value of the association counter.  This counter is initialized with 0 (that means
     *  no association exists) and increased by 1 for every new association.  The counter is
     *  only reset when clear() is called.
     *  @return value of the association counter
     */
    unsigned long getAssociationCounter() const;

    /** get number of SOP instances stored in the transfer list
     *  @return number of SOP instances stored in the transfer list
     */
    size_t getNumberOfSOPInstances() const;

    /** get number of SOP instances that are to be sent (i.e.\ that are not yet sent).  Please
     *  note that internally the transfer list is iterated to determine this number, so
     *  depending on the size of the list it might take some time.
     *  @return number of SOP instances that are to be sent
     */
    size_t getNumberOfSOPInstancesToBeSent() const;

    /** get mode that specifies whether or not compressed datasets are decompressed if needed,
     *  i.e.\ whether the transfer syntax of the dataset is changed for network transmission.
     *  @return decompression mode. See definition of DcmStorageSCU::E_DecompressionMode for
     *    possible values.
     */
    E_DecompressionMode getDecompressionMode() const;

    /** get mode that specifies whether to halt if an invalid file is encountered during batch
     *  processing (e.g.\ when adding SOP instances from a DICOMDIR) or whether to continue
     *  with the next SOP instance.
     *  @return mode indicating whether to halt on invalid file or not
     */
    OFBool getHaltOnInvalidFileMode() const;

    /** get mode that specifies whether to halt if unsuccessful store encountered or whether
     *  to continue with the next SOP instance.
     *  @return mode indicating whether to halt on unsuccessful store or not
     */
    OFBool getHaltOnUnsuccessfulStoreMode() const;

    /** get mode that specifies whether to propose presentation contexts that do not contain
     *  the default transfer syntax although it is needed, which might result in a violation
     *  of the DICOM standard.
     *  @return mode indicating whether illegal proposals are allowed or not
     */
    OFBool getAllowIllegalProposalMode() const;

    /** get mode that specifies whether to read information on SOP instances to be sent from
     *  the DICOMDIR files that are added to the transfer list.
     *  @return mode indicating whether to read from DICOMDIR files or not
     */
    OFBool getReadFromDICOMDIRMode() const;

    /** get C-MOVE originator information (if set)
     *  @param  aeTitle    the AE title of the originating C-MOVE client.  Empty if not set.
     *  @param  messageID  the message ID used within the originating C-MOVE request.  0 if
     *                     not set.
     *  @return OFTrue if either of both parameters is set
     */
    OFBool getMOVEOriginatorInfo(OFString &aeTitle,
                                 Uint16 &messageID) const;

    /** set mode that specifies whether or not compressed datasets are decompressed if needed,
     *  i.e.\ whether the transfer syntax of the dataset is changed for network transmission.
     *  @param  decompressionMode  decompression mode. See definition of
     *                             DcmStorageSCU::E_DecompressionMode for both possible values
     *                             and the default value.
     */
    void setDecompressionMode(const E_DecompressionMode decompressionMode);

    /** set mode that specifies whether to halt if an invalid file is encountered during batch
     *  processing (e.g.\ when adding SOP instances from a DICOMDIR) or whether to continue
     *  with the next SOP instance.
     *  @param  haltMode  mode indicating whether to halt or not (default: OFTrue, i.e.\ halt)
     */
    void setHaltOnInvalidFileMode(const OFBool haltMode);

    /** set mode that specifies whether to halt if unsuccessful store encountered or whether
     *  to continue with the next SOP instance.
     *  @param  haltMode  mode indicating whether to halt or not (default: OFTrue, i.e.\ halt)
     */
    void setHaltOnUnsuccessfulStoreMode(const OFBool haltMode);

    /** set mode that specifies whether to propose presentation contexts that do not contain
     *  the default transfer syntax, although it is needed, which might result in a violation
     *  of the DICOM standard.  For example, if a lossless compressed SOP instance is to be
     *  sent, there should be at least one presentation context for this SOP class that also
     *  proposes the default transfer syntax (Implicit VR Little Endian).
     *  @note Please note that support for CP-1704 has not been implemented yet, i.e. it is
     *    not checked whether the decompressed data cannot be encoded in the default transfer
     *    syntax because it would be too large, which would also waive this requirement.
     *  @param  allowMode  mode indicating whether illegal proposals are allowed or not
     *                     (default: OFTrue, i.e.\ allowed)
     */
    void setAllowIllegalProposalMode(const OFBool allowMode);

    /** set mode that specifies whether to read information on SOP instances to be sent from
     *  the DICOMDIR files that are added to the transfer list.  If this mode is disabled, a
     *  DICOMDIR file is treated like any other input file.  If this mode is enabled, a
     *  DICOMDIR file is not added to the transfer list, but the DICOM files referenced from
     *  it are.
     *  @param  readMode  mode indicating whether to read from DICOMDIR files or not
     *                    (default: OFFalse, i.e.\ do not read)
     */
    void setReadFromDICOMDIRMode(const OFBool readMode);

    /** set C-MOVE originator information.
     *  If the C-STORE operation was initiated by a client's C-MOVE request, it is possible
     *  to convey the C-MOVE originating information (AE title and the message ID of the
     *  corresponding C-MOVE message) as part of the C-STORE messages in order to inform the
     *  C-STORE receiver (Storage SCP) about the original sender (Move SCU).
     *  @param  aeTitle    the AE title of the originating C-MOVE client. If empty, none is
     *                     sent.
     *  @param  messageID  message ID of the originating C-MOVE request.  If 0, none is sent.
     */
    void setMOVEOriginatorInfo(const OFString &aeTitle = "",
                               const Uint16 messageID = 0);

    /** reset the sent status for all SOP instances in the transfer list.  This allows for
     *  sending the same SOP instances again - on the same or a different association.
     *  @param  sameAssociation  flag indicating whether the same association will be used for
     *                           the transfer as last time.  If a different association will
     *                           be used, also the presentation context IDs are set to 0
     *                           (undefined), which means that addPresentationContexts() has
     *                           to be called again.  Please make sure that all dataset
     *                           pointers in the transfer list are still valid, i.e. the
     *                           datasets have not been deleted.
     */
    void resetSentStatus(const OFBool sameAssociation = OFFalse);

    /** remove all SOP instances from the transfer list.  If an entry contains a reference to
     *  a DICOM dataset, this dataset is deleted if the handling mode HM_deleteAfterRemove was
     *  used to add it to the transfer list.
     */
    void removeAllSOPInstances();

    /** remove a particular SOP instance from the transfer list.  If the corresponding entry
     *  contains a reference to a DICOM dataset, this dataset is deleted if the handling mode
     *  HM_deleteAfterRemove was used to add it to the transfer list.
     *  @param  sopClassUID     SOP Class UID of the SOP instance to be removed
     *  @param  sopInstanceUID  SOP Instance UID of the SOP instance to be removed
     *  @param  allOccurrences  flag specifying whether to delete all occurrences of the
     *                          SOP instance if it has been added to the list multiple times.
     *                          If OFFalse, only the first occurrence is removed.
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition removeSOPInstance(const OFString &sopClassUID,
                                  const OFString &sopInstanceUID,
                                  const OFBool allOccurrences = OFTrue);

    /** add a SOP instance stored as a DICOM file or a number of SOP instances referenced from
     *  a DICOMDIR to the list of instances to be transferred.  Before adding a SOP instance
     *  to the list, it is checked for validity and conformance to the DICOM standard
     *  (see checkSOPInstance() for details).  However, duplicate instances are not
     *  recognized, i.e. they are added to the list and later on transferred to the storage
     *  SCP when calling sendSOPInstances().  If the specified DICOM file is a DICOMDIR and
     *  the 'ReadFromDICOMDIRMode' is enabled, the referenced SOP instances are added to the
     *  transfer list (using the relevant information from the DICOMDIR) and not the DICOMDIR
     *  itself, which is not meant to be transferred anyway.  Please note that it is not
     *  checked whether the referenced files really exist.
     *  @param  filename     name of the DICOM file that contains the SOP instance or name of
     *                       the DICOMDIR file that references the SOP instances to be sent
     *  @param  readMode     read mode passed to the DcmFileFormat::loadFile() method.  If
     *                       ERM_fileOnly, only the file meta information header is loaded,
     *                       i.e. the behavior is identical to using ERM_metaOnly.
     *  @param  checkValues  flag indicating whether to check the UID values for validity and
     *                       conformance.  If OFFalse, only empty values are rejected.
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition addDicomFile(const OFFilename &filename,
                             const E_FileReadMode readMode = ERM_fileOnly,
                             const OFBool checkValues = OFTrue);

    /** add a SOP instance from a given DICOM dataset to the list of instances to be
     *  transferred.  Before adding the SOP instance to the list, it is checked for validity
     *  and conformance to the DICOM standard (see checkSOPInstance() for details).  However,
     *  duplicate instances are not recognized, i.e. they are added to the list and later on
     *  transferred to the storage SCP when calling sendSOPInstances().
     *  @param  dataset       DICOM dataset that contains the SOP instance to be sent
     *  @param  datasetXfer   transfer syntax of the dataset (determined automatically if
     *                        unknown, which is also the default)
     *  @param  handlingMode  mode specifying what to do with the dataset if no longer needed.
     *                        HM_xxxAfterSend has no effect if the C-STORE request could not
     *                        be sent.
     *                        Please do not add the same dataset multiple times with a mode of
     *                        HM_deleteAfterXXX, since it will result in deleting the same
     *                        object multiple times!
     *  @param  checkValues   flag indicating whether to check the UID values for validity and
     *                        conformance.  If OFFalse, only empty values are rejected.
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition addDataset(DcmDataset *dataset,
                           const E_TransferSyntax datasetXfer = EXS_Unknown,
                           const E_HandlingMode handlingMode = HM_compactAfterSend,
                           const OFBool checkValues = OFTrue);

    /** add presentation contexts for all SOP instances in the transfer list, which were not
     *  yet sent (either successfully or unsuccessfully).  Initially, the internal list of
     *  presentation contexts is cleared.  Then, the transfer list is iterated and a new
     *  presentation context is added for each SOP instance that cannot be sent using any of
     *  the previously added presentation contexts.  If the maximum of 128 presentation
     *  contexts, which can be negotiated during a single association, is reached, this method
     *  returns and any subsequent call adds the next bunch of presentation contexts needed.
     *  @return status, EC_Normal if successful, an error code otherwise.  If no presentation
     *    contexts have been added, NET_EC_NoPresentationContextsDefined is returned.  This
     *    code can, therefore, be used to check that all SOP instances from the transfer list
     *    have been negotiated and sent in previous calls.
     */
    OFCondition addPresentationContexts();

    /** negotiate association by using presentation contexts and parameters as defined by
     *  earlier method calls.  If negotiation fails, there is no need to close the
     *  association or to do anything else with this class.
     *  In addition to the implementation inherited from the base class DcmSCU, this method
     *  also handles the case that none of the proposed presentation contexts was accepted.
     *  And, it also increases the association counter by 1 for each new association
     *  (including the ones that are not successful).
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition negotiateAssociation();

    /** send SOP instances to be transferred to the specified peer.  After the presentation
     *  contexts for the current association have been added and negotiated, the SOP instances
     *  that are to be sent on this association can be transferred with this method.  Unless
     *  the corresponding mode is disabled using setHaltOnUnsuccessfulStoreMode(), the
     *  transfer is stopped on the first SOP instance that could not be "stored" successfully.
     *  Please note, however, that the DIMSE status is not checked for this purpose, i.e. the
     *  transfer is never stopped when the DIMSE status is different from 0x0000 (success).
     *  Each time a SOP instance from the transfer list has been processed, the virtual method
     *  notifySOPInstanceSent() is called, which can be overwritten by a derived class.
     *  The sending process can be stopped by overwriting shouldStopAfterCurrentSOPInstance()
     *  in a derived class.  The sending process can be continued with the next SOP instance
     *  by calling sendSOPInstances() again.
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition sendSOPInstances();

    /** get some status information on the overall sending process.  This text can for example
     *  be output to the logger (on the level at the user's option).
     *  @param  summary  reference to a string in which the summary is stored
     */
    void getStatusSummary(OFString &summary) const;

    /** create a text file with a detailed report on the transfer of DICOM SOP instances.  In
     *  addition to a general header and some status information at the end (as generated by
     *  getStatusSummary()), a couple of basic details on each SOP instance in the transfer
     *  list are written to the file:
     *  - Consecutive number
     *  - Name of the DICOM file
     *  - SOP Instance UID
     *  - SOP Class UID (and associated name)
     *  - Transfer Syntax UID (and associated name)
     *  - Number of the association (that was used to transfer the instance)
     *  - Presentation Context ID (that was used to transfer the instance)
     *  - DIMSE Status (if instance was sent) or reason why it was not sent
     *  @param  filename  name of the text file in which the report is stored
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition createReportFile(const OFString &filename) const;


  protected:

    /** internal class/struct for a single transfer entry
     */
    struct DCMTK_DCMNET_EXPORT TransferEntry
    {
        /** constructor. Initializes member variables with reasonable values.
         *  @param  filename           filename of the SOP instance to be transferred
         *  @param  readMode           mode to read the given SOP instance from file
         *  @param  sopClassUID        SOP Class UID of the SOP instance to be transferred
         *  @param  sopInstanceUID     SOP Instance UID of the SOP instance to be transferred
         *  @param  transferSyntaxUID  Transfer Syntax UID of the SOP instance to be
         *                             transferred
         */
        TransferEntry(const OFFilename &filename,
                      const E_FileReadMode readMode,
                      const OFString &sopClassUID,
                      const OFString &sopInstanceUID,
                      const OFString &transferSyntaxUID);

        /** constructor. Initializes member variables with reasonable values.
         *  @param  dataset            pointer to the dataset of the SOP instance to be
         *                             transferred
         *  @param  handlingMode       mode specifying what to do with the dataset if no
         *                             longer needed
         *  @param  sopClassUID        SOP Class UID of the SOP instance to be transferred
         *  @param  sopInstanceUID     SOP Instance UID of the SOP instance to be transferred
         *  @param  transferSyntaxUID  Transfer Syntax UID of the SOP instance to be
         *                             transferred
         */
        TransferEntry(DcmDataset *dataset,
                      const E_HandlingMode handlingMode,
                      const OFString &sopClassUID,
                      const OFString &sopInstanceUID,
                      const OFString &transferSyntaxUID);

        /** destructor
         */
        ~TransferEntry();

        /// filename of the SOP instance to be transferred (if no 'Dataset' given)
        const OFFilename Filename;
        /// read mode that should be used to read the given SOP instance from file
        const E_FileReadMode FileReadMode;
        /// dataset of the SOP instance to be transferred (if no 'Filename' given)
        DcmDataset *Dataset;
        /// handling mode specifying what to do with the dataset if no longer needed
        const E_HandlingMode DatasetHandlingMode;
        /// SOP Class UID of the SOP instance to be transferred
        const OFString SOPClassUID;
        /// SOP Instance UID of the SOP instance to be transferred
        const OFString SOPInstanceUID;
        /// Transfer Syntax UID of the SOP instance to be transferred
        const OFString TransferSyntaxUID;
        /// transfer syntax that was used to send this SOP instance
        E_TransferSyntax NetworkTransferSyntax;
        /// flag indicating whether the SOP instance is uncompressed, i.e.\ uses any of
        /// the three uncompressed transfer syntaxes
        OFBool Uncompressed;
        /// size of the dataset of the SOP instance (in bytes, 0 = not yet determined).
        /// NB: The number of bytes that are actually transferred might deviate because
        /// of minor changes to the DICOM dataset when appended to the C-STORE request.
        unsigned long DatasetSize;
        /// association number that was used to send this SOP instance (0 = not sent)
        unsigned long AssociationNumber;
        /// presentation context ID to be used for sending this SOP instance
        T_ASC_PresentationContextID PresentationContextID;
        /// flag indicating whether the C-STORE request has been sent (OFTrue = sent)
        OFBool RequestSent;
        /// DIMSE status of the C-STORE response (0x0000 = success)
        Uint16 ResponseStatusCode;

      private:

        /** initialize further member variables
         */
        void Init();

        // private undefined copy constructor
        TransferEntry(const TransferEntry &);

        // private undefined assignment operator
        TransferEntry &operator=(const TransferEntry &);
    };

    /** add SOP instances referenced from a given DICOMDIR to the list of instances to be
     *  transferred.  Please note that the referenced DICOM files are not loaded during this
     *  process.  Only the relevant information contained in the DICOMDIR is used.
     *  @param  filename     name of the DICOMDIR file that contains the references to SOP
     *                       instances to be sent
     *  @param  readMode     read mode passed to the DcmFileFormat::loadFile() method when
     *                       reading the referenced DICOM files.  Not used for the DICOMDIR.
     *  @param  checkValues  flag indicating whether to check the UID values of the SOP
     *                       instances to be added for validity and conformance.  If OFFalse,
     *                       only empty values are rejected.
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    OFCondition addDicomFilesFromDICOMDIR(const OFFilename &filename,
                                          const E_FileReadMode readMode,
                                          const OFBool checkValues);

    /** check given SOP Class UID, SOP Instance UID and Transfer Syntax UID for validity and
     *  conformance to the DICOM standard.  For all UID values, the compliance with the
     *  requirements of the value representation "Unique Identifier" (UI) and value
     *  multiplicity is checked.  For the SOP Class UID, it is also checked whether it is a
     *  known or at least a possible storage SOP class (either a standard or a private one).
     *  For the Transfer Syntax UID, it is checked whether it is known and generally
     *  supported.  Further checks will be performed when the list of presentation contexts is
     *  created for the association negotiation.
     *  This method is called for each SOP instance that is added to the transfer list by one
     *  of the following methods: addDicomFile(), addDataset(), addDicomFilesFromDICOMDIR()
     *  @param  sopClassUID        value of the SOP Class UID to be checked
     *  @param  sopInstanceUID     value of the SOP Instance UID to be checked
     *  @param  transferSyntaxUID  value of the Transfer Syntax UID to be checked
     *  @param  checkValues        flag indicating whether to check the UID values for
     *                             validity and conformance.  If OFFalse, only empty values
     *                             are rejected.
     *  @return status, EC_Normal if successful, an error code otherwise
     */
    virtual OFCondition checkSOPInstance(const OFString &sopClassUID,
                                         const OFString &sopInstanceUID,
                                         const OFString &transferSyntaxUID,
                                         const OFBool checkValues);

    /** this method is called each time before a SOP instance is sent to a peer.  Therefore,
     *  the transfer entry passed to this method does not yet contain all information.
     *  @param  transferEntry  reference to current transfer entry that will be processed
     */
    virtual void notifySOPInstanceToBeSent(const TransferEntry &transferEntry);

    /** this method is called each time after a SOP instance has been sent to a peer.
     *  Therefore, the transfer entry passed to this method contains current and usually
     *  also complete information, e.g. the DIMSE status of the C-STORE response.  This
     *  allows for counting the number of successful and failed transfers.
     *  @param  transferEntry  reference to current transfer entry that has been processed
     */
    virtual void notifySOPInstanceSent(const TransferEntry &transferEntry);

    /** this method is called each time after a SOP instance is sent to a peer.  If the
     *  return value is OFTrue, the SCU will stop the sending process after the current SOP
     *  instance.  This could for example make sense when transferring SOP instances due to
     *  a C-MOVE request, which is externally canceled by a C-CANCEL message.  The default
     *  implementation always returns OFFalse.  A derived class may change this behavior.
     *  @return OFTrue if sending should stop after current SOP instance, OFFalse otherwise.
     */
    virtual OFBool shouldStopAfterCurrentSOPInstance();


  private:

    /// association counter
    unsigned long AssociationCounter;
    /// presentation context counter
    unsigned long PresentationContextCounter;
    /// decompression mode, i.e.\ whether a dataset is decompressed for transmission
    E_DecompressionMode DecompressionMode;
    /// flag indicating whether to halt on invalid file
    OFBool HaltOnInvalidFileMode;
    /// flag indicating whether to halt on unsuccessful store
    OFBool HaltOnUnsuccessfulStoreMode;
    /// flag indicating whether to allow illegal proposals
    OFBool AllowIllegalProposalMode;
    /// flag indicating whether to read from DICOMDIR files
    OFBool ReadFromDICOMDIRMode;
    /// AE title of the C-MOVE client that initiated the C-STORE operation (if applicable)
    OFString MoveOriginatorAETitle;
    /// message ID of the C-MOVE message that initiated the C-STORE operation (if applicable)
    Uint16 MoveOriginatorMsgID;
    /// list of SOP instances to be transferred
    OFList<TransferEntry *> TransferList;
    /// iterator pointing to the current entry in the list of SOP instances to be transferred
    OFListIterator(TransferEntry *) CurrentTransferEntry;

    // private undefined copy constructor
    DcmStorageSCU(const DcmStorageSCU &);

    // private undefined assignment operator
    DcmStorageSCU &operator=(const DcmStorageSCU &);
};

#endif // DSTORSCU_H
