/** 
 * @file lliopipe.h
 * @author Phoenix
 * @date 2004-11-18
 * @brief Declaration of base IO class
 *
 * $LicenseInfo:firstyear=2004&license=viewergpl$
 * 
 * Copyright (c) 2004-2009, Linden Research, Inc.
 * 
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at
 * http://secondlifegrid.net/programs/open_source/licensing/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */

#ifndef LL_LLIOPIPE_H
#define LL_LLIOPIPE_H

#include <boost/intrusive_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include "apr_poll.h"

#include "llsd.h"

class LLIOPipe;
class LLPumpIO;
class LLBufferArray;
class LLChannelDescriptors;

// Debugging schmutz for deadlocks
//#define LL_DEBUG_PUMPS
#ifdef LL_DEBUG_PUMPS
void pump_debug(const char *file, S32 line);
#define PUMP_DEBUG pump_debug(__FILE__, __LINE__);
#define END_PUMP_DEBUG pump_debug("none", 0);
#else /* LL_DEBUG_PUMPS */
#define PUMP_DEBUG
#define END_PUMP_DEBUG
#endif


/**
 * intrusive pointer support
 */
namespace boost
{
	void intrusive_ptr_add_ref(LLIOPipe* p);
	void intrusive_ptr_release(LLIOPipe* p);
};

/** 
 * @class LLIOPipe
 * @brief This class is an abstract base class for data processing units
 * @see LLPumpIO
 *
 * The LLIOPipe is a base class for implementing the basic non-blocking
 * processing of data subsystem in our system.
 *
 * Implementations of this class should behave like a stateful or
 * stateless signal processor. Each call to <code>process()</code>
 * hands the pipe implementation a buffer and a set of channels in the
 * buffer to process, and the pipe returns the status of the
 * operation. This is an abstract base class and developer created
 * concrete implementations provide block or stream based processing
 * of data to implement a particular protocol.
 */
class LLIOPipe
{
public:
	/** 
	 * @brief I have decided that IO objects should have a reference
	 * count. In general, you can pass bald LLIOPipe pointers around
	 * as you need, but if you need to maintain a reference to one,
	 * you need to hold a ptr_t.
	 */
	typedef boost::intrusive_ptr<LLIOPipe> ptr_t;

	/** 
	 * @brief Scattered memory container.
	 */
	typedef boost::shared_ptr<LLBufferArray> buffer_ptr_t;

	/** 
	 * @brief Enumeration for IO return codes
	 *
	 * A status code a positive integer value is considered a success,
	 * but may indicate special handling for future calls, for
	 * example, issuing a STATUS_STOP to an LLIOSocketReader instance
	 * will tell the instance to stop reading the socket. A status
	 * code with a negative value means that a problem has been
	 * encountered which will require further action on the caller or
	 * a developer to correct. Some mechanisms, such as the LLPumpIO
	 * may depend on this definition of success and failure.
	 */
	enum EStatus
	{
		// Processing occurred normally, future calls will be accepted.
		STATUS_OK = 0,

		// Processing occured normally, but stop unsolicited calls to
		// process.
		STATUS_STOP = 1,

		// This pipe is done with the processing. Future calls to
		// process will be accepted as long as new data is available.
		STATUS_DONE = 2,

		// This pipe is requesting that it become the head in a process.
		STATUS_BREAK = 3,

		// This pipe is requesting that it become the head in a process.
		STATUS_NEED_PROCESS = 4,

		// Keep track of the highest number of success codes here.
		STATUS_SUCCESS_COUNT = 5,

		// A generic error code.
		STATUS_ERROR = -1,

		// This method has not yet been implemented. This usually
		// indicates the programmer working on the pipe is not yet
		// done.
		STATUS_NOT_IMPLEMENTED = -2,

		// This indicates that a pipe precondition was not met. For
		// example, many pipes require an element to appear after them
		// in a chain (ie, mNext is not null) and will return this in
		// response to method calls. To recover from this, it will
		// require the caller to adjust the pipe state or may require
		// a dev to adjust the code to satisfy the preconditions.
		STATUS_PRECONDITION_NOT_MET = -3,

		// This means we could not connect to a remote host.
		STATUS_NO_CONNECTION = -4,

		// The connection was lost.
		STATUS_LOST_CONNECTION = -5,

		// The totoal process time has exceeded the timeout.
		STATUS_EXPIRED = -6,

