#pragma once

#include <LibGfx/Point.h>
#include <LibWeb/Page/InputEvent.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::CompositorServer {

enum class MessageID : i32 {
    AsyncScrollBy = 1,
    AsyncScrollByResponse = 2,
    MouseEvent = 3,
    MouseEventResponse = 4,
    ReadyToPaint = 5,
};

class AsyncScrollByResponse final : public IPC::Message {
public:
    AsyncScrollByResponse(bool handled)
        : m_handled(move(handled))
    {
    }

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

    template<typename WrappedReturnType>
    requires(!SameAs<WrappedReturnType, bool>)
    AsyncScrollByResponse(WrappedReturnType&& value)
        : m_handled(forward<WrappedReturnType>(value))
    {
    }

    static constexpr u32 ENDPOINT_MAGIC = 3290088511;

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

    static ErrorOr<NonnullOwnPtr<AsyncScrollByResponse>> decode(Stream& stream, Queue<IPC::Attachment>& attachments)
    {
        IPC::Decoder decoder { stream, attachments };
        auto handled = TRY((decoder.decode<bool>()));
        return make<AsyncScrollByResponse>(move(handled));
    }

    static ErrorOr<IPC::MessageBuffer> static_encode(bool handled)
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::AsyncScrollByResponse));
        TRY(stream.encode(handled));
        return buffer;
    }

    virtual ErrorOr<IPC::MessageBuffer> encode() const override
    {
        return static_encode(m_handled);
    }

    bool handled() const { return m_handled; }

private:
    bool m_handled;
};

class AsyncScrollBy final : public IPC::Message {
public:
    typedef class AsyncScrollByResponse ResponseType;

    AsyncScrollBy(u64 page_id, Gfx::FloatPoint position, Gfx::FloatPoint delta_in_device_pixels)
        : m_page_id(move(page_id))
        , m_position(move(position))
        , m_delta_in_device_pixels(move(delta_in_device_pixels))
    {
    }

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

    static constexpr u32 ENDPOINT_MAGIC = 3290088511;

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

    static ErrorOr<NonnullOwnPtr<AsyncScrollBy>> decode(Stream& stream, Queue<IPC::Attachment>& attachments)
    {
        IPC::Decoder decoder { stream, attachments };
        auto page_id = TRY((decoder.decode<u64>()));
        auto position = TRY((decoder.decode<Gfx::FloatPoint>()));
        auto delta_in_device_pixels = TRY((decoder.decode<Gfx::FloatPoint>()));
        return make<AsyncScrollBy>(move(page_id), move(position), move(delta_in_device_pixels));
    }

    static ErrorOr<IPC::MessageBuffer> static_encode(u64 page_id, Gfx::FloatPoint position, Gfx::FloatPoint delta_in_device_pixels)
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::AsyncScrollBy));
        TRY(stream.encode(page_id));
        TRY(stream.encode(position));
        TRY(stream.encode(delta_in_device_pixels));
        return buffer;
    }

    virtual ErrorOr<IPC::MessageBuffer> encode() const override
    {
        return static_encode(m_page_id, m_position, m_delta_in_device_pixels);
    }

    u64 page_id() const { return m_page_id; }

    Gfx::FloatPoint position() const { return m_position; }

    Gfx::FloatPoint delta_in_device_pixels() const { return m_delta_in_device_pixels; }

private:
    u64 m_page_id;
    Gfx::FloatPoint m_position;
    Gfx::FloatPoint m_delta_in_device_pixels;
};

class MouseEventResponse final : public IPC::Message {
public:
    MouseEventResponse(bool handled)
        : m_handled(move(handled))
    {
    }

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

    template<typename WrappedReturnType>
    requires(!SameAs<WrappedReturnType, bool>)
    MouseEventResponse(WrappedReturnType&& value)
        : m_handled(forward<WrappedReturnType>(value))
    {
    }

    static constexpr u32 ENDPOINT_MAGIC = 3290088511;

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

    static ErrorOr<NonnullOwnPtr<MouseEventResponse>> decode(Stream& stream, Queue<IPC::Attachment>& attachments)
    {
        IPC::Decoder decoder { stream, attachments };
        auto handled = TRY((decoder.decode<bool>()));
        return make<MouseEventResponse>(move(handled));
    }

