/*
 *
 *  Copyright (C) 1994-2021, 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:  Andrew Hewett
 *
 *  Purpose: Interface for loadable DICOM data dictionary
 *
 */


#ifndef DCMDICT_H
#define DCMDICT_H

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

#include "dcmtk/ofstd/ofthread.h"
#include "dcmtk/dcmdata/dchashdi.h"

/// maximum length of a line in the loadable DICOM dictionary
#define DCM_MAXDICTLINESIZE     2048

/// maximum number of fields per entry in the loadable DICOM dictionary
#define DCM_MAXDICTFIELDS       6

/// environment variable pointing to the data dictionary file
#define DCM_DICT_ENVIRONMENT_VARIABLE   "DCMDICTPATH"

#ifndef DCM_DICT_DEFAULT_PATH
/*
** The default dictionary path is system dependent.  It should
** be defined in a configuration file included from "osconfig.h"
*/
#error "DCM_DICT_DEFAULT_PATH is not defined via osconfig.h"
#endif /* !DCM_DICT_DEFAULT_PATH */

#ifndef ENVIRONMENT_PATH_SEPARATOR
#define ENVIRONMENT_PATH_SEPARATOR '\n' /* at least define something unlikely */
#endif

/*
** Loading of default dictionary:
**  DCM_DICT_DEFAULT_USE_NONE: Do not load any default dictionary on startup
**  DCM_DICT_DEFAULT_USE_BUILTIN: Load builtin dictionary on startup
**  DCM_DICT_DEFAULT_USE_EXTERNAL: Load external (i.e. file-based) dictionary on startup
*/
#define DCM_DICT_DEFAULT_USE_NONE     0
#define DCM_DICT_DEFAULT_USE_BUILTIN  1
#define DCM_DICT_DEFAULT_USE_EXTERNAL 2


/** this class implements a loadable DICOM Data Dictionary
 */
class DCMTK_DCMDATA_EXPORT DcmDataDictionary
{
public:

    /** constructor
     *  @param loadBuiltin flag indicating if a built-in data dictionary
     *    (if any) should be loaded.
     *  @param loadExternal flag indicating if an external data dictionary
     *    should be read from file.
     */
    DcmDataDictionary(OFBool loadBuiltin, OFBool loadExternal);

    /// destructor
    ~DcmDataDictionary();

    /** checks if a data dictionary is loaded (excluding the skeleton dictionary)
     *  @return true if loaded, false if no dictionary is present
     */
    OFBool isDictionaryLoaded() const { return dictionaryLoaded; }

    /// returns the number of normal (non-repeating) tag entries
    int numberOfNormalTagEntries() const { return hashDict.size(); }

    /// returns the number of repeating tag entries
    int numberOfRepeatingTagEntries() const { return OFstatic_cast(int, repDict.size()); }

    /** returns the number of dictionary entries that were loaded
     *  either from file or from a built-in dictionary or both.
     */
    int numberOfEntries() const
        { return numberOfNormalTagEntries()
              + numberOfRepeatingTagEntries() - skeletonCount; }

    /** returns the number of skeleton entries. The skeleton is a collection
     *  of dictionary entries which are always present, even if neither internal
     *  nor external dictionary have been loaded. It contains very basic
     *  things like item delimitation and sequence delimitation.
     */
    int numberOfSkeletonEntries() const { return skeletonCount; }

    /** reload data dictionaries. First, all dictionary entries are deleted.
     *  @param loadBuiltin flag indicating if a built-in data dictionary
     *    (if any) should be loaded.
     *  @param loadExternal flag indicating if an external data dictionary
     *    should be read from file.
     *  @return true if reload was successful, false if an error occurred
     */
    OFBool reloadDictionaries(OFBool loadBuiltin, OFBool loadExternal);

    /** load a particular dictionary from file.
     *  @param fileName filename
     *  @param errorIfAbsent causes the method to return false
     *     if the file cannot be opened
     *  @return false if the file contains a parse error or if the file could
     *     not be opened and errorIfAbsent was set, true otherwise.
     */
    OFBool loadDictionary(const char* fileName, OFBool errorIfAbsent = OFTrue);

    /** dictionary lookup for the given tag key and private creator name.
     *  First the normal tag dictionary is searched.  If not found
     *  then the repeating tag dictionary is searched.
     *  @param key tag key
     *  @param privCreator private creator name, may be NULL
     */
    const DcmDictEntry* findEntry(const DcmTagKey& key, const char *privCreator) const;

    /** dictionary lookup for the given attribute name.
     *  First the normal tag dictionary is searched.  If not found
     *  then the repeating tag dictionary is searched.
     *  Only considers standard attributes (i. e. without private creator)
     *  @param name attribute name
     */
    const DcmDictEntry* findEntry(const char *name) const;

    /// deletes all dictionary entries
    void clear();

    /** adds an entry to the dictionary.  Must be allocated via new.
     *  The entry becomes the property of the dictionary and will be
     *  deallocated (via delete) upon clear() or dictionary destruction.
     *  If an equivalent entry already exists it will be replaced by
     *  the new entry and the old entry deallocated (via delete).
     *  @param entry pointer to new entry
     */
    void addEntry(DcmDictEntry* entry);

