/** 
 * @file llurlrequest.h
 * @author Phoenix
 * @date 2005-04-21
 * @brief Declaration of url based requests on pipes.
 *
 * $LicenseInfo:firstyear=2005&license=viewergpl$
 * 
 * Copyright (c) 2005-2008, 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_LLURLREQUEST_H
#define LL_LLURLREQUEST_H

/** 
 * This file holds the declaration of useful classes for dealing with
 * url based client requests. 
 */

#include <string>
#include "lliopipe.h"
#include "llchainio.h"
#include "llerror.h"

class LLURLRequestDetail;

class LLURLRequestComplete;

/** 
 * @class LLURLRequest
 * @brief Class to handle url based requests.
 * @see LLIOPipe
 *
 * Currently, this class is implemented on top of curl. From the
 * vantage of a programmer using this class, you do not care so much,
 * but it's useful to know since in order to accomplish 'non-blocking'
 * behavior, we have to use a more expensive curl interface which can
 * still block if the server enters a half-accepted state. It would be
 * worth the time and effort to eventually port this to a raw client
 * socket.
 */
class LLURLRequest : public LLIOPipe
{
	LOG_CLASS(LLURLRequest);
public:
	/** 
	 * @brief This enumeration is for specifying the type of request.
	 */
	enum ERequestAction
	{
		INVALID,
		HTTP_HEAD,
		HTTP_GET,
		HTTP_PUT,
		HTTP_POST,
		HTTP_DELETE,
		HTTP_MOVE, // Caller will need to set 'Destination' header
		REQUEST_ACTION_COUNT
	};

	/** 
	 * @brief Constructor.
	 *
	 * @param action One of the ERequestAction enumerations.
	 */
	LLURLRequest(ERequestAction action);

	/** 
	 * @brief Constructor.
	 *
	 * @param action One of the ERequestAction enumerations.
	 * @param url The url of the request. It should already be encoded.
	 */
	LLURLRequest(ERequestAction action, const std::string& url);

	/** 
	 * @brief Destructor.
	 */
	virtual ~LLURLRequest();

	/* @name Instance methods
	 */
	//@{
	/** 
	 * @brief Set the url for the request
	 *
	 * This method assumes the url is encoded appropriately for the
	 * request. 
	 * The url must be set somehow before the first call to process(),
	 * or the url will not be set correctly.
	 * 
	 */
	void setURL(const std::string& url);

	/** 
	 * @brief Add a header to the http post.
	 *
	 * The header must be correctly formatted for HTTP requests. This
	 * provides a raw interface if you know what kind of request you
	 * will be making during construction of this instance. All
	 * required headers will be automatically constructed, so this is
	 * usually useful for encoding parameters.
	 */
	void addHeader(const char* header);

	/**
	 * @brief Check remote server certificate signed by a known root CA.
	 *
	 * Set whether request will check that remote server
	 * certificates are signed by a known root CA when using HTTPS.
	 */
	void checkRootCertificate(bool check);

	/**
	 * @brief Return at most size bytes of body.
	 *
	 * If the body had more bytes than this limit, they will not be
	 * returned and the connection closed.  In this case, STATUS_STOP
	 * will be passed to responseStatus();
	 */
	void setBodyLimit(U32 size);

	/** 
	 * @brief Set a completion callback for this URLRequest.
	 *
	 * The callback is added to this URLRequet's pump when either the
	 * entire buffer is known or an error like timeout or connection
	 * refused has happened. In the case of a complete transfer, this
	 * object builds a response chain such that the callback and the
	 * next process consumer get to read the output.
	 *
	 * This setup is a little fragile since the url request consumer
	 * might not just read the data - it may do a channel change,
	 * which invalidates the input to the callback, but it works well
	 * in practice.
	 */
	void setCallback(LLURLRequestComplete* callback);
	//@}

	/* @name LLIOPipe virtual implementations
	 */

    /**
     * @ brief Turn off (or on) the CURLOPT_PROXY header.
     */
    void useProxy(bool use_proxy);

    /**
     * @ brief Set the CURLOPT_PROXY header to the given value.
     */
	void useProxy(const std::string& proxy);

public:
	/** 
	 * @brief Give this pipe a chance to handle a generated error
	 */
	virtual EStatus handleError(EStatus status, LLPumpIO* pump);
	
protected:
	/** 
	 * @brief Process the data in buffer
	 */
	virtual EStatus process_impl(
		const LLChannelDescriptors& channels,
		buffer_ptr_t& buffer,
		bool& eos,
		LLSD& context,
		LLPumpIO* pump);
	//@}

protected:
	enum EState
	{
		STATE_INITIALIZED,
		STATE_WAITING_FOR_RESPONSE,
		STATE_PROCESSING_RESPONSE,
		STATE_HAVE_RESPONSE,
	};
	EState mState;
	ERequestAction mAction;
	LLURLRequestDetail* mDetail;
	LLIOPipe::ptr_t mCompletionCallback;

private:
	/** 
	 * @brief Initialize the object. Called during construction.
	 */
	void initialize();

