#pragma once
#include <luna/OwnedPtr.h>
#include <luna/Result.h>
#include <luna/StringView.h>

namespace os
{
    /**
     * @brief A local domain server, used to communicate between processes on the same machine.
     */
    class LocalServer
    {
      public:
        /**
         * @brief Create a new server object and bind it to a local address.
         *
         * @param path The path to use for the server socket.
         * @param blocking Whether the server should block if no connections are available when calling accept().
         * @return Result<OwnedPtr<LocalServer>> An error, or a new server object.
         */
        static Result<OwnedPtr<LocalServer>> create(StringView path, bool blocking);

        /**
         * @brief Activate the server and start listening for connections.
         *
         * @param backlog The number of unaccepted connections to keep.
         * @return Result<void> Whether the operation succeded.
         */
        Result<void> listen(int backlog);

        /**
         * @brief Return the underlying socket file descriptor used by this object.
         *
         * @return int The file descriptor.
         */
        int fd() const
        {
            return m_fd;
        }

        /**
         * @brief An interface to communicate with clients connected to a local server.
         */
        class Client
        {
          public:
            /**
             * @brief Read arbitrary data from the client. The call will block if there is no data and the parent server
             * object has not been created as non-blocking.
             *
             * @param buf The buffer to read data into.
             * @param length The maximum amount of bytes to read.
             * @return Result<usize> An error, or the number of bytes read.
             */
            Result<usize> recv(u8* buf, usize length);

            /**
             * @brief Read an object from the client. The call will block if there is no data and the parent server
             * object has not been created as non-blocking.
             *
             * @tparam T The type of the object.
             * @param out A reference to the object to read data into.
             * @return Result<void> Whether the operation succeded.
             */
            template <typename T> Result<void> recv_typed(T& out)
            {
                TRY(recv((u8*)&out, sizeof(T)));
                return {};
            }

            /**
             * @brief Send arbitrary data to the client.
             *
             * @param buf The buffer to send data from.
             * @param length The amount of bytes to send.
             * @return Result<usize> An error, or the number of bytes actually sent.
             */
            Result<usize> send(const u8* buf, usize length);

            /**
             * @brief Send an object to the client.
             *
             * @tparam T The type of the object.
             * @param out A reference to the object to send data from.
             * @return Result<void> Whether the operation succeded.
             */
            template <typename T> Result<void> send_typed(const T& out)
            {
                TRY(send((const u8*)&out, sizeof(T)));
                return {};
            }

            /**
             * @brief Disconnect from the attached client.
             *
             * This will make any further reads on the client return ECONNRESET, and will make this object invalid.
             */
            void disconnect();

            /**
             * @brief Return the underlying socket file descriptor used by this object.
             *
             * @return int The file descriptor.
             */
            int fd() const
            {
                return m_fd;
            }

            Client(Client&& other);
            Client(int fd);
            ~Client();

          private:
            int m_fd;
        };

        /**
         * @brief Accept a new incoming connection and return a handle to it. If there are no incoming connections,
         * accept() either blocks until there is one (if the object was created with blocking=true), or returns EAGAIN
         * (if the object was created with blocking=false).
         *
         * @return Result<Client> An error, or a handle to the new connection.
         */
        Result<Client> accept();

        ~LocalServer();

      private:
        int m_fd;
        bool m_blocking;
    };
}