		// Keep track of the count of codes here.
		STATUS_ERROR_COUNT = 6,
	};

	/** 
	 * @brief Helper function to check status.
	 *
	 * When writing code to check status codes, if you do not
	 * specifically check a particular value, use this method for
	 * checking an error condition.
	 * @param status The status to check.
	 * @return Returns true if the code indicates an error occurred.
	 */
	inline static bool isError(EStatus status)
	{
		return ((S32)status < 0);
	}

	/** 
	 * @brief Helper function to check status.
	 *
	 * When writing code to check status codes, if you do not
	 * specifically check a particular value, use this method for
	 * checking an error condition.
	 * @param status The status to check.
	 * @return Returns true if the code indicates no error was generated.
	 */
	inline static bool isSuccess(EStatus status)
	{
		return ((S32)status >= 0);
	}

	/** 
	 * @brief Helper function to turn status into a string.
	 *
	 * @param status The status to check.
	 * @return Returns the name of the status code or empty string on failure.
	 */
	static std::string lookupStatusString(EStatus status);

	/** 
	 * @brief Process the data in buffer.
	 *
	 * @param data The data processed
	 * @param eos True if this function call is the last because end of stream.
	 * @param pump The pump which is calling process. May be NULL.
	 * @param context Shared meta-data for the process.
	 * @return Returns a status code from the operation.
	 */
	EStatus process(
		const LLChannelDescriptors& channels,
		buffer_ptr_t& buffer,
		bool& eos,
		LLSD& context,
		LLPumpIO* pump);

	/** 
	 * @brief Give this pipe a chance to handle a generated error
	 *
	 * If this pipe is in a chain being processed by a pump, and one
	 * of the pipes generates an error, the pump will rewind through
	 * the chain to see if any of the links can handle the error. For
	 * example, if a connection is refused in a socket connection, the
	 * socket client can try to find a new destination host. Return an
	 * error code if this pipe does not handle the error passed in.
	 * @param status The status code for the error
	 * @param pump The pump which was calling process before the error
	 * was generated.
	 * @return Returns a status code from the operation. Returns an
	 * error code if the error passed in was not handled. Returns
	 * STATUS_OK to indicate the error has been handled.
	 */
	virtual EStatus handleError(EStatus status, LLPumpIO* pump);

	/**
	 * @brief Base Destructor - do not call <code>delete</code> directly.
	 */
	virtual ~LLIOPipe();

protected:
	/**
	 * @brief Base Constructor.
	 */
	LLIOPipe();

	/** 
	 * @brief Process the data in buffer
	 */
	virtual EStatus process_impl(
		const LLChannelDescriptors& channels,
		buffer_ptr_t& buffer,
		bool& eos,
		LLSD& context,
		LLPumpIO* pump) = 0;

private:
	friend void boost::intrusive_ptr_add_ref(LLIOPipe* p);
	friend void boost::intrusive_ptr_release(LLIOPipe* p);
	U32 mReferenceCount;
};

namespace boost
{
	inline void intrusive_ptr_add_ref(LLIOPipe* p)
	{
		++p->mReferenceCount;
	}
	inline void intrusive_ptr_release(LLIOPipe* p)
	{
		if(p && 0 == --p->mReferenceCount)
		{
			delete p;
		}
	}
};


#if 0
/** 
 * @class LLIOBoiler
 * @brief This class helps construct new LLIOPipe specializations
 * @see LLIOPipe
 *
 * THOROUGH_DESCRIPTION
 */
class LLIOBoiler : public LLIOPipe
{
public:
	LLIOBoiler();
	virtual ~LLIOBoiler();

protected:
	/* @name LLIOPipe virtual implementations
	 */
	//@{
	/** 
	 * @brief Process the data in buffer
	 */
	virtual EStatus process_impl(
		const LLChannelDescriptors& channels,
		buffer_ptr_t& buffer,
		bool& eos,
		LLSD& context,
		LLPumpIO* pump);
	//@}
};

// virtual
LLIOPipe::EStatus process_impl(
	const LLChannelDescriptors& channels,
	buffer_ptr_t& buffer,
	bool& eos,
	LLSD& context,
	LLPumpIO* pump)
{
	return STATUS_NOT_IMPLEMENTED;
}

#endif // #if 0 - use this block as a boilerplate

#endif // LL_LLIOPIPE_H