////////////////////////////////////////////////////////////
//
// SFML - Simple and Fast Multimedia Library
// Copyright (C) 2007-2026 Laurent Gomila (laurent@sfml-dev.org)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
//    you must not claim that you wrote the original software.
//    If you use this software in a product, an acknowledgment
//    in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
//    and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/Config.hpp> // Include here to be able to check SFML_SYSTEM_WINDOWS
#if defined(SFML_SYSTEM_WINDOWS)
#include <SFML/System/Win32/WindowsHeader.hpp> // Include as early as possible to define _WIN32_WINNT before anyone else does
#endif
#include <SFML/Network/Socket.hpp>
#include <SFML/Network/SocketImpl.hpp>
#include <SFML/Network/SocketSelector.hpp>

#include <SFML/System/Err.hpp>

#if defined(SFML_SYSTEM_WINDOWS)
#define SFML_USE_EPOLL
#include <wepoll.h>
using epoll_t = HANDLE;
#define EPOLL_CLOEXEC 0
#elif defined(SFML_SYSTEM_LINUX) || defined(SFML_SYSTEM_ANDROID)
#define SFML_USE_EPOLL
#include <sys/epoll.h>
using epoll_t = int;
#define epoll_close close
#elif defined(SFML_SYSTEM_MACOS) || defined(SFML_SYSTEM_IOS) || defined(SFML_SYSTEM_FREEBSD) || \
    defined(SFML_SYSTEM_OPENBSD) || defined(SFML_SYSTEM_NETBSD)
#define SFML_USE_KQUEUE
#include <sys/event.h>
using kevent_t = struct kevent;
#else
// select()-based implementation is used if none of the above applies
#endif

#include <algorithm>
#include <memory>
#include <ostream>
#include <utility>
#include <vector>

#include <cassert>

#ifdef _MSC_VER
#pragma warning(disable : 4127) // "conditional expression is constant" generated by the FD_SET macro
#endif


namespace sf
{
////////////////////////////////////////////////////////////
struct SocketSelector::SocketSelectorImpl
{
    struct UserData
    {
        const Socket*                                    socket{};
        std::function<void(ReadinessType readinessType)> callback;
    };

#if defined(SFML_USE_EPOLL)
    SocketSelectorImpl() : epollHandle(epoll_create1(EPOLL_CLOEXEC))
    {
        if (!epollHandle)
        {
            err() << "Failed to create epoll handle" << std::endl;
            assert(false && "Failed to create epoll handle");
        }
    }

    ~SocketSelectorImpl()
    {
        if (epollHandle)
        {
            if (epoll_close(epollHandle) < 0)
                err() << "Failed to close epoll handle" << std::endl;
        }
    }

    static constexpr auto readMask  = EPOLLRDNORM | EPOLLRDBAND | EPOLLIN | EPOLLHUP | EPOLLERR;
    static constexpr auto writeMask = EPOLLWRBAND | EPOLLWRNORM | EPOLLOUT | EPOLLERR;

    bool add(const Socket*                                    socket,
             SocketHandle                                     handle,
             ReadinessType                                    readinessType,
             std::function<void(ReadinessType readinessType)> readyCallback)
    {
        epoll_event event{};

        if (readinessType & Receive)
            event.events |= readMask;
        if (readinessType & Send)
            event.events |= writeMask;

        if (const auto iter = std::find_if(sockets.begin(),
                                           sockets.end(),
                                           [socket, handle](const auto& entry)
                                           { return (entry.socket == socket) && (entry.handle == handle); });
            iter != sockets.end())
        {
            // Socket already added to epoll

            // Check if we just have to update the callback
            if (iter->readinessType == readinessType)
            {
                iter->userData->callback = std::move(readyCallback);
                return true;
            }

            event.data.ptr = iter->userData.get();

            if (epoll_ctl(epollHandle, EPOLL_CTL_MOD, handle, &event) < 0)
            {
                err() << "Failed to modify epoll socket events" << std::endl;
                return false;
            }

            iter->readinessType      = readinessType;
            iter->userData->callback = std::move(readyCallback);
        }
        else
        {
            // Socket not added to epoll yet
            auto userData  = std::make_unique<UserData>();
            event.data.ptr = userData.get();

            if (epoll_ctl(epollHandle, EPOLL_CTL_ADD, handle, &event) < 0)
            {
                err() << "Failed to add socket handle to epoll" << std::endl;
                return false;
            }

            auto& entry = sockets.emplace_back(SocketEntry{socket, handle, readinessType, std::move(userData)});
            entry.userData->socket   = socket;
            entry.userData->callback = std::move(readyCallback);
        }

        return true;
    }

