/*
 * Copyright (C) 2011 Google 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 GOOGLE 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 GOOGLE 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.
 */

#include "config.h"

#if ENABLE(INSPECTOR)

#include "NetworkResourcesData.h"

#include "DOMImplementation.h"
#include "SharedBuffer.h"
#include "TextResourceDecoder.h"
#include <wtf/MemoryInstrumentationHashMap.h>

namespace {
// 100MB
static size_t maximumResourcesContentSize = 100 * 1000 * 1000;

// 10MB
static size_t maximumSingleResourceContentSize = 10 * 1000 * 1000;
}

namespace WebCore {


PassRefPtr<XHRReplayData> XHRReplayData::create(const String &method, const KURL& url, bool async, PassRefPtr<FormData> formData, bool includeCredentials)
{
    return adoptRef(new XHRReplayData(method, url, async, formData, includeCredentials));
}

void XHRReplayData::addHeader(const AtomicString& key, const String& value)
{
    m_headers.set(key, value);
}

XHRReplayData::XHRReplayData(const String &method, const KURL& url, bool async, PassRefPtr<FormData> formData, bool includeCredentials)
    : m_method(method)
    , m_url(url)
    , m_async(async)
    , m_formData(formData)
    , m_includeCredentials(includeCredentials)
{
}

void XHRReplayData::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
    MemoryClassInfo info(memoryObjectInfo, this);
    info.addMember(m_method, "method");
    info.addMember(m_url, "url");
    info.addMember(m_formData, "formData");
    info.addMember(m_headers, "headers");
}

// ResourceData
NetworkResourcesData::ResourceData::ResourceData(const String& requestId, const String& loaderId)
    : m_requestId(requestId)
    , m_loaderId(loaderId)
    , m_base64Encoded(false)
    , m_isContentEvicted(false)
    , m_type(InspectorPageAgent::OtherResource)
    , m_cachedResource(0)
{
}

void NetworkResourcesData::ResourceData::setContent(const String& content, bool base64Encoded)
{
    ASSERT(!hasData());
    ASSERT(!hasContent());
    m_content = content;
    m_base64Encoded = base64Encoded;
}

static size_t contentSizeInBytes(const String& content)
{
    return content.isNull() ? 0 : content.impl()->sizeInBytes();
}

unsigned NetworkResourcesData::ResourceData::removeContent()
{
    unsigned result = 0;
    if (hasData()) {
        ASSERT(!hasContent());
        result = m_dataBuffer->size();
        m_dataBuffer = nullptr;
    }

    if (hasContent()) {
        ASSERT(!hasData());
        result = contentSizeInBytes(m_content);
        m_content = String();
    }
    return result;
}

unsigned NetworkResourcesData::ResourceData::evictContent()
{
    m_isContentEvicted = true;
    return removeContent();
}

size_t NetworkResourcesData::ResourceData::dataLength() const
{
    return m_dataBuffer ? m_dataBuffer->size() : 0;
}

void NetworkResourcesData::ResourceData::appendData(const char* data, size_t dataLength)
{
    ASSERT(!hasContent());
    if (!m_dataBuffer)
        m_dataBuffer = SharedBuffer::create(data, dataLength);
    else
        m_dataBuffer->append(data, dataLength);
}

size_t NetworkResourcesData::ResourceData::decodeDataToContent()
{
    ASSERT(!hasContent());
    size_t dataLength = m_dataBuffer->size();
    m_content = m_decoder->decode(m_dataBuffer->data(), m_dataBuffer->size());
    m_content.append(m_decoder->flush());
    m_dataBuffer = nullptr;
    return contentSizeInBytes(m_content) - dataLength;
}

void NetworkResourcesData::ResourceData::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
    MemoryClassInfo info(memoryObjectInfo, this);
    info.addMember(m_requestId, "requestId");
    info.addMember(m_loaderId, "loaderId");
    info.addMember(m_frameId, "frameId");
    info.addMember(m_url, "url");
    info.addMember(m_content, "content");
    info.addMember(m_xhrReplayData, "xhrReplayData");
    info.addMember(m_dataBuffer, "dataBuffer");
    info.addMember(m_textEncodingName, "textEncodingName");
    info.addMember(m_decoder, "decoder");
    info.addMember(m_buffer, "buffer");
    info.addMember(m_cachedResource, "cachedResource");
}

// NetworkResourcesData
NetworkResourcesData::NetworkResourcesData()
    : m_contentSize(0)
    , m_maximumResourcesContentSize(maximumResourcesContentSize)
    , m_maximumSingleResourceContentSize(maximumSingleResourceContentSize)
{
}

NetworkResourcesData::~NetworkResourcesData()
{
    clear();
}

