#pragma once

#include <LibGfx/Rect.h>
#include <LibGfx/SharedImage.h>
#include <LibWeb/Compositor/Types.h>
#include <AK/Error.h>
#include <AK/MemoryStream.h>
#include <AK/OwnPtr.h>
#include <AK/Platform.h>
#include <AK/Result.h>
#include <AK/Utf8View.h>
#include <LibIPC/Attachment.h>
#include <LibIPC/Connection.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
#include <LibIPC/File.h>
#include <LibIPC/Message.h>
#include <LibIPC/Stub.h>

#if defined(AK_COMPILER_CLANG)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdefaulted-function-deleted"
#endif

namespace Messages::CompositorControlClient {

enum class MessageID : i32 {
    DidAllocateBackingStores = 1,
    DidPresentFrame = 2,
};

class DidAllocateBackingStores final : public IPC::Message {
public:
    DidAllocateBackingStores(Web::Compositor::CompositorContextId context_id, i32 front_bitmap_id, Gfx::SharedImage front_backing_store, i32 back_bitmap_id, Gfx::SharedImage back_backing_store)
        : m_context_id(move(context_id))
        , m_front_bitmap_id(move(front_bitmap_id))
        , m_front_backing_store(move(front_backing_store))
        , m_back_bitmap_id(move(back_bitmap_id))
        , m_back_backing_store(move(back_backing_store))
    {
    }

    DidAllocateBackingStores(DidAllocateBackingStores const&) = default;
    DidAllocateBackingStores(DidAllocateBackingStores&&) = default;
    DidAllocateBackingStores& operator=(DidAllocateBackingStores const&) = default;

    static constexpr u32 ENDPOINT_MAGIC = 2697464627;

    virtual u32 endpoint_magic() const override { return ENDPOINT_MAGIC; }
    virtual i32 message_id() const override { return (int)MessageID::DidAllocateBackingStores; }
    static i32 static_message_id() { return (int)MessageID::DidAllocateBackingStores; }
    virtual StringView message_name() const override { return "CompositorControlClient::DidAllocateBackingStores"sv; }

    static ErrorOr<NonnullOwnPtr<DidAllocateBackingStores>> decode(Stream& stream, Queue<IPC::Attachment>& attachments)
    {
        IPC::Decoder decoder { stream, attachments };
        auto context_id = TRY((decoder.decode<Web::Compositor::CompositorContextId>()));
        auto front_bitmap_id = TRY((decoder.decode<i32>()));
        auto front_backing_store = TRY((decoder.decode<Gfx::SharedImage>()));
        auto back_bitmap_id = TRY((decoder.decode<i32>()));
        auto back_backing_store = TRY((decoder.decode<Gfx::SharedImage>()));
        return make<DidAllocateBackingStores>(move(context_id), move(front_bitmap_id), move(front_backing_store), move(back_bitmap_id), move(back_backing_store));
    }

    static ErrorOr<IPC::MessageBuffer> static_encode(Web::Compositor::CompositorContextId const& context_id, i32 front_bitmap_id, Gfx::SharedImage const& front_backing_store, i32 back_bitmap_id, Gfx::SharedImage const& back_backing_store)
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::DidAllocateBackingStores));
        TRY(stream.encode(context_id));
        TRY(stream.encode(front_bitmap_id));
        TRY(stream.encode(front_backing_store));
        TRY(stream.encode(back_bitmap_id));
        TRY(stream.encode(back_backing_store));
        return buffer;
    }

    virtual ErrorOr<IPC::MessageBuffer> encode() const override
    {
        return static_encode(m_context_id, m_front_bitmap_id, m_front_backing_store, m_back_bitmap_id, m_back_backing_store);
    }

    Web::Compositor::CompositorContextId const& context_id() const { return m_context_id; }
    Web::Compositor::CompositorContextId take_context_id() { return move(m_context_id); }

    i32 front_bitmap_id() const { return m_front_bitmap_id; }

    Gfx::SharedImage const& front_backing_store() const { return m_front_backing_store; }
    Gfx::SharedImage take_front_backing_store() { return move(m_front_backing_store); }

    i32 back_bitmap_id() const { return m_back_bitmap_id; }

    Gfx::SharedImage const& back_backing_store() const { return m_back_backing_store; }
    Gfx::SharedImage take_back_backing_store() { return move(m_back_backing_store); }