    bool remove(const Socket* socket)
    {
        const auto iter = std::find_if(sockets.begin(),
                                       sockets.end(),
                                       [&](const auto& entry) { return entry.socket == socket; });
        if (iter != sockets.end())
        {
            // Only try to remove the handle from epoll if it is still valid i.e. the socket hasn't been closed yet
            if (socket->getNativeHandle() != priv::SocketImpl::invalidSocket())
            {
                if (epoll_ctl(epollHandle, EPOLL_CTL_DEL, socket->getNativeHandle(), nullptr) < 0)
                {
                    err() << "Failed to remove socket handle from epoll: " << errno << std::endl;
                    return false;
                }
            }
            sockets.erase(iter);
            return true;
        }
        return false;
    }

    void clear()
    {
        for (const auto& entry : sockets)
        {
            // Since epoll automatically removes registered fds when we close them treat ENOENT and EBADF as non-errors
            if ((epoll_ctl(epollHandle, EPOLL_CTL_DEL, entry.handle, nullptr) < 0) && (errno != ENOENT) && (errno != EBADF))
                err() << "Failed to remove socket handle from epoll" << std::endl;
        }
        sockets.clear();
    }

    bool wait(Time timeout)
    {
        events.resize(sockets.size());
        const auto result = epoll_wait(epollHandle,
                                       events.data(),
                                       static_cast<int>(events.size()),
                                       timeout != Time::Zero ? timeout.asMilliseconds() : -1);
        if (result > 0)
        {
            eventsCount = result;
            return true;
        }
        if (result < 0)
            err() << "Failed to wait on epoll" << std::endl;
        eventsCount = 0;
        return false;
    }

    bool isReady(const Socket* socket, ReadinessType readinessType) const
    {
        const auto end  = events.begin() + eventsCount;
        const auto iter = std::find_if(events.begin(),
                                       end,
                                       [&socket](const epoll_event& event)
                                       { return static_cast<const UserData*>(event.data.ptr)->socket == socket; });
        if (iter == end)
            return false;

        const auto canRead  = ((iter->events & readMask) != 0);
        const auto canWrite = ((iter->events & writeMask) != 0);

        auto result = false;
        if (readinessType & Receive)
            result |= canRead;
        if (readinessType & Send)
            result |= canWrite;
        return result;
    }

    void dispatchReadyCallbacks()
    {
        for (auto i = 0; i < eventsCount; ++i)
        {
            const auto& event    = events[static_cast<std::size_t>(i)];
            const auto& callback = static_cast<const UserData*>(event.data.ptr)->callback;
            if (!callback)
                continue;

            ReadinessType readinessType{};
            if ((event.events & readMask) != 0)
                readinessType |= Receive;
            if ((event.events & writeMask) != 0)
                readinessType |= Send;
            if (readinessType)
                callback(readinessType);
        }
    }

    epoll_t                  epollHandle;
    std::vector<epoll_event> events;
    int                      eventsCount = 0;
#elif defined(SFML_USE_KQUEUE)
    SocketSelectorImpl() : kqueueHandle(kqueue())
    {
        if (kqueueHandle == -1)
        {
            err() << "Failed to create kqueue handle" << std::endl;
            assert(false && "Failed to create kqueue handle");
        }
    }

    ~SocketSelectorImpl()
    {
        if (kqueueHandle != -1)
        {
            if (close(kqueueHandle) < 0)
                err() << "Failed to close kqueue handle" << std::endl;
        }
    }

