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

  Program: DICOM for VTK

  Copyright (c) 2012-2019 David Gobbi
  All rights reserved.
  See Copyright.txt or http://dgobbi.github.io/bsd3.txt 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 vtkDICOMCharacterSet_h
#define vtkDICOMCharacterSet_h

#include "vtkSystemIncludes.h"
#include "vtkDICOMModule.h" // For export macro
#include "vtkDICOMConfig.h" // For configuration details

#include <string>

//! Character sets.
/*!
 *  This class provides the means to convert the various international
 *  text encodings used by DICOM to the popular UTF-8 encoding.  It does
 *  not at this time provide the means to convert to any encoding other
 *  than UTF-8.
 *
 *  During conversion to UTF-8, any codes in the original encoding that
 *  cannot be converted to Unicode are replaced by "REPLACEMENT CHARACTER",
 *  which is a question mark in a black diamond.  For instance, if the
 *  original encoding is ISO_IR_6, any octets with the 8th bit set will
 *  be replaced.
 *
 *  DICOM supports a fairly small number of single-byte and multi-byte
 *  character sets.  The only VRs that support these character sets are
 *  PN, LO, SH, ST, LT, and ST (all other text VRs must be ASCII). In
 *  total, there is one 7-bit encoding (ASCII), eleven 8-bit single-byte
 *  encodings, three variable-length encodings (UTF-8, GB18030, GBK), and
 *  three iso-2022 multi-byte encodings.  It is possible to use iso-2022
 *  escape codes to switch between most of the encodings, with UTF-8 and
 *  GB18030/GBK as the most noteworthy exceptions.
 *
 *  In some DICOM data sets, especially old ones, the SpecificCharacterSet
 *  attribute will be missing and it might be necessary to manually specify
 *  a character set for the application to use.  Use SetGlobalDefault() to
 *  do so.  The vtkDICOMCharacterSet constructor can take the desired
 *  character encoding as a string, where the following encodings are
 *  allowed: 'ascii', 'latin1', 'latin2', 'latin3', 'latin4', 'latin5'
 *  'latin7', 'latin9', 'cyrillic' (iso-8859-5), 'arabic' (iso-8859-6),
 *  'greek' (iso-8859-7), 'hebrew' (iso-8859-8), 'tis-620', 'shift-jis',
 *  'euc-jp', 'iso-2022-jp', 'korean' (euc-kr), 'chinese' (gbk), 'gb18030',
 *  'big5', 'cp1250', 'cp1251', 'cp1252', 'cp1253', 'cp1254', 'cp1255',
 *  'cp1256', 'cp1257', and 'utf-8'.  Common aliases of these character
 *  sets can also be used.
 */
