/*
 *
 *  Copyright (C) 2015-2019, Open Connections GmbH
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  This software and supporting documentation are maintained by
 *
 *    OFFIS e.V.
 *    R&D Division Health
 *    Escherweg 2
 *    D-26121 Oldenburg, Germany
 *
 *
 *  Module:  dcmfg
 *
 *  Author:  Michael Onken
 *
 *  Purpose: Main interface class for managing Functional Groups
 *
 */

#ifndef FGINTERFACE_H
#define FGINTERFACE_H

#include "dcmtk/config/osconfig.h"

#include "dcmtk/dcmdata/dcdatset.h"
#include "dcmtk/dcmdata/dcsequen.h"
#include "dcmtk/dcmdata/dctk.h"
#include "dcmtk/dcmfg/fg.h"
#include "dcmtk/dcmfg/fgdefine.h"
#include "dcmtk/dcmfg/fgtypes.h"
#include "dcmtk/dcmiod/iodrules.h"
#include "dcmtk/ofstd/ofmap.h"
#include "dcmtk/ofstd/ofvector.h"

// Forward declaration
class IODMultiframeDimensionModule;

/** Main interface class to access functional groups from DICOM Enhanced
 *  objects. Allows reading, modifying and writing functional groups back
 *  and forth from DICOM datasets.
 */
class DCMTK_DCMFG_EXPORT FGInterface
{

public:
    /// Type representing per-frame functional groups, i.e.\ a number of
    /// functional groups assigned to each frame
    typedef OFMap<Uint32, FunctionalGroups*> PerFrameGroups;

    /// Iterator type for iterating over functional groups
    typedef FunctionalGroups::iterator iterator;

    /// Const iterator type for iterating over functional groups
    typedef FunctionalGroups::const_iterator const_iterator;

    /** Constructor, constructs empty sets of per-frame and shared
     *  functional groups
     */
    FGInterface();

    /** Virtual destructor, frees memory
     */
    virtual ~FGInterface();

    /** Delete all functional groups (shared and per-frame)
     */
    virtual void clear();

    /** Checks the functional groups for consistency. The following checks are
     *  performed:
     *    -# Check that every frame has a FrameContent functional group.
     *    -# Check that any per-frame group is not shared at the same time.
     *    -# Check for each per-frame group, that it is allowed to be per-frame.
     *    -# Check for each shared group, that it is allowed to be shared.
     *  @return OFTrue, if check consistency is ok, error otherwise
     */
    virtual OFBool check();

    /** Returns number of frames. Computed by number of per-frame
     *  functional group items (i.e.\ the Number of Frames attribute
     *  is not taken into account).
     *  @return Number of frames
     */
    virtual size_t getNumberOfFrames();

    /** Read enhanced multi-frame information from DICOM item, usually
     *  DcmDataset, i.e.\ must contain Shared and Per-frame Functional Group
     *  Sequences
     *  @param  dataset The item to read from
     *  @return EC_Normal if reading was successful, error otherwise
     */
    virtual OFCondition read(DcmItem& dataset);

    /** Write enhanced multi-frame information to DICOM item,
     *  usually DcmDataset, i.e.\ writes  Shared and Per-frame Functional Group
     *  Sequences
     *  @param  dataset The item to write to
     *  @return EC_Normal if successful, error otherwise
     */
    virtual OFCondition write(DcmItem& dataset);

    /** Get specific functional group for a frame, no matter whether it is stored
     *  per frame or shared
     *  @param  frameNo The frame number the functional group should apply to
     *          (starts with 0)
     *  @param  fgType The type of functional group to look for
     *  @return The functional group if found, NULL otherwise
     */
    virtual FGBase* get(const Uint32 frameNo, const DcmFGTypes::E_FGType fgType);

    // TODO Add get(..) version that takes the sequence tag (e.g.\ for unknown
    // functional groups

