#include "sock_channel.hpp"

#include <errno.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>

#include <iostream>
#include <string>

namespace udpsocket
{

std::string Channel::getRemoteAddress() const
{
    char tmp[INET_ADDRSTRLEN] = {0};
    inet_ntop(AF_INET6, &address.inAddr.sin6_addr, tmp, sizeof(tmp));
    return std::string(tmp);
}

std::tuple<int, buffer> Channel::read()
{
    int rc = 0;
    int readSize = 0;
    ssize_t readDataLen = 0;
    buffer outBuffer;

    if (ioctl(sockfd, FIONREAD, &readSize) < 0)
    {
        std::cerr << "Channel::Read : ioctl failed with errno = " << errno;
        rc = -errno;
        return std::make_tuple(rc, std::move(outBuffer));
    }

    outBuffer.resize(readSize);
    auto bufferSize = outBuffer.size();
    auto outputPtr = outBuffer.data();

    address.addrSize = static_cast<socklen_t>(sizeof(address.inAddr));

    do
    {
        readDataLen = recvfrom(sockfd,             // File Descriptor
                               outputPtr,          // Buffer
                               bufferSize,         // Bytes requested
                               0,                  // Flags
                               &address.sockAddr,  // Address
                               &address.addrSize); // Address Length

        if (readDataLen == 0) // Peer has performed an orderly shutdown
        {
            outBuffer.resize(0);
            rc = -1;
        }
        else if (readDataLen < 0) // Error
        {
            rc = -errno;
            std::cerr << "Channel::Read : Receive Error Fd[" << sockfd << "]"
                      << "errno = " << rc << "\n";
            outBuffer.resize(0);
        }
    } while ((readDataLen < 0) && (-(rc) == EINTR));

    // Resize the vector to the actual data read from the socket
    outBuffer.resize(readDataLen);
    return std::make_tuple(rc, std::move(outBuffer));
}

int Channel::write(buffer& inBuffer)
{
    int rc = 0;
    auto outputPtr = inBuffer.data();
    auto bufferSize = inBuffer.size();
    auto spuriousWakeup = false;
    ssize_t writeDataLen = 0;
    timeval varTimeout = timeout;

    fd_set writeSet;
    FD_ZERO(&writeSet);
    FD_SET(sockfd, &writeSet);

    do
    {
        spuriousWakeup = false;

        rc = select((sockfd + 1), nullptr, &writeSet, NULL, &varTimeout);

        if (rc > 0)
        {
            if (FD_ISSET(sockfd, &writeSet))
            {
                address.addrSize =
                    static_cast<socklen_t>(sizeof(address.inAddr));
                do
                {
                    writeDataLen =
                        sendto(sockfd,            // File Descriptor
                               outputPtr,         // Message
                               bufferSize,        // Length
                               MSG_NOSIGNAL,      // Flags
                               &address.sockAddr, // Destination Address
                               address.addrSize); // Address Length

                    if (writeDataLen < 0)
                    {
                        rc = -errno;
                        std::cerr
                            << "Channel::Write: Write failed with errno:" << rc
                            << "\n";
                    }
                    else if (static_cast<size_t>(writeDataLen) < bufferSize)
                    {
                        rc = -1;
                        std::cerr << "Channel::Write: Complete data not written"
                                     " to the socket\n";
                    }
                } while ((writeDataLen < 0) && (-(rc) == EINTR));
            }
            else
            {
                // Spurious wake up
                std::cerr << "Spurious wake up on select (writeset)\n";
                spuriousWakeup = true;
            }
        }
        else
        {
            if (rc == 0)
            {
                // Timed out
                rc = -1;
                std::cerr << "We timed out on select call (writeset)\n";
            }
            else
            {
                // Error
                rc = -errno;
                std::cerr << "select call (writeset) had an error : " << rc
                          << "\n";
            }
        }
    } while (spuriousWakeup);

    return rc;
}

} // namespace udpsocket