/**
 * @file httpcommon.h
 * @brief Public-facing declarations and definitions of common types
 *
 * $LicenseInfo:firstyear=2012&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2012-2013, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#ifndef	_LLCORE_HTTP_COMMON_H_
#define	_LLCORE_HTTP_COMMON_H_

/// @package LLCore::HTTP
///
/// This library implements a high-level, Indra-code-free (somewhat) client
/// interface to HTTP services based on actual patterns found in the viewer
/// and simulator.  Interfaces are similar to those supplied by the legacy classes
/// LLCurlRequest and LLHTTPClient.  To that is added a policy scheme that
/// allows an application to specify connection behaviors:  limits on
/// connections, HTTP keepalive, HTTP pipelining, retry-on-error limits, etc.
///
/// Features of the library include:
/// - Single, private working thread where all transport and processing occurs.
/// - Support for multiple consumers running in multiple threads.
/// - Scatter/gather (a.k.a. buffer array) model for bulk data movement.
/// - Reference counting used for many object instance lifetimes.
/// - Minimal data sharing across threads for correctness and low latency.
/// 
/// The public interface is declared in a few key header files:
/// - "llcorehttp/bufferarray.h"
/// - "llcorehttp/httpcommon.h"
/// - "llcorehttp/httphandler.h"
/// - "llcorehttp/httpheaders.h"
/// - "llcorehttp/httpoptions.h"
/// - "llcorehttp/httprequest.h"
/// - "llcorehttp/httpresponse.h"
///
/// The library is still under development and particular users
/// may need access to internal implementation details that are found
/// in the _*.h header files.  But this is a crutch to be avoided if at
/// all possible and probably indicates some interface work is neeeded.
///
/// Using the library is fairly easy.  Global setup needs a few
/// steps:
///
/// - libcurl initialization including thread-safely callbacks for SSL:
///   .  curl_global_init(...)
///   .  CRYPTO_set_locking_callback(...)
///   .  CRYPTO_set_id_callback(...)
/// - HttpRequest::createService() called to instantiate singletons
///   and support objects.
/// - HttpRequest::startThread() to kick off the worker thread and
///   begin servicing requests.
///
/// An HTTP consumer in an application, and an application may have many
/// consumers, does a few things:
///
/// - Instantiate and retain an object based on HttpRequest.  This
///   object becomes the portal into runtime services for the consumer.
/// - Derive or mixin the HttpHandler class if you want notification
///   when requests succeed or fail.  This object's onCompleted()
///   method is invoked and an instance can be shared across
///   requests.
///
/// Issuing a request is straightforward:
/// - Construct a suitable URL.
/// - Configure HTTP options for the request.  (optional)
/// - Build a list of additional headers.  (optional)
/// - Invoke one of the requestXXXX() methods (requestGetByteRange,
///   requestPost, etc.) on the HttpRequest instance supplying the
///   above along with a policy class, a priority and an optional
///   pointer to an HttpHandler instance.  Work is then queued to
///   the worker thread and occurs asynchronously.
/// - Periodically invoke the update() method on the HttpRequest
///   instance which performs completion notification to HttpHandler
///   objects.
/// - Do completion processing in your onCompletion() method.
///
/// Code fragments.
///
/// Initialization.  Rather than a poorly-maintained example in
/// comments, look in the example subdirectory which is a minimal
/// yet functional tool to do GET request performance testing.
/// With four calls:
///
///   	init_curl();
///     LLCore::HttpRequest::createService();
///     LLCore::HttpRequest::startThread();
///     LLCore::HttpRequest * hr = new LLCore::HttpRequest();
///
/// the program is basically ready to issue requests.
///
/// HttpHandler.  Having started life as a non-indra library,
/// this code broke away from the classic Responder model and
/// introduced a handler class to represent an interface for
/// request responses.  This is a non-reference-counted entity
/// which can be used as a base class or a mixin.  An instance
/// of a handler can be used for each request or can be shared
/// among any number of requests.  Your choice but expect to
/// code something like the following:
///
///     class AppHandler : public LLCore::HttpHandler
///     {
///     public:
///         virtual void onCompleted(HttpHandle handle,
///                                  HttpResponse * response)
///         {
///             ...
///         }
///         ...
///     };
///     ...
///     handler = new handler(...);
///
///
/// Issuing requests.  Using 'hr' above,
///
///     hr->requestGet(HttpRequest::DEFAULT_POLICY_ID,
///                    0,				// Priority, not used yet
///                    url,
///                    NULL,			// options
///                    NULL,            // additional headers
///                    handler);
///
/// If that returns a value other than LLCORE_HTTP_HANDLE_INVALID,
/// the request was successfully issued and there will eventally
/// be a status delivered to the handler.  If invalid is returnedd,
/// the actual status can be retrieved by calling hr->getStatus().
///
/// Completing requests and delivering notifications.  Operations
/// are all performed by the worker thread and will be driven to
/// completion regardless of caller actions.  Notification of
/// completion (success or failure) is done by calls to
/// HttpRequest::update() which will invoke handlers for completed
/// requests:
///
///     hr->update(0);
///       // Callbacks into handler->onCompleted()
///
///
/// Threads.
///
/// Threads are supported and used by this library.  The various
/// classes, methods and members are documented with thread
/// constraints which programmers must follow and which are
/// defined as follows:
///
/// consumer	Any thread that has instanced HttpRequest and is
///             issuing requests.  A particular instance can only
///             be used by one consumer thread but a consumer may
///             have many instances available to it.
/// init		Special consumer thread, usually the main thread,
///             involved in setting up the library at startup.
/// worker      Thread used internally by the library to perform
///             HTTP operations.  Consumers will not have to deal
///             with this thread directly but some APIs are reserved
///             to it.
/// any         Consumer or worker thread.
///
/// For the most part, API users will not have to do much in the
/// way of ensuring thread safely.  However, there is a tremendous
/// amount of sharing between threads of read-only data.  So when
/// documentation declares that an option or header instance
/// becomes shared between consumer and worker, the consumer must
/// not modify the shared object.
///
/// Internally, there is almost no thread synchronization.  During
/// normal operations (non-init, non-term), only the request queue
/// and the multiple reply queues are shared between threads and
/// only here are mutexes used.
///

#include "linden_common.h"		// Modifies curl/curl.h interfaces
#include "llsd.h"
#include <boost/weak_ptr.hpp>
#include <boost/function.hpp>
#include <string>
#include <curl/curl.h>
#include <boost/make_shared.hpp>

namespace LLCore
{


/// All queued requests are represented by an HttpHandle value.
/// The invalid value is returned when a request failed to queue.
/// The actual status for these failures is then fetched with
/// HttpRequest::getStatus().
///
/// The handle is valid only for the life of a request.  On
/// return from any HttpHandler notification, the handle immediately
/// becomes invalid and may be recycled for other queued requests.

typedef void * HttpHandle;

#define LLCORE_HTTP_HANDLE_INVALID		(NULL)

/// For internal scheduling and metrics, we use a microsecond
/// timebase compatible with the environment.
typedef U64 HttpTime;

/// Error codes defined by the library itself as distinct from
/// libcurl (or any other transport provider).
enum HttpError
{
	// Successful value compatible with the libcurl codes.
	HE_SUCCESS = 0,

	// Intended for HTTP reply codes 100-999, indicates that
	// the reply should be considered an error by the application.
	HE_REPLY_ERROR = 1,
	
	// Service is shutting down and requested operation will
	// not be queued or performed.
	HE_SHUTTING_DOWN = 2,
	
	// Operation was canceled by request.
	HE_OP_CANCELED = 3,
	
	// Invalid content range header received.
	HE_INV_CONTENT_RANGE_HDR = 4,
	
	// Request handle not found
	HE_HANDLE_NOT_FOUND = 5,

	// Invalid datatype for option/setting
	HE_INVALID_ARG = 6,

	// Option hasn't been explicitly set
	HE_OPT_NOT_SET = 7,
	
	// Option not dynamic, must be set during init phase
	HE_OPT_NOT_DYNAMIC = 8,
	
	// Invalid HTTP status code returned by server
	HE_INVALID_HTTP_STATUS = 9,
	
	// Couldn't allocate resource, typically libcurl handle
	HE_BAD_ALLOC = 10
	
}; // end enum HttpError


/// HttpStatus encapsulates errors from libcurl (easy, multi), HTTP
/// reply status codes and internal errors as well.  The encapsulation
/// isn't expected to completely isolate the caller from libcurl but
/// basic operational tests (success or failure) are provided.
///
/// Non-HTTP status are encoded as (type, status) with type being
/// one of:  EXT_CURL_EASY, EXT_CURL_MULTI or LLCORE and status
/// being the success/error code from that domain.  HTTP status
/// is encoded as (status, error_flag).  Status should be in the
/// range [100, 999] and error_flag is either HE_SUCCESS or
/// HE_REPLY_ERROR to indicate whether this should be treated as
/// a successful status or an error.  The application is responsible
/// for making that determination and a range like [200, 299] isn't
/// automatically assumed to be definitive.
///
/// Examples:
///
/// 1.  Construct a default, successful status code:
///				HttpStatus();
///
/// 2.  Construct a successful, HTTP 200 status code:
///				HttpStatus(200);
///
/// 3.  Construct a failed, HTTP 404 not-found status code:
///				HttpStatus(404);
///
/// 4.  Construct a failed libcurl couldn't connect status code:
///				HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_CONNECT);
///
/// 5.  Construct an HTTP 301 status code to be treated as success:
///				HttpStatus(301, HE_SUCCESS);
///
/// 6.	Construct a failed status of HTTP Status 499 with a custom error message
///				HttpStatus(499, "Failed LLSD Response");

struct HttpStatus
{
	typedef unsigned short type_enum_t;
	enum : type_enum_t
	{
		EXT_CURL_EASY = 0,  ///< mStatus is an error from a curl_easy_*() call
		EXT_CURL_MULTI = 1, ///< mStatus is an error from a curl_multi_*() call
		LLCORE = 2			///< mStatus is an HE_* error code
							///< 100-999 directly represent HTTP status codes
	};

	HttpStatus()
	{
		mDetails = std::make_shared<Details>(LLCORE, HE_SUCCESS);
    }

	HttpStatus(type_enum_t type, short status)
	{
        mDetails = std::make_shared<Details>(type, status);
	}
	
	HttpStatus(int http_status)
	{
        mDetails = std::make_shared<Details>(http_status,
                                               http_status >= 200 && http_status <= 299 ? HE_SUCCESS : HE_REPLY_ERROR);
		llassert(http_status >= 100 && http_status <= 999);
	}

	HttpStatus(int http_status, const std::string &message)
	{
        mDetails = std::make_shared<Details>(http_status,
                                               http_status >= 200 && http_status <= 299 ? HE_SUCCESS : HE_REPLY_ERROR);
		llassert(http_status >= 100 && http_status <= 999);
		mDetails->mMessage = message;
	}
	
	HttpStatus(const HttpStatus & rhs)
	{
		mDetails = rhs.mDetails;
	}

	~HttpStatus()
	{
	}

	HttpStatus & operator=(const HttpStatus & rhs)
	{
        mDetails = rhs.mDetails;
		return *this;
	}

    HttpStatus & clone(const HttpStatus &rhs)
    {
        mDetails = std::make_shared<Details>(*rhs.mDetails);
        return *this;
    }
	
	/// Test for successful status in the code regardless
	/// of error source (internal, libcurl).
	///
	/// @return			'true' when status is successful.
	///
	operator bool() const
	{
		return 0 == mDetails->mStatus;
	}

	/// Inverse of previous operator.
	///
	/// @return			'true' on any error condition
	bool operator !() const
	{
		return 0 != mDetails->mStatus;
	}

	/// Equality and inequality tests to bypass bool conversion
	/// which will do the wrong thing in conditional expressions.
	bool operator==(const HttpStatus & rhs) const
	{
        return (*mDetails == *rhs.mDetails); 
	}

	bool operator!=(const HttpStatus & rhs) const
	{
		return ! operator==(rhs);
	}

	/// Convert to single numeric representation.  Mainly
	/// for logging or other informal purposes.  Also
	/// creates an ambiguous second path to integer conversion
	/// which tends to find programming errors such as formatting
	/// the status to a stream (operator<<).
	operator unsigned long() const;
	unsigned long toULong() const
	{
		return operator unsigned long();
	}

	/// And to convert to a hex string.
	std::string toHex() const;
	
	/// Convert status to a string representation.  For
	/// success, returns an empty string.  For failure
	/// statuses, a string as appropriate for the source of
	/// the error code (libcurl easy, libcurl multi, or
	/// LLCore itself).
	std::string toString() const;

	/// Convert status to a compact string representation
	/// of the form:  "<type>_<value>".  The <type> will be
	/// one of:  Core, Http, Easy, Multi, Unknown.  And
	/// <value> will be an unsigned integer.  More easily
	/// interpreted than the hex representation, it's still
	/// compact and easily searched. </value>
	std::string toTerseString() const;

	/// Returns true if the status value represents an
	/// HTTP response status (100 - 999).
	bool isHttpStatus() const
	{
		return 	mDetails->mType >= type_enum_t(100) && mDetails->mType <= type_enum_t(999);
	}

	/// Returns true if the status is one that will be retried
	/// internally.  Provided for external consumption for cases
	/// where that logic needs to be replicated.  Only applies
	/// to failed statuses, successful statuses will return false.
	bool isRetryable() const;

	/// Returns the currently set status code as a raw number
	///
	short getStatus() const
	{
		return mDetails->mStatus;
	}

	/// Returns the currently set status type 
	/// 
	type_enum_t getType() const
	{
		return mDetails->mType;
	}

	/// Returns an optional error message if one has been set.
	///
	std::string getMessage() const
	{
		return mDetails->mMessage;
	}

	/// Sets an optional error message
	/// 
	void setMessage(const std::string &message)
	{
		mDetails->mMessage = message;
	}

	/// Retrieves data about an optionally recorded SSL certificate.
	LLSD getErrorData() const
	{
		return mDetails->mErrorData;
	}

	/// Optionally sets an SSL certificate on this status.
	void setErrorData(LLSD data)
	{
		mDetails->mErrorData = data;
	}

private:

	struct Details
	{
		Details(type_enum_t type, short status):
			mType(type),
			mStatus(status),
			mMessage(),
			mErrorData()
		{}

		Details(const Details &rhs) :
			mType(rhs.mType),
			mStatus(rhs.mStatus),
			mMessage(rhs.mMessage),
			mErrorData(rhs.mErrorData)
		{}

        Details& operator=(const Details &rhs)
        {
            if (this != &rhs)
            {
                mType = rhs.mType;
                mStatus = rhs.mStatus;
                mMessage = rhs.mMessage;
                mErrorData = rhs.mErrorData;
            }
            return *this;
        }

        bool operator == (const Details &rhs) const
        {
            return (mType == rhs.mType) && (mStatus == rhs.mStatus);
        }

		type_enum_t	mType;
		short		mStatus;
		std::string	mMessage;
		LLSD		mErrorData;
	};

    std::shared_ptr<Details> mDetails;

}; // end struct HttpStatus

///  A namespace for several free methods and low level utilities. 
namespace LLHttp
{
    typedef std::shared_ptr<CURL> CURL_ptr;

    void initialize();
    void cleanup();

    CURL_ptr createEasyHandle();
    std::string getCURLVersion();

    void check_curl_code(CURLcode code, int curl_setopt_option);
}

}  // end namespace LLCore

#endif	// _LLCORE_HTTP_COMMON_H_
