#pragma once

#include <LibGfx/BitmapSequence.h>
#include <LibGfx/ColorSpace.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::ImageDecoderClient {

enum class MessageID : i32 {
    DidDecodeImage = 1,
    DidFailToDecodeImage = 2,
    DidDecodeAnimationFrames = 3,
    DidFailAnimationDecode = 4,
};

class DidDecodeImage final : public IPC::Message {
public:
    DidDecodeImage(i64 request_id, bool is_animated, u32 loop_count, Gfx::BitmapSequence bitmaps, Vector<u32> durations, Gfx::FloatPoint scale, Gfx::ColorSpace color_profile, i64 session_id)
        : m_request_id(move(request_id))
        , m_is_animated(move(is_animated))
        , m_loop_count(move(loop_count))
        , m_bitmaps(move(bitmaps))
        , m_durations(move(durations))
        , m_scale(move(scale))
        , m_color_profile(move(color_profile))
        , m_session_id(move(session_id))
    {
    }

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

    static constexpr u32 ENDPOINT_MAGIC = 945471142;

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

    static ErrorOr<NonnullOwnPtr<DidDecodeImage>> decode(Stream& stream, Queue<IPC::Attachment>& attachments)
    {
        IPC::Decoder decoder { stream, attachments };
        auto request_id = TRY((decoder.decode<i64>()));
        auto is_animated = TRY((decoder.decode<bool>()));
        auto loop_count = TRY((decoder.decode<u32>()));
        auto bitmaps = TRY((decoder.decode<Gfx::BitmapSequence>()));
        auto durations = TRY((decoder.decode<Vector<u32>>()));
        auto scale = TRY((decoder.decode<Gfx::FloatPoint>()));
        auto color_profile = TRY((decoder.decode<Gfx::ColorSpace>()));
        auto session_id = TRY((decoder.decode<i64>()));
        return make<DidDecodeImage>(move(request_id), move(is_animated), move(loop_count), move(bitmaps), move(durations), move(scale), move(color_profile), move(session_id));
    }

    static ErrorOr<IPC::MessageBuffer> static_encode(i64 request_id, bool is_animated, u32 loop_count, Gfx::BitmapSequence const& bitmaps, ReadonlySpan<u32> durations, Gfx::FloatPoint scale, Gfx::ColorSpace const& color_profile, i64 session_id)
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::DidDecodeImage));
        TRY(stream.encode(request_id));
        TRY(stream.encode(is_animated));
        TRY(stream.encode(loop_count));
        TRY(stream.encode(bitmaps));
        TRY(stream.encode(durations));
        TRY(stream.encode(scale));
        TRY(stream.encode(color_profile));
        TRY(stream.encode(session_id));
        return buffer;
    }

    virtual ErrorOr<IPC::MessageBuffer> encode() const override
    {
        return static_encode(m_request_id, m_is_animated, m_loop_count, m_bitmaps, m_durations, m_scale, m_color_profile, m_session_id);
    }

    i64 request_id() const { return m_request_id; }

    bool is_animated() const { return m_is_animated; }

    u32 loop_count() const { return m_loop_count; }

    Gfx::BitmapSequence const& bitmaps() const { return m_bitmaps; }
    Gfx::BitmapSequence take_bitmaps() { return move(m_bitmaps); }

    Vector<u32> const& durations() const { return m_durations; }
    Vector<u32> take_durations() { return move(m_durations); }

    Gfx::FloatPoint scale() const { return m_scale; }

    Gfx::ColorSpace const& color_profile() const { return m_color_profile; }
    Gfx::ColorSpace take_color_profile() { return move(m_color_profile); }

    i64 session_id() const { return m_session_id; }

private:
    i64 m_request_id;
    bool m_is_animated;
    u32 m_loop_count;
    Gfx::BitmapSequence m_bitmaps;
    Vector<u32> m_durations;
    Gfx::FloatPoint m_scale;
    Gfx::ColorSpace m_color_profile;
    i64 m_session_id;
};

class DidFailToDecodeImage final : public IPC::Message {
public:
    DidFailToDecodeImage(i64 request_id, String error_message)
        : m_request_id(move(request_id))
        , m_error_message(move(error_message))
    {
    }

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

    static constexpr u32 ENDPOINT_MAGIC = 945471142;

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

    static ErrorOr<NonnullOwnPtr<DidFailToDecodeImage>> decode(Stream& stream, Queue<IPC::Attachment>& attachments)
    {
        IPC::Decoder decoder { stream, attachments };
        auto request_id = TRY((decoder.decode<i64>()));
        auto error_message = TRY((decoder.decode<String>()));
        return make<DidFailToDecodeImage>(move(request_id), move(error_message));
    }

