/*=========================================================================

  Program: GDCM (Grassroots DICOM). A DICOM library

  Copyright (c) 2006-2011 Mathieu Malaterre
  All rights reserved.
  See Copyright.txt or http://gdcm.sourceforge.net/Copyright.html for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#ifndef GDCMDATAELEMENT_H
#define GDCMDATAELEMENT_H

#include "gdcmTag.h"
#include "gdcmVL.h"
#include "gdcmVR.h"
#include "gdcmByteValue.h"
#include "gdcmSmartPointer.h"

#include <set>

namespace gdcm_ns
{
// Data Element
// Contains multiple fields:
// -> Tag
// -> Optional VR (Explicit Transfer Syntax)
// -> ValueLength
// -> Value
// TODO: This class SHOULD be pure virtual. I don't want a user
// to shoot himself in the foot.

class SequenceOfItems;
class SequenceOfFragments;
/**
 * \brief Class to represent a Data Element
 * either Implicit or Explicit
 *
 * \details
 * DATA ELEMENT:
 * A unit of information as defined by a single entry in the data dictionary.
 * An encoded Information Object Definition (IOD) Attribute that is composed
 * of, at a minimum, three fields: a Data Element Tag, a Value Length, and a
 * Value Field. For some specific Transfer Syntaxes, a Data Element also
 * contains a VR Field where the Value Representation of that Data Element is
 * specified explicitly.
 *
 * Design:
 * \li A DataElement in GDCM always store VL (Value Length) on a 32 bits integer even when VL is 16 bits
 * \li A DataElement always store the VR even for Implicit TS, in which case VR is defaulted to VR::INVALID
 * \li For Item start/end (See 0xfffe tags), Value is NULL
 *
 * \see ExplicitDataElement ImplicitDataElement
 */
class GDCM_EXPORT DataElement
{
public:
  DataElement(const Tag& t = Tag(0), const VL& vl = 0, const VR &vr = VR::INVALID):TagField(t),ValueLengthField(vl),VRField(vr),ValueField(nullptr) {}
  //DataElement( Attribute const &att );

  friend std::ostream& operator<<(std::ostream &_os, const DataElement &_val);

  /// Get Tag
  const Tag& GetTag() const { return TagField; }
  Tag& GetTag() { return TagField; }
  /// Set Tag
  /// Use with cautious (need to match Part 6)
  void SetTag(const Tag &t) { TagField = t; }

  /// Get VL
  const VL& GetVL() const { return ValueLengthField; }
  VL& GetVL() { return ValueLengthField; }
  /// Set VL
  /// Use with cautious (need to match Part 6), advanced user only
  /// \see SetByteValue
  void SetVL(const VL &vl) { ValueLengthField = vl; }
  void SetVLToUndefined();

  /// Get VR
  /// do not set VR::SQ on bytevalue data element
  VR const &GetVR() const { return VRField; }
  /// Set VR
  /// Use with cautious (need to match Part 6), advanced user only
  /// \pre vr is a VR::VRALL (not a dual one such as OB_OW)
  void SetVR(VR const &vr) {
    if( vr.IsVRFile() )
      VRField = vr;
  }

  /// Set/Get Value (bytes array, SQ of items, SQ of fragments):
  Value const &GetValue() const { gdcmAssertAlwaysMacro(ValueField); return *ValueField; }
  Value &GetValue() {
    gdcmAssertAlwaysMacro(ValueField);
    return *ValueField;
  }
  /// \warning you need to set the ValueLengthField explicitly
  void SetValue(Value const & vl) {
    //assert( ValueField == 0 );
    ValueField = vl;
    ValueLengthField = vl.GetLength();
  }
  /// Check if Data Element is empty
  bool IsEmpty() const { return ValueField == nullptr || (GetByteValue() && GetByteValue()->IsEmpty()); }

  /// Make Data Element empty (no Value)
  void Empty() { ValueField = nullptr; ValueLengthField = 0; }

  /// Clear Data Element (make Value empty and invalidate Tag & VR)
  void Clear()
    {
    TagField = 0;
    VRField = VR::INVALID;
    ValueField = nullptr;
    ValueLengthField = 0;
    }

  // Helper:
  /// Set the byte value
  /// \warning user need to read DICOM standard for an understanding of:
  /// * even padding
  /// * \0 vs space padding
  /// By default even padding is achieved using \0 regardless of the of VR
  void SetByteValue(const char *array, VL length)
    {
    ByteValue *bv = new ByteValue(array,length);
    SetValue( *bv );
    }
  /// Return the Value of DataElement as a ByteValue (if possible)
  /// \warning: You need to check for NULL return value
  const ByteValue* GetByteValue() const {
    // Get the raw pointer from the gdcm::SmartPointer
    const ByteValue *bv = dynamic_cast<const ByteValue*>(ValueField.GetPointer());
    return bv; // Will return NULL if not ByteValue
  }

