/* $NoKeywords: $ */
/*
//
// Copyright (c) 1993-2012 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//				
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
*/

#if !defined(OPENNURBS_TEXTITERATOR_INC_)
#define OPENNURBS_TEXTITERATOR_INC_

#define RTFFIRSTCHAR

typedef struct tagFontKey 
{
  int            rtf_font_index;
  const ON_Font* managed_font;
} ON_FontKey;

class ON_FaceNameKey
{
public:
  int m_rtf_font_index = -1;

  // In reality, the RTF "face" name can be
  // a PostScript name, LOGFONT.lfFaceName, DWrite Family Name,
  // and probably more. It depends on what tool is used to create the RTF and we see
  // all three of the above names in Rhino parsing and the current Eto Text controls
  // generate RTF with all three of those "names" OS version.
  ON_wString m_rtf_font_name;

  unsigned int m_codepage = 1252;
  unsigned int m_charset = 0;
};

// Converts wchar_t characters to Unicode codepoints 
class ON_TextIterator
{
private:
  ON_TextIterator() = delete;

public:
  ON_TextIterator(const ON_wString& str);
  ON_TextIterator(const wchar_t* str, size_t length);

public:
  ~ON_TextIterator() = default;
  ON_TextIterator(const ON_TextIterator&) = default;
  ON_TextIterator& operator=(const ON_TextIterator&) = default;

  /*
  Parameters:
    unicode_code_point - [out]
      current unicode code point returned here.
      0 = end of string
  Returns:
     true if returned unicode_code_point is not zero
  */
  bool PeekCodePoint(ON__UINT32& unicode_code_point) const;

  /*
  Description:
    Gets the current unicode code point and calls Step() to advance the text iterator
    by one code point.
  Parameters:
    unicode_code_point - [out]
      current unicode code point returned here.
      0 = end of string
  Returns:
     true if returned unicode_code_point is not zero
  */
  bool ReadCodePoint(ON__UINT32& unicode_code_point);


  bool        Back();  // move position back and return current codepoint after moving back

  // Get the next UNICODE code point encoded in m_text beginning at m_text[m_next_text_ci];
  // Save this code point in m_cur_codepoint.
  // Advance m_next_text_ci.
  bool        Step();

  bool AtBackslashTic() const;
  bool ReadCharValue(
    unsigned char& c
  );
private:

  const wchar_t*                    m_text = nullptr;
  size_t                            m_length = 0;
  size_t                            m_prev_text_ci = 0;   // previous offset in m_text wchar_t string
  size_t                            m_next_text_ci = 0;   // previous offset in m_text wchar_t string
  size_t                            m_cur_text_ci = 0;    // current offset in m_text wchar_t string
  ON__UINT32                        m_prev_codepoint = 0; // previous UNICODE code point
  ON__UINT32                        m_cur_codepoint = 0;  // UNICODE code point read by last call to Step()
  struct ON_UnicodeErrorParameters  m_ue = ON_UnicodeErrorParameters::MaskErrors;
};

class ON_TextBuilder
{
public:
  ON_TextBuilder();

  virtual ~ON_TextBuilder();
  
  class TextProps
  {
  public:
    TextProps()
    {}
    TextProps(
      double height,
      double stackscale,
      ON_Color color,
      ON_DimStyle::stack_format stackformat,
      bool bold,
      bool italic,
      bool underlined,
      bool strikethrough,
      unsigned int charset)
      : m_height(height)
      , m_stackscale(stackscale)
      , m_color(color)
      , m_stackformat(stackformat)
      , m_bold(bold)
      , m_italic(italic)
      , m_underlined(underlined)
      , m_strikethrough(strikethrough)
      , m_codepage(1252)
    {}
    double Height() const
    {
      return m_height;
    }
    void SetHeight(double h)
    {
      if (h > 1e-8)
        m_height = h;
    }
    double StackScale() const
    {
      return m_stackscale;
    }
    void SetStackScale(double s)
    {
      if (0.0 < s && 10.0 > s)
        m_stackscale = s;
    }
    ON_Color Color() const
    {
      return m_color;
    }
    void SetColor(ON_Color c)
    {
      m_color = c;
    }
    ON_DimStyle::stack_format StackFormat() const
    {
      return m_stackformat;
    }
    void SetStackFormat(ON_DimStyle::stack_format s)
    {
      m_stackformat = s;
    }
    bool IsBold()
    {
      return m_bold;
    }
    void SetBold(bool bold)
    {
      m_bold = bold;
    }
    bool IsItalic()
    {
      return m_italic;
    }
    void SetItalic(bool italic)
    {
      m_italic = italic;
    }
    bool IsUnderlined()
    {
      return m_underlined;
    }
    void SetUnderlined(bool underlined)
    {
      m_underlined = underlined;
    }
    bool IsStrikethrough()
    {
      return m_strikethrough;
    }
    void SetStrikethrough(bool strikethrough)
    {
      m_strikethrough = strikethrough;
    }
    unsigned int CodePage()
    {
      return m_codepage;
    }
    void SetCodePage(unsigned int codepage)
    {
      m_codepage = codepage;
    }

