#include <SFML/Copyright.hpp> // LICENSE AND COPYRIGHT (C) INFORMATION

////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include "SFML/Network/Socket.hpp"
#include "SFML/Network/SocketImpl.hpp"
#include "SFML/Network/SocketSelector.hpp"

#include "SFML/System/Err.hpp"

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


namespace sf
{
////////////////////////////////////////////////////////////
SocketSelector::~SocketSelector() = default;


////////////////////////////////////////////////////////////
struct SocketSelector::Impl
{
    priv::FDSet allSockets;    //!< Set containing all the sockets handles
    priv::FDSet socketsReady;  //!< Set containing handles of the sockets that are ready
    int         maxSocket{};   //!< Maximum socket handle
    int         socketCount{}; //!< Number of socket handles
};


////////////////////////////////////////////////////////////
SocketSelector::SocketSelector()
{
    clear();
}


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


////////////////////////////////////////////////////////////
SocketSelector& SocketSelector::operator=(const SocketSelector&) = default;


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


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


////////////////////////////////////////////////////////////
bool SocketSelector::add(Socket& socket)
{
    const SocketHandle handle = socket.getNativeHandle();

    if (handle == priv::SocketImpl::invalidSocket())
    {
        priv::err() << "Attempted to add invalid socket to socket selector";
        return false;
    }

#if defined(SFML_SYSTEM_WINDOWS)

    if (m_impl->socketCount >= priv::SocketImpl::getFDSetSize())
    {
        priv::err() << "The socket can't be added to the selector because the selector is full. This is a limitation "
                       "of your operating system's FD_SETSIZE setting.";

        return false;
    }

    if (priv::SocketImpl::fdIsSet(handle, m_impl->allSockets))
        return true; // Already added

    ++m_impl->socketCount;

#else

    if (handle >= priv::SocketImpl::getFDSetSize())
    {
        priv::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;
    }

    // SocketHandle is an int in POSIX
    if (m_impl->maxSocket < handle)
        m_impl->maxSocket = handle;

#endif

    priv::SocketImpl::fdSet(handle, m_impl->allSockets);
    return true;
}


////////////////////////////////////////////////////////////
bool SocketSelector::remove(Socket& socket)
{
    const SocketHandle handle = socket.getNativeHandle();

    if (handle == priv::SocketImpl::invalidSocket())
    {
        priv::err() << "Attempted to remove invalid socket from socket selector";
        return false;
    }

#if defined(SFML_SYSTEM_WINDOWS)

    if (!priv::SocketImpl::fdIsSet(handle, m_impl->allSockets))
        return true; // Already removed or never added

    --m_impl->socketCount;

#else

    if (handle >= priv::SocketImpl::getFDSetSize())
    {
        priv::err() << "The socket can't be removed from the selector because its ID is too high. This is a limitation "
                       "of your operating system's FD_SETSIZE setting.";

        return false;
    }

#endif

    priv::SocketImpl::fdClear(handle, m_impl->allSockets);
    priv::SocketImpl::fdClear(handle, m_impl->socketsReady);
    return true;
}


////////////////////////////////////////////////////////////
void SocketSelector::clear()
{
    priv::SocketImpl::fdZero(m_impl->allSockets);
    priv::SocketImpl::fdZero(m_impl->socketsReady);

    m_impl->maxSocket   = 0;
    m_impl->socketCount = 0;
}


////////////////////////////////////////////////////////////
bool SocketSelector::wait(Time timeout)
{
    // Initialize the set that will contain the sockets that are ready
    m_impl->socketsReady = m_impl->allSockets;

    // Wait until one of the sockets is ready for reading, or timeout is reached
    // The first parameter is ignored on Windows
    const int count = priv::SocketImpl::select(m_impl->maxSocket + 1,
                                               &m_impl->socketsReady,
                                               nullptr,
                                               nullptr,
                                               timeout.asMicroseconds());

    return count > 0;
}


////////////////////////////////////////////////////////////
bool SocketSelector::isReady(Socket& socket) const
{
    const SocketHandle handle = socket.getNativeHandle();

    if (handle == priv::SocketImpl::invalidSocket())
    {
        priv::err() << "Attempted to check readiness of invalid socket in socket selector";
        return false;
    }

#if !defined(SFML_SYSTEM_WINDOWS)

    if (handle >= priv::SocketImpl::getFDSetSize())
    {
        priv::err() << "The socket can't be checked for readiness in the selector because its ID is too high. This is "
                       "a limitation of your operating system's FD_SETSIZE setting.";

        return false;
    }

#endif

    return priv::SocketImpl::fdIsSet(handle, m_impl->socketsReady) != 0;
}

} // namespace sf