private:
    Web::Compositor::CompositorContextId m_context_id;
    i32 m_front_bitmap_id;
    Gfx::SharedImage m_front_backing_store;
    i32 m_back_bitmap_id;
    Gfx::SharedImage m_back_backing_store;
};

class DidPresentFrame final : public IPC::Message {
public:
    DidPresentFrame(Web::Compositor::CompositorContextId context_id, Gfx::IntRect content_rect, i32 bitmap_id)
        : m_context_id(move(context_id))
        , m_content_rect(move(content_rect))
        , m_bitmap_id(move(bitmap_id))
    {
    }

    DidPresentFrame(DidPresentFrame const&) = default;
    DidPresentFrame(DidPresentFrame&&) = default;
    DidPresentFrame& operator=(DidPresentFrame const&) = default;

    static constexpr u32 ENDPOINT_MAGIC = 2697464627;

    virtual u32 endpoint_magic() const override { return ENDPOINT_MAGIC; }
    virtual i32 message_id() const override { return (int)MessageID::DidPresentFrame; }
    static i32 static_message_id() { return (int)MessageID::DidPresentFrame; }
    virtual StringView message_name() const override { return "CompositorControlClient::DidPresentFrame"sv; }

    static ErrorOr<NonnullOwnPtr<DidPresentFrame>> decode(Stream& stream, Queue<IPC::Attachment>& attachments)
    {
        IPC::Decoder decoder { stream, attachments };
        auto context_id = TRY((decoder.decode<Web::Compositor::CompositorContextId>()));
        auto content_rect = TRY((decoder.decode<Gfx::IntRect>()));
        auto bitmap_id = TRY((decoder.decode<i32>()));
        return make<DidPresentFrame>(move(context_id), move(content_rect), move(bitmap_id));
    }

    static ErrorOr<IPC::MessageBuffer> static_encode(Web::Compositor::CompositorContextId const& context_id, Gfx::IntRect const& content_rect, i32 bitmap_id)
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::DidPresentFrame));
        TRY(stream.encode(context_id));
        TRY(stream.encode(content_rect));
        TRY(stream.encode(bitmap_id));
        return buffer;
    }

    virtual ErrorOr<IPC::MessageBuffer> encode() const override
    {
        return static_encode(m_context_id, m_content_rect, m_bitmap_id);
    }

    Web::Compositor::CompositorContextId const& context_id() const { return m_context_id; }
    Web::Compositor::CompositorContextId take_context_id() { return move(m_context_id); }

    Gfx::IntRect const& content_rect() const { return m_content_rect; }
    Gfx::IntRect take_content_rect() { return move(m_content_rect); }

    i32 bitmap_id() const { return m_bitmap_id; }

private:
    Web::Compositor::CompositorContextId m_context_id;
    Gfx::IntRect m_content_rect;
    i32 m_bitmap_id;
};

} // namespace Messages::CompositorControlClient

template<typename LocalEndpoint, typename PeerEndpoint>
class CompositorControlClientProxy {
public:
    // Used to disambiguate the constructor call.
    struct Tag { };

    CompositorControlClientProxy(IPC::Connection<LocalEndpoint, PeerEndpoint>& connection, Tag)
        : m_connection(connection)
    {
    }

    void async_did_allocate_backing_stores(Web::Compositor::CompositorContextId const& context_id, i32 front_bitmap_id, Gfx::SharedImage const& front_backing_store, i32 back_bitmap_id, Gfx::SharedImage const& back_backing_store)
    {
        auto message_buffer = MUST(Messages::CompositorControlClient::DidAllocateBackingStores::static_encode(move(context_id), front_bitmap_id, move(front_backing_store), back_bitmap_id, move(back_backing_store)));
        (void)m_connection.post_message(message_buffer);
    }