    unsigned int CharSet()
    {
      return m_charset;
    }
    void SetCharSet(unsigned int charset, bool setcodepage)
    {
      m_charset = charset;
      if (setcodepage)
      {
        m_codepage = ON_MapRTFcharsetToWindowsCodePage(charset, 1252);
      }
    }
    bool FormatPending()
    {
      return m_format_pending;
    }
    void SetFormatPending(bool b)
    {
      m_format_pending = b;
    }

  private:
    double                    m_height = 1.0;
    double                    m_stackscale = 0.7;
    ON_Color                  m_color = ON_Color::Black;
    ON_DimStyle::stack_format m_stackformat = ON_DimStyle::stack_format::StackHorizontal;
    bool                      m_bold = false;
    bool                      m_italic = false;
    bool                      m_underlined = false;
    bool                      m_strikethrough = false;
    unsigned int              m_codepage = 1252;
    unsigned int              m_charset = 0;   // Charset isn't really needed but is here to make debugging a little easier
    bool                      m_format_pending = false;
  };

  ON_ClassArray< TextProps >  m_prop_stack;
  TextProps                   m_current_props;
  TextProps                   m_pending_props;

  // Rtf uses UTF-16 encoding and surrogate pairs need to be properly handled.
  // For example, the single UNICODE code point ON_UnicodeCodePoint::Wastebasket U+1F5D1 (decimal 128465)
  // is in the RTF string as ...{\ltrch \u-10179?\u-8751?}...
  // The UNICODE code point U+1F5D1 is encoded as a UTF-16 surrogate pair is (0xD83D, 0xDDD1).
  //   \u-10179? -> unsigned short 0xD83D
  //   \u-8751?  -> unsigned short 0xDDD1
  enum : ON__UINT16
  {
    m_UFT16_waiting_mark = 0xEEEE, // value must be > 0xE000 and uncommon unicode code point
    m_UFT16_unused_mark = 0xFFFF  // value must be > m_UFT16_waiting, <= 0xFFFF, and uncommon unicode code point
  };
  ON__INT32 m_current_UTF16_buffer_count = 0;
  ON__UINT16 m_current_UTF16_buffer[2];

  ON_SimpleArray< ON__UINT32 >       m_current_codepoints;
  ON__INT32                          m_in_run;
  ON__INT32                          m_level = 0;
  ON__INT32                          m_font_table_level = 10000;
  ON__INT32                          m_font_index = 0;
  ON__INT32                          m_default_font_index = 0;
private:
  bool                               m_bReadingFontDefinition = false;
public:
  ON_ClassArray< ON_FaceNameKey >    m_facename_map;

  virtual void InitBuilder(const ON_Font* default_font);
  virtual void FlushText(size_t count, ON__UINT32* cp_array);
  virtual void GroupBegin();
  virtual void GroupEnd();
  virtual void RunBegin();
  virtual void RunEnd();
  virtual void FinishFontDef();
  virtual bool ReadingFontTable();
  virtual bool ReadingFontDefinition();
  virtual void SetReadingFontDefinition(bool b);


