/**
 * @file httprequest.cpp
 * @brief Implementation of the HTTPRequest class
 *
 * $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$
 */

#include "httprequest.h"

#include "_httprequestqueue.h"
#include "_httpreplyqueue.h"
#include "_httpservice.h"
#include "_httppolicy.h"
#include "_httpoperation.h"
#include "_httpoprequest.h"
#include "_httpopcancel.h"
#include "_httpopsetget.h"

#include "lltimer.h"
#include "httpstats.h"

namespace
{

bool has_inited(false);

}

namespace LLCore
{

// ====================================
// HttpRequest Implementation
// ====================================


HttpRequest::HttpRequest()
    : mReplyQueue(),
      mRequestQueue(NULL)
{
    mRequestQueue = HttpRequestQueue::instanceOf();
    mRequestQueue->addRef();

    mReplyQueue.reset( new HttpReplyQueue() );

    HTTPStats::instance().recordHTTPRequest();
}


HttpRequest::~HttpRequest()
{
    if (mRequestQueue)
    {
        mRequestQueue->release();
        mRequestQueue = NULL;
    }

    mReplyQueue.reset();
}


// ====================================
// Policy Methods
// ====================================


HttpRequest::policy_t HttpRequest::createPolicyClass()
{
    if (HttpService::RUNNING == HttpService::instanceOf()->getState())
    {
        return 0;
    }
    return HttpService::instanceOf()->createPolicyClass();
}


HttpStatus HttpRequest::setStaticPolicyOption(EPolicyOption opt, policy_t pclass,
                                              long value, long * ret_value)
{
    if (HttpService::RUNNING == HttpService::instanceOf()->getState())
    {
        return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
    }
    return HttpService::instanceOf()->setPolicyOption(opt, pclass, value, ret_value);
}


HttpStatus HttpRequest::setStaticPolicyOption(EPolicyOption opt, policy_t pclass,
                                              const std::string & value, std::string * ret_value)
{
    if (HttpService::RUNNING == HttpService::instanceOf()->getState())
    {
        return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
    }
    return HttpService::instanceOf()->setPolicyOption(opt, pclass, value, ret_value);
}

HttpStatus HttpRequest::setStaticPolicyOption(EPolicyOption opt, policy_t pclass, policyCallback_t value, policyCallback_t * ret_value)
{
    if (HttpService::RUNNING == HttpService::instanceOf()->getState())
    {
        return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC);
    }

    return HttpService::instanceOf()->setPolicyOption(opt, pclass, value, ret_value);
}

HttpHandle HttpRequest::setPolicyOption(EPolicyOption opt, policy_t pclass,
                                        long value, HttpHandler::ptr_t handler)
{
    HttpStatus status;

    HttpOpSetGet::ptr_t op(new HttpOpSetGet());
    if (! (status = op->setupSet(opt, pclass, value)))
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }
    op->setReplyPath(mReplyQueue, handler);
    if (! (status = mRequestQueue->addOp(op)))          // transfers refcount
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }

    mLastReqStatus = status;
    return op->getHandle();
}


HttpHandle HttpRequest::setPolicyOption(EPolicyOption opt, policy_t pclass,
                                        const std::string & value, HttpHandler::ptr_t handler)
{
    HttpStatus status;

    HttpOpSetGet::ptr_t op (new HttpOpSetGet());
    if (! (status = op->setupSet(opt, pclass, value)))
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }
    op->setReplyPath(mReplyQueue, handler);
    if (! (status = mRequestQueue->addOp(op)))          // transfers refcount
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }

    mLastReqStatus = status;
    return op->getHandle();
}


// ====================================
// Request Methods
// ====================================


HttpStatus HttpRequest::getStatus() const
{
    return mLastReqStatus;
}


HttpHandle HttpRequest::requestGet(policy_t policy_id,
                                   const std::string & url,
                                   const HttpOptions::ptr_t & options,
                                   const HttpHeaders::ptr_t & headers,
                                   HttpHandler::ptr_t user_handler)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
    HttpStatus status;

    HttpOpRequest::ptr_t op(new HttpOpRequest());
    if (! (status = op->setupGet(policy_id, url, options, headers)))
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }
    op->setReplyPath(mReplyQueue, user_handler);
    if (! (status = mRequestQueue->addOp(op)))          // transfers refcount
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }

    mLastReqStatus = status;
    return op->getHandle();
}