    static ErrorOr<IPC::MessageBuffer> static_encode(bool handled)
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::MouseEventResponse));
        TRY(stream.encode(handled));
        return buffer;
    }

    virtual ErrorOr<IPC::MessageBuffer> encode() const override
    {
        return static_encode(m_handled);
    }

    bool handled() const { return m_handled; }

private:
    bool m_handled;
};

class MouseEvent final : public IPC::Message {
public:
    typedef class MouseEventResponse ResponseType;

    MouseEvent(u64 page_id, Web::MouseEvent event)
        : m_page_id(move(page_id))
        , m_event(move(event))
    {
    }

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

    static constexpr u32 ENDPOINT_MAGIC = 3290088511;

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

    static ErrorOr<NonnullOwnPtr<MouseEvent>> decode(Stream& stream, Queue<IPC::Attachment>& attachments)
    {
        IPC::Decoder decoder { stream, attachments };
        auto page_id = TRY((decoder.decode<u64>()));
        auto event = TRY((decoder.decode<Web::MouseEvent>()));
        return make<MouseEvent>(move(page_id), move(event));
    }

    static ErrorOr<IPC::MessageBuffer> static_encode(u64 page_id, Web::MouseEvent const& event)
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::MouseEvent));
        TRY(stream.encode(page_id));
        TRY(stream.encode(event));
        return buffer;
    }

    virtual ErrorOr<IPC::MessageBuffer> encode() const override
    {
        return static_encode(m_page_id, m_event);
    }

    u64 page_id() const { return m_page_id; }

    Web::MouseEvent const& event() const { return m_event; }
    Web::MouseEvent take_event() { return move(m_event); }

private:
    u64 m_page_id;
    Web::MouseEvent m_event;
};

class ReadyToPaint final : public IPC::Message {
public:
    ReadyToPaint(u64 page_id, i32 bitmap_id)
        : m_page_id(move(page_id))
        , m_bitmap_id(move(bitmap_id))
    {
    }

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

    static constexpr u32 ENDPOINT_MAGIC = 3290088511;

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

    static ErrorOr<NonnullOwnPtr<ReadyToPaint>> decode(Stream& stream, Queue<IPC::Attachment>& attachments)
    {
        IPC::Decoder decoder { stream, attachments };
        auto page_id = TRY((decoder.decode<u64>()));
        auto bitmap_id = TRY((decoder.decode<i32>()));
        return make<ReadyToPaint>(move(page_id), move(bitmap_id));
    }

    static ErrorOr<IPC::MessageBuffer> static_encode(u64 page_id, i32 bitmap_id)
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::ReadyToPaint));
        TRY(stream.encode(page_id));
        TRY(stream.encode(bitmap_id));
        return buffer;
    }

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

    u64 page_id() const { return m_page_id; }

    i32 bitmap_id() const { return m_bitmap_id; }

private:
    u64 m_page_id;
    i32 m_bitmap_id;
};

} // namespace Messages::CompositorServer

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

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

    bool async_scroll_by(u64 page_id, Gfx::FloatPoint position, Gfx::FloatPoint delta_in_device_pixels)
    {
        return m_connection.template send_sync<Messages::CompositorServer::AsyncScrollBy>(page_id, position, delta_in_device_pixels)->handled();
    }

    void async_async_scroll_by(u64 page_id, Gfx::FloatPoint position, Gfx::FloatPoint delta_in_device_pixels)
    {
        auto message_buffer = MUST(Messages::CompositorServer::AsyncScrollBy::static_encode(page_id, position, delta_in_device_pixels));
        (void)m_connection.post_message(message_buffer);
    }

    IPC::IPCErrorOr<bool> try_async_scroll_by(u64 page_id, Gfx::FloatPoint position, Gfx::FloatPoint delta_in_device_pixels)
    {
        if (auto result = m_connection.template send_sync_but_allow_failure<Messages::CompositorServer::AsyncScrollBy>(page_id, position, delta_in_device_pixels))
            return move(*result);
        m_connection.shutdown();
        return IPC::ErrorCode::PeerDisconnected;
    }

    bool mouse_event(u64 page_id, Web::MouseEvent event)
    {
        return m_connection.template send_sync<Messages::CompositorServer::MouseEvent>(page_id, move(event))->handled();
    }

    void async_mouse_event(u64 page_id, Web::MouseEvent const& event)
    {
        auto message_buffer = MUST(Messages::CompositorServer::MouseEvent::static_encode(page_id, move(event)));
        (void)m_connection.post_message(message_buffer);
    }

    IPC::IPCErrorOr<bool> try_mouse_event(u64 page_id, Web::MouseEvent event)
    {
        if (auto result = m_connection.template send_sync_but_allow_failure<Messages::CompositorServer::MouseEvent>(page_id, move(event)))
            return move(*result);
        m_connection.shutdown();
        return IPC::ErrorCode::PeerDisconnected;
    }

    void async_ready_to_paint(u64 page_id, i32 bitmap_id)
    {
        auto message_buffer = MUST(Messages::CompositorServer::ReadyToPaint::static_encode(page_id, bitmap_id));
        (void)m_connection.post_message(message_buffer);
    }

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