class VTKDICOM_EXPORT vtkDICOMCharacterSet
{
public:
  enum EnumType
  {
    ISO_IR_6   = 0,  // US_ASCII
    ISO_IR_13  = 1,  // JIS X 0201,  japanese romaji + katakana
    ISO_IR_100 = 8,  // ISO-8859-1,  latin1, western europe
    ISO_IR_101 = 9,  // ISO-8859-2,  latin2, central europe
    ISO_IR_109 = 10, // ISO-8859-3,  latin3, maltese
    ISO_IR_110 = 11, // ISO-8859-4,  latin4, baltic
    ISO_IR_144 = 12, // ISO-8859-5,  cyrillic
    ISO_IR_127 = 13, // ISO-8859-6,  arabic
    ISO_IR_126 = 14, // ISO-8859-7,  greek
    ISO_IR_138 = 15, // ISO-8859-8,  hebrew
    ISO_IR_148 = 16, // ISO-8859-9,  latin5, turkish
    X_LATIN6   = 17, // ISO-8859-10, latin6, nordic
    ISO_IR_166 = 18, // ISO-8859-11, thai
    X_LATIN7   = 19, // ISO-8859-13, latin7, baltic rim
    X_LATIN8   = 20, // ISO-8859-14, latin8, celtic
    X_LATIN9   = 21, // ISO-8859-15, latin9, western europe
    X_LATIN10  = 22, // ISO-8859-16, latin10, southeastern europe
    X_EUCKR    = 24, // euc-kr,      ISO_IR_149 without escape codes
    X_GB2312   = 25, // gb2312,      ISO_IR_58 without escape codes
    ISO_2022_IR_6   = 32, // US_ASCII
    ISO_2022_IR_13  = 33, // JIS X 0201,  japanese katakana
    ISO_2022_IR_87  = 34, // JIS X 0208,  japanese 94x94 primary
    ISO_2022_IR_159 = 36, // JIS X 0212,  japanese 94x94 secondary
    ISO_2022_IR_100 = 40, // ISO-8859-1,  latin1, western europe
    ISO_2022_IR_101 = 41, // ISO-8859-2,  latin2, central europe
    ISO_2022_IR_109 = 42, // ISO-8859-3,  latin3, maltese
    ISO_2022_IR_110 = 43, // ISO-8859-4,  latin4, baltic
    ISO_2022_IR_144 = 44, // ISO-8859-5,  cyrillic
    ISO_2022_IR_127 = 45, // ISO-8859-6,  arabic
    ISO_2022_IR_126 = 46, // ISO-8859-7,  greek
    ISO_2022_IR_138 = 47, // ISO-8859-8,  hebrew
    ISO_2022_IR_148 = 48, // ISO-8859-9,  latin5, turkish
    ISO_2022_IR_166 = 50, // ISO-8859-11, thai
    ISO_2022_IR_149 = 56, // the KS X 1001 part of ISO-2022-KR
    ISO_2022_IR_58  = 57, // the GB2312 part of ISO-2022-CN
    ISO_IR_192 = 64, // UTF-8,       unicode
    GB18030    = 65, // gb18030,     chinese with full unicode mapping
    GBK        = 66, // gbk,         chinese
    X_BIG5     = 67, // big5 + ETEN, traditional chinese
    X_EUCJP    = 69, // euc-jp,      unix encoding for japanese
    X_SJIS     = 70, // windows-31j, aka shift-jis, code page 932
    X_CP874    = 76, // cp1162,      thai (windows-874)
    X_CP1250   = 80, // cp1250,      central europe
    X_CP1251   = 81, // cp1251,      cyrillic
    X_CP1252   = 82, // cp1252,      western europe
    X_CP1253   = 83, // cp1253,      greek
    X_CP1254   = 84, // cp1254,      turkish
    X_CP1255   = 85, // cp1255,      hebrew
    X_CP1256   = 86, // cp1256,      arabic
    X_CP1257   = 87, // cp1257,      baltic rim
    X_KOI8     = 90, // koi8,        cyrillic
    Unknown    = 255  // signifies unknown character set
  };

  //@{
  //! Construct an object that describes the default (ASCII) character set.
  vtkDICOMCharacterSet() : Key(0) {}

  //! Construct a character set object from a given code.
  /*!
   *  The code can be any of the enumerated code values.  The ISO_2022 flag
   *  can be added to any of the ISO-8859 codes to indicate that the character
   *  set allows the use of escape codes.  Also note that ISO_2022_IR_87 and
   *  ISO_2022_IR_159 are combining codes that can be added to each other and
   *  to ISO_IR_13.  Specifying any other codes in combination can lead to
   *  undefined results, for example "ISO_2022_IR_100 | ISO_2022_IR_101" is
   *  not permitted and "ISO_2022_IR_100" must be used instead.
   */
  vtkDICOMCharacterSet(int k) : Key(static_cast<unsigned char>(k)) {}

  //! Construct a character set object from a SpecificCharacterSet value.
  /*!
   *  This generates an 8-bit code that uniquely identifies a DICOM
   *  character set plus its code extensions.
   */
  explicit vtkDICOMCharacterSet(const std::string& name) {
    this->Key = KeyFromString(name.data(), name.length()); }
  vtkDICOMCharacterSet(const char *name, size_t nl) {
    this->Key = KeyFromString(name, nl); }
  //@}

  //@{
  //! Set the character set to use if SpecificCharacterSet is missing.
  /*!
   *  Some DICOM files do not list a SpecificCharacterSet attribute, but
   *  nevertheless use a non-ASCII character encoding.  This method can be
   *  used to specify the character set in absence of SpecificCharacterSet.
   *  If SpecificCharacterSet is present, the default will not override it
   *  unless OverrideCharacterSet is true.
   */
  static void SetGlobalDefault(vtkDICOMCharacterSet cs) {
    GlobalDefault = cs.GetKey(); }
  static vtkDICOMCharacterSet GetGlobalDefault() {
    return GlobalDefault; }

