/*
 * Copyright (C) 2016 Canon Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted, provided that the following conditions
 * are required to be met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Canon Inc. nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "FetchResponse.h"

#if ENABLE(FETCH_API)

#include "AbortSignal.h"
#include "Dictionary.h"
#include "ExceptionCode.h"
#include "FetchRequest.h"
#include "HTTPParsers.h"
#include "JSBlob.h"
#include "JSDOMPromiseDeferred.h"
#include "JSFetchResponse.h"
#include "MIMETypeRegistry.h"
#include "ReadableStreamSink.h"
#include "ScriptExecutionContext.h"

namespace WebCore {

static inline bool isRedirectStatus(int status)
{
    return status == 301 || status == 302 || status == 303 || status == 307 || status == 308;
}

// https://fetch.spec.whatwg.org/#null-body-status
static inline bool isNullBodyStatus(int status)
{
    return status == 101 || status == 204 || status == 205 || status == 304;
}

Ref<FetchResponse> FetchResponse::create(ScriptExecutionContext* context, PassOwnPtr<FetchBody> body, PassRefPtr<FetchHeaders> headers, const ResourceResponse& response)
{
	Ref<FetchResponse> fetchResponse = adoptRef(*new FetchResponse(context, body, headers, response));
    fetchResponse->m_filteredResponse = ResourceResponseBase::filter(fetchResponse->m_internalResponse);
    return fetchResponse;
}

PassRefPtr<FetchResponse> FetchResponse::create(JSC::ExecState& exec, ScriptExecutionContext* context, JSC::JSValue body, const Init& init, ExceptionCode& ec)
{
	JSC::VM& vm = exec.vm();

    // 1. If init’s status member is not in the range 200 to 599, inclusive, then throw a RangeError.
    if (init.status < 200  || init.status > 599) {
    	ec = RangeError;
    	setDOMException(&exec, RangeError, ASCIILiteral("Status must be between 200 and 599"));
    	return nullptr;
    }

    // 2. If init’s statusText member does not match the reason-phrase token production, then throw a TypeError.
    if (!isValidReasonPhrase(init.statusText)) {
        ec = TypeError;
        setDOMException(&exec, TypeError, ASCIILiteral("Status text must be a valid reason-phrase."));
        return nullptr;
    }

    // 3. Let r be a new Response object associated with a new response.
    // NOTE: Creation of the Response object is delayed until all potential exceptional cases are handled.

    // 4. Set r’s headers to a new Headers object, whose header list is r’s response’s header list, and guard is "response".
    RefPtr<FetchHeaders> headers = FetchHeaders::create(FetchHeaders::Guard::Response);

    // 5. Set r’s response’s status to init’s status member.
    auto status = init.status;

    // 6. Set r’s response’s status message to init’s statusText member.
    auto statusText = init.statusText;

    // 7. If init’s headers member is present, then fill r’s headers with init’s headers member.
    if (!init.headers.isUndefinedOrNull() && !init.headers.isEmpty()) {
        headers->fill(exec, init.headers, ec);
        if (vm.exception())
            return nullptr;
        if(ec)
        	return nullptr;
    }

    OwnPtr<FetchBody> extractedBody;

    // 8. If body is non-null, run these substeps:
    if (!body.isUndefined()) {
        // 8.1 If init’s status member is a null body status, then throw a TypeError.
        //     (NOTE: 101 is included in null body status due to its use elsewhere. It does not affect this step.)
        if (isNullBodyStatus(init.status)) {
            ec = TypeError;
            setDOMException(&exec, ec, ASCIILiteral("Response cannot have a body with the given status."));
            return nullptr;
        }

        // 8.2 Let Content-Type be null.
        String contentType;

        // 8.3 Set r’s response’s body and Content-Type to the result of extracting body.
        Optional<Exception> exception;
        extractedBody = FetchBody::extract(exec, *context, body, contentType, exception);
        if (exception) {
        	ec = exception->code();
        	setDOMException(&exec, *exception);
        	return nullptr;
        }

        // 8.4 If Content-Type is non-null and r’s response’s header list does not contain `Content-Type`, then append
        //     `Content-Type`/Content-Type to r’s response’s header list.
        if (!contentType.isNull() && !headers->fastHas(HTTPHeaderName::ContentType))
            headers->fastSet(HTTPHeaderName::ContentType, contentType);
    }

    // 9. Set r’s MIME type to the result of extracting a MIME type from r’s response’s header list.
    auto contentType = headers->fastGet(HTTPHeaderName::ContentType);

    // 10. Set r’s response’s HTTPS state to current settings object’s HTTPS state.
    // FIXME: Implement.

    // 11. Resolve r’s trailer promise with a new Headers object whose guard is "immutable".
    // FIXME: Implement.

    // 12. Return r.
    RefPtr<FetchResponse> r = adoptRef(new FetchResponse(context, extractedBody.release(), headers.release(), ResourceResponse()));

    r->m_contentType = contentType;
    String mimeType = extractMIMETypeFromMediaType(contentType);
    r->m_internalResponse.setMimeType(mimeType.isEmpty() ? defaultMIMEType() : mimeType);

    r->m_internalResponse.setHTTPStatusCode(status);
    r->m_internalResponse.setHTTPStatusText(statusText);

    return r.release();
}

Ref<FetchResponse> FetchResponse::error(ScriptExecutionContext* context)
{
	Ref<FetchResponse> response = adoptRef(*new FetchResponse(context, PassOwnPtr<FetchBody>(), FetchHeaders::create(FetchHeaders::Guard::Immutable), ResourceResponse()));
	response->m_internalResponse.setType(Type::Error);
	return response;
}

PassRefPtr<FetchResponse> FetchResponse::redirect(ScriptExecutionContext* context, const String& url, int status, ExceptionCode& ec)
{
    KURL requestURL = context->completeURL(url, ScriptExecutionContext::ForceUTF8::Yes);
    if (!requestURL.isValid() || !requestURL.user().isEmpty() || !requestURL.pass().isEmpty()) {
        ec = TypeError;
        return nullptr;
    }
    if (!isRedirectStatus(status)) {
        ec = RangeError;
        return nullptr;
    }
    Ref<FetchResponse> redirectResponse = adoptRef(*new FetchResponse(context, nullptr, FetchHeaders::create(FetchHeaders::Guard::Immutable), ResourceResponse()));
    redirectResponse->suspendIfNeeded();
    redirectResponse->m_internalResponse.setHTTPStatusCode(status);
    redirectResponse->m_internalResponse.setHTTPHeaderField(HTTPHeaderName::Location, requestURL.string());
	redirectResponse->m_headers->fastSet(HTTPHeaderName::Location, requestURL.string());
    return redirectResponse;
}

FetchResponse::FetchResponse(ScriptExecutionContext* context, PassOwnPtr<FetchBody> body, PassRefPtr<FetchHeaders> headers, const ResourceResponse& response)
    : FetchBodyOwner(context, body, headers)
    , m_internalResponse(response)
{
}

PassRefPtr<FetchResponse> FetchResponse::clone(ExceptionCode& ec)
{
    if (isContextStopped()) {
        ec = INVALID_STATE_ERR;
        return nullptr;
    }

    if (isDisturbedOrLocked()) {
        ec = TypeError;
        return nullptr;
    }

    ASSERT(scriptExecutionContext());
    auto& context = *scriptExecutionContext();

    // If loading, let's create a stream so that data is teed on both clones.
    if (isLoading() && !m_readableStreamSource) {
        auto* globalObject = context.globalObject();
        if (!globalObject) {
            ec = INVALID_STATE_ERR;
            return nullptr;
        }

        createReadableStream(*globalObject->globalExec());
        if (globalObject->vm().exception())
        	return nullptr;
    }

    RefPtr<FetchResponse> clone = adoptRef(new FetchResponse(&context, nullptr, FetchHeaders::create(*headers()), ResourceResponse(m_internalResponse)));
    clone->cloneBody(*this);
    if (isBodyOpaque())
        clone->setBodyAsOpaque();
    return clone.releaseNonNull();
}

void FetchResponse::runAbortSteps()
{
    m_abortSignal = nullptr;

    setLoadingError(Exception(ABORT_ERR, "Fetch is aborted"));

    if (m_bodyLoader) {
#if 0
        if (auto callback = m_bodyLoader->takeNotificationCallback())
            callback(Exception { AbortError, "Fetch is aborted"_s });

        if (auto callback = m_bodyLoader->takeConsumeDataCallback())
            callback(Exception { AbortError, "Fetch is aborted"_s });
#endif

        if (RefPtr<DeferredPromise> promise = m_bodyLoader->takepromise())
        	promise->reject(Exception(ABORT_ERR, "Fetch is aborted"));
    }

    if (m_readableStreamSource) {
        if (!m_readableStreamSource->isCancelling())
            m_readableStreamSource->error(*loadingException());
        m_readableStreamSource = nullptr;
    }
    if (m_body)
        m_body->loadingFailed(*loadingException());

    if (OwnPtr<BodyLoader> bodyLoader = m_bodyLoader.release())
        bodyLoader->stop();
}

void FetchResponse::addAbortSteps(PassRefPtr<AbortSignal> signal)
{
    m_abortSignal = signal;

    class AlgorithmImpl final : public AbortSignal::Algorithm
	{
	public:
    	AlgorithmImpl(FetchResponse& thisRef) : m_weakThis(makeWeakPtr(thisRef)) { }
    	virtual ~AlgorithmImpl() { }
    	virtual void operator()() override
    	{
    		RefPtr<FetchResponse> protectedThis = m_weakThis.get();

			if (!protectedThis)
				return;

			protectedThis->runAbortSteps();
    	}
	private:
    	WeakPtr<FetchResponse> m_weakThis;
	};
    m_abortSignal->addAlgorithm(adoptPtr(new AlgorithmImpl(*this)));
}

void FetchResponse::fetch(ScriptExecutionContext& context, FetchRequest& request, RefPtr<DeferredPromise> promise)
{
    if (request.signal()->aborted()) {
    	promise->reject(Exception(ABORT_ERR, "Request signal is aborted"));
        // FIXME: Cancel request body if it is a stream.
        return;
    }

    if (request.hasReadableStreamBody()) {
    	promise->reject(Exception(NOT_SUPPORTED_ERR, "ReadableStream uploading is not supported"));
        return;
    }
    RefPtr<FetchResponse> response = adoptRef(new FetchResponse(&context, adoptPtr(new FetchBody()), FetchHeaders::create(FetchHeaders::Guard::Immutable), ResourceResponse()));

    response->body().consumer().setAsLoading();

    response->addAbortSteps(request.signal());

    response->m_bodyLoader = adoptPtr(new BodyLoader(*response, promise));
    if (!response->m_bodyLoader->start(context, request))
        response->m_bodyLoader = nullptr;
}

const String& FetchResponse::url() const
{
    if (m_responseURL.isNull()) {
        KURL url = filteredResponse().url();
        url.removeFragmentIdentifier();
        m_responseURL = url.string();
    }
    return m_responseURL;
}

const ResourceResponse& FetchResponse::filteredResponse() const
{
    if (m_filteredResponse)
        return m_filteredResponse.value();
    return m_internalResponse;
}

void FetchResponse::BodyLoader::didSucceed()
{
    ASSERT(m_response.hasPendingActivity());
    m_response.m_body->loadingSucceeded(m_response.contentType());

#if ENABLE(STREAMS_API)
    if (m_response.m_readableStreamSource) {
        if (m_response.body().consumer().hasData())
            m_response.m_readableStreamSource->enqueue(m_response.body().consumer().takeAsArrayBuffer());

        m_response.closeStream();
    }
#endif

    if (m_loader->isStarted()) {
        Ref<FetchResponse> protector(m_response);
    	m_response.m_bodyLoader = nullptr;
    }
}

void FetchResponse::BodyLoader::didFail(const ResourceError& error)
{
    ASSERT(m_response.hasPendingActivity());

    m_response.setLoadingError(error);

    if (m_promise)
    	m_promise.releaseNonNull()->reject<ExceptionCode>(TypeError);

#if ENABLE(STREAMS_API)
    if (m_response.m_readableStreamSource) {
        if (!m_response.m_readableStreamSource->isCancelling())
            m_response.m_readableStreamSource->error(*m_response.loadingException());
        m_response.m_readableStreamSource.clear();
    }
#endif

    if (m_response.m_body)
        m_response.m_body->loadingFailed(*m_response.loadingException());

    // Check whether didFail is called as part of FetchLoader::start.
    if (m_loader && m_loader->isStarted()){
        Ref<FetchResponse> protector(m_response);
        m_response.m_bodyLoader = nullptr;
    }
}

FetchResponse::BodyLoader::BodyLoader(FetchResponse& response, RefPtr<DeferredPromise> promise)
    : m_response(response)
    , m_promise(promise)
	, m_pendingActivity(m_response.makePendingActivity(m_response))
{
}

FetchResponse::BodyLoader::~BodyLoader()
{
}

void FetchResponse::BodyLoader::didReceiveResponse(const ResourceResponse& resourceResponse)
{
    m_response.m_filteredResponse = ResourceResponseBase::filter(resourceResponse);
    m_response.m_internalResponse = resourceResponse;
    m_response.m_internalResponse.setType(m_response.m_filteredResponse->type());
    if (resourceResponse.tainting() == ResourceResponse::Tainting::Opaque)
        m_response.setBodyAsOpaque();

    m_response.m_headers->filterAndFill(m_response.m_filteredResponse->httpHeaderFields(), FetchHeaders::Guard::Response);
    m_response.updateContentType();

    if (m_promise)
    	m_promise.releaseNonNull()->resolve(&m_response);
}

void FetchResponse::BodyLoader::didReceiveData(const char* data, size_t size)
{
#if ENABLE(STREAMS_API)
    ASSERT(m_response.m_readableStreamSource);
    FetchBodySource& source = *m_response.m_readableStreamSource;

    if (!source.isPulling()) {
        m_response.body().consumer().append(data, size);
        return;
    }

    if (m_response.body().consumer().hasData() && !source.enqueue(m_response.body().consumer().takeAsArrayBuffer())) {
        stop();
        return;
    }
    if (!source.enqueue(ArrayBuffer::tryCreate(data, size))) {
        stop();
        return;
    }
    source.resolvePullPromise();
#else
    UNUSED_PARAM(data);
    UNUSED_PARAM(size);
#endif
}

bool FetchResponse::BodyLoader::start(ScriptExecutionContext& context, const FetchRequest& request)
{
    m_loader = adoptPtr(new FetchLoader(*this, &m_response.m_body->consumer()));
    m_loader->start(context, request);
    return m_loader->isStarted();
}

void FetchResponse::BodyLoader::stop()
{
    m_promise = nullptr;
    if (m_loader)
        m_loader->stop();
}

#if 0
FetchResponse::ResponseData FetchResponse::consumeBody()
{
    ASSERT(!isLoading());

    if (isBodyNull())
        return nullptr;

    ASSERT(!m_isDisturbed);
    m_isDisturbed = true;

    return body().take();
}

void FetchResponse::consumeBodyFromReadableStream(ConsumeDataByChunkCallback&& callback)
{
    ASSERT(m_body);
    ASSERT(m_body->readableStream());

    ASSERT(!isDisturbed());
    m_isDisturbed = true;

    m_body->consumer().extract(*m_body->readableStream(), WTFMove(callback));
}

void FetchResponse::consumeBodyWhenLoaded(ConsumeDataCallback&& callback)
{
    ASSERT(isLoading());

    ASSERT(!isDisturbed());
    m_isDisturbed = true;

    m_bodyLoader->setConsumeDataCallback(WTFMove(callback));
}
#endif

#if 0
void FetchResponse::setBodyData(ResponseData&& data)
{
    WTF::switchOn(data,
        [this](Ref<FormData>& formData) {
            if (isBodyNull())
                setBody({ });
            body().setAsFormData(WTFMove(formData));
        },
        [this](Ref<SharedBuffer>& buffer) {
            if (isBodyNull())
                setBody({ });
            body().consumer().setData(WTFMove(buffer));
        },
        [](std::nullptr_t&) {
        }
    );
}
#else
void FetchResponse::setBodyData(PassRefPtr<FormData> formData) {
	if (isBodyNull())
		setBody(nullptr);
	body().setAsFormData(formData);
}
void FetchResponse::setBodyData(PassRefPtr<SharedBuffer> buffer) {
	if (isBodyNull())
		setBody(nullptr);
	body().consumer().setData(buffer);
}
void FetchResponse::setBodyData(std::nullptr_t)
{
}
#endif

#if ENABLE(STREAMS_API)
void FetchResponse::consumeChunk(PassRefPtr<JSC::Uint8Array> chunk)
{
	body().consumer().append(chunk->data(), chunk->byteLength());
}

void FetchResponse::consumeBodyAsStream()
{
    ASSERT(m_readableStreamSource);
    if (!isLoading()) {
        FetchBodyOwner::consumeBodyAsStream();
        return;
    }

    ASSERT(m_bodyLoader);

    auto data = m_bodyLoader->startStreaming();
    if (data) {
    	unsigned size = data->size();
        if (!m_readableStreamSource->enqueue(JSC::ArrayBuffer::createAdopted(data->releaseBuffer(), size))) {
            stop();
            return;
        }
        m_readableStreamSource->resolvePullPromise();
    }
}

void FetchResponse::closeStream()
{
    ASSERT(m_readableStreamSource);
    m_readableStreamSource->close();
    m_readableStreamSource = nullptr;
}

void FetchResponse::feedStream()
{
    ASSERT(m_readableStreamSource);
    bool shouldCloseStream = !m_bodyLoader && !isActive();

    if (body().consumer().hasData()) {
        if (!m_readableStreamSource->enqueue(body().consumer().takeAsArrayBuffer())) {
            stop();
            return;
        }
        if (!shouldCloseStream) {
            m_readableStreamSource->resolvePullPromise();
            return;
        }
    } else if (!shouldCloseStream)
        return;

    closeStream();
}

RefPtr<SharedBuffer> FetchResponse::BodyLoader::startStreaming()
{
    ASSERT(m_loader);
    return m_loader->startStreaming();
}

void FetchResponse::cancel()
{
    m_isDisturbed = true;
    stop();
}

#endif

void FetchResponse::stop()
{
    RefPtr<FetchResponse> protect(this);
    FetchBodyOwner::stop();
    if (OwnPtr<BodyLoader> bodyLoader = m_bodyLoader.release())
        bodyLoader->stop();
}

const char* FetchResponse::activeDOMObjectName() const
{
    return "Response";
}

bool FetchResponse::canSuspendForDocumentSuspension() const
{
    // FIXME: We can probably do the same strategy as XHR.
    return !isActive();
}

ResourceResponse FetchResponse::resourceResponse() const
{
	ResourceResponse response = m_internalResponse;

    if (headers()->guard() != FetchHeaders::Guard::Immutable) {
        // FIXME: Add a setHTTPHeaderFields on ResourceResponseBase.
        for (auto& header : headers()->internalHeaders())
            response.setHTTPHeaderField(header.first, header.second);
    }

    return response;
}

} // namespace WebCore

#endif // ENABLE(FETCH_API)