HttpHandle HttpRequest::requestGetByteRange(policy_t policy_id,
                                            const std::string & url,
                                            size_t offset,
                                            size_t len,
                                            const HttpOptions::ptr_t & options,
                                            const HttpHeaders::ptr_t & headers,
                                            HttpHandler::ptr_t user_handler)
{
    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
    HttpStatus status;

    HttpOpRequest::ptr_t op(new HttpOpRequest());
    if (! (status = op->setupGetByteRange(policy_id, url, offset, len, options, headers)))
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }
    op->setReplyPath(mReplyQueue, user_handler);
    if (! (status = mRequestQueue->addOp(op)))          // transfers refcount
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }

    mLastReqStatus = status;
    return op->getHandle();
}


HttpHandle HttpRequest::requestPost(policy_t policy_id,
                                    const std::string & url,
                                    BufferArray * body,
                                    const HttpOptions::ptr_t & options,
                                    const HttpHeaders::ptr_t & headers,
                                    HttpHandler::ptr_t user_handler)
{
    HttpStatus status;

    HttpOpRequest::ptr_t op(new HttpOpRequest());
    if (! (status = op->setupPost(policy_id, url, body, options, headers)))
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }
    op->setReplyPath(mReplyQueue, user_handler);
    if (! (status = mRequestQueue->addOp(op)))          // transfers refcount
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }

    mLastReqStatus = status;
    return op->getHandle();
}


HttpHandle HttpRequest::requestPut(policy_t policy_id,
                                   const std::string & url,
                                   BufferArray * body,
                                   const HttpOptions::ptr_t & options,
                                   const HttpHeaders::ptr_t & headers,
                                   HttpHandler::ptr_t user_handler)
{
    HttpStatus status;

    HttpOpRequest::ptr_t op (new HttpOpRequest());
    if (! (status = op->setupPut(policy_id, url, body, options, headers)))
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }
    op->setReplyPath(mReplyQueue, user_handler);
    if (! (status = mRequestQueue->addOp(op)))          // transfers refcount
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }

    mLastReqStatus = status;
    return op->getHandle();
}

HttpHandle HttpRequest::requestDelete(policy_t policy_id,
    const std::string & url,
    const HttpOptions::ptr_t & options,
    const HttpHeaders::ptr_t & headers,
    HttpHandler::ptr_t user_handler)
{
    HttpStatus status;

    HttpOpRequest::ptr_t op(new HttpOpRequest());
    if (!(status = op->setupDelete(policy_id, url, options, headers)))
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }
    op->setReplyPath(mReplyQueue, user_handler);
    if (!(status = mRequestQueue->addOp(op)))           // transfers refcount
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }

    mLastReqStatus = status;
    return op->getHandle();
}

HttpHandle HttpRequest::requestPatch(policy_t policy_id,
    const std::string & url,
    BufferArray * body,
    const HttpOptions::ptr_t & options,
    const HttpHeaders::ptr_t & headers,
    HttpHandler::ptr_t user_handler)
{
    HttpStatus status;

    HttpOpRequest::ptr_t op (new HttpOpRequest());
    if (!(status = op->setupPatch(policy_id, url, body, options, headers)))
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }
    op->setReplyPath(mReplyQueue, user_handler);
    if (!(status = mRequestQueue->addOp(op)))           // transfers refcount
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }

    mLastReqStatus = status;
    return op->getHandle();
}

HttpHandle HttpRequest::requestCopy(policy_t policy_id,
    const std::string & url,
    const HttpOptions::ptr_t & options,
    const HttpHeaders::ptr_t & headers,
    HttpHandler::ptr_t user_handler)
{
    HttpStatus status;

    HttpOpRequest::ptr_t op(new HttpOpRequest());
    if (!(status = op->setupCopy(policy_id, url, options, headers)))
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }
    op->setReplyPath(mReplyQueue, user_handler);
    if (!(status = mRequestQueue->addOp(op)))           // transfers refcount
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }

    mLastReqStatus = status;
    return op->getHandle();

}