  virtual void BeginHeader();
  virtual void BeginFontTable();
  virtual void DefaultFont(const wchar_t* value);
  virtual void FontTag(const wchar_t* value);
  virtual void FontSize(const wchar_t* value);
  virtual void CharSet(const wchar_t* value);
  virtual void CodePage(const wchar_t* value);

  virtual void Newline();
  virtual void Paragraph();
  virtual void ParagraphDefaults();
  virtual void Section();
  virtual void Tab();

  virtual void Bold(const wchar_t* value);
  virtual void Italic(const wchar_t* value);
  virtual void UnderlineOn();
  virtual void UnderlineOff();
  virtual void Strikethrough(const wchar_t* value);
  
  virtual void Superscript();
  virtual void Subscript();
  virtual void NoSuperSub();

  virtual void BeginColorTable();
  virtual void ColorRed(const wchar_t* value);
  virtual void ColorGreen(const wchar_t* value);
  virtual void ColorBlue(const wchar_t* value);
  virtual void ColorForeground(const wchar_t* value);
  virtual void ColorBackground(const wchar_t* value);

  virtual void SetStackScale(const wchar_t* value);
  virtual void StackFraction(const wchar_t* value);
  virtual void StackEnd();

  virtual void TextField(const wchar_t* name);

  virtual void UniEmbeddedDest(const wchar_t* value);
  virtual void UniDest(const wchar_t* value);

  virtual void UniCpCount(const wchar_t* value);
  virtual void UniDecimal(const wchar_t* value);

  virtual void LQuote();
  virtual void RQuote();
  virtual void LDblQuote();
  virtual void RDblQuote();
  virtual void Bullet();
  virtual void EnDash();
  virtual void EmDash();

  virtual bool AppendCodePoint(ON__UINT32 codept);
  virtual void FormatChange();

  ON__UINT32* RunCodePoints(const ON_TextRun& run);

  const ON_wString FaceNameFromMap(int nval);
  unsigned int CodePageFromMap(int nval);
  unsigned int CharSetFromMap(int nval);

private:
  ON_TextBuilder operator=(const ON_TextBuilder& src);
};



class ON_TextRunBuilder : public ON_TextBuilder
{
public:
  ON_TextRunBuilder(
    ON_TextContent& text,
    ON_TextRunArray& runs,
    const ON_DimStyle* dimstyle,
    double height,
    ON_Color color);

  virtual ~ON_TextRunBuilder();

  ON_SimpleArray< const ON_Font* >   m_font_stack;

  const ON_Font* CurrentFont() const
  {
    // Keep this as a simple one-liner that unconditionally returns m_private_current_font.
    return m_private_current_font;
  }

  void SetCurrentFont(const ON_Font* font)
  {
    // Keep this as a simple one-liner that unconditionally sets m_private_current_font.
    m_private_current_font = font;
  }

private:
  // Please, in order to keep people debugging sane, always use 
  // CurrentFont() / SetCurrentFont() and never directly
  // set m_private_current_font.
  const ON_Font*                     m_private_current_font = &ON_Font::Default;

public:
  ON_TextRun                         m_current_run;
  ON_TextRunArray&                   m_runs;
  ON_TextContent&                    m_text;

  void FinishCurrentRun();
  void AppendCurrentRun();

  void InitBuilder(const ON_Font* default_font) override;
  void FlushText(size_t count, ON__UINT32* cp_array) override;
  void GroupBegin() override;
  void GroupEnd() override;
  void RunBegin() override;
  void RunEnd() override;
  void FinishFontDef() override;
  bool AppendCodePoint(ON__UINT32 codept) override;
  void FormatChange() override;

  void BeginHeader() override;
  void BeginFontTable() override;
  void DefaultFont(const wchar_t* value) override;
  void FontTag(const wchar_t* value) override;
  void FontSize(const wchar_t* value) override;

  void Newline() override;
  void Paragraph() override;
  void ParagraphDefaults() override;
  void Section() override;
  void Tab() override;

  void Bold(const wchar_t* value) override;
  void Italic(const wchar_t* value) override;
  void UnderlineOn() override;
  void UnderlineOff() override;
  void Strikethrough(const wchar_t* value) override;

  void Superscript() override;
  void Subscript() override;
  void NoSuperSub() override;