    static ErrorOr<IPC::MessageBuffer> static_encode(i64 request_id, StringView error_message)
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::DidFailToDecodeImage));
        TRY(stream.encode(request_id));
        TRY(stream.encode(error_message));
        return buffer;
    }

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

    i64 request_id() const { return m_request_id; }

    String const& error_message() const { return m_error_message; }
    String take_error_message() { return move(m_error_message); }

private:
    i64 m_request_id;
    String m_error_message;
};

class DidDecodeAnimationFrames final : public IPC::Message {
public:
    DidDecodeAnimationFrames(i64 session_id, Gfx::BitmapSequence bitmaps)
        : m_session_id(move(session_id))
        , m_bitmaps(move(bitmaps))
    {
    }

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

    static constexpr u32 ENDPOINT_MAGIC = 945471142;

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

    static ErrorOr<NonnullOwnPtr<DidDecodeAnimationFrames>> decode(Stream& stream, Queue<IPC::Attachment>& attachments)
    {
        IPC::Decoder decoder { stream, attachments };
        auto session_id = TRY((decoder.decode<i64>()));
        auto bitmaps = TRY((decoder.decode<Gfx::BitmapSequence>()));
        return make<DidDecodeAnimationFrames>(move(session_id), move(bitmaps));
    }

    static ErrorOr<IPC::MessageBuffer> static_encode(i64 session_id, Gfx::BitmapSequence const& bitmaps)
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::DidDecodeAnimationFrames));
        TRY(stream.encode(session_id));
        TRY(stream.encode(bitmaps));
        return buffer;
    }

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

    i64 session_id() const { return m_session_id; }

    Gfx::BitmapSequence const& bitmaps() const { return m_bitmaps; }
    Gfx::BitmapSequence take_bitmaps() { return move(m_bitmaps); }

private:
    i64 m_session_id;
    Gfx::BitmapSequence m_bitmaps;
};

class DidFailAnimationDecode final : public IPC::Message {
public:
    DidFailAnimationDecode(i64 session_id, String error_message)
        : m_session_id(move(session_id))
        , m_error_message(move(error_message))
    {
    }

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

    static constexpr u32 ENDPOINT_MAGIC = 945471142;

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

    static ErrorOr<NonnullOwnPtr<DidFailAnimationDecode>> decode(Stream& stream, Queue<IPC::Attachment>& attachments)
    {
        IPC::Decoder decoder { stream, attachments };
        auto session_id = TRY((decoder.decode<i64>()));
        auto error_message = TRY((decoder.decode<String>()));
        return make<DidFailAnimationDecode>(move(session_id), move(error_message));
    }

    static ErrorOr<IPC::MessageBuffer> static_encode(i64 session_id, StringView error_message)
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::DidFailAnimationDecode));
        TRY(stream.encode(session_id));
        TRY(stream.encode(error_message));
        return buffer;
    }

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

    i64 session_id() const { return m_session_id; }

    String const& error_message() const { return m_error_message; }
    String take_error_message() { return move(m_error_message); }

private:
    i64 m_session_id;
    String m_error_message;
};

} // namespace Messages::ImageDecoderClient

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

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

    void async_did_decode_image(i64 request_id, bool is_animated, u32 loop_count, Gfx::BitmapSequence const& bitmaps, ReadonlySpan<u32> durations, Gfx::FloatPoint scale, Gfx::ColorSpace const& color_profile, i64 session_id)
    {
        auto message_buffer = MUST(Messages::ImageDecoderClient::DidDecodeImage::static_encode(request_id, is_animated, loop_count, move(bitmaps), durations, scale, move(color_profile), session_id));
        (void)m_connection.post_message(message_buffer);
    }

    void async_did_fail_to_decode_image(i64 request_id, StringView error_message)
    {
        VERIFY(Utf8View { error_message }.validate());
        auto message_buffer = MUST(Messages::ImageDecoderClient::DidFailToDecodeImage::static_encode(request_id, error_message));
        (void)m_connection.post_message(message_buffer);
    }

    void async_did_fail_to_decode_image(i64 request_id, String const& error_message)
    {
        auto message_buffer = MUST(Messages::ImageDecoderClient::DidFailToDecodeImage::static_encode(request_id, error_message));
        (void)m_connection.post_message(message_buffer);
    }

    void async_did_decode_animation_frames(i64 session_id, Gfx::BitmapSequence const& bitmaps)
    {
        auto message_buffer = MUST(Messages::ImageDecoderClient::DidDecodeAnimationFrames::static_encode(session_id, move(bitmaps)));
        (void)m_connection.post_message(message_buffer);
    }

    void async_did_fail_animation_decode(i64 session_id, StringView error_message)
    {
        VERIFY(Utf8View { error_message }.validate());
        auto message_buffer = MUST(Messages::ImageDecoderClient::DidFailAnimationDecode::static_encode(session_id, error_message));
        (void)m_connection.post_message(message_buffer);
    }

    void async_did_fail_animation_decode(i64 session_id, String const& error_message)
    {
        auto message_buffer = MUST(Messages::ImageDecoderClient::DidFailAnimationDecode::static_encode(session_id, error_message));
        (void)m_connection.post_message(message_buffer);
    }

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

