/*
 *
 *  Copyright (C) 2015-2024, 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: dcmiod
 *
 *  Author: Michael Onken
 *
 *  Purpose: Abstract base class for IOD Modules or other attribute collections
 *
 */

#ifndef MODBASE_H
#define MODBASE_H

#include "dcmtk/config/osconfig.h"
#include "dcmtk/dcmdata/dcitem.h"
#include "dcmtk/dcmiod/ioddef.h"
#include "dcmtk/dcmiod/iodrules.h"
#include "dcmtk/ofstd/ofmem.h"

/** Class for managing sets of DICOM attributes (e.g.\ macros and modules).
 *  The data is hold in a container (DcmItem) that can be shared with other
 *  components, i.e.\ different modules that hold their data in a common
 *  container. In order to know which attributes in the container should be
 *  actually handled by a component, the component also has a set of rules, which
 *  can also be shared between different components (e.g.\ all modules of an IOD
 *  can share the same set of rules). For each attribute there is one rule in
 *  the rule set, denoting the requirement type (1,2,3,1C,2C), the VM, and
 *  besides others also the name of the component that the attribute belongs
 *  to (e.g.\ "PatientModule"). Since the component knows its own name, it can
 *  decide which attributes in the data container it is responsible for.
 *  This class is meant to support nested substructures but so far only writes
 *  attributes on a single level (including sequences). Also, in this context
 *  the class carries a parent relationship which is not used at the moment.
 */
class DCMTK_DCMIOD_EXPORT IODComponent
{

public:
    /** Constructor
     *  @param  item The item to be used for data storage. If NULL, the
     *          class creates an empty data container.
     *  @param  rules The rule set for this class. If NULL, the class creates
     *          an empty rule set.
     *  @param  parent The parent of the IOD component (NULL if none or unknown)
     */
    IODComponent(OFshared_ptr<DcmItem> item, OFshared_ptr<IODRules> rules, IODComponent* parent = NULL);

    /** Constructor, creates rules and item from scratch.
     *  @param  parent The parent of the IOD component (NULL if none or unknown)
     */
    IODComponent(IODComponent* parent = NULL);

    /** Assignment operator, copies contained item and rule set from rhs to
     *  "this" attribute set. Performs deep copy, i.e.\ the contained item
     *  and the rule set are copied. The parent component is set to NULL.
     *  @param  rhs The IODComponent to be assigned
     *  @return Reference to this module
     */
    IODComponent& operator=(const IODComponent& rhs);

    /** Copy constructor, copies reference to contained item and
     *  rule set to "this" attribute set.
     *  @param  rhs The component to be assigned
     */
    IODComponent(const IODComponent& rhs);

    /** Virtual Destructor
     */
    virtual ~IODComponent();

    /** Clear all attributes from the data that are handled by this module.
     *  An attribute is considered belonging to the module if there are rules
     *  marked as belonging to this module via the rule's module name.
     */
    void clearData();

    /** Set missing values by inventing "default values". Automatically
     *  called during write() in IODComponent. In this bas class implementation,
     *  this method checks for every rule of this component whether it has a default
     *  value, and if, invents the attribute if it is not present in the data,
     *  i.e. creating it with the default value or setting it to the default value
     *  if no value is present. This happens independently of the requirement type.
     */
    virtual void inventMissing();

    /** Resets rules to their original values
     */
    virtual void resetRules() = 0;

    /** Get rules handled by this module
     *  @return The rules
     */
    OFshared_ptr<IODRules> getRules()
    {
        return m_Rules;
    }

    /** Make component optional by turning all attributes requirement types of it
     *  to type 3. In order to reset to the attribute's original types,
     *  resetRules() can be used.
     */
    virtual void makeOptional();

    /** Get name of component
     *  @return Name of the module
     */
    virtual OFString getName() const = 0;

    /** Get the data handled by this module
     *  @return The item containing the data of this module
     */
    DcmItem& getData()
    {
        return *m_Item;
    }

    /** Read attributes from given item into this class
     *  @param source  The source to read from
     *  @param clearOldData If OFTrue, old data is cleared before reading. Otherwise
     *         old data is overwritten (or amended)
     *  @result EC_Normal if reading was successful, error otherwise
     */
    virtual OFCondition read(DcmItem& source, const OFBool clearOldData = OFTrue);

    /** Write attributes from this class into given item
     *  @param  destination The item to write to
     *  @result EC_Normal if writing was successful, error otherwise
     */
    virtual OFCondition write(DcmItem& destination);

    /** Check whether this component's data satisfies the underlying
     *  rules
     *  @param  quiet If OFTrue, not error / warning messages will be produced. Only
     *                the returned error code will indicate error or OK. Per default,
     *                logging output is produced (OFFalse).
     *  @result EC_Normal if rules are satisfied, error otherwise
     */
    virtual OFCondition check(const OFBool quiet = OFFalse);