    bool modifyEvents(SocketHandle handle, ReadinessType eventType, u_short flags, void* userData) const
    {
        const auto doModify = [&](short filter)
        {
            kevent_t event{};
            EV_SET(&event, handle, filter, flags, 0, 0, userData);
            // Since kqueue automatically removes registered fds when we close them
            // treat ENOENT and EBADF as non-errors when removing events
            if (const auto result = kevent(kqueueHandle, &event, 1, nullptr, 0, nullptr);
                (result < 0) && ((flags == EV_ADD) || ((errno != ENOENT) && (errno != EBADF))))
            {
                err() << "Failed to modify kqueue event" << std::endl;
                return false;
            }
            return true;
        };

        // kqueue doesn't support combined read+write events
        // We will have to add/delete separate events for both read and write
        if (eventType & Receive)
        {
            if (!doModify(EVFILT_READ))
                return false;
        }
        if (eventType & Send)
        {
            if (!doModify(EVFILT_WRITE))
                return false;
        }

        return true;
    }

    bool add(const Socket*                                    socket,
             SocketHandle                                     handle,
             ReadinessType                                    readinessType,
             std::function<void(ReadinessType readinessType)> readyCallback)
    {
        if (const auto iter = std::find_if(sockets.begin(),
                                           sockets.end(),
                                           [socket, handle](const auto& entry)
                                           { return (entry.socket == socket) && (entry.handle == handle); });
            iter != sockets.end())
        {
            // Socket already added to kqueue

            // Check if we just have to update the callback
            if (iter->readinessType == readinessType)
            {
                iter->userData->callback = std::move(readyCallback);
                return true;
            }

            // First remove existing event(s)
            if (!modifyEvents(iter->handle, iter->readinessType, EV_DELETE, iter->userData.get()))
                return false;

            // Re-add event(s) with new type
            if (!modifyEvents(iter->handle, readinessType, EV_ADD, iter->userData.get()))
                return false;

            iter->readinessType      = readinessType;
            iter->userData->callback = std::move(readyCallback);
        }
        else
        {
            // Socket not added to kqueue yet
            auto userData = std::make_unique<UserData>(UserData{socket, std::move(readyCallback)});
            if (!modifyEvents(handle, readinessType, EV_ADD, userData.get()))
                return false;

            sockets.emplace_back(SocketEntry{socket, handle, readinessType, std::move(userData)});
        }

        return true;
    }

    bool remove(const Socket* socket)
    {
        const auto iter = std::find_if(sockets.begin(),
                                       sockets.end(),
                                       [&](const auto& entry) { return entry.socket == socket; });
        if (iter != sockets.end())
        {
            // Only try to remove the handle from kqueue if it is still valid i.e. the socket hasn't been closed yet
            if (socket->getNativeHandle() != priv::SocketImpl::invalidSocket())
            {
                if (!modifyEvents(socket->getNativeHandle(), iter->readinessType, EV_DELETE, iter->userData.get()))
                    return false;
            }

            sockets.erase(iter);
            return true;
        }

        return false;
    }

    void clear()
    {
        for (const auto& entry : sockets)
            modifyEvents(entry.handle, entry.readinessType, EV_DELETE, entry.userData.get());
        sockets.clear();
    }