  void BeginColorTable() override;
  void ColorRed(const wchar_t* value) override;
  void ColorGreen(const wchar_t* value) override;
  void ColorBlue(const wchar_t* value) override;
  void ColorForeground(const wchar_t* value) override;
  void ColorBackground(const wchar_t* value) override;

  void SetStackScale(const wchar_t* value) override;
  void StackFraction(const wchar_t* value) override;
  void StackEnd() override;

  void TextField(const wchar_t* name) override;

  void UniEmbeddedDest(const wchar_t* value) override;
  void UniDest(const wchar_t* value) override;

private:
  ON_TextRunBuilder operator=(const ON_TextRunBuilder& src);
};

class ON_RtfStringBuilder : public ON_TextBuilder
{
public:
  ON_RtfStringBuilder(
    const ON_DimStyle* dimstyle,
    double height,
    ON_Color color);
  
  virtual ~ON_RtfStringBuilder();

  class TextRun
  {
  public: 
    TextRun() {}

    ON_TextRun::RunType Type() const
    {
      return m_run_type;
    }
    void SetType(ON_TextRun::RunType type)
    {
      m_run_type = type;
    }
    void InitRun()
    {
      m_run_type = ON_TextRun::RunType::kNone;
      m_font_index = -1;
      m_text.Empty();
      m_bold = false;
      m_italic = false;
      m_underlined = false;
      m_strikethrough = false;
    }
    int FontIndex()
    {
      return m_font_index;
    }
    void SetFontIndex(int index) 
    {
      if(index >= -1)
        m_font_index = index;
    }

    bool IsBold() const
    {
      return m_bold;
    }
    bool IsItalic() const
    {
      return m_italic;
    }
    bool IsUnderlined() const
    {
      return m_underlined;
    }
    bool IsStrikeThrough() const
    {
      return m_strikethrough;
    }
    void SetBold(bool b)
    {
      m_bold = b;
    }
    void SetItalic(bool b)
    {
      m_italic = b;
    }
    void SetUnderlined(bool b)
    {
      m_underlined = b;
    }
    void SetStrikeThrough(bool b)
    {
      m_strikethrough = b;
    }

    void AddControl(const wchar_t* str)
    {
      m_text += str;
      size_t i = wcslen(str);
      if(str[i-1] == L' ' || str[i-1] == L'{' || str[i-1] == L'}')
        m_terminated = true;
      else
        m_terminated = false;
      m_has_content = true;
    }

    void AddText(const wchar_t* str)
    {
      if (!m_terminated)
        m_text += L' ';
      m_terminated = true;
      m_text += str;
      m_has_content = true;
    }

    void AddChar(const wchar_t ch)
    {
      if (!m_terminated)
        m_text += L' ';
      m_terminated = true;
      m_text += ch;
      m_has_content = true;
    }

    void SetTerminated(bool terminated)
    {
      m_terminated = terminated;
    }

    bool IsTerminated()
    {
      return m_terminated;
    }

    void EmptyText()
    {
      m_text = ON_wString::EmptyString;
    }

    const ON_wString& TextString()
    {
      return m_text;
    }

    const bool HasContent()
    {
      return m_has_content;
    }

    const void ClearHasContent()
    {
      m_has_content = false;
    }
    
  private:
    bool                   m_has_content = false;
    bool                   m_terminated = true;
    ON_wString             m_text;
    bool                   m_bold = false;
    bool                   m_italic = false;
    bool                   m_underlined = false;
    bool                   m_strikethrough = false;
    int                    m_font_index = -1;
    ON_TextRun::RunType    m_run_type = ON_TextRun::RunType::kNone;
  };

private:        
  ON_wString    m_string_out;
  bool          m_in_font_table = false;
  bool          m_in_color_table = false;

  bool          m_skip_color_tbl = false;
  bool          m_skip_bold = false;
  bool          m_skip_italic = false;
  bool          m_skip_underline = false;
  bool          m_skip_facename = false;

  bool          m_make_bold = false;
  bool          m_make_italic = false;
  bool          m_make_underline = false;
  bool          m_make_facename = false;

  ON_wString    m_default_facename;
  ON_wString    m_override_facename;

  bool          m_have_rtf = false;

public:

