#ifndef binpac_buffer_h
#define binpac_buffer_h

#include <sys/types.h>

#include "binpac.h"

namespace binpac
	{

class FlowBuffer
	{
public:
	struct Policy
		{
		int max_capacity;
		int min_capacity;
		int contract_threshold;
		};

	enum LineBreakStyle
		{
		CR_OR_LF, // CR or LF or CRLF
		STRICT_CRLF, // CR followed by LF
		CR_LF_NUL, // CR or LF or CR-LF or CR-NUL
		LINE_BREAKER, // User specified linebreaker
		};

	FlowBuffer(LineBreakStyle linebreak_style = CR_OR_LF);
	virtual ~FlowBuffer();

	void NewData(const_byteptr begin, const_byteptr end);
	void NewGap(int length);

	// Interface for delayed parsing. Sometimes BinPAC doesn't get the
	// buffering right and then one can use these to feed parts
	// individually and assemble them internally. After calling
	// FinishBuffer(), one can send the uppper-layer flow an FlowEOF() to
	// trigger parsing.
	void BufferData(const_byteptr data, const_byteptr end);
	void FinishBuffer();

	// Discard unprocessed data
	void DiscardData();

	// Whether there is enough data for the frame
	bool ready() const { return message_complete_ || mode_ == UNKNOWN_MODE; }

	inline const_byteptr begin() const
		{
		BINPAC_ASSERT(ready());
		return (buffer_n_ == 0) ? orig_data_begin_ : buffer_;
		}

	inline const_byteptr end() const
		{
		BINPAC_ASSERT(ready());
		if ( buffer_n_ == 0 )
			{
			BINPAC_ASSERT(frame_length_ >= 0);
			const_byteptr end = orig_data_begin_ + frame_length_;
			BINPAC_ASSERT(end <= orig_data_end_);
			return end;
			}
		else
			return buffer_ + buffer_n_;
		}

	inline int data_length() const
		{
		if ( buffer_n_ > 0 )
			return buffer_n_;

		if ( frame_length_ < 0 || orig_data_begin_ + frame_length_ > orig_data_end_ )
			return orig_data_end_ - orig_data_begin_;
		else
			return frame_length_;
		}

	inline bool data_available() const
		{
		return buffer_n_ > 0 || orig_data_end_ > orig_data_begin_;
		}

	void SetLineBreaker(unsigned char* lbreaker);
	void UnsetLineBreaker();
	void NewLine();
	// A negative frame_length represents a frame till EOF
	void NewFrame(int frame_length, bool chunked_);
	void GrowFrame(int new_frame_length);

	int data_seq() const
		{
		int data_seq_at_orig_data_begin = data_seq_at_orig_data_end_ -
		                                  (orig_data_end_ - orig_data_begin_);
		if ( buffer_n_ > 0 )
			return data_seq_at_orig_data_begin;
		else
			return data_seq_at_orig_data_begin + data_length();
		}
	bool eof() const { return eof_; }
	void set_eof();

	bool have_pending_request() const { return have_pending_request_; }

	static void init(Policy p) { policy = p; }

protected:
	// Reset the buffer for a new message
	void NewMessage();

	void ClearPreviousData();

	// Expand the buffer to at least <length> bytes. If there
	// are contents in the existing buffer, copy them to the new
	// buffer.
	void ExpandBuffer(int length);

	// Contract the buffer to some minimum capacity.
	// Existing contents in the buffer are preserved (but only usage
	// at the time of creation this function is when the contents
	// are being discarded due to parsing exception or have already been
	// copied out after parsing a complete unit).
	void ContractBuffer();

	// Reset line state when transit from frame mode to line mode.
	void ResetLineState();

	void AppendToBuffer(const_byteptr data, int len);

	// MarkOrCopy{Line,Frame} sets message_complete_ and
	// marks begin/end pointers if a line/frame is complete,
	// otherwise it clears message_complete_ and copies all
	// the original data to the buffer.
	//
	void MarkOrCopy();
	void MarkOrCopyLine();
	void MarkOrCopyFrame();

	void MarkOrCopyLine_CR_OR_LF();
	void MarkOrCopyLine_STRICT_CRLF();
	void MarkOrCopyLine_LINEBREAK();

	int buffer_n_; // number of bytes in the buffer
	int buffer_length_; // size of the buffer
	unsigned char* buffer_;
	bool message_complete_;
	int frame_length_;
	bool chunked_;
	const_byteptr orig_data_begin_, orig_data_end_;

	LineBreakStyle linebreak_style_;
	LineBreakStyle linebreak_style_default;
	unsigned char linebreaker_;

	enum
		{
		UNKNOWN_MODE,
		LINE_MODE,
		FRAME_MODE,
		} mode_;

	enum
		{
		CR_OR_LF_0,
		CR_OR_LF_1,
		STRICT_CRLF_0,
		STRICT_CRLF_1,
		FRAME_0,
		} state_;

	int data_seq_at_orig_data_end_;
	bool eof_;
	bool have_pending_request_;

	static Policy policy;
	};

typedef FlowBuffer* flow_buffer_t;

	} // namespace binpac

#endif // binpac_buffer_h