    bool wait(Time timeout)
    {
        // Setup the timeout
        timespec time{};
        time.tv_sec  = static_cast<decltype(time.tv_sec)>(timeout.asMicroseconds() / 1'000'000);
        time.tv_nsec = static_cast<decltype(time.tv_nsec)>((timeout.asMicroseconds() % 1'000'000) * 1'000);

        events.resize(sockets.size());
        if (auto result = kevent(kqueueHandle,
                                 nullptr,
                                 0,
                                 events.data(),
                                 static_cast<int>(events.size()),
                                 timeout != Time::Zero ? &time : nullptr);
            result > 0)
        {
            eventsCount = result;
            return true;
        }
        eventsCount = 0;
        return false;
    }

    bool isReady(const Socket* socket, ReadinessType readinessType) const
    {
        const auto end       = events.begin() + eventsCount;
        const auto iterRead  = std::find_if(events.begin(),
                                           end,
                                           [&socket](const struct kevent& event) {
                                               return (static_cast<const UserData*>(event.udata)->socket == socket) &&
                                                      ((event.filter & EVFILT_READ) != 0);
                                           });
        const auto iterWrite = std::find_if(events.begin(),
                                            end,
                                            [&socket](const struct kevent& event) {
                                                return (static_cast<const UserData*>(event.udata)->socket == socket) &&
                                                       ((event.filter & EVFILT_WRITE) != 0);
                                            });

        auto result = false;
        if (readinessType & Receive)
            result |= (iterRead != end);
        if (readinessType & Send)
            result |= (iterWrite != end);
        return result;
    }

    void dispatchReadyCallbacks()
    {
        for (auto i = 0; i < eventsCount; ++i)
        {
            const auto& event    = events[static_cast<std::size_t>(i)];
            const auto& callback = static_cast<const UserData*>(event.udata)->callback;
            if (!callback)
                continue;

            // kqueue doesn't support combined read+write events
            // If a socket is ready for receiving and sending the callback will be
            // called once with Receive and once with Send
            if (event.filter == EVFILT_READ)
            {
                callback(Receive);
            }
            else if (event.filter == EVFILT_WRITE)
            {
                callback(Send);
            }
        }
    }

    int                   kqueueHandle;
    std::vector<kevent_t> events;
    int                   eventsCount = 0;
#else
    SocketSelectorImpl() = default;

    bool add(const Socket*                                    socket,
             SocketHandle                                     handle,
             ReadinessType                                    readinessType,
             std::function<void(ReadinessType readinessType)> readyCallback)
    {
        if (const auto iter = std::find_if(sockets.begin(),
                                           sockets.end(),
                                           [socket, handle](const auto& entry)
                                           { return (entry.socket == socket) && (entry.handle == handle); });
            iter != sockets.end())
        {
            // Socket already added to fd set

            // Check if we just have to update the callback
            if (iter->readinessType == readinessType)
            {
                iter->userData->callback = std::move(readyCallback);
                return true;
            }

            FD_CLR(handle, &allSocketsRead);
            FD_CLR(handle, &allSocketsWrite);

            if (readinessType & Receive)
                FD_SET(handle, &allSocketsRead);
            if (readinessType & Send)
                FD_SET(handle, &allSocketsWrite);

            iter->readinessType      = readinessType;
            iter->userData->callback = std::move(readyCallback);
        }
        else
        {
            if (handle >= FD_SETSIZE)
            {
                err() << "The socket can't be added to the selector because its "
                      << "ID is too high. This is a limitation of your operating "
                      << "system's FD_SETSIZE setting.";
                return false;
            }

            // Socket not added to fd set yet
            sockets.emplace_back(SocketEntry{socket,
                                             handle,
                                             readinessType,
                                             std::make_unique<UserData>(UserData{socket, std::move(readyCallback)})});

            // SocketHandle is an int in POSIX
            maxSocket = std::max(maxSocket, handle);

            if (readinessType & Receive)
                FD_SET(handle, &allSocketsRead);
            if (readinessType & Send)
                FD_SET(handle, &allSocketsWrite);
        }

        return true;
    }

    bool remove(const Socket* socket)
    {
        const auto iter = std::find_if(sockets.begin(),
                                       sockets.end(),
                                       [&](const auto& entry) { return entry.socket == socket; });
        if (iter != sockets.end())
        {
            FD_CLR(iter->handle, &allSocketsRead);
            FD_CLR(iter->handle, &allSocketsWrite);
            FD_CLR(iter->handle, &socketsReadable);
            FD_CLR(iter->handle, &socketsWritable);

            sockets.erase(iter);

            return true;
        }

        return false;
    }

    void clear()
    {
        FD_ZERO(&allSocketsRead);
        FD_ZERO(&allSocketsWrite);
        FD_ZERO(&socketsReadable);
        FD_ZERO(&socketsWritable);

        maxSocket = 0;

        sockets.clear();
    }

    bool wait(Time timeout)
    {
        // Setup the timeout
        timeval time{};
        time.tv_sec  = static_cast<long>(timeout.asMicroseconds() / 1'000'000);
        time.tv_usec = static_cast<int>(timeout.asMicroseconds() % 1'000'000);

        // Initialize the set that will contain the sockets that are ready
        socketsReadable = allSocketsRead;
        socketsWritable = allSocketsWrite;

        // Wait until one of the sockets is ready, or timeout is reached
        // The first parameter is ignored on Windows
        const int count = select(maxSocket + 1, &socketsReadable, &socketsWritable, nullptr, timeout != Time::Zero ? &time : nullptr);

        return count > 0;
    }

    bool isReady(const Socket* socket, ReadinessType readinessType) const
    {
        const SocketHandle handle = socket->getNativeHandle();

        if (handle >= FD_SETSIZE)
            return false;

        auto result = false;
        if (readinessType & Receive)
            result |= (FD_ISSET(handle, &socketsReadable) != 0);
        if (readinessType & Send)
            result |= (FD_ISSET(handle, &socketsWritable) != 0);
        return result;
    }

    void dispatchReadyCallbacks()
    {
        for (const auto& socket : sockets)
        {
            const auto& callback = socket.userData->callback;
            if (!callback)
                continue;

            ReadinessType readinessType{};
            if (FD_ISSET(socket.handle, &socketsReadable) != 0)
                readinessType |= Receive;
            if (FD_ISSET(socket.handle, &socketsWritable) != 0)
                readinessType |= Send;
            if (readinessType)
                callback(readinessType);
        }
    }

    fd_set allSocketsRead{};  //!< Set containing all the sockets handles (read)
    fd_set allSocketsWrite{}; //!< Set containing all the sockets handles (write)
    fd_set socketsReadable{}; //!< Set containing handles of the sockets that are ready (read)
    fd_set socketsWritable{}; //!< Set containing handles of the sockets that are ready (write)
    int    maxSocket{};       //!< Maximum socket handle
#endif

    // Copy constructor
    SocketSelectorImpl(const SocketSelectorImpl& other) : SocketSelectorImpl()
    {
        // Add all sockets to the new selector
        for (const auto& entry : other.sockets)
            add(entry.socket, entry.handle, entry.readinessType, entry.userData->callback);
    }

    struct SocketEntry
    {
        const Socket* socket{};
        SocketHandle  handle{};
        ReadinessType readinessType = Receive;

        // User data has to sit behind another indirection because it has to
        // have a stable memory address that we can pass to epoll/kqueue
        std::unique_ptr<UserData> userData;
    };
    std::vector<SocketEntry> sockets;
};


////////////////////////////////////////////////////////////
SocketSelector::SocketSelector() : m_impl(std::make_unique<SocketSelectorImpl>())
{
    clear();
}


////////////////////////////////////////////////////////////
SocketSelector::~SocketSelector() = default;


////////////////////////////////////////////////////////////
SocketSelector::SocketSelector(const SocketSelector& copy) : m_impl(std::make_unique<SocketSelectorImpl>(*copy.m_impl))
{
}


////////////////////////////////////////////////////////////
SocketSelector& SocketSelector::operator=(const SocketSelector& right)
{
    SocketSelector temp(right);
    std::swap(m_impl, temp.m_impl);
    return *this;
}


////////////////////////////////////////////////////////////
SocketSelector::SocketSelector(SocketSelector&&) noexcept = default;


////////////////////////////////////////////////////////////]
SocketSelector& SocketSelector::operator=(SocketSelector&&) noexcept = default;


////////////////////////////////////////////////////////////
bool SocketSelector::add(const Socket&                                    socket,
                         ReadinessType                                    readinessType,
                         std::function<void(ReadinessType readinessType)> readyCallback)
{
    if (const SocketHandle handle = socket.getNativeHandle(); handle != priv::SocketImpl::invalidSocket())
        return m_impl->add(&socket, handle, readinessType, std::move(readyCallback));

    return false;
}


////////////////////////////////////////////////////////////
bool SocketSelector::remove(const Socket& socket)
{
    return m_impl->remove(&socket);
}


////////////////////////////////////////////////////////////
void SocketSelector::clear()
{
    m_impl->clear();
}


////////////////////////////////////////////////////////////
bool SocketSelector::wait(Time timeout)
{
    return m_impl->wait(timeout);
}


////////////////////////////////////////////////////////////
bool SocketSelector::isReady(const Socket& socket, ReadinessType readinessType) const
{
    return m_impl->isReady(&socket, readinessType);
}


////////////////////////////////////////////////////////////
void SocketSelector::dispatchReadyCallbacks()
{
    m_impl->dispatchReadyCallbacks();
}

} // namespace sf