  TextRun                            m_current_run;
  ON_ClassArray< TextRun >           m_run_stack;

  void InitStringBuilder(const ON_DimStyle* default_style);
  const ON_wString OutputString();
  void PushRun(TextRun& run);
  TextRun PopRun();
  
  //bool InFontTable();
  //void SetInFontTable(bool b);
  bool InColorTable();
  void SetInColorTable(bool b);

  void SetSkipColorTbl(bool b);
  void SetSkipBold(bool b);
  void SetSkipItalic(bool b);
  void SetSkipUnderline(bool b);
  void SetSkipFacename(bool b);

  bool SkipColorTbl();
  bool SkipBold();
  bool SkipItalic();
  bool SkipUnderline();
  bool SkipFacename();

  void SetMakeBold(bool b);
  void SetMakeItalic(bool b);
  void SetMakeUnderline(bool b);
  void SetMakeFacename(bool b);

  bool MakeBold();
  bool MakeItalic();
  bool MakeUnderline();
  bool MakeFacename();

  bool SkippingFacename();
  bool SettingFacename();

  void SetDefaultFacename(const wchar_t* facename);
  void SetOverrideFacename(const wchar_t* facename);

  // virtuals
  
  void GroupBegin() override;
  void GroupEnd() override;
  void RunBegin() override;
  void RunEnd() override;

  void BeginHeader() override;
  void BeginFontTable() override;
  void DefaultFont(const wchar_t* value) override;
  void FontTag(const wchar_t* value) override;
  void FontSize(const wchar_t* value) override;

  void Newline() override;
  void Paragraph() override;
  void ParagraphDefaults() override;
  void Section() override;
  void Tab() override;

  void Bold(const wchar_t* value) override;
  void Italic(const wchar_t* value) override;
  void UnderlineOn() override;
  void UnderlineOff() override;
  void Strikethrough(const wchar_t* value) override;

  void Superscript() override;
  void Subscript() override;
  void NoSuperSub() override;

  void BeginColorTable() override;
  void ColorRed(const wchar_t* value) override;
  void ColorGreen(const wchar_t* value) override;
  void ColorBlue(const wchar_t* value) override;
  void ColorForeground(const wchar_t* value) override;
  void ColorBackground(const wchar_t* value) override;

  void TextField(const wchar_t* name) override;

  bool AppendCodePoint(ON__UINT32 codept) override;

  void UniEmbeddedDest(const wchar_t* value) override;
  void UniDecimal(const wchar_t* value) override;
  void UniDest(const wchar_t* value) override;

private:
  ON_RtfStringBuilder operator=(const ON_RtfStringBuilder& src);
};

#ifdef RTFFIRSTCHAR

class ON_RtfFirstChar : public ON_TextBuilder
{
public:
  ON_RtfFirstChar(
    const ON_DimStyle* dimstyle,
    double height,
    ON_Color color);

  virtual ~ON_RtfFirstChar();

  class TextRun
  {
  public:
    TextRun() {}

    ON_TextRun::RunType Type() const
    {
      return m_run_type;
    }
    void SetType(ON_TextRun::RunType type)
    {
      m_run_type = type;
    }
    void InitRun()
    {
      m_run_type = ON_TextRun::RunType::kNone;
      m_font_index = -1;
      m_text.Empty();
      m_bold = false;
      m_italic = false;
      m_underlined = false;
      m_strikethrough = false;
    }
    int FontIndex()
    {
      return m_font_index;
    }
    void SetFontIndex(int index)
    {
      if (index >= -1)
        m_font_index = index;
    }

    bool IsBold() const
    {
      return m_bold;
    }
    bool IsItalic() const
    {
      return m_italic;
    }
    bool IsUnderlined() const
    {
      return m_underlined;
    }
    bool IsStrikeThrough() const
    {
      return m_strikethrough;
    }
    void SetBold(bool b)
    {
      m_bold = b;
    }
    void SetItalic(bool b)
    {
      m_italic = b;
    }
    void SetUnderlined(bool b)
    {
      m_underlined = b;
    }
    void SetStrikeThrough(bool b)
    {
      m_strikethrough = b;
    }