    void async_did_present_frame(Web::Compositor::CompositorContextId const& context_id, Gfx::IntRect const& content_rect, i32 bitmap_id)
    {
        auto message_buffer = MUST(Messages::CompositorControlClient::DidPresentFrame::static_encode(move(context_id), move(content_rect), bitmap_id));
        (void)m_connection.post_message(message_buffer);
    }

private:
    IPC::Connection<LocalEndpoint, PeerEndpoint>& m_connection;
};

template<typename LocalEndpoint, typename PeerEndpoint>
class CompositorControlClientProxy;
class CompositorControlClientStub;

class CompositorControlClientEndpoint {
public:
    template<typename LocalEndpoint>
    using Proxy = CompositorControlClientProxy<LocalEndpoint, CompositorControlClientEndpoint>;
    using Stub = CompositorControlClientStub;

    static u32 static_magic() { return 2697464627; }

    static ErrorOr<NonnullOwnPtr<IPC::Message>> decode_message(ReadonlyBytes buffer, [[maybe_unused]] Queue<IPC::Attachment>& attachments)
    {
        FixedMemoryStream stream { buffer };
        auto message_endpoint_magic = TRY(stream.read_value<u32>());

        if (message_endpoint_magic != static_magic())
            return Error::from_string_literal("Endpoint magic number mismatch, not my message!");

        auto message_id = TRY(stream.read_value<i32>());

        switch (message_id) {
        case (int)Messages::CompositorControlClient::MessageID::DidAllocateBackingStores:
            return Messages::CompositorControlClient::DidAllocateBackingStores::decode(stream, attachments);
        case (int)Messages::CompositorControlClient::MessageID::DidPresentFrame:
            return Messages::CompositorControlClient::DidPresentFrame::decode(stream, attachments);
        default:
            return Error::from_string_literal("Failed to decode CompositorControlClient message");
        }

        VERIFY_NOT_REACHED();
    }
};

class CompositorControlClientStub : public IPC::Stub {
public:
    CompositorControlClientStub() { }
    virtual ~CompositorControlClientStub() override { }

    virtual u32 magic() const override { return 2697464627; }
    virtual ByteString name() const override { return "CompositorControlClient"; }

    virtual ErrorOr<OwnPtr<IPC::MessageBuffer>> handle(NonnullOwnPtr<IPC::Message> message) override
    {
        switch (message->message_id()) {
        case (int)Messages::CompositorControlClient::MessageID::DidAllocateBackingStores:
            return handle_did_allocate_backing_stores(*message);
        case (int)Messages::CompositorControlClient::MessageID::DidPresentFrame:
            return handle_did_present_frame(*message);
        default:
            return Error::from_string_literal("Unknown message ID for CompositorControlClient endpoint");
        }
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_did_allocate_backing_stores(IPC::Message& message)
    {
        auto& request = static_cast<Messages::CompositorControlClient::DidAllocateBackingStores&>(message);
        did_allocate_backing_stores(request.take_context_id(), request.front_bitmap_id(), request.take_front_backing_store(), request.back_bitmap_id(), request.take_back_backing_store());
        return nullptr;
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_did_present_frame(IPC::Message& message)
    {
        auto& request = static_cast<Messages::CompositorControlClient::DidPresentFrame&>(message);
        did_present_frame(request.take_context_id(), request.take_content_rect(), request.bitmap_id());
        return nullptr;
    }

    virtual void did_allocate_backing_stores(Web::Compositor::CompositorContextId context_id, i32 front_bitmap_id, Gfx::SharedImage front_backing_store, i32 back_bitmap_id, Gfx::SharedImage back_backing_store) = 0;
    virtual void did_present_frame(Web::Compositor::CompositorContextId context_id, Gfx::IntRect content_rect, i32 bitmap_id) = 0;
};

#if defined(AK_COMPILER_CLANG)
#pragma clang diagnostic pop
#endif