template<typename LocalEndpoint, typename PeerEndpoint>
class CompositorServerProxy;
class CompositorServerStub;

class CompositorServerEndpoint {
public:
    template<typename LocalEndpoint>
    using Proxy = CompositorServerProxy<LocalEndpoint, CompositorServerEndpoint>;
    using Stub = CompositorServerStub;

    static u32 static_magic() { return 3290088511; }

    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::CompositorServer::MessageID::AsyncScrollBy:
            return Messages::CompositorServer::AsyncScrollBy::decode(stream, attachments);
        case (int)Messages::CompositorServer::MessageID::AsyncScrollByResponse:
            return Messages::CompositorServer::AsyncScrollByResponse::decode(stream, attachments);
        case (int)Messages::CompositorServer::MessageID::MouseEvent:
            return Messages::CompositorServer::MouseEvent::decode(stream, attachments);
        case (int)Messages::CompositorServer::MessageID::MouseEventResponse:
            return Messages::CompositorServer::MouseEventResponse::decode(stream, attachments);
        case (int)Messages::CompositorServer::MessageID::ReadyToPaint:
            return Messages::CompositorServer::ReadyToPaint::decode(stream, attachments);
        default:
            return Error::from_string_literal("Failed to decode CompositorServer message");
        }

        VERIFY_NOT_REACHED();
    }
};

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

    virtual u32 magic() const override { return 3290088511; }
    virtual ByteString name() const override { return "CompositorServer"; }

    virtual ErrorOr<OwnPtr<IPC::MessageBuffer>> handle(NonnullOwnPtr<IPC::Message> message) override
    {
        switch (message->message_id()) {
        case (int)Messages::CompositorServer::MessageID::AsyncScrollBy:
            return handle_async_scroll_by(*message);
        case (int)Messages::CompositorServer::MessageID::MouseEvent:
            return handle_mouse_event(*message);
        case (int)Messages::CompositorServer::MessageID::ReadyToPaint:
            return handle_ready_to_paint(*message);
        default:
            return Error::from_string_literal("Unknown message ID for CompositorServer endpoint");
        }
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_async_scroll_by(IPC::Message& message)
    {
        auto& request = static_cast<Messages::CompositorServer::AsyncScrollBy&>(message);
        auto response = async_scroll_by(request.page_id(), request.position(), request.delta_in_device_pixels());
        return make<IPC::MessageBuffer>(TRY(response.encode()));
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_mouse_event(IPC::Message& message)
    {
        auto& request = static_cast<Messages::CompositorServer::MouseEvent&>(message);
        auto response = mouse_event(request.page_id(), request.take_event());
        return make<IPC::MessageBuffer>(TRY(response.encode()));
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_ready_to_paint(IPC::Message& message)
    {
        auto& request = static_cast<Messages::CompositorServer::ReadyToPaint&>(message);
        ready_to_paint(request.page_id(), request.bitmap_id());
        return nullptr;
    }

    virtual Messages::CompositorServer::AsyncScrollByResponse async_scroll_by(u64 page_id, Gfx::FloatPoint position, Gfx::FloatPoint delta_in_device_pixels) = 0;
    virtual Messages::CompositorServer::MouseEventResponse mouse_event(u64 page_id, Web::MouseEvent event) = 0;
    virtual void ready_to_paint(u64 page_id, i32 bitmap_id) = 0;
};

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