#ifndef	rfc3676_h
#define	rfc3676_h
/*
** Copyright 2011 S. Varshavchik.  See COPYING for
** distribution information.
*/

/*
*/

#include	"rfc2045/rfc2045_config.h"
#include	<courier-unicode.h>
#include	<string_view>
#include	<string>

namespace mail {

	// Parse a text/plain message into logical text lines.

	class textplainparser : private unicode::iconvert::tou,
		public unicode::linebreakc_callback_base {

		// Callback from the underlying unicode::iconvert::tou class.
		int converted(const char32_t *, size_t) override;

	public:
		// Constructor. Pass in the character set used in the message
		// and whether the message is flowed and/or delsp.
		textplainparser(
			const std::string &charset,
			bool isflowed,
			bool isdelsp
		);

		virtual ~textplainparser();

		// MIME format flowed flag set 
		const bool isflowed;

		// MIME delsp=yes flag is set
		const bool isdelsp;

		// Parsing started. Set to FALSE if parsing could not be
		// initialized for some reason. This should not happen normally.
		bool begun=false;

		// End parsing.
		//
		// The handle gets destroyed, and the parsing finishes.
		//
		// NOTE: rfc3676_deinit() WILL LIKELY invoke some leftover
		// callback methods.
		//
		// Returns non-0 value returned by any callback method, or 0 if all
		// invoked callback methods returned 0.

		void end(
			// Set to true if a unicode conversion error occurred.
			bool &unicode_errflag
		);

		// A convenience version of end()
		void end()
		{
			bool dummy;

			return end(dummy);
		}

		// Feed raw contents to be parsed
		void operator<<(const std::string_view &text);

	private:
		// Initial parsing of flowed text format.
		//
		// Start of a logical line.
		// Invoked at the start of a logical line.
		virtual void line_begin(size_t);

		// Invoked with the contents of a logical line.
		virtual void line_contents(const char32_t *,size_t);

		// Notified when a physical line flowed into the next
		// physical line.
		virtual void line_flowed_notify();

		// Invoked at the end of the contents of a logical line.
		virtual void line_end();

		// Current error status of the parser
		bool errflag{false};

	public:
		// Set to true if the character set passed to the constructor
		// was unknown to the unicode conversion library. We should
		// have defaulted to iso-8859-1 instead.
		bool unknown_charset{false};

	private:
		// Receive raw text stream, converted to unicode
		size_t (textplainparser::*line_handler)(
			const char32_t *ptr,
			size_t cnt
		);

		// Receive mostly raw text stream: CRs that precede an LF
		// are removed from the stream received by content_handler.
		size_t (textplainparser::*content_handler)(
			const char32_t *ptr,
			size_t cnt
		);

		size_t quote_level{0};
		size_t sig_block_index{0};

		// Flag: previous line ended in a flowed space, and the previous
		// line's quoting level was this.
		int has_previous_quote_level{0};
		size_t previous_quote_level{0};

		// Flag: current line was flowed into from a previous line with the
		// same quoting level.
		int was_previous_quote_level{0};

		// A line has begun
		void (textplainparser::*line_begin_handler)();

		// Content of this line
		void (textplainparser::*line_content_handler)(
			const char32_t *uc,
			size_t cnt
		);

		// End of this line
		void (textplainparser::*line_end_handler)();

		std::u32string nonflowed_line;
		// Collect unflowed line until it reaches the given size

		std::u32string nonflowed_next_word;
		// Collects unicode stream until a linebreaking opportunity

		size_t nonflowed_line_target_width{0};
		// Targeted width of nonflowed lines

		size_t nonflowed_line_width{0};
		// Width of nonflowed_line

		size_t nonflowed_next_word_width{0};
		// Width of nonflowed_next_word

		// Callback invoked by the unicode linebreak analysis.
		int callback(int opportunity, char32_t ch) override;

		// Current handle of non-flowd content.
		void (textplainparser::*nonflowed_line_process)(
			int linebreak_opportunity,
			char32_t ch,
			size_t ch_width
		);

		void (textplainparser::*nonflowed_line_end)();

		// API: begin logical line, contents, flowed, end.
		void emit_line_begin();

		// Emit contents of the current logical line as is.  Flowed
		// content should not contain any whitespace at the end of the
		// line, and should not contain any leading whitespace either.
		// Delsp=yes content is expected to have the leading whitespace
		// removed already by scan_content_line().
		//
		// This function will emit the contents of the current logical
		// line as is.
		void emit_line_contents(const char32_t *uc, size_t cnt);

		void emit_line_flowed_wrap();

		void emit_line_end();

		void nonflowed_line_begin();

		void nonflowed_line_contents(const char32_t *uc, size_t cnt);

		void nonflowed_line_end_default();

		static int nonflowed_line_process_trampoline(
			int linebreak_opportunity,
			char32_t ch,
			void *dummy
		);

		// Look for a CR that might precede an LF.
		size_t scan_crlf(const char32_t *ptr, size_t cnt);

		// Check the first character after a CR.
		size_t scan_crlf_seen_cr(const char32_t *ptr, size_t cnt);

		// Check for an EOF indication at the start of the line.
		size_t start_of_line(const char32_t *ptr, size_t cnt);

		// Count leading > in flowed content.
		size_t count_quote_level(const char32_t *ptr, size_t cnt);

		// This line's quote level has now been counted.
		size_t counted_quote_level(const char32_t *ptr, size_t cnt);

		// Check for a magical sig block
		size_t check_signature_block(const char32_t *ptr, size_t cnt);

		// Minor deviation from RFC3676, but this fixes a lot of broken
		// text.  If the previous line was flowed, but this is an empty
		// line (optionally space-stuffed), unflow the last line (make
		// it fixed), and this becomes a fixed line too.
		size_t start_content_line(const char32_t *ptr, size_t cnt);

		// Pass through the line, until encountering an NL, or a space
		// in flowable content.
		size_t scan_content_line(const char32_t *ptr, size_t cnt);

		// Checking for a magical signature block.
		size_t seen_sig_block(const char32_t *ptr, size_t cnt);

		// This is not a sig block line.
		size_t seen_notsig_block(const char32_t *ptr, size_t cnt);

		// Pass through the line, until encountering an NL, or a space
		// in flowable content.
		size_t seen_content_sp(const char32_t *ptr, size_t cnt);

		// Collecting initial nonflowed line.
		void initial_nonflowed_line(
			int linebreak_opportunity,
			char32_t ch,
			size_t ch_width
		);

		// End of line handler. The line did not reach its threshold, so
		// output it.
		void initial_nonflowed_end();

		void begin_forced_rewrap();

		void forced_rewrap_line(
			int linebreak_opportunity,
			char32_t ch,
			size_t ch_width
		);

		void forced_rewrap_end();

		// Check for the abnormal situation where we're ready to wrap
		// something but nonflowed_line is empty because all this text did
		// not have a linebreaking opportunity.
		void check_abnormal_line();

		// We've decided that the line is too long, so begin rewrapping it.
		void emit_rewrapped_line();

	};
}

#endif
