/*
 * Copyright (C) 2020-2021 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 <WebCore/DisplayList.h>
#include <WebCore/DisplayListItems.h>
#include <WebCore/DisplayListIterator.h>
#include <WebCore/Filter.h>
#include <WebCore/Gradient.h>
#include <WebCore/InMemoryDisplayList.h>

namespace TestWebKitAPI {
using namespace WebCore;
using DisplayList::DisplayList;
using namespace DisplayList;

constexpr size_t globalItemBufferCapacity = 1 << 12;
static uint8_t globalItemBuffer[globalItemBufferCapacity];

static Ref<Gradient> createGradient()
{
    auto gradient = Gradient::create(Gradient::ConicData { { 0., 0. }, 1.25 }, { ColorInterpolationMethod::SRGB { }, AlphaPremultiplication::Unpremultiplied });
    gradient->addColorStop({ 0.1, Color::red });
    gradient->addColorStop({ 0.5, Color::green });
    gradient->addColorStop({ 0.9, Color::blue });
    return gradient;
}

static Path createComplexPath()
{
    Path path;
    path.moveTo({ 10., 10. });
    path.addLineTo({ 50., 50. });
    path.addQuadCurveTo({ 100., 100. }, { 0., 200. });
    path.addLineTo({ 10., 10. });
    return path;
}

TEST(DisplayListTests, AppendItems)
{
    InMemoryDisplayList list;

    EXPECT_TRUE(list.isEmpty());

    auto gradient = createGradient();
    auto path = createComplexPath();

    for (int i = 0; i < 50; ++i) {
        list.append<SetStrokeThickness>(1.5);
        list.append<FillPath>(path);
        list.append<FillRectWithGradient>(FloatRect { 1., 1., 10., 10. }, gradient);
        list.append<SetInlineFillColor>(Color::red);
#if ENABLE(INLINE_PATH_DATA)
        list.append<StrokeLine>(LineData {{ 0., 0. }, { 10., 15. }});
#endif
    }

    EXPECT_FALSE(list.isEmpty());

    bool observedUnexpectedItem = false;
    for (auto displayListItem : list) {
        auto handle = displayListItem->item;
        switch (handle.type()) {
        case ItemType::SetStrokeThickness: {
            EXPECT_FALSE(handle.isDrawingItem());
            EXPECT_TRUE(handle.is<SetStrokeThickness>());
            auto& item = handle.get<SetStrokeThickness>();
            EXPECT_EQ(item.thickness(), 1.5);
            break;
        }
        case ItemType::FillPath: {
            EXPECT_TRUE(handle.isDrawingItem());
            EXPECT_TRUE(handle.is<FillPath>());
            auto& item = handle.get<FillPath>();
            EXPECT_EQ(item.path().platformPath(), path.platformPath());
            break;
        }
        case ItemType::FillRectWithGradient: {
            EXPECT_TRUE(handle.isDrawingItem());
            EXPECT_TRUE(handle.is<FillRectWithGradient>());
            auto& item = handle.get<FillRectWithGradient>();
            EXPECT_EQ(item.rect(), FloatRect(1., 1., 10., 10.));
            EXPECT_EQ(&item.gradient(), gradient.ptr());
            break;
        }
        case ItemType::SetInlineFillColor: {
            EXPECT_FALSE(handle.isDrawingItem());
            EXPECT_TRUE(handle.is<SetInlineFillColor>());
            auto& item = handle.get<SetInlineFillColor>();
            EXPECT_EQ(item.color(), Color::red);
            break;
        }
#if ENABLE(INLINE_PATH_DATA)
        case ItemType::StrokeLine: {
            EXPECT_TRUE(handle.isDrawingItem());
            EXPECT_TRUE(handle.is<StrokeLine>());
            auto& item = handle.get<StrokeLine>();
            EXPECT_EQ(item.start(), FloatPoint(0, 0));
            EXPECT_EQ(item.end(), FloatPoint(10., 15.));
            break;
        }
#endif
        default: {
            observedUnexpectedItem = true;
            break;
        }
        }
    }

    EXPECT_FALSE(observedUnexpectedItem);
    EXPECT_GT(list.sizeInBytes(), 0U);

    list.clear();

    observedUnexpectedItem = false;
    for (auto itemAndExtent : list) {
        UNUSED_PARAM(itemAndExtent);
        observedUnexpectedItem = true;
    }

    EXPECT_TRUE(list.isEmpty());
    EXPECT_EQ(list.sizeInBytes(), 0U);
    EXPECT_FALSE(observedUnexpectedItem);

    list.append<FillRectWithColor>(FloatRect { 0, 0, 100, 100 }, Color::black);

    for (auto displayListItem : list) {
        auto handle = displayListItem->item;
        EXPECT_EQ(handle.type(), ItemType::FillRectWithColor);
        EXPECT_TRUE(handle.is<FillRectWithColor>());

        auto& item = handle.get<FillRectWithColor>();
        EXPECT_EQ(*item.color().tryGetAsSRGBABytes(), Color::black);
        EXPECT_EQ(item.rect(), FloatRect(0, 0, 100, 100));
    }

    EXPECT_FALSE(list.isEmpty());
    EXPECT_GT(list.sizeInBytes(), 0U);
}

TEST(DisplayListTests, ItemBufferClient)
{
    Vector<StrokePath> strokePathItems;
    static ItemBufferIdentifier globalBufferIdentifier = ItemBufferIdentifier::generate();

    class StrokePathReader : public ItemBufferReadingClient {
    public:
        StrokePathReader(const Vector<StrokePath>& items)
            : m_items(items)
        {
        }

    private:
        std::optional<ItemHandle> WARN_UNUSED_RETURN decodeItem(const uint8_t* data, size_t dataLength, ItemType type, uint8_t* handleLocation) final
        {
            EXPECT_EQ(type, ItemType::StrokePath);
            EXPECT_EQ(dataLength, sizeof(size_t));
            new (handleLocation + sizeof(uint64_t)) StrokePath(m_items[*reinterpret_cast<const size_t*>(data)]);
            return {{ handleLocation }};
        }

        const Vector<StrokePath>& m_items;
    };

    class StrokePathWriter : public ItemBufferWritingClient {
    public:
        StrokePathWriter(Vector<StrokePath>& items)
            : m_items(items)
        {
        }

    private:
        ItemBufferHandle createItemBuffer(size_t capacity) final
        {
            EXPECT_LT(capacity, globalItemBufferCapacity);
            return { globalBufferIdentifier, globalItemBuffer, globalItemBufferCapacity };
        }

        RefPtr<FragmentedSharedBuffer> encodeItemOutOfLine(const DisplayListItem& displayListItem) const final
        {
            auto index = m_items.size();
            m_items.append(std::get<StrokePath>(displayListItem));
            return SharedBuffer::create(reinterpret_cast<uint8_t*>(&index), sizeof(size_t));
        }

        Vector<StrokePath>& m_items;
    };

    DisplayList list;
    StrokePathWriter writer { strokePathItems };
    list.setItemBufferWritingClient(&writer);

    auto path = createComplexPath();
    list.append<SetInlineStrokeColor>(Color::blue);
    list.append<StrokePath>(path);
    list.append<SetInlineStrokeColor>(Color::red);
    list.append<StrokePath>(path);

    DisplayList shallowCopy {{ ItemBufferHandle { globalBufferIdentifier, globalItemBuffer, list.sizeInBytes() } }};
    StrokePathReader reader { strokePathItems };
    shallowCopy.setItemBufferReadingClient(&reader);

    Vector<ItemType> itemTypes;
    for (auto displayListItem : shallowCopy)
        itemTypes.append(displayListItem->item.type());

    EXPECT_FALSE(shallowCopy.isEmpty());
    EXPECT_EQ(itemTypes.size(), 4U);
    EXPECT_EQ(itemTypes[0], ItemType::SetInlineStrokeColor);
    EXPECT_EQ(itemTypes[1], ItemType::StrokePath);
    EXPECT_EQ(itemTypes[2], ItemType::SetInlineStrokeColor);
    EXPECT_EQ(itemTypes[3], ItemType::StrokePath);
}

} // namespace TestWebKitAPI