    /** Comparison operator for IOD Components
     *  @param  rhs The right hand side of the comparison
     *  @return 0, if the given object is equal to this object, other value otherwise
     */
    virtual int compare(const IODComponent& rhs) const;

    /** Static helper function that reads attributes from given
     *  item into destination item, as determined by the provided
     *  rules and component name. The rules are only applied when reading
     *  from the source (which may result in warning messages on the logger),
     *  but if they could be found they are taken over into the destination
     *  item no matter whether the element validates against the rule.
     *  @param  source The item to read from
     *  @param  rules  The rules that provide the attributes and requirements
     *          for these attributes
     *  @param  destination The destination to write to
     *  @param  componentName The name of the module/component to write
     *  @result EC_Normal if reading was successful, error otherwise
     */
    static OFCondition read(DcmItem& source, IODRules& rules, DcmItem& destination, const OFString& componentName);

    /** Static helper function that writes attributes from given
     *  item into destination item, as determined by the provided
     *  rules and component name. The rules are only applied when writing
     *  to the destination (which may result in warning messages on the logger,
     *  and the whole call returning with an error). During reading from the
     *  source item the elements read are not validated against the rules.
     *  @param  source The item to read from
     *  @param  rules  The rules that provide the attributes and requirements
     *          for these attributes
     *  @param  destination The destination to write to
     *  @param  componentName The name of the module/component to write
     *  @param  checkValue If OFTrue, attribute value errors are handled as errors on writing, if OFFalse
     *          any errors are ignored.
     *  @result EC_Normal if reading was successful, error otherwise
     */
    static OFCondition write(DcmItem& source, IODRules& rules, DcmItem& destination, const OFString& componentName, const OFBool checkValue);

    /** Get whether attribute value errors will be handled as errors on writing.
     *  @return OFTrue if attribute value errors are handled as errors on writing, OFFalse otherwise.
     */
    virtual bool getValueCheckOnWrite() const;

    /** Set whether attribute values should be checked on writing, i.e. if writing
     *  should fail if attribute values violate their VR, VM, character set or value length.
     *  A missing but required value is always considered an error, independent of this setting.
     *  If set to OFFalse, writing will always succeed, even if attribute value constraints
     *  are violated. A warning instead of an error will be printed to the logger.
     *  @param  checkValue If OFTrue, attribute value errors are handled as errors on writing, if OFFalse
     *          any errors are ignored.
     */
    virtual void setValueCheckOnWrite(const OFBool checkValue);

protected:
    /// Shared pointer to the data handled by this class. The item may contain
    /// more attributes than this class is actually responsible for
    OFshared_ptr<DcmItem> m_Item;

    /// Rules describing the attributes governed by this class
    OFshared_ptr<IODRules> m_Rules;

    /// The parent component (may be NULL) of this class
    IODComponent* m_Parent;

    /// Denotes whether attribute values should be checked on writing, i.e. if writing
    /// should fail if attribute values violate their VR, VM, character set or value length.
    /// A missing but required value is always considered an error, independent of this setting.
    /// If set to OFFalse, writing will always succeed, even if attribute values constraints
    /// are violated. A warning instead of an error will be printed to the logger.
    /// OFTrue if attribute value errors are handled as errors on writing, OFFalse otherwise.
    OFBool m_CheckValueOnWrite;
};

/** The class IODModule is an IODComponent without parent component since
 *  a module does always belong to the top level dataset.
 *  Also, different from IODComponents, modules usually share data and
 *  rules. This is taken into account in the assignment operator and
 *  copy constructor which only create a shallow copy, i.e. modules
 *  share the same data and rules afterwards.
 */
class DCMTK_DCMIOD_EXPORT IODModule : public IODComponent
{
public:
    /** Constructor. Similar to the one of IODComponent but no parent
     *  can be defined since a module is always at top level.
     *  @param  item The item to be used for data storage. If NULL, the
     *          class creates an empty data container.
     *  @param  rules The rule set for this class. If NULL, the class creates
     *          an empty rule set.
     */
    IODModule(OFshared_ptr<DcmItem> item, OFshared_ptr<IODRules> rules);

    /** Constructor. Creates new empty data container and new empty
     *  ruleset. No parent component is defined (since a module is always
     *  on top level.
     */
    IODModule();

    /** Copy constructor, creates shallow copy
     *  @param  rhs The module to copy from
     */
    IODModule(const IODModule& rhs);

    /** Assignment operator, creates shallow copy
     *  @param  rhs The module to copy from
     *  @return Returns reference to this object
     */
    IODModule& operator=(const IODModule& rhs);

    /** Destructor
     */
    ~IODModule() {};
};

#endif // MODBASE_H