    /** Get specific functional group for a frame, no matter whether it is stored
     *  per frame or shared.
     *  @param  frameNo The frame number of group of interest (starts from 0)
     *  @param  fgType The type of functional group to look for
     *  @param  isPerFrame If OFTrue, the group found was found as per-frame,
     *          otherwise it is a shared functional group
     *  @return The functional group if found, error otherwise
     */
    virtual FGBase* get(const Uint32 frameNo, const DcmFGTypes::E_FGType fgType, OFBool& isPerFrame);

    /** Return all per-frame functional groups, e.g.\ to iterate over them
     *  @param  frameNo The frame number of the groups of interest (starts from 0)
     *  @return The per-frame functional groups for the given frame
     */
    const FunctionalGroups* getPerFrame(const Uint32 frameNo) const;

    /** Return all shared functional groups, e.g.\ to iterate over them
     *  @return The shared functional groups
     */
    const FunctionalGroups* getShared() const;

    /** Add functional group that should be shared for all frames. This will
     *  delete all per-frame groups of the same type if existing.
     *  @param  group   The group to be added. The group is copied.
     *  @return EC_Normal, if adding was successful, error otherwise
     */
    virtual OFCondition addShared(const FGBase& group);

    /** Add functional group for given frame. If there is already a shared
     *  functional group with identical values, the call returns without
     *  errors, too. If there is a shared group that differs, the shared group
     *  is converted to be "per-frame" for all frames and then the given group
     *  is inserted for the frame specified by the user.
     *  If a per-frame functional group of the same type already exists it is
     *  overwritten.
     *  @param  frameNo The frame number this group should be added for (starts
     *          from 0)
     *  @param  group The group to be added. The group is copied when adding,
     *          so the ownership stays with the caller, no matter what the
     *          method returns.
     *  @return EC_Normal, if adding was successful, error otherwise
     */
    virtual OFCondition addPerFrame(const Uint32 frameNo, const FGBase& group);

    /** Deletes a shared functional group of the given type
     *  @param fgType The type of functional group to delete
     *  @return OFTrue if group existed and could be deleted, OFFalse (group did
     *          not exist) otherwise
     */
    virtual OFBool deleteShared(const DcmFGTypes::E_FGType fgType);

    /** Deletes per-frame functional group of the given type for a specific frame
     *  @param frameNo The frame number for the functional group of interest.
     *         First frame is frame 0.
     *  @param fgType The type of functional group to delete
     *  @return OFTrue if group existed and could be deleted, OFFalse (group did
     *          not exist) otherwise
     */
    virtual OFBool deletePerFrame(const Uint32 frameNo, const DcmFGTypes::E_FGType fgType);

    /** Deletes per-frame functional group for all frames
     *  @param  fgType The type of functional group to delete
     *  @return Number of per-frame groups deleted (usually equal to number of
     *          frames)
     */
    size_t deletePerFrame(const DcmFGTypes::E_FGType fgType);

    /** Deletes all functional groups for a specific frame
     *  @param frameNo The frame number whose functional groups should be deleted.
     *         First frame is frame 0.
     *  @return Number of per-frame groups deleted for this frame
     */
    size_t deleteFrame(const Uint32 frameNo);

    /** If enabled, functional group structure is checked before actual writing
     *  is performed in the write() method. Checking might be time consuming
     *  on functional groups with many frames, though disabling might result in
     *  invalid functional group structures. Disabling should only be done if the
     *  user knows that the functional groups are valid, wants to to adapt the
     *  functional groups manually after calling write() or knows what he's doing
     *  otherwise.<br>
     *  Per default, checking is enabled.
     *  @param  doCheck If OFTrue, checking will be performed. If OFFalse,
     *          no checks are performed.
     */
    virtual void setCheckOnWrite(const OFBool doCheck);

    /** Returns whether functional group structure is checked before actual
     *  writing is performed in the write() method.
     *  @return OFTrue if checking is performed, OFFalse otherwise
     */
    virtual OFBool getCheckOnWrite();

protected:
    /** Get shared functional group based on its type
     *  @param  fgType The type of functional group
     *  @return The functional group or NULL if not existent
     */
    virtual FGBase* getShared(const DcmFGTypes::E_FGType fgType);

