/**
 * @file _httplibcurl.h
 * @brief Declarations for internal class providing libcurl transport.
 *
 * $LicenseInfo:firstyear=2012&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2012-2014, 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_LIBCURL_H_
#define _LLCORE_HTTP_LIBCURL_H_

#include "linden_common.h"      // Modifies curl/curl.h interfaces

#include <curl/curl.h>
#include <curl/multi.h>

#include <set>

#include "httprequest.h"
#include "_httpservice.h"
#include "_httpinternal.h"


namespace LLCore
{


class HttpPolicy;
class HttpOpRequest;
class HttpHeaders;


/// Implements libcurl-based transport for an HttpService instance.
///
/// Threading:  Single-threaded.  Other than for construction/destruction,
/// all methods are expected to be invoked in a single thread, typically
/// a worker thread of some sort.

class HttpLibcurl
{
public:
    HttpLibcurl(HttpService * service);
    virtual ~HttpLibcurl();

private:
    HttpLibcurl(const HttpLibcurl &);           // Not defined
    void operator=(const HttpLibcurl &);        // Not defined

public:
    typedef std::shared_ptr<HttpOpRequest> opReqPtr_t;

    /// Give cycles to libcurl to run active requests.  Completed
    /// operations (successful or failed) will be retried or handed
    /// over to the reply queue as final responses.
    ///
    /// @return         Indication of how long this method is
    ///                 willing to wait for next service call.
    ///
    /// Threading:  called by worker thread.
    HttpService::ELoopSpeed processTransport();

    /// Add request to the active list.  Caller is expected to have
    /// provided us with a reference count on the op to hold the
    /// request.  (No additional references will be added.)
    ///
    /// Threading:  called by worker thread.
    void addOp(const opReqPtr_t & op);

    /// One-time call to set the number of policy classes to be
    /// serviced and to create the resources for each.  Value
    /// must agree with HttpPolicy::setPolicies() call.
    ///
    /// Threading:  called by init thread.
    void start(int policy_count);

    /// Synchronously stop libcurl operations.  All active requests
    /// are canceled and removed from libcurl's handling.  Easy
    /// handles are detached from their multi handles and released.
    /// Multi handles are also released.  Canceled requests are
    /// completed with canceled status and made available on their
    /// respective reply queues.
    ///
    /// Can be restarted with a start() call.
    ///
    /// Threading:  called by worker thread.
    void shutdown();

    /// Return global and per-class counts of active requests.
    ///
    /// Threading:  called by worker thread.
    int getActiveCount() const;
    int getActiveCountInClass(unsigned int policy_class) const;

    /// Attempt to cancel a request identified by handle.
    ///
    /// Interface shadows HttpService's method.
    ///
    /// @return         True if handle was found and operation canceled.
    ///
    /// Threading:  called by worker thread.
    bool cancel(HttpHandle handle);

    /// Informs transport that a particular policy class has had
    /// options changed and so should effect any transport state
    /// change necessary to effect those changes.  Used mainly for
    /// initialization and dynamic option setting.
    ///
    /// Threading:  called by worker thread.
    void policyUpdated(unsigned int policy_class);

    /// Allocate a curl handle for caller.  May be freed using
    /// either the freeHandle() method or calling curl_easy_cleanup()
    /// directly.
    ///
    /// @return         Libcurl handle (CURL *) or NULL on allocation
    ///                 problem.  Handle will be in curl_easy_reset()
    ///                 condition.
    ///
    /// Threading:  callable by worker thread.
    ///
    /// Deprecation:  Expect this to go away after _httpoprequest is
    /// refactored bringing code into this class.
    CURL * getHandle()
        {
            return mHandleCache.getHandle();
        }

protected:
    /// Invoked when libcurl has indicated a request has been processed
    /// to completion and we need to move the request to a new state.
    bool completeRequest(CURLM * multi_handle, CURL * handle, CURLcode status);

    /// Invoked to cancel an active request, mainly during shutdown
    /// and destroy.
    void cancelRequest(const opReqPtr_t &op);

protected:
    typedef std::set<opReqPtr_t> active_set_t;

    /// Simple request handle cache for libcurl.
    ///
    /// Handle creation is somewhat slow and chunky in libcurl and there's
    /// a pretty good speedup to be had from handle re-use.  So, a simple
    /// vector is kept of 'freed' handles to be reused as needed.  When
    /// that is empty, the first freed handle is kept as a template for
    /// handle duplication.  This is still faster than creation from nothing.
    /// And when that fails, we init fresh from curl_easy_init().
    ///
    /// Handles allocated with getHandle() may be freed with either
    /// freeHandle() or curl_easy_cleanup().  Choice may be dictated
    /// by thread constraints.
    ///
    /// Threading:  Single-threaded.  May only be used by a single thread,
    /// typically the worker thread.  If freeing requests' handles in an
    /// unknown threading context, use curl_easy_cleanup() for safety.

    class HandleCache
    {
    public:
        HandleCache();
        ~HandleCache();

    private:
        HandleCache(const HandleCache &);               // Not defined
        void operator=(const HandleCache &);            // Not defined

    public:
        /// Allocate a curl handle for caller.  May be freed using
        /// either the freeHandle() method or calling curl_easy_cleanup()
        /// directly.
        ///
        /// @return         Libcurl handle (CURL *) or NULL on allocation
        ///                 problem.
        ///
        /// Threading:  Single-thread (worker) only.
        CURL * getHandle();

        /// Free a libcurl handle acquired by whatever means.  Thread
        /// safety is left to the caller.
        ///
        /// Threading:  Single-thread (worker) only.
        void freeHandle(CURL * handle);

    protected:
        typedef std::vector<CURL *> handle_cache_t;

    protected:
        CURL *              mHandleTemplate;        // Template for duplicating new handles
        handle_cache_t      mCache;                 // Cache of old handles
    }; // end class HandleCache

protected:
    HttpService *       mService;           // Simple reference, not owner
    HandleCache         mHandleCache;       // Handle allocator, owner
    active_set_t        mActiveOps;
    unsigned int        mPolicyCount;
    CURLM **            mMultiHandles;      // One handle per policy class
    int *               mActiveHandles;     // Active count per policy class
    bool *              mDirtyPolicy;       // Dirty policy update waiting for stall (per pc)

}; // end class HttpLibcurl

}  // end namespace LLCore

#endif // _LLCORE_HTTP_LIBCURL_H_