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

#include "config.h"
#include "PasteboardCustomData.h"

#include "SharedBuffer.h"
#include <wtf/URLParser.h>
#include <wtf/persistence/PersistentCoders.h>
#include <wtf/text/StringHash.h>

namespace WebCore {

static Variant<String, Ref<SharedBuffer>> copyPlatformData(const Variant<String, Ref<SharedBuffer>>& other)
{
    if (WTF::holds_alternative<String>(other))
        return { WTF::get<String>(other) };

    if (WTF::holds_alternative<Ref<SharedBuffer>>(other))
        return { WTF::get<Ref<SharedBuffer>>(other).copyRef() };

    return { };
}

PasteboardCustomData::Entry::Entry(const Entry& entry)
    : type(entry.type)
    , customData(entry.customData)
    , platformData(copyPlatformData(entry.platformData))
{
}

PasteboardCustomData::Entry::Entry(const String& dataType)
    : type(dataType)
{
}

PasteboardCustomData::Entry::Entry() = default;
PasteboardCustomData::Entry::Entry(Entry&&) = default;

PasteboardCustomData::Entry& PasteboardCustomData::Entry::operator=(const Entry& entry)
{
    type = entry.type;
    customData = entry.customData;
    platformData = copyPlatformData(entry.platformData);
    return *this;
}

PasteboardCustomData::Entry& PasteboardCustomData::Entry::operator=(Entry&&) = default;

PasteboardCustomData::PasteboardCustomData() = default;
PasteboardCustomData::PasteboardCustomData(const PasteboardCustomData&) = default;
PasteboardCustomData::PasteboardCustomData(PasteboardCustomData&&) = default;
PasteboardCustomData::~PasteboardCustomData() = default;

PasteboardCustomData::PasteboardCustomData(String&& origin, Vector<Entry>&& data)
    : m_origin(WTFMove(origin))
    , m_data(WTFMove(data))
{
}

Ref<SharedBuffer> PasteboardCustomData::createSharedBuffer() const
{
    constexpr unsigned currentCustomDataSerializationVersion = 1;

    WTF::Persistence::Encoder encoder;
    encoder << currentCustomDataSerializationVersion;
    encoder << m_origin;
    encoder << sameOriginCustomStringData();
    encoder << orderedTypes();
    return SharedBuffer::create(encoder.buffer(), encoder.bufferSize());
}

PasteboardCustomData PasteboardCustomData::fromPersistenceDecoder(WTF::Persistence::Decoder&& decoder)
{
    constexpr unsigned maxSupportedDataSerializationVersionNumber = 1;

    PasteboardCustomData result;
    Optional<unsigned> version;
    decoder >> version;
    if (!version || *version > maxSupportedDataSerializationVersionNumber)
        return { };

    Optional<String> origin;
    decoder >> origin;
    if (!origin)
        return { };
    result.m_origin = WTFMove(*origin);

    Optional<HashMap<String, String>> sameOriginCustomStringData;
    decoder >> sameOriginCustomStringData;
    if (!sameOriginCustomStringData)
        return { };

    Optional<Vector<String>> orderedTypes;
    decoder >> orderedTypes;
    if (!orderedTypes)
        return { };

    for (auto& type : *orderedTypes)
        result.writeStringInCustomData(type, sameOriginCustomStringData->get(type));

    return result;
}

PasteboardCustomData PasteboardCustomData::fromSharedBuffer(const SharedBuffer& buffer)
{
    return fromPersistenceDecoder(buffer.decoder());
}

void PasteboardCustomData::writeString(const String& type, const String& value)
{
    addOrMoveEntryToEnd(type).platformData = { value };
}

void PasteboardCustomData::writeData(const String& type, Ref<SharedBuffer>&& data)
{
    addOrMoveEntryToEnd(type).platformData = { WTFMove(data) };
}

void PasteboardCustomData::writeStringInCustomData(const String& type, const String& value)
{
    addOrMoveEntryToEnd(type).customData = value;
}

PasteboardCustomData::Entry& PasteboardCustomData::addOrMoveEntryToEnd(const String& type)
{
    auto index = m_data.findMatching([&] (auto& entry) {
        return entry.type == type;
    });
    auto entry = index == notFound ? Entry(type) : m_data[index];
    if (index != notFound)
        m_data.remove(index);
    m_data.append(WTFMove(entry));
    return m_data.last();
}

void PasteboardCustomData::clear()
{
    m_data.clear();
}

void PasteboardCustomData::clear(const String& type)
{
    m_data.removeFirstMatching([&] (auto& entry) {
        return entry.type == type;
    });
}

PasteboardCustomData& PasteboardCustomData::operator=(const PasteboardCustomData& other)
{
    m_origin = other.origin();
    m_data = other.m_data;
    return *this;
}

Vector<String> PasteboardCustomData::orderedTypes() const
{
    return m_data.map([&] (auto& entry) {
        return entry.type;
    });
}

bool PasteboardCustomData::hasData() const
{
    return !m_data.isEmpty();
}

bool PasteboardCustomData::hasSameOriginCustomData() const
{
    return notFound != m_data.findMatching([&] (auto& entry) {
        return !entry.customData.isNull();
    });
}

HashMap<String, String> PasteboardCustomData::sameOriginCustomStringData() const
{
    HashMap<String, String> customData;
    for (auto& entry : m_data)
        customData.set(entry.type, entry.customData);
    return customData;
}

RefPtr<SharedBuffer> PasteboardCustomData::readBuffer(const String& type) const
{
    for (auto& entry : m_data) {
        if (entry.type != type)
            continue;

        if (WTF::holds_alternative<Ref<SharedBuffer>>(entry.platformData))
            return makeRefPtr(WTF::get<Ref<SharedBuffer>>(entry.platformData).get());

        return nullptr;
    }
    return nullptr;
}

String PasteboardCustomData::readString(const String& type) const
{
    for (auto& entry : m_data) {
        if (entry.type != type)
            continue;

        if (WTF::holds_alternative<String>(entry.platformData))
            return WTF::get<String>(entry.platformData);

        return { };
    }
    return { };
}

String PasteboardCustomData::readStringInCustomData(const String& type) const
{
    for (auto& entry : m_data) {
        if (entry.type == type)
            return entry.customData;
    }
    return { };
}

void PasteboardCustomData::forEachType(Function<void(const String&)>&& function) const
{
    for (auto& entry : m_data)
        function(entry.type);
}

void PasteboardCustomData::forEachPlatformString(Function<void(const String& type, const String& data)>&& function) const
{
    for (auto& entry : m_data) {
        if (!WTF::holds_alternative<String>(entry.platformData))
            continue;

        auto string = WTF::get<String>(entry.platformData);
        if (!string.isNull())
            function(entry.type, string);
    }
}

void PasteboardCustomData::forEachCustomString(Function<void(const String& type, const String& data)>&& function) const
{
    for (auto& entry : m_data) {
        if (!entry.customData.isNull())
            function(entry.type, entry.customData);
    }
}

void PasteboardCustomData::forEachPlatformStringOrBuffer(Function<void(const String& type, const Variant<String, Ref<SharedBuffer>>& data)>&& function) const
{
    for (auto& entry : m_data) {
        auto& data = entry.platformData;
        if ((WTF::holds_alternative<String>(data) && !WTF::get<String>(data).isNull()) || WTF::holds_alternative<Ref<SharedBuffer>>(data))
            function(entry.type, data);
    }
}

} // namespace WebCore