	/** 
	 * @brief Handle action specific url request configuration.
	 *
	 * @return Returns true if this is configured.
	 */
	bool configure();

	/** 
	 * @brief Download callback method.
	 */
 	static size_t downCallback(
		char* data,
		size_t size,
		size_t nmemb,
		void* user);

	/** 
	 * @brief Upload callback method.
	 */
 	static size_t upCallback(
		char* data,
		size_t size,
		size_t nmemb,
		void* user);

	/** 
	 * @brief Declaration of unimplemented method to prevent copy
	 * construction.
	 */
	LLURLRequest(const LLURLRequest&);
};


/** 
 * @class LLContextURLExtractor
 * @brief This class unpacks the url out of a agent usher service so
 * it can be packed into a LLURLRequest object.
 * @see LLIOPipe
 *
 * This class assumes that the context is a map that contains an entry
 * named CONTEXT_DEST_URI_SD_LABEL.
 */
class LLContextURLExtractor : public LLIOPipe
{
public:
	LLContextURLExtractor(LLURLRequest* req) : mRequest(req) {}
	~LLContextURLExtractor() {}

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);
	//@}

protected:
	LLURLRequest* mRequest;
};


/** 
 * @class LLURLRequestComplete
 * @brief Class which can optionally be used with an LLURLRequest to
 * get notification when the url request is complete.
 */
class LLURLRequestComplete : public LLIOPipe
{
public:
	
	virtual void header(const std::string& header, const std::string& value);
	    ///< Called once for each header received, prior to httpStatus

	virtual void httpStatus(U32 status, const std::string& reason);
	    ///< Always called on request completion, prior to complete

	virtual void complete(
		const LLChannelDescriptors& channels,
		const buffer_ptr_t& buffer);

	/** 
	 * @brief This method is called when we got a valid response.
	 *
	 * It is up to class implementers to do something useful here.
	 */
	virtual void response(
		const LLChannelDescriptors& channels,
		const buffer_ptr_t& buffer);

	/** 
	 * @brief This method is called if there was no response.
	 *
	 * It is up to class implementers to do something useful here.
	 */
	virtual void noResponse();

	/** 
	 * @brief This method will be called by the LLURLRequest object.
	 *
	 * If this is set to STATUS_OK or STATUS_STOP, then the transfer
	 * is asssumed to have worked. This will lead to calling response()
	 * on the next call to process(). Otherwise, this object will call
	 * noResponse() on the next call to process.
	 * @param status The status of the URLRequest.
	 */
	void responseStatus(EStatus status);

	// constructor & destructor.
	LLURLRequestComplete();
	virtual ~LLURLRequestComplete();

	// The first line of an http response must be the status line
	// true if we have already parsed this line.
	bool haveHTTPStatus() const { return mHaveHTTPStatus; }
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);
	//@}

	// value to note if we actually got the response. This value
	// depends on correct useage from the LLURLRequest instance.
	EStatus mRequestStatus;

	bool mHaveHTTPStatus;
};


/** 
 * @class LLURLRequestClientFactory
 * @brief Template class to build url request based client chains 
 *
 * This class eases construction of a basic sd rpc client. Here is an
 * example of it's use:
 * <code>
 *  class LLUsefulService : public LLService { ... }<br>
 *  LLService::registerCreator(<br>
 *    "useful",<br>
 *    LLService::creator_t(new LLURLRequestClientFactory<LLUsefulService>))<br>
 * </code>
 *
 * This class should work, but I never got around to using/testing it.
 *
 */
#if 0
template<class Client>
class LLURLRequestClientFactory : public LLChainIOFactory
{
public:
	LLURLRequestClientFactory(LLURLRequest::ERequestAction action) {}
	LLURLRequestClientFactory(
		LLURLRequest::ERequestAction action,
		const std::string& fixed_url) :
		mAction(action),
		mURL(fixed_url)
	{
	}
	virtual bool build(LLPumpIO::chain_t& chain, LLSD context) const
	{
		lldebugs << "LLURLRequestClientFactory::build" << llendl;
		LLIOPipe::ptr_t service(new Client);
		chain.push_back(service);
		LLURLRequest* http(new LLURLRequest(mAction));
		LLIOPipe::ptr_t http_pipe(http);
		// *FIX: how do we know the content type?
		//http->addHeader("Content-Type: text/llsd");
		if(mURL.empty())
		{
			chain.push_back(LLIOPipe::ptr_t(new LLContextURLExtractor(http)));
		}
		else
		{
			http->setURL(mURL);
		}
		chain.push_back(http_pipe);
		chain.push_back(service);
		return true;
	}

protected:
	LLURLRequest::ERequestAction mAction;
	std::string mURL;
};
#endif

/**
 * External constants
 */
extern const std::string CONTEXT_DEST_URI_SD_LABEL;

#endif // LL_LLURLREQUEST_H