    /** Insert shared functional group
     *  @param  group The functional group to be inserted
     *  @param  replaceExisting If OFTrue, an existing shared functional group
     *          will be deleted, otherwise the old group is not overwritten
     *  @return EC_Normal if insertion worked, FG_EC_DoubledFG if group exists and
     *          is not overwritten, other error code for other cases
     */
    virtual OFCondition insertShared(FGBase* group, const OFBool replaceExisting = OFTrue);

    /** Get per-frame functional group
     *  @param  frameNo  The frame number of the group
     *  @param  fgType The type of the group
     *  @return The functional group or NULL if not existent
     */
    virtual FGBase* getPerFrame(const Uint32 frameNo, const DcmFGTypes::E_FGType fgType);

    /** Insert per-frame functional group
     *  @param  frameNo The frame number the group should be added for
     *  @param  group The functional group to be inserted
     *  @param  replaceExisting If OFTrue, an existing per-frame functional group
     *          will be deleted, otherwise the old group is not overwritten
     *  @return EC_Normal if insertion worked, FG_EC_DoubledFG if group exists and
     *          is not overwritten, other error code for other cases
     */
    virtual OFCondition insertPerFrame(const Uint32 frameNo, FGBase* group, const OFBool replaceExisting = OFTrue);

    /** Get existing per-frame group or create it for the given frame. Note that
     *  the per-frame groups do not have to be created "in order", i.e.\ one could
     *  add groups in order 3,5,1 ,... .
     *  @param  frameNo The frame number to get/create per-frame groups for
     *  @return The functional groups if found/created, NULL in case of error
     */
    virtual FunctionalGroups* getOrCreatePerFrameGroups(const Uint32 frameNo);

    /** Read Shared Functional Group Sequence from given item
     *  @param  dataset The item to read from
     *  @return EC_Normal if reading was successful, error otherwise
     */
    virtual OFCondition readSharedFG(DcmItem& dataset);

    /** Read Per-Frame Functional Group Sequence from given item
     *  @param  dataset The item to read from
     *  @return EC_Normal if reading was successful, error otherwise
     */
    virtual OFCondition readPerFrameFG(DcmItem& dataset);

    /** Read single functional group into the item provided
     *  @param  fgItem The item to read from
     *  @param  groups The resulting group after reading
     *  @return EC_Normal if reading was successful, error otherwise
     */
    virtual OFCondition readSingleFG(DcmItem& fgItem, FunctionalGroups& groups);

    /** Write Shared Functional Group Sequence to given item
     *  @param  dataset The item to write to
     *  @return EC_Normal if writing was successful, error otherwise
     */
    virtual OFCondition writeSharedFG(DcmItem& dataset);

    /** Write Per-Frame Functional Group Sequence to given item
     *  @param  dataset The item to write to
     *  @return EC_Normal if writing was successful, error otherwise
     */
    virtual OFCondition writePerFrameFG(DcmItem& dataset);

    /** Convert a shared functional group to a per-frame one by copying the
     *  shared one into a per-frame one for each frame and deleting the shared one
     *  aftewrards.
     *  @param  fgType The type of functional group to convert
     *  @return EC_Normal if conversion worked out, FG_EC_NoSuchGroup if such a
     *          group does not exist and other error otherwise. In the last case
     *          the functional groups may be left in invalid state, but that
     *          should only happen for fatal errors like exhausted memory.
     */
    virtual OFCondition convertSharedToPerFrame(const DcmFGTypes::E_FGType fgType);

private:
    /// Shared functional groups
    FunctionalGroups m_shared;

    /// Link from frame number (map key) to the list of functional groups (value)
    /// relevant for the frame
    PerFrameGroups m_perFrame;

    /// If enabled, functional group structure is checked on write(). Otherwise,
    /// checks are skipped.
    OFBool m_checkOnWrite;
};

#endif // MODMULTIFRAMEFGH_H