  /// Interpret the Value stored in the DataElement. This is more robust (but also more
  /// expensive) to call this function rather than the simplest form: GetSequenceOfItems()
  /// It also return NULL when the Value is NOT of type SequenceOfItems
  /// \warning in case GetSequenceOfItems() succeed the function return this value, otherwise
  /// it creates a new SequenceOfItems, you should handle that in your case, for instance:
  /// SmartPointer<SequenceOfItems> sqi = de.GetValueAsSQ();
  SmartPointer<SequenceOfItems> GetValueAsSQ() const;

  /// Return the Value of DataElement as a Sequence Of Fragments (if possible)
  /// \warning: You need to check for NULL return value
  const SequenceOfFragments* GetSequenceOfFragments() const;
  SequenceOfFragments* GetSequenceOfFragments();

  /// return if Value Length if of undefined length
  bool IsUndefinedLength() const {
    return ValueLengthField.IsUndefined();
  }

  DataElement(const DataElement &_val)
    {
    if( this != &_val)
      {
      *this = _val;
      }
    }

  bool operator<(const DataElement &de) const
    {
    return GetTag() < de.GetTag();
    }
  DataElement &operator=(const DataElement &)
    = default;

  bool operator==(const DataElement &de) const
    {
    bool b = TagField == de.TagField
      && ValueLengthField == de.ValueLengthField
      && VRField == de.VRField;
    if( !ValueField && !de.ValueField )
      {
      return b;
      }
    if( ValueField && de.ValueField )
      {
      return b && (*ValueField == *de.ValueField);
      }
    // ValueField != de.ValueField
    return false;
    }

  // The following functionalities are dependent on:
  // # The Transfer Syntax: Explicit or Implicit
  // # The Byte encoding: Little Endian / Big Endian

  /*
   * The following was inspired by a C++ idiom: Curiously Recurring Template Pattern
   * Ref: http://en.wikipedia.org/wiki/Curiously_Recurring_Template_Pattern
   * The typename TDE is typically a derived class *without* any data
   * while TSwap is a simple template parameter to achieve byteswapping (and allow factorization of
   * highly identical code)
   */
  template <typename TDE>
  VL GetLength() const {
    return static_cast<const TDE*>(this)->GetLength();
  }

  template <typename TDE, typename TSwap>
  std::istream &Read(std::istream &is) {
    return static_cast<TDE*>(this)->template Read<TSwap>(is);
  }

  template <typename TDE, typename TSwap>
  std::istream &ReadOrSkip(std::istream &is, std::set<Tag> const &skiptags) {
    (void)skiptags;
    return static_cast<TDE*>(this)->template Read<TSwap>(is);
  }

  template <typename TDE, typename TSwap>
  std::istream &ReadPreValue(std::istream &is, std::set<Tag> const &skiptags) {
    (void)skiptags;
    return static_cast<TDE*>(this)->template ReadPreValue<TSwap>(is);
  }
  template <typename TDE, typename TSwap>
  std::istream &ReadValue(std::istream &is, std::set<Tag> const &skiptags) {
    (void)skiptags;
    return static_cast<TDE*>(this)->template ReadValue<TSwap>(is);
  }
  template <typename TDE, typename TSwap>
  std::istream &ReadValueWithLength(std::istream &is, VL & length, std::set<Tag> const &skiptags) {
    (void)skiptags;
    return static_cast<TDE*>(this)->template ReadValueWithLength<TSwap>(is, length);
  }

  template <typename TDE, typename TSwap>
  std::istream &ReadWithLength(std::istream &is, VL &length) {
    return static_cast<TDE*>(this)->template ReadWithLength<TSwap>(is,length);
  }

  template <typename TDE, typename TSwap>
  const std::ostream &Write(std::ostream &os) const {
    return static_cast<const TDE*>(this)->template Write<TSwap>(os);
  }

protected:
  Tag TagField;
  // This is the value read from the file, might be different from the length of Value Field
  VL ValueLengthField; // Can be 0xFFFFFFFF

  // Value Representation
  VR VRField;
  typedef SmartPointer<Value> ValuePtr;
  ValuePtr ValueField;

  void SetValueFieldLength( VL vl, bool readvalues );
};
//-----------------------------------------------------------------------------
inline std::ostream& operator<<(std::ostream &os, const DataElement &val)
{
  os << val.TagField;
  os << "\t" << val.VRField;
  os << "\t" << val.ValueLengthField;
  if( val.ValueField )
    {
    val.ValueField->Print( os << "\t" );
    }
  return os;
}

inline bool operator!=(const DataElement& lhs, const DataElement& rhs)
{
  return ! ( lhs == rhs );
}

} // end namespace gdcm_ns

#endif //GDCMDATAELEMENT_H
