#ifndef OSTREAM_WRAPPER_H
#define OSTREAM_WRAPPER_H

#include "ostream-private.h"

/* The wrapper output stream allows turning any form* of activity involving data
   output into a standard Dovecot output stream. The wrapper output stream can
   operate both in blocking and non-blocking mode. When the wrapped activity is
   non-blocking, a blocking wrapper output stream will implicitly run its own
   ioloop.

   It is possible to have the wrapper output stream object available even before
   the data can be written anywhere, even before any form of output object (a
   connection) exists. In that case, any data written to the wrapper stream is
   buffered until the buffer is full. Once that happens, the stream will block
   or refuse writes until the underlying output becomes available.

   The wrapper output stream is not meant to be used directly. Instead, it is
   to be used as part of the implementation of an application-specific output
   stream. The wrapper output stream serves as the means to prevent code
   duplication between similar output stream implementations. It defines several
   methods that need to be implemented by the application-specific output
   stream.

   * Currently, the wrapper stream still expects an output stream object when
     data is to be written somewhere, but that should be easily circumvented
     once such behavior is needed (FIXME).
 */

struct wrapper_ostream {
	struct ostream_private ostream;
	struct event *event;

	/* Called when the implementation should start making the parent output
	   stream available, e.g. connect to the server. This happens when data
	   was written to the wrapper ostream (when it is corked this only
	   happens when the wrapper ostream buffer is full or the wrapper
	   ostream is finished). */
	void (*output_start)(struct wrapper_ostream *wostream);
	/* Returns TRUE when the output is ready for data. */
	bool (*output_ready)(struct wrapper_ostream *wostream);
	/* Called when an error occurred while writing to the output stream. */
	void (*output_error)(struct wrapper_ostream *wostream);
	/* Called when the wrapper ostream was finished using o_stream_finish()
	   and the wrapper ostream buffer is empty. Also, the parent output
	   was flushed successfully. */
	int (*output_finish)(struct wrapper_ostream *wostream);
	/* Called when the wrapper ostream does not need write to parent output
	   stream. This is will e.g. drop the parent output's flush callback or
	   equivalent notification mechanism. */
	void (*output_halt)(struct wrapper_ostream *wostream);
	/* Called when the wrapper ostream has data available for the parent
	   output and wants wrapper_ostream_continue() to be called when the
	   parent stream is writeable. */
	void (*output_resume)(struct wrapper_ostream *wostream);
	/* Update the timeouts. The sender_blocking parameter indicates which
	   side of the data transfer is blocking, so whether a timeout needs to
	   be set for limiting the time other side is not doing anything. */
	void (*output_update_timeouts)(struct wrapper_ostream *wostream,
				       bool sender_blocking);

	/* Called before and after running ioloop for performing blocking I/O
	   wait. Use these vfuncs to switch to and from the temporary ioloop. */
	struct ioloop *(*wait_begin)(struct wrapper_ostream *wostream,
				     struct ioloop *ioloop);
	void (*wait_end)(struct wrapper_ostream *wostream,
			 struct ioloop *prev_ioloop);

	/* Called before and after running the flush callback for the ostream.
	  */
	void (*callback_pre)(struct wrapper_ostream *wostream);
	void (*callback_post)(struct wrapper_ostream *wostream);

	/* Called when the ostream is switched to a different ioloop. */
	void (*switch_ioloop_to)(struct wrapper_ostream *wostream,
				 struct ioloop *ioloop);

	/* Called when the wrapper ostream is forcibly closed using
	   o_stream_close() (or indirectly through e.g. o_stream_destroy()). */
	void (*close)(struct wrapper_ostream *wostream);
	/* Called when the ostream is destroyed. */
	void (*destroy)(struct wrapper_ostream *wostream);

	buffer_t *buffer; // FIXME: use a ringbuffer instead (file_ostream)

	/* The (parent) output stream. */
	struct ostream *output;

	/* The ioloop used while flushing/sending output for when the wrapper
	   ostream is blocking. */
	struct ioloop *flush_ioloop;

	/* Error set using wrapper_ostream_return_error(). This is returned to
	   the application once it continues using the wrapper ostream. */
	char *pending_error;
	int pending_errno;

	/* Timeout for delayed execution of wrapper_ostream_continue(). */
	struct timeout *to_event;

	/* Output was started (output_start() vfunc was called). */
	bool output_started:1;
	/* Output was finished (output_finish() vfunc was called). */
	bool output_finished:1;
	/* Output was closed somehow. This means that the output is no
	   longer available. This is not the same as the ostream close flag. */
	bool output_closed:1;
	/* Output was closed directly or indirectly by the application action.
	 */
	bool output_closed_api:1;

	bool flush_pending:1;
	bool flush_waiting:1;
	bool flushing:1;
	bool continuing:1;
	bool returned_error:1;
};

/* Create the wrapper output stream. This function calls o_stream_create()
   internally. The initial maximum buffer size is set to max_buffer_size. When
   blocking is TRUE, a blocking output stream will be created. The provided
   event is used internally for debug logging. */
struct ostream *
wrapper_ostream_create(struct wrapper_ostream *wostream,
		       size_t max_buffer_size, bool blocking,
		       struct event *event) ATTR_NULL(4);

/* Continue sending output. Returns 1 if all buffered data is sent so far,
   0 if not, and -1 if an error occurred. */
int wrapper_ostream_continue(struct wrapper_ostream *wostream);
/* Trigger an (asynchronous) flush on the output stream. */
void wrapper_ostream_trigger_flush(struct wrapper_ostream *wostream);

/* This function returns the size of the data buffered in the wrapper stream,
   but only when the output stream is finished using o_stream_finish(). When the
   output stream is finished, the data is complete and this function returns
   TRUE and size_r is set to the size. If it is not complete, this function
   returns FALSE and size_r is not assigned. This function is meant to be called
   just before sending the first block of data internally for deciding between
   sending the data using a chunked transfer encoding or, when it is already
   complete, as a single blob with known size. E.g., for HTTP this is the choice
   between sending the message using the Transfer-Encoding: chunked header or
   the Content-Length header. */
bool wrapper_ostream_get_buffered_size(struct wrapper_ostream *wostream,
				       uoff_t *size_r);

/* Call this when the underlying output stream first becomes available. */
void wrapper_ostream_output_available(struct wrapper_ostream *wostream,
				      struct ostream *output);
/* Call this to notify the wrapper that the underlying output is destroyed and
   no more data can be written ever. */
void wrapper_ostream_output_destroyed(struct wrapper_ostream *wostream);

/* Call this to notify the wrapper that an error has occurred. It will be
   returned as such for the next stream write/flush and subsequent
   o_stream_get_error(). */
void wrapper_ostream_set_error(struct wrapper_ostream *wostream,
			       int stream_errno, const char *stream_error);
/* Notify the application immediately about any error condition set earlier
   using wrapper_ostream_set_error() by calling the ostream flush callback
   right now.
 */
void wrapper_ostream_notify_error(struct wrapper_ostream *wostream);

#endif
