/*
 * Copyright (C) 2014 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.
 */

#include "config.h"
#include "SessionStateConversion.h"

#include "SessionState.h"
#include <WebCore/BlobData.h>
#include <WebCore/FormData.h>
#include <WebCore/HistoryItem.h>
#include <wtf/FileSystem.h>

namespace WebKit {
using namespace WebCore;

static HTTPBody toHTTPBody(const FormData& formData)
{
    HTTPBody httpBody;

    for (const auto& formDataElement : formData.elements()) {
        HTTPBody::Element element;

        switchOn(formDataElement.data,
            [&] (const Vector<uint8_t>& bytes) {
                element.data = bytes;
            }, [&] (const FormDataElement::EncodedFileData& fileData) {
                HTTPBody::Element::FileData data;
                data.filePath = fileData.filename;
                data.fileStart = fileData.fileStart;
                if (fileData.fileLength != BlobDataItem::toEndOfFile)
                    data.fileLength = fileData.fileLength;
                data.expectedFileModificationTime = fileData.expectedFileModificationTime;
                element.data = WTFMove(data);
            }, [&] (const FormDataElement::EncodedBlobData& blobData) {
                element.data = blobData.url.string();
            }
        );

        httpBody.elements.append(WTFMove(element));
    }

    return httpBody;
}

static FrameState toFrameState(const HistoryItem& historyItem)
{
    FrameState frameState;

    frameState.urlString = historyItem.urlString();
    frameState.originalURLString = historyItem.originalURLString();
    frameState.referrer = historyItem.referrer();
    frameState.target = historyItem.target();

    frameState.setDocumentState(historyItem.documentState());
    if (RefPtr<SerializedScriptValue> stateObject = historyItem.stateObject())
        frameState.stateObjectData = stateObject->wireBytes();

    frameState.documentSequenceNumber = historyItem.documentSequenceNumber();
    frameState.itemSequenceNumber = historyItem.itemSequenceNumber();

    frameState.scrollPosition = historyItem.scrollPosition();
    frameState.shouldRestoreScrollPosition = historyItem.shouldRestoreScrollPosition();
    frameState.pageScaleFactor = historyItem.pageScaleFactor();

    if (FormData* formData = const_cast<HistoryItem&>(historyItem).formData()) {
        HTTPBody httpBody = toHTTPBody(*formData);
        httpBody.contentType = historyItem.formContentType();

        frameState.httpBody = WTFMove(httpBody);
    }

#if PLATFORM(IOS_FAMILY)
    frameState.exposedContentRect = historyItem.exposedContentRect();
    frameState.unobscuredContentRect = historyItem.unobscuredContentRect();
    frameState.minimumLayoutSizeInScrollViewCoordinates = historyItem.minimumLayoutSizeInScrollViewCoordinates();
    frameState.contentSize = historyItem.contentSize();
    frameState.scaleIsInitial = historyItem.scaleIsInitial();
    frameState.obscuredInsets = historyItem.obscuredInsets();
#endif

    frameState.children = historyItem.children().map([](auto& childHistoryItem) {
        return toFrameState(childHistoryItem);
    });

    return frameState;
}

BackForwardListItemState toBackForwardListItemState(const WebCore::HistoryItem& historyItem)
{
    static constexpr unsigned maxTitleLength = 1000; // Closest power of 10 above the W3C recommendation for Title length.

    BackForwardListItemState state;
    state.identifier = historyItem.identifier();
    state.pageState.title = historyItem.title().left(maxTitleLength);
    state.pageState.mainFrameState = toFrameState(historyItem);
    state.pageState.shouldOpenExternalURLsPolicy = historyItem.shouldOpenExternalURLsPolicy();
    state.pageState.sessionStateObject = historyItem.stateObject();
    state.pageState.wasCreatedByJSWithoutUserInteraction = historyItem.wasCreatedByJSWithoutUserInteraction();
    state.hasCachedPage = historyItem.isInBackForwardCache();
    return state;
}

static Ref<FormData> toFormData(const HTTPBody& httpBody)
{
    auto formData = FormData::create();

    for (const auto& element : httpBody.elements) {
        switchOn(element.data, [&] (const Vector<uint8_t>& data) {
            formData->appendData(data.data(), data.size());
        }, [&] (const HTTPBody::Element::FileData& data) {
            formData->appendFileRange(data.filePath, data.fileStart, data.fileLength.value_or(BlobDataItem::toEndOfFile), data.expectedFileModificationTime);
        }, [&] (const String& blobURLString) {
            formData->appendBlob(URL { blobURLString });
        });
    }

    return formData;
}

static void applyFrameState(HistoryItem& historyItem, const FrameState& frameState)
{
    historyItem.setOriginalURLString(frameState.originalURLString);
    historyItem.setReferrer(frameState.referrer);
    historyItem.setTarget(frameState.target);

    historyItem.setDocumentState(frameState.documentState());

    if (frameState.stateObjectData) {
        Vector<uint8_t> stateObjectData = frameState.stateObjectData.value();
        historyItem.setStateObject(SerializedScriptValue::createFromWireBytes(WTFMove(stateObjectData)));
    }

    historyItem.setDocumentSequenceNumber(frameState.documentSequenceNumber);
    historyItem.setItemSequenceNumber(frameState.itemSequenceNumber);

    historyItem.setScrollPosition(frameState.scrollPosition);
    historyItem.setShouldRestoreScrollPosition(frameState.shouldRestoreScrollPosition);
    historyItem.setPageScaleFactor(frameState.pageScaleFactor);

    if (frameState.httpBody) {
        const auto& httpBody = frameState.httpBody.value();
        historyItem.setFormContentType(httpBody.contentType);

        historyItem.setFormData(toFormData(httpBody));
    }

#if PLATFORM(IOS_FAMILY)
    historyItem.setExposedContentRect(frameState.exposedContentRect);
    historyItem.setUnobscuredContentRect(frameState.unobscuredContentRect);
    historyItem.setMinimumLayoutSizeInScrollViewCoordinates(frameState.minimumLayoutSizeInScrollViewCoordinates);
    historyItem.setContentSize(frameState.contentSize);
    historyItem.setScaleIsInitial(frameState.scaleIsInitial);
    historyItem.setObscuredInsets(frameState.obscuredInsets);
#endif

    for (const auto& childFrameState : frameState.children) {
        Ref<HistoryItem> childHistoryItem = HistoryItem::create(childFrameState.urlString, { }, { });
        applyFrameState(childHistoryItem, childFrameState);

        historyItem.addChildItem(WTFMove(childHistoryItem));
    }
}

Ref<HistoryItem> toHistoryItem(const BackForwardListItemState& itemState)
{
    Ref<HistoryItem> historyItem = HistoryItem::create(itemState.pageState.mainFrameState.urlString, itemState.pageState.title, { }, itemState.identifier);
    historyItem->setShouldOpenExternalURLsPolicy(itemState.pageState.shouldOpenExternalURLsPolicy);
    historyItem->setStateObject(itemState.pageState.sessionStateObject.get());
    applyFrameState(historyItem, itemState.pageState.mainFrameState);

    return historyItem;
}

} // namespace WebKit