  //! Override the value stored in SpecificCharacterSet with the default.
  /*!
   *  This method can be used if the SpecificCharacterSet attribute of a
   *  file is incorrect.  It forces the use of the character set that
   *  was set with SetGlobalDefault.
   */
  static void SetGlobalOverride(bool b) {
    GlobalOverride = b; }
  static void GlobalOverrideOn() { GlobalOverride = true; }
  static void GlobalOverrideOff() { GlobalOverride = false; }
  static bool GetGlobalOverride() { return GlobalOverride; }
  //@}

  //@{
  //! Generate SpecificCharacterSet code values (diagnostic only).
  /*!
   *  Attempt to generate SpecificCharacterSet code values.  If ISO 2022
   *  encoding is not used, then a single code value is returned.  If
   *  ISO 2022 encoding is used with the single-byte character sets, then
   *  only the code value for first character set will be returned (due to
   *  limitations in the way this class stores the information).  However,
   *  if ISO 2022 encoding is used with the multi-byte character sets,
   *  the result is a set of backslash-separated code values, where
   *  the first value will be empty if the initial coding is ASCII.
   */
  std::string GetCharacterSetString() const;

  //! Get the numerical code for this character set object.
  unsigned char GetKey() const { return this->Key; }
  //@}

  //@{
  //! Convert text from UTF-8 to this encoding.
  /*!
   *  Attempt to convert from UTF-8 to this character set.  Every
   *  non-convertible character will be replaced with '?'.  If you
   *  pass a non-null value for the "lp" parameter, it will return
   *  the position in the input UTF-8 string where the first conversion
   *  error occurred.  If a successful conversion was returned, then
   *  lp will be set to the length of the input string.
  */
  std::string FromUTF8(const char *text, size_t l, size_t *lp=0) const;
  std::string FromUTF8(const std::string& text) const {
    return FromUTF8(text.data(), text.length()); }

  //! Convert text from this encoding to UTF-8.
  /*!
   *  This will convert text to UTF-8, which is generally a lossless
   *  process for western languages but not for the CJK languages.
   *  Characters that cannot be mapped to unicode, or whose place in
   *  unicode is not known, will be printed as unicode U+FFFD which
   *  appears as a question mark in a diamond.  If you pass a non-null
   *  value for the "lp" parameter, it will return the position in the
   *  input string where the first conversion error occurred.  If a
   *  successful conversion was returned, then lp will be set to the
   *  length of the input string.
   */
  std::string ToUTF8(const char *text, size_t l, size_t *lp=0) const;
  std::string ToUTF8(const std::string& text) const {
    return ToUTF8(text.data(), text.length()); }

  //! Obsolete method for converting to UTF8.
  std::string ConvertToUTF8(const char *text, size_t l) const;

  //! Convert text to UTF-8 that is safe to print to the console.
  /*!
   *  All control characters or unconvertible characters will be replaced
   *  by four-byte octal codes, e.g. '\033'.  Backslashes will be replaced
   *  by '\134' to avoid any potential ambiguity.
   */
  std::string ToSafeUTF8(const char *text, size_t l) const;
  std::string ToSafeUTF8(const std::string& text) const {
    return ToSafeUTF8(text.data(), text.length()); }

  //! Convert text into a form suitable for case-insensitive matching.
  /*!
   *  This function will perform case normalization on a string by
   *  converting it to lowercase, and by normalizing the forms of
   *  lowercase characters that do not have an exact uppercase
   *  equivalent.  In some cases, it might increase the length of
   *  the string.  It covers modern European scripts (including Greek
   *  and Cyrillic) and latin characters used in East Asian languages.
   */
  std::string CaseFoldedUTF8(const char *text, size_t l) const;
  std::string CaseFoldedUTF8(const std::string& text) const {
    return CaseFoldedUTF8(text.data(), text.length()); }

  //! Returns true if ISO 2022 escape codes are used.
  /*!
   *  If this method returns true, then escape codes can be used to
   *  switch between character sets.
   */
  bool IsISO2022() const {
    return ((this->Key & (ISO_2022 + ISO_2022 - 1)) == (this->Key | ISO_2022));
  }

  //! Returns true if this uses an ISO 8859 code page.
  bool IsISO8859() const {
    return (this->Key >= ISO_IR_100 && this->Key <= X_LATIN10);
  }

