// -*- c++ -*-
//------------------------------------------------------------------------------
//                       CharInBuffer.h
//------------------------------------------------------------------------------
//  Copyright (C) 2002  Vladislav Grinchenko <vladg@erols.com>
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Library General Public
//  License as published by the Free Software Foundation; either
//  version 2 of the License, or (at your option) any later version.
//------------------------------------------------------------------------------
#ifndef CHAR_IN_BUFFER_H
#define CHAR_IN_BUFFER_H

/** @file CharInBuffer.h 

    A bucket for collecting character-based stream records of certain
    length or terminated by designated character(s).
*/

#include <sys/types.h>

#include "assa/Assert.h"
#include "assa/Socket.h"

#include <string>
using std::string;

namespace ASSA {

/**
 * CharInBuffer is a bucket for the character-based streams/messages. 
 * It helps in reading, parsing, and storing record-oriented character
 * streams from Socket stream asynchronously. The record terminator can
 * be multibyte. The terminator is detected and removed from the bucket.
 * When terminator is detected, the block of characters collected in the bucket
 * is ready to be processed further by the application according to its 
 * communication protocol.
 * If either Socket read() error is encountered, or an overflow occurs
 * (number of characters read exceeds the maximum limit), the object goes into
 * the error state and won't accept further input, unless reset.
 */

class CharInBuffer
{
public:
	/** Constructor
	 * @param size_ Maximum expected size before buffer overflow
	 * @param delimiter_ End-of-record character(s). Can be multi-byte.
	 */
	CharInBuffer (size_t size_, const string& delimiter_);

	/** Read bytes from Socket stream until either record delimiter is 
	 * detected, or EOF occured, or Socket stream is exhausted.
	 * @return Socket reference
	 */
	friend ASSA::Socket& operator>>(ASSA::Socket&, ASSA::CharInBuffer&);

	/** Test the state of an object
	 * @return true if the object holds a complete message;
	 *         false otherwise (eof, buffer overflow, or incomplete)
	 */
	operator void* () const;

	/// Get the constant character pointer to the buffer
	const char* c_str () const { return m_buffer.c_str (); }

	/// Bytes in the buffer so far
	size_t length () const { return m_buffer.length (); }

	/// Bytes in the buffer so far
	size_t size () const { return m_buffer.size (); }

	/** Discard all accumulated characters and be ready to receive
	 *  a new message.
	 */
	void reset ();

	/// Write the state of an object to the log file.
	void dump () const;

	/** @enum state_t
	 *  States: start, waiting, complete, error
	 */
	enum state_t { 
		start,					/**< start state */
		waiting,				/**< incomplete record is in the buffer */
		complete,				/**< matched end-of-record - full record */
		error					/**< overflow or Socket I/O error */
	};

	/// Report the current state of the object
	state_t state () const { return m_state; }

private:
	/// Report the state name 
	static const char* state_name (state_t state_);

	/// Go to the new state
	void state (state_t new_state_) { m_state = new_state_; }

	/// Remove the delimiter from the end of the buffer
	void chop ();

private:
	/// Internal state of an object
	state_t m_state;

	/// Buffer to store the bytes received
	std::string  m_buffer;

	/// Maximum allowable size (delimiter included) before overflow occurs
	size_t  m_max_size;

	/// Delimiter. Multibyte delimiter is allowed.
	std::string  m_delimiter;
};

} // end namespace ASSA

/*******************************************************************************
 Inline member functions
*******************************************************************************/
using namespace ASSA;

inline
CharInBuffer::
operator void* () const 
{ 
	return (m_state == complete 
			? (void *) (-1)		// good state
			: (void *) 0);		// bad state
}

inline void 
CharInBuffer::
reset () 
{ 
	m_buffer = ""; 
	state (waiting); 
}

inline void 
CharInBuffer::
chop () 
{
	m_buffer.replace (m_buffer.find (m_delimiter), m_delimiter.length (), "");
}

#endif /* CHAR_IN_BUFFER_H */  