HttpHandle HttpRequest::requestMove(policy_t policy_id,
    const std::string & url,
    const HttpOptions::ptr_t & options,
    const HttpHeaders::ptr_t & headers,
    HttpHandler::ptr_t user_handler)
{
    HttpStatus status;

    HttpOpRequest::ptr_t op (new HttpOpRequest());
    if (!(status = op->setupMove(policy_id, url, options, headers)))
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }
    op->setReplyPath(mReplyQueue, user_handler);
    if (!(status = mRequestQueue->addOp(op)))           // transfers refcount
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }

    mLastReqStatus = status;
    return op->getHandle();
}


HttpHandle HttpRequest::requestNoOp(HttpHandler::ptr_t user_handler)
{
    HttpStatus status;

    HttpOperation::ptr_t op (new HttpOpNull());
    op->setReplyPath(mReplyQueue, user_handler);
    if (! (status = mRequestQueue->addOp(op)))          // transfers refcount
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }

    mLastReqStatus = status;
    return op->getHandle();
}


HttpStatus HttpRequest::update(long usecs)
{
    HttpOperation::ptr_t op;

    if (usecs)
    {
        const HttpTime limit(totalTime() + HttpTime(usecs));
        while (limit >= totalTime() && (op = mReplyQueue->fetchOp()))
        {
            // Process operation
            op->visitNotifier(this);

            // We're done with the operation
            op.reset();
        }
    }
    else
    {
        // Same as above, just no time limit
        HttpReplyQueue::OpContainer replies;
        mReplyQueue->fetchAll(replies);
        if (! replies.empty())
        {
            for (HttpReplyQueue::OpContainer::iterator iter(replies.begin());
                 replies.end() != iter;
                 ++iter)
            {
                // Swap op pointer for NULL;
                op.reset();
                op.swap(*iter);

                // Process operation
                op->visitNotifier(this);

                // We're done with the operation
            }
        }
    }

    return HttpStatus();
}




// ====================================
// Request Management Methods
// ====================================

HttpHandle HttpRequest::requestCancel(HttpHandle request, HttpHandler::ptr_t user_handler)
{
    HttpStatus status;

    HttpOperation::ptr_t op(new HttpOpCancel(request));
    op->setReplyPath(mReplyQueue, user_handler);
    if (! (status = mRequestQueue->addOp(op)))          // transfers refcount
    {
        mLastReqStatus = status;
        return LLCORE_HTTP_HANDLE_INVALID;
    }

    mLastReqStatus = status;
    return op->getHandle();
}


// ====================================
// Utility Methods
// ====================================

HttpStatus HttpRequest::createService()
{
    HttpStatus status;

    if (! has_inited)
    {
        HttpRequestQueue::init();
        HttpRequestQueue * rq = HttpRequestQueue::instanceOf();
        HttpService::init(rq);
        HTTPStats::createInstance();
        has_inited = true;
    }

    return status;
}


HttpStatus HttpRequest::destroyService()
{
    HttpStatus status;

    if (has_inited)
    {
        HTTPStats::deleteSingleton();
        HttpService::term();
        HttpRequestQueue::term();
        has_inited = false;
    }

    return status;
}


HttpStatus HttpRequest::startThread()
{
    HttpStatus status;

    HttpService::instanceOf()->startThread();

    return status;
}


HttpHandle HttpRequest::requestStopThread(HttpHandler::ptr_t user_handler)
{
    HttpStatus status;
    HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID);

    HttpOperation::ptr_t op(new HttpOpStop());
    op->setReplyPath(mReplyQueue, user_handler);
    if (! (status = mRequestQueue->addOp(op)))          // transfers refcount
    {
        mLastReqStatus = status;
        return handle;
    }

    mLastReqStatus = status;
    handle = op->getHandle();

    return handle;
}


HttpHandle HttpRequest::requestSpin(int mode)
{
    HttpStatus status;
    HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID);

    HttpOperation::ptr_t op(new HttpOpSpin(mode));
    op->setReplyPath(mReplyQueue, HttpHandler::ptr_t());
    if (! (status = mRequestQueue->addOp(op)))          // transfers refcount
    {
        mLastReqStatus = status;
        return handle;
    }

    mLastReqStatus = status;
    handle = op->getHandle();

    return handle;
}


}   // end namespace LLCore