    /* Iterators to access the normal and the repeating entries */

    /// returns an iterator to the start of the normal (non-repeating) dictionary
    DcmHashDictIterator normalBegin() { return hashDict.begin(); }

    /// returns an iterator to the end of the normal (non-repeating) dictionary
    DcmHashDictIterator normalEnd() { return hashDict.end(); }

    /// returns an iterator to the start of the repeating tag dictionary
    DcmDictEntryListIterator repeatingBegin() { return repDict.begin(); }

    /// returns an iterator to the end of the repeating tag dictionary
    DcmDictEntryListIterator repeatingEnd() { return repDict.end(); }

private:

    /** private undefined assignment operator
     */
    DcmDataDictionary &operator=(const DcmDataDictionary &);

    /** private undefined copy constructor
     */
    DcmDataDictionary(const DcmDataDictionary &);

    /** loads external dictionaries defined via environment variables
     *  @return true if successful
     */
    OFBool loadExternalDictionaries();

    /** loads a builtin (compiled) data dictionary.
     *  Depending on which code is in use, this function may not
     *  do anything.
     */
    void loadBuiltinDictionary();

    /** loads the skeleton dictionary (the bare minimum needed to run)
     *  @return true if successful
     */
    OFBool loadSkeletonDictionary();

    /** looks up the given directory entry in the two dictionaries.
     *  @param entry the entry to look up
     *  @return pointer to entry if found, NULL otherwise
     */
    const DcmDictEntry* findEntry(const DcmDictEntry& entry) const;

    /** deletes the given entry from either dictionary
     * @param entry the entry to delete
     */
    void deleteEntry(const DcmDictEntry& entry);


    /** dictionary of normal tags
     */
    DcmHashDict hashDict;

    /** dictionary of repeating tags
     */
    DcmDictEntryList repDict;

    /** the number of skeleton entries
     */
    int skeletonCount;

    /** is a dictionary loaded (more than skeleton)
     */
    OFBool dictionaryLoaded;

};


/** global singleton dicom dictionary that is used by DCMTK in order to lookup
 *  attribute VR, tag names and so on.  The dictionary is internally populated
 *  on first use, if the user accesses it via rdlock() or wrlock().  The
 *  dictionary allows safe read (shared) and write (exclusive) access from
 *  multiple threads in parallel.
 */
class DCMTK_DCMDATA_EXPORT GlobalDcmDataDictionary
{
public:
  /** constructor.
   */
  GlobalDcmDataDictionary();

  /** destructor
   */
  ~GlobalDcmDataDictionary();

  /** acquires a read lock and returns a const reference to
   *  the dictionary.
   *  @return const reference to dictionary
   */
  const DcmDataDictionary& rdlock();

  /** acquires a write lock and returns a non-const reference
   *  to the dictionary.
   *  @return non-const reference to dictionary.
   */
  DcmDataDictionary& wrlock();

  /** unlocks the read lock which must have been acquired previously.
   */
  void rdunlock();

  /** unlocks the write lock which must have been acquired previously.
   */
  void wrunlock();

  /** checks if a data dictionary has been loaded. This method acquires and
   *  releases a read lock. It must not be called with another lock on the
   *  dictionary being held by the calling thread.
   *  @return OFTrue if dictionary has been loaded, OFFalse otherwise.
   */
  OFBool isDictionaryLoaded();

  /** erases the contents of the dictionary. This method acquires and
   *  releases a write lock. It must not be called with another lock on the
   *  dictionary being held by the calling thread.  This method is intended
   *  as a help for debugging memory leaks.
   */
  void clear();

private:
  /** private undefined assignment operator
   */
  GlobalDcmDataDictionary &operator=(const GlobalDcmDataDictionary &);

  /** private undefined copy constructor
   */
  GlobalDcmDataDictionary(const GlobalDcmDataDictionary &);

  /** create the data dictionary instance for this class. Used for first
   * intialization.  The caller must not have dataDictLock locked.
   */
  void createDataDict();

  /** the data dictionary managed by this class
   */
  DcmDataDictionary *dataDict;

#ifdef WITH_THREADS
  /** the read/write lock used to protect access from multiple threads
   *  @remark this member is only available if DCMTK is compiled with thread
   *  support enabled.
   */
  OFReadWriteLock dataDictLock;
#endif
};


/** The Global DICOM Data Dictionary.
 *  Will be created before main() starts and gets populated on its first use.
 *  Tries to load a builtin data dictionary (if compiled in).
 *  Tries to load data dictionaries from files specified by
 *  the DCMDICTPATH environment variable.  If this environment
 *  variable does not exist then a default file is loaded (if
 *  it exists).
 *  It is possible that no data dictionary gets loaded.  This
 *  is likely to cause unexpected behaviour in the dcmdata
 *  toolkit classes.
 */
extern DCMTK_DCMDATA_EXPORT GlobalDcmDataDictionary dcmDataDict;

#endif
