/*
 * Copyright (C) 2009-2019 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE 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 APPLE INC. OR 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.
 */

#import "config.h"
#import "LegacyPreviewLoader.h"

#if USE(QUICK_LOOK)

#import "DocumentLoader.h"
#import "Frame.h"
#import "FrameLoader.h"
#import "FrameLoaderClient.h"
#import "LegacyPreviewLoaderClient.h"
#import "Logging.h"
#import "PreviewConverter.h"
#import "QuickLook.h"
#import "ResourceLoader.h"
#import "Settings.h"
#import <wtf/NeverDestroyed.h>

namespace WebCore {

static RefPtr<LegacyPreviewLoaderClient>& testingClient()
{
    static NeverDestroyed<RefPtr<LegacyPreviewLoaderClient>> testingClient;
    return testingClient.get();
}

static LegacyPreviewLoaderClient& emptyClient()
{
    static NeverDestroyed<LegacyPreviewLoaderClient> emptyClient;
    return emptyClient.get();
}

static Ref<LegacyPreviewLoaderClient> makeClient(const ResourceLoader& loader, const String& previewFileName, const String& previewType)
{
    if (auto client = testingClient())
        return client.releaseNonNull();
    if (auto client = loader.frameLoader()->client().createPreviewLoaderClient(previewFileName, previewType))
        return client.releaseNonNull();
    return emptyClient();
}

bool LegacyPreviewLoader::didReceiveBuffer(const SharedBuffer& buffer)
{
    if (m_finishedLoadingDataIntoConverter)
        return false;

    LOG(Network, "LegacyPreviewLoader appending buffer with size %ld.", buffer.size());
    m_originalData->append(buffer);
    m_converter->updateMainResource();
    m_client->didReceiveBuffer(buffer);
    return true;
}

bool LegacyPreviewLoader::didFinishLoading()
{
    if (m_finishedLoadingDataIntoConverter)
        return false;

    LOG(Network, "LegacyPreviewLoader finished appending data.");
    m_finishedLoadingDataIntoConverter = true;
    m_converter->finishUpdating();
    m_client->didFinishLoading();
    return true;
}

void LegacyPreviewLoader::didFail()
{
    if (m_finishedLoadingDataIntoConverter)
        return;

    LOG(Network, "LegacyPreviewLoader failed.");
    m_finishedLoadingDataIntoConverter = true;
    m_converter->failedUpdating();
    m_client->didFail();
    m_converter = nullptr;
}

void LegacyPreviewLoader::previewConverterDidStartConverting(PreviewConverter& converter)
{
    auto resourceLoader = m_resourceLoader.get();
    if (!resourceLoader)
        return;

    if (resourceLoader->reachedTerminalState())
        return;

    ASSERT(!m_hasProcessedResponse);
    m_originalData->clear();
    resourceLoader->documentLoader()->setPreviewConverter(WTFMove(m_converter));
    auto response { converter.previewResponse() };

    if (m_shouldDecidePolicyBeforeLoading) {
        m_hasProcessedResponse = true;
        resourceLoader->didReceivePreviewResponse(response);
        return;
    }

    resourceLoader->didReceiveResponse(response, [this, weakThis = makeWeakPtr(static_cast<PreviewConverterClient&>(*this)), converter = makeRef(converter)] {
        if (!weakThis)
            return;

        m_hasProcessedResponse = true;

        auto resourceLoader = m_resourceLoader.get();
        if (!resourceLoader)
            return;

        if (resourceLoader->reachedTerminalState())
            return;

        if (!converter->previewData().isEmpty()) {
            auto bufferSize = converter->previewData().size();
            resourceLoader->didReceiveBuffer(converter->previewData().copy(), bufferSize, DataPayloadBytes);
        }

        if (resourceLoader->reachedTerminalState())
            return;

        if (m_needsToCallDidFinishLoading) {
            m_needsToCallDidFinishLoading = false;
            resourceLoader->didFinishLoading(NetworkLoadMetrics { });
        }
    });
}

void LegacyPreviewLoader::previewConverterDidReceiveData(PreviewConverter&, const SharedBuffer& data)
{
    auto resourceLoader = m_resourceLoader.get();
    if (!resourceLoader)
        return;

    if (resourceLoader->reachedTerminalState())
        return;

    if (data.isEmpty())
        return;

    if (!m_hasProcessedResponse)
        return;

    auto dataCopy = data.copy();
    resourceLoader->didReceiveBuffer(WTFMove(dataCopy), dataCopy->size(), DataPayloadBytes);
}

void LegacyPreviewLoader::previewConverterDidFinishConverting(PreviewConverter&)
{
    auto resourceLoader = m_resourceLoader.get();
    if (!resourceLoader)
        return;

    if (resourceLoader->reachedTerminalState())
        return;

    if (!m_hasProcessedResponse) {
        m_needsToCallDidFinishLoading = true;
        return;
    }

    resourceLoader->didFinishLoading(NetworkLoadMetrics { });
}

void LegacyPreviewLoader::previewConverterDidFailUpdating(PreviewConverter&)
{
    if (auto resourceLoader = m_resourceLoader.get())
        resourceLoader->didFail(resourceLoader->cannotShowURLError());
}

void LegacyPreviewLoader::previewConverterDidFailConverting(PreviewConverter& converter)
{
    auto resourceLoader = m_resourceLoader.get();
    if (!resourceLoader)
        return;

    if (resourceLoader->reachedTerminalState())
        return;

    resourceLoader->didFail(converter.previewError());
}

void LegacyPreviewLoader::providePasswordForPreviewConverter(PreviewConverter& converter, CompletionHandler<void(const String&)>&& completionHandler)
{
    ASSERT_UNUSED(converter, &converter == m_converter);

    auto resourceLoader = m_resourceLoader.get();
    if (!resourceLoader) {
        completionHandler({ });
        return;
    }

    if (resourceLoader->reachedTerminalState()) {
        completionHandler({ });
        return;
    }

    if (!m_client->supportsPasswordEntry()) {
        completionHandler({ });
        return;
    }

    m_client->didRequestPassword(WTFMove(completionHandler));
}

void LegacyPreviewLoader::provideMainResourceForPreviewConverter(PreviewConverter& converter, CompletionHandler<void(const SharedBuffer*)>&& completionHandler)
{
    ASSERT_UNUSED(converter, &converter == m_converter);
    completionHandler(m_originalData.ptr());
}

LegacyPreviewLoader::~LegacyPreviewLoader() = default;

LegacyPreviewLoader::LegacyPreviewLoader(ResourceLoader& loader, const ResourceResponse& response)
    : m_converter { PreviewConverter::create(response, *this) }
    , m_client { makeClient(loader, m_converter->previewFileName(), m_converter->previewUTI()) }
    , m_originalData { SharedBuffer::create() }
    , m_resourceLoader { makeWeakPtr(loader) }
    , m_shouldDecidePolicyBeforeLoading { loader.frame()->settings().shouldDecidePolicyBeforeLoadingQuickLookPreview() }
{
    ASSERT(PreviewConverter::supportsMIMEType(response.mimeType()));
    m_converter->addClient(*this);
    LOG(Network, "LegacyPreviewLoader created with preview file name \"%s\".", m_converter->previewFileName().utf8().data());
}

bool LegacyPreviewLoader::didReceiveData(const char* data, unsigned length)
{
    return didReceiveBuffer(SharedBuffer::create(data, length).get());
}

bool LegacyPreviewLoader::didReceiveResponse(const ResourceResponse&)
{
    return !m_shouldDecidePolicyBeforeLoading;
}

void LegacyPreviewLoader::setClientForTesting(RefPtr<LegacyPreviewLoaderClient>&& client)
{
    testingClient() = WTFMove(client);
}

} // namespace WebCore

#endif // USE(QUICK_LOOK)