  //! Check for bidirectional character sets.
  /*!
   *  This is used to check for character sets that are likely to
   *  contain characters that print right-to-left, specifically Hebrew
   *  and Arabic.  Note that even though some parts of unicode fall
   *  into this category, this flag is off for unicode and GB18030/GBK.
   */
  bool IsBiDirectional() const {
    return (this->Key == ISO_IR_127 ||
            this->Key == ISO_IR_138 ||
            this->Key == X_CP1255 ||
            this->Key == X_CP1256); }
  //@}

  //@{
  //! Count the number of backslashes in an encoded string.
  /*!
   *  The backslash byte is sometimes present as half of a multibyte
   *  character in the Japanese and Chinese encodings.  This method
   *  skips these false backslashes and counts only real backslashes.
   */
  unsigned int CountBackslashes(const char *text, size_t l) const;

  //! Get the offset to the next backslash, or to the end of the string.
  /*!
   *  In order to work properly, this method requires that its input is
   *  either at the beginning of the string or just after a backslash.
   */
  size_t NextBackslash(const char *text, const char *end) const;
  //@}

  //@{
  bool operator==(vtkDICOMCharacterSet b) const { return (this->Key == b.Key); }
  bool operator!=(vtkDICOMCharacterSet b) const { return (this->Key != b.Key); }
  bool operator<=(vtkDICOMCharacterSet a) const { return (this->Key <= a.Key); }
  bool operator>=(vtkDICOMCharacterSet a) const { return (this->Key >= a.Key); }
  bool operator<(vtkDICOMCharacterSet a) const { return (this->Key < a.Key); }
  bool operator>(vtkDICOMCharacterSet a) const { return (this->Key > a.Key); }
  //@}

private:
  size_t AnyToUTF8(const char *t, size_t l, std::string *s, int m) const;
  size_t UTF8ToSingleByte(const char *t, size_t l, std::string *s) const;
  size_t SingleByteToUTF8(const char *t, size_t l, std::string *s, int m) const;
  size_t ISO8859ToUTF8(const char *t, size_t l, std::string *s, int) const;
  size_t UTF8ToISO2022(const char *t, size_t l, std::string *s) const;
  size_t ISO2022ToUTF8(const char *t, size_t l, std::string *s, int m) const;
  static size_t UTF8ToEUCKR(const char *t, size_t l, std::string *s);
  static size_t EUCKRToUTF8(const char *t, size_t l, std::string *s, int m);
  static size_t UTF8ToGB2312(const char *t, size_t l, std::string *s);
  static size_t GB2312ToUTF8(const char *t, size_t l, std::string *s, int m);
  static size_t UTF8ToGB18030(const char *t, size_t l, std::string *s);
  static size_t GB18030ToUTF8(const char *t, size_t l, std::string *s, int m);
  static size_t UTF8ToGBK(const char *t, size_t l, std::string *s);
  static size_t GBKToUTF8(const char *t, size_t l, std::string *s, int m);
  static size_t UTF8ToBig5(const char *t, size_t l, std::string *s);
  static size_t Big5ToUTF8(const char *t, size_t l, std::string *s, int m);
  static size_t UTF8ToEUCJP(const char *t, size_t l, std::string *s);
  static size_t EUCJPToUTF8(const char *t, size_t l, std::string *s, int m);
  static size_t UTF8ToSJIS(const char *t, size_t l, std::string *s);
  static size_t SJISToUTF8(const char *t, size_t l, std::string *s, int m);
  static size_t UTF8ToJISX(
    int charset, const char *t, size_t l, std::string *s);
  static size_t JISXToUTF8(
    int csGL, int csGR, const char *t, size_t l, std::string *s, int m);
  static unsigned char KeyFromString(const char *name, size_t nl);

  unsigned char Key;

  enum {
    ISO_2022_JP_BASE = 7,
    ISO_2022_BASE = 31,
    ISO_2022 = 32
  };

  static unsigned char GlobalDefault;
  static bool GlobalOverride;

public:
  static const unsigned short *Table[256];
  static const unsigned short *Reverse[256];
};

VTKDICOM_EXPORT ostream& operator<<(ostream& o, const vtkDICOMCharacterSet& a);

#endif /* vtkDICOMCharacterSet_h */
// VTK-HeaderTest-Exclude: vtkDICOMCharacterSet.h