void NetworkResourcesData::resourceCreated(const String& requestId, const String& loaderId)
{
    ensureNoDataForRequestId(requestId);
    m_requestIdToResourceDataMap.set(requestId, new ResourceData(requestId, loaderId));
}

static PassRefPtr<TextResourceDecoder> createOtherResourceTextDecoder(const String& mimeType, const String& textEncodingName)
{
    RefPtr<TextResourceDecoder> decoder;
    if (!textEncodingName.isEmpty())
        decoder = TextResourceDecoder::create("text/plain", textEncodingName);
    else if (DOMImplementation::isXMLMIMEType(mimeType.lower())) {
        decoder = TextResourceDecoder::create("application/xml");
        decoder->useLenientXMLDecoding();
    } else if (equalIgnoringCase(mimeType, "text/html"))
        decoder = TextResourceDecoder::create("text/html", "UTF-8");
    else if (mimeType == "text/plain")
        decoder = TextResourceDecoder::create("text/plain", "ISO-8859-1");
    return decoder;
}

void NetworkResourcesData::responseReceived(const String& requestId, const String& frameId, const ResourceResponse& response)
{
    ResourceData* resourceData = m_requestIdToResourceDataMap.get(requestId);
    if (!resourceData)
        return;
    resourceData->setFrameId(frameId);
    resourceData->setUrl(response.url());
    resourceData->setDecoder(createOtherResourceTextDecoder(response.mimeType(), response.textEncodingName()));
    resourceData->setHTTPStatusCode(response.httpStatusCode());
}

void NetworkResourcesData::setResourceType(const String& requestId, InspectorPageAgent::ResourceType type)
{
    ResourceData* resourceData = m_requestIdToResourceDataMap.get(requestId);
    if (!resourceData)
        return;
    resourceData->setType(type);
}

InspectorPageAgent::ResourceType NetworkResourcesData::resourceType(const String& requestId)
{
    ResourceData* resourceData = m_requestIdToResourceDataMap.get(requestId);
    if (!resourceData)
        return InspectorPageAgent::OtherResource;
    return resourceData->type();
}

void NetworkResourcesData::setResourceContent(const String& requestId, const String& content, bool base64Encoded)
{
    ResourceData* resourceData = m_requestIdToResourceDataMap.get(requestId);
    if (!resourceData)
        return;
    size_t dataLength = contentSizeInBytes(content);
    if (dataLength > m_maximumSingleResourceContentSize)
        return;
    if (resourceData->isContentEvicted())
        return;
    if (ensureFreeSpace(dataLength) && !resourceData->isContentEvicted()) {
        // We can not be sure that we didn't try to save this request data while it was loading, so remove it, if any.
        if (resourceData->hasContent())
            m_contentSize -= resourceData->removeContent();
        m_requestIdsDeque.append(requestId);
        resourceData->setContent(content, base64Encoded);
        m_contentSize += dataLength;
    }
}

void NetworkResourcesData::maybeAddResourceData(const String& requestId, const char* data, size_t dataLength)
{
    ResourceData* resourceData = m_requestIdToResourceDataMap.get(requestId);
    if (!resourceData)
        return;
    if (!resourceData->decoder())
        return;
    if (resourceData->dataLength() + dataLength > m_maximumSingleResourceContentSize)
        m_contentSize -= resourceData->evictContent();
    if (resourceData->isContentEvicted())
        return;
    if (ensureFreeSpace(dataLength) && !resourceData->isContentEvicted()) {
        m_requestIdsDeque.append(requestId);
        resourceData->appendData(data, dataLength);
        m_contentSize += dataLength;
    }
}

void NetworkResourcesData::maybeDecodeDataToContent(const String& requestId)
{
    ResourceData* resourceData = m_requestIdToResourceDataMap.get(requestId);
    if (!resourceData)
        return;
    if (!resourceData->hasData())
        return;
    m_contentSize += resourceData->decodeDataToContent();
    size_t dataLength = contentSizeInBytes(resourceData->content());
    if (dataLength > m_maximumSingleResourceContentSize)
        m_contentSize -= resourceData->evictContent();
}

void NetworkResourcesData::addCachedResource(const String& requestId, CachedResource* cachedResource)
{
    if (!m_requestIdToResourceDataMap.contains(requestId))
        return;
    ResourceData* resourceData = m_requestIdToResourceDataMap.get(requestId);

    resourceData->setCachedResource(cachedResource);
}

void NetworkResourcesData::addResourceSharedBuffer(const String& requestId, PassRefPtr<SharedBuffer> buffer, const String& textEncodingName)
{
    ResourceData* resourceData = m_requestIdToResourceDataMap.get(requestId);
    if (!resourceData)
        return;
    resourceData->setBuffer(buffer);
    resourceData->setTextEncodingName(textEncodingName);
}