template<typename LocalEndpoint, typename PeerEndpoint>
class ImageDecoderClientProxy;
class ImageDecoderClientStub;

class ImageDecoderClientEndpoint {
public:
    template<typename LocalEndpoint>
    using Proxy = ImageDecoderClientProxy<LocalEndpoint, ImageDecoderClientEndpoint>;
    using Stub = ImageDecoderClientStub;

    static u32 static_magic() { return 945471142; }

    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::ImageDecoderClient::MessageID::DidDecodeImage:
            return Messages::ImageDecoderClient::DidDecodeImage::decode(stream, attachments);
        case (int)Messages::ImageDecoderClient::MessageID::DidFailToDecodeImage:
            return Messages::ImageDecoderClient::DidFailToDecodeImage::decode(stream, attachments);
        case (int)Messages::ImageDecoderClient::MessageID::DidDecodeAnimationFrames:
            return Messages::ImageDecoderClient::DidDecodeAnimationFrames::decode(stream, attachments);
        case (int)Messages::ImageDecoderClient::MessageID::DidFailAnimationDecode:
            return Messages::ImageDecoderClient::DidFailAnimationDecode::decode(stream, attachments);
        default:
            return Error::from_string_literal("Failed to decode ImageDecoderClient message");
        }

        VERIFY_NOT_REACHED();
    }
};

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

    virtual u32 magic() const override { return 945471142; }
    virtual ByteString name() const override { return "ImageDecoderClient"; }

    virtual ErrorOr<OwnPtr<IPC::MessageBuffer>> handle(NonnullOwnPtr<IPC::Message> message) override
    {
        switch (message->message_id()) {
        case (int)Messages::ImageDecoderClient::MessageID::DidDecodeImage:
            return handle_did_decode_image(*message);
        case (int)Messages::ImageDecoderClient::MessageID::DidFailToDecodeImage:
            return handle_did_fail_to_decode_image(*message);
        case (int)Messages::ImageDecoderClient::MessageID::DidDecodeAnimationFrames:
            return handle_did_decode_animation_frames(*message);
        case (int)Messages::ImageDecoderClient::MessageID::DidFailAnimationDecode:
            return handle_did_fail_animation_decode(*message);
        default:
            return Error::from_string_literal("Unknown message ID for ImageDecoderClient endpoint");
        }
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_did_decode_image(IPC::Message& message)
    {
        auto& request = static_cast<Messages::ImageDecoderClient::DidDecodeImage&>(message);
        did_decode_image(request.request_id(), request.is_animated(), request.loop_count(), request.take_bitmaps(), request.take_durations(), request.scale(), request.take_color_profile(), request.session_id());
        return nullptr;
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_did_fail_to_decode_image(IPC::Message& message)
    {
        auto& request = static_cast<Messages::ImageDecoderClient::DidFailToDecodeImage&>(message);
        did_fail_to_decode_image(request.request_id(), request.take_error_message());
        return nullptr;
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_did_decode_animation_frames(IPC::Message& message)
    {
        auto& request = static_cast<Messages::ImageDecoderClient::DidDecodeAnimationFrames&>(message);
        did_decode_animation_frames(request.session_id(), request.take_bitmaps());
        return nullptr;
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_did_fail_animation_decode(IPC::Message& message)
    {
        auto& request = static_cast<Messages::ImageDecoderClient::DidFailAnimationDecode&>(message);
        did_fail_animation_decode(request.session_id(), request.take_error_message());
        return nullptr;
    }

    virtual void did_decode_image(i64 request_id, bool is_animated, u32 loop_count, Gfx::BitmapSequence bitmaps, Vector<u32> durations, Gfx::FloatPoint scale, Gfx::ColorSpace color_profile, i64 session_id) = 0;
    virtual void did_fail_to_decode_image(i64 request_id, String error_message) = 0;
    virtual void did_decode_animation_frames(i64 session_id, Gfx::BitmapSequence bitmaps) = 0;
    virtual void did_fail_animation_decode(i64 session_id, String error_message) = 0;
};

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