    void AddText(const wchar_t* str)
    {
      if (!m_terminated)
        m_text += L' ';
      m_terminated = true;
      m_text += str;
      m_has_content = true;
    }

    const ON_wString& Text()
    {
      return m_text;
    }
    
  private:
    bool                   m_has_content = false;
    bool                   m_terminated = true;
    ON_wString             m_text;
    bool                   m_bold = false;
    bool                   m_italic = false;
    bool                   m_underlined = false;
    bool                   m_strikethrough = false;
    int                    m_font_index = -1;
    ON_TextRun::RunType    m_run_type = ON_TextRun::RunType::kNone;
  };

private:
  bool          m_in_font_table = false;
  bool          m_in_color_table = false;

  bool          m_have_rtf = false;

public:

  TextRun                            m_current_run;
  ON_ClassArray< TextRun >           m_run_stack;

  void InitStringBuilder(const ON_DimStyle* default_style);
  const ON_wString OutputString();
  void PushRun(TextRun& run);
  TextRun PopRun();

  //bool InFontTable();
  //void SetInFontTable(bool b);
  bool InColorTable();
  void SetInColorTable(bool b);
  
  // virtuals

  void GroupBegin() override;
  void GroupEnd() override;
  void RunBegin() override;
  void RunEnd() override;

  void BeginHeader() override;
  void BeginFontTable() override;
  void BeginColorTable() override;
  void TextField(const wchar_t* name) override;
  bool AppendCodePoint(ON__UINT32 codept) override;

  void FontTag(const wchar_t* value) override;

  void Bold(const wchar_t* value) override;
  void Italic(const wchar_t* value) override;
  void UnderlineOn() override;
  void UnderlineOff() override;
  void Strikethrough(const wchar_t* value) override;

private:
  ON_RtfFirstChar operator=(const ON_RtfFirstChar& src);
};

#endif

class ON_RtfParser
{
public:
  ON_RtfParser(ON_TextIterator& iter, ON_TextBuilder& builder);
  bool Parse();

private:
  ON__UINT32 Internal_ParseMBCSString(
    const ON__UINT32 windows_code_page
    );

  ON_TextIterator&   m_ti;

  ON_TextBuilder&    m_builder;
  int                m_p_level = 0;
  bool               m_in_real_rtf = false;
  // suspend to close means ignore everything until the current group is closed
  // It's used to skip over commands and parameters in optional tags
  int                m_suspend_to_close = 0;

  bool FlushCurText(ON_SimpleArray< ON__UINT32 >& cp_array);
  bool ReadTag(bool optional);
  bool ReadOptionalTag();
  bool ProcessTag(const wchar_t* name, const wchar_t* value, bool optional);

  ON_RtfParser operator=(const ON_RtfParser& src);
};

class ON_CLASS RtfComposer
{
public: 
  class RunInfo
  {
  public:
    ON_TextRun::RunType m_run_type = ON_TextRun::RunType::kNone;
    ON_TextRun* m_text_run = nullptr;
    ON_wString m_run_text;
    bool m_bold = false;
    bool m_italic = false;
    bool m_underline = false;
    bool m_strikeout = false;
    ON_wString m_facename = L"Arial";
    int m_facename_key = -1;
  };

  static bool Compose(
    const ON_TextContent* text,
    ON_wString& rtf,
    bool bForceRtf);

  static const ON_wString ComposeAppleRTF(
    const ON_TextContent* text);

  static bool RecomposeRTF();
  static void SetRecomposeRTF(bool b);

  static bool ComposeFS();
  static void SetComposeFS(bool b);
  static int TextEditorFontSize();
  static void SetTextEditorFontSize(unsigned int size);


private:
  static bool m_bComposeRTF;
  static bool m_bComposeFS;
  static int m_TextEditorSize;

  RtfComposer();

  static unsigned int GetFacenameKey(const ON_Font* font, ON_ClassArray< ON_wString >& fonttable);
  static unsigned int GetFacenameKey(const wchar_t* facename, ON_ClassArray< ON_wString >& fonttable);
  static unsigned int GetColorKey(ON_Color color, ON_SimpleArray< unsigned int >& colortable);
  static bool FormatTextHeight(double height, ON_wString& str);
};


#endif 