NetworkResourcesData::ResourceData const* NetworkResourcesData::data(const String& requestId)
{
    return m_requestIdToResourceDataMap.get(requestId);
}

XHRReplayData* NetworkResourcesData::xhrReplayData(const String& requestId)
{
    if (m_reusedXHRReplayDataRequestIds.contains(requestId))
        return xhrReplayData(m_reusedXHRReplayDataRequestIds.get(requestId));

    ResourceData* resourceData = m_requestIdToResourceDataMap.get(requestId);
    if (!resourceData)
        return 0;
    return resourceData->xhrReplayData();
}

void NetworkResourcesData::setXHRReplayData(const String& requestId, XHRReplayData* xhrReplayData)
{
    ResourceData* resourceData = m_requestIdToResourceDataMap.get(requestId);
    if (!resourceData) {
        Vector<String> result;
        ReusedRequestIds::iterator it;
        ReusedRequestIds::iterator end = m_reusedXHRReplayDataRequestIds.end();
        for (it = m_reusedXHRReplayDataRequestIds.begin(); it != end; ++it) {
            if (it->value == requestId)
                setXHRReplayData(it->key, xhrReplayData);
        }
        return;
    }

    resourceData->setXHRReplayData(xhrReplayData);
}

void NetworkResourcesData::reuseXHRReplayData(const String& requestId, const String& reusedRequestId)
{
    ResourceData* reusedResourceData = m_requestIdToResourceDataMap.get(reusedRequestId);
    ResourceData* resourceData = m_requestIdToResourceDataMap.get(requestId);
    if (!reusedResourceData || !resourceData) {
        m_reusedXHRReplayDataRequestIds.set(requestId, reusedRequestId);
        return;
    }

    resourceData->setXHRReplayData(reusedResourceData->xhrReplayData());
}

Vector<String> NetworkResourcesData::removeCachedResource(CachedResource* cachedResource)
{
    Vector<String> result;
    ResourceDataMap::iterator it;
    ResourceDataMap::iterator end = m_requestIdToResourceDataMap.end();
    for (it = m_requestIdToResourceDataMap.begin(); it != end; ++it) {
        ResourceData* resourceData = it->value;
        if (resourceData->cachedResource() == cachedResource) {
            resourceData->setCachedResource(0);
            result.append(it->key);
        }
    }

    return result;
}

void NetworkResourcesData::clear(const String& preservedLoaderId)
{
    m_requestIdsDeque.clear();
    m_contentSize = 0;

    ResourceDataMap preservedMap;

    ResourceDataMap::iterator it;
    ResourceDataMap::iterator end = m_requestIdToResourceDataMap.end();
    for (it = m_requestIdToResourceDataMap.begin(); it != end; ++it) {
        ResourceData* resourceData = it->value;
        if (!preservedLoaderId.isNull() && resourceData->loaderId() == preservedLoaderId)
            preservedMap.set(it->key, it->value);
        else
            delete resourceData;
    }
    m_requestIdToResourceDataMap.swap(preservedMap);

    m_reusedXHRReplayDataRequestIds.clear();
}

void NetworkResourcesData::setResourcesDataSizeLimits(size_t maximumResourcesContentSize, size_t maximumSingleResourceContentSize)
{
    clear();
    m_maximumResourcesContentSize = maximumResourcesContentSize;
    m_maximumSingleResourceContentSize = maximumSingleResourceContentSize;
}


void NetworkResourcesData::ensureNoDataForRequestId(const String& requestId)
{
    ResourceData* resourceData = m_requestIdToResourceDataMap.get(requestId);
    if (resourceData) {
        if (resourceData->hasContent() || resourceData->hasData())
            m_contentSize -= resourceData->evictContent();
        delete resourceData;
        m_requestIdToResourceDataMap.remove(requestId);
    }
}

bool NetworkResourcesData::ensureFreeSpace(size_t size)
{
    if (size > m_maximumResourcesContentSize)
        return false;

    while (size > m_maximumResourcesContentSize - m_contentSize) {
        String requestId = m_requestIdsDeque.takeFirst();
        ResourceData* resourceData = m_requestIdToResourceDataMap.get(requestId);
        if (resourceData)
            m_contentSize -= resourceData->evictContent();
    }
    return true;
}

void NetworkResourcesData::reportMemoryUsage(MemoryObjectInfo* memoryObjectInfo) const
{
    MemoryClassInfo info(memoryObjectInfo, this);
    info.addMember(m_requestIdsDeque, "requestIdsDeque");
    info.addMember(m_reusedXHRReplayDataRequestIds, "reusedXHRReplayDataRequestIds");
    info.addMember(m_requestIdToResourceDataMap, "requestIdToResourceDataMap");
}

} // namespace WebCore

#endif // ENABLE(INSPECTOR)
