/**
 * @file IPC.h
 * @author apio (cloudapio.eu)
 * @brief Inter-process communication primitives.
 *
 * @copyright Copyright (c) 2023, the Luna authors.
 *
 */

#pragma once
#include <os/LocalClient.h>
#include <os/LocalServer.h>

#define IPC_ENUM_SERVER(name) __##name##_SERVER_ERROR = 0
#define IPC_ENUM_CLIENT(name) __##name##_CLIENT_ERROR = 0

/**
 * @brief Called to handle IPC events (client-side).
 *
 * @param conn The connection object being used.
 * @param id The ID of the message.
 * @return Result<void> Whether the operation succeded.
 */
extern Result<void> handle_ipc_client_event(os::LocalClient& conn, u8 id);

/**
 * @brief Called to handle IPC events (server-side).
 *
 * @param conn The connection object being used.
 * @param id The ID of the message.
 * @return Result<void> Whether the operation succeded.
 */
extern Result<void> handle_ipc_server_event(os::LocalServer::Client& conn, u8 id);

namespace os
{
    namespace IPC
    {
        static constexpr usize IPC_STRING_LENGTH = 256;

#define IPC_STRING(name) char name[os::IPC::IPC_STRING_LENGTH];
#define COPY_IPC_STRING(name)                                                                                          \
    TRY(String::from_string_view(StringView::from_fixed_size_cstring(name, os::IPC::IPC_STRING_LENGTH)))
#define SET_IPC_STRING(name, value) strlcpy(name, value, os::IPC::IPC_STRING_LENGTH)

        /**
         * @brief Sends an IPC message without waiting for a reply.
         *
         * @tparam Client The type of the client interface being used to communicate.
         * @tparam T The type of the message.
         * @param client The connection object being used to communicate.
         * @param message The IPC message.
         * @return Result<void> Whether the operation succeded.
         */
        template <typename Client, typename T> Result<void> send_async(Client& client, const T& message)
        {
            u8 id = T::ID;
            TRY(client.send_typed(id));
            TRY(client.send_typed(message));
            return {};
        }

        /**
         * @brief Sends an error result to the IPC connection, indicating that an operation could not be performed.
         *
         * @tparam Client The type of the client interface being used to communicate.
         * @param client The connection object being used to communicate.
         * @param error The error code.
         * @return Result<void> Whether the operation succeded.
         */
        template <typename Client> Result<void> send_error(Client& client, int error)
        {
            u8 id = 0;
            TRY(client.send_typed(id));
            TRY(client.send_typed(error));
            return {};
        }

        /**
         * @brief Sends an IPC message and waits for a reply (client-only).
         *
         * @tparam ResponseType The type of the response.
         * @tparam T The type of the message.
         * @param client The connection object being used to communicate.
         * @param message The IPC message.
         * @param handler The function used to handle messages that do not match the reply.
         * @return Result<ResponseType> An error, or the response.
         */
        template <typename ResponseType, typename T>
        Result<ResponseType> send_sync(os::LocalClient& client, const T& message,
                                       decltype(handle_ipc_client_event) handler = handle_ipc_client_event)
        {
            u8 id = T::ID;
            TRY(client.send_typed(id));
            TRY(client.send_typed(message));

            // We allow receiving 5 messages of different types, but if those have passed and we still don't have a
            // reply, fail with ENOMSG.
            int max_other_messages = 5;

            while (max_other_messages)
            {
                u8 response_id;
                auto rc = client.recv_typed(response_id);
                if (rc.has_error() && (rc.error() == EAGAIN || rc.error() == EINTR)) continue;

                if (response_id == 0) // Error result
                {
                    while (1)
                    {
                        int code;
                        rc = client.recv_typed(code);
                        if (rc.has_error() && (rc.error() == EAGAIN || rc.error() == EINTR)) continue;
                        return err(code);
                    }
                }

                if (response_id != ResponseType::ID)
                {
                    TRY(handler(client, response_id));
                    max_other_messages--;
                    continue;
                }

                while (1)
                {
                    ResponseType response;
                    rc = client.recv_typed(response);
                    if (rc.has_error() && (rc.error() == EAGAIN || rc.error() == EINTR)) continue;
                    return response;
                }
            }

            return err(ENOMSG);
        }

        /**
         * @brief Check for new IPC messages on a connection and handle them appropriately.
         *
         * @param client The client connection.
         * @param handler The function used to handle messages.
         * @return Result<void> Whether the operation succeded.
         */
        Result<void> check_for_messages(os::LocalClient& client,
                                        decltype(handle_ipc_client_event) handler = handle_ipc_client_event);

        /**
         * @brief Check for new IPC messages on a connection and handle them appropriately.
         *
         * @param server The server connection.
         * @param handler The function used to handle messages.
         * @return Result<void> Whether the operation succeded.
         */
        Result<void> check_for_messages(os::LocalServer::Client& server,
                                        decltype(handle_ipc_server_event) handler = handle_ipc_server_event);
    }
}