#ifndef _UDPSOCKET_HPP
#define _UDPSOCKET_HPP

#include "sockaddr.hpp"

namespace netlib
{


/**
 * @brief The UdpSocket can be used to receive UDP packets from and send UDP
 * packets to any target address.
 */
class UdpSocket
{
private:
    /**
     * @brief The local SockAddr that is used to bind to and listen.
     */
    SockAddr local;

    /**
     * @brief The filedescriptor of the current socket. If this is 0, the socket
     * is closed (unbound).
     */
    int sockfd;

    /**
     * @brief The length in bytes of the raw sockaddr for either sockaddr_in 
     * (Ipv4) or sockaddr_in6 (Ipv6) depending on what address type is in use.
     */
    socklen_t raw_socklen;

    /**
     * @brief The address family (address type) should either be AF_INET for 
     * Ipv4 or AF_INET6 for Ipv6.
     */
    int address_family;

    /**
     * @brief If set to true, the socket is automatically closed on destruction
     */
    bool autoclose = true;

public:

    /**
     * @brief Create a UdpSocket that will be bound to Ipv4 0.0.0.0 and the 
     * port 0. This will use a random free port and is useful for sending 
     * only.
     */
    UdpSocket();

    /**
     * @brief Create a UdpSocket that will be bound to the local address and 
     * port that is specified in the SockAddr. 
     * If the socket is only used to send UDP packets, the "any" address 
     * (0.0.0.0 for Ipv4, ::0 for Ipv6) should be used with the port 0. That 
     * will use a random free port.
     * If the socket is used to receive packets, the right port must be used.
     * 
     * @param local The SockAddr that will be listened to.
     */
    UdpSocket(SockAddr local);

    /**
     * @brief Same as UdpSocket(SockAddr) and the parameters are passed to 
     * SockAddr constructor.
     * 
     * @see SockAddr
     */
    UdpSocket(IpAddr localAddress, uint16_t port);

    /**
     * @brief Same as UdpSocket(SockAddr) and the parameters are passed to 
     * SockAddr constructor.
     * 
     * @see SockAddr
     */
    UdpSocket(const std::string &localAddress, uint16_t port);

    /**
     * @brief Same as UdpSocket(SockAddr) and the parameters are passed to 
     * SockAddr constructor.
     * 
     * @see SockAddr
     */
    UdpSocket(const std::string &localAddressPort);

    /**
     * @brief The socket is automatically closed when the UdpSocket is 
     * destroyed.
     */
    ~UdpSocket();

    UdpSocket(UdpSocket &&other);

    UdpSocket& operator=(UdpSocket &&other);

    /**
     * @brief Copying UdpSocket is not allowed. See clone() for explicit copies.
     */
    UdpSocket(const UdpSocket &other) = delete;

    /**
     * @brief Copying UdpSocket is not allowed. See clone() for explicit copies.
     */
    UdpSocket& operator=(const UdpSocket &other) = delete;

    /**
     * @brief Create the socket and bind it to the socket address specified in 
     * the constructor.
     * This has to be called before the socket can be used to send or receive 
     * data.
     */
    void bind();

    /**
     * @brief Send a number of len bytes as UDP packet to the provided remote 
     * socket address.
     * 
     * @param remote The target address and port to which the UDP packet will 
     * be sent.
     * @param data Pointer to at least len bytes that will be sent as UDP packet.
     * @param len The number of bytes that will be sent.
     *  
     * @return The number of bytes that were actually sent.
     */
    ssize_t sendTo(const SockAddr &remote, const void *data, size_t len);

    /**
     * @brief Same as sendTo(SockAddr, data, len) but the SockAddr is created 
     * from the provided parameters.
     * 
     * @see SockAddr
     */
    ssize_t sendTo(const std::string &remoteAddr, uint16_t port, const void *data, size_t len);

    /**
     * @brief Same as sendTo(SockAddr, data, len) but the SockAddr is created 
     * from the provided parameters.
     * 
     * @see SockAddr
     */
    ssize_t sendTo(const std::string &remoteAddrPort, const void *data, size_t len);

    /**
     * @brief Receive a UDP packet and copy a maximum number of len bytes from 
     * the packet payload into data. Store the senders origin socket address 
     * into remote.
     * 
     * @param data Pointer to at least len bytes of data in which the payload 
     * will be copied.
     * @param len The maximum number of bytes that can be copied into data.
     * @param remote A reference to a SockAddr that is used to store the origin 
     * of the UDP packet.
     * 
     * @return The number of bytes that were actually copied.
     */
    ssize_t receive(void *data, size_t len, SockAddr &remote);

    /**
     * @brief Receive a UDP packet and copy a maximum number of len bytes from 
     * the packet payload into data. The packets origin is not stored.
     * 
     * @param data Pointer to at least len bytes of data in which the payload 
     * will be copied.
     * @param len The maximum number of bytes that can be copied into data.
     * 
     * @return The number of bytes that were actually copied.
     */
    ssize_t receive(void *data, size_t len);

    /**
     * @brief Receive a UDP packet and copy a maximum number of len bytes from 
     * the packet payload into data. Store the senders origin socket address 
     * into remote. If the timeout occurs, 0 is returned. 
     * 
     * @param data Pointer to at least len bytes of data in which the payload 
     * will be copied.
     * @param len The maximum number of bytes that can be copied into data.
     * @param remote A reference to a SockAddr that is used to store the origin 
     * of the UDP packet.
     * @param timeoutMs The number of milliseconds before a timeout occurs. 
     * 
     * @return The number of bytes that were actually copied, or 0 if a timeout
     * occured.
     */
    ssize_t receiveTimeout(void *data, size_t len, SockAddr &remote, int timeoutMs);

    /**
     * @brief Receive a UDP packet and copy a maximum number of len bytes from 
     * the packet payload into data. The packets origin is not stored. If the 
     * timeout occurs, 0 is returned. 
     * 
     * @param data Pointer to at least len bytes of data in which the payload 
     * will be copied.
     * @param len The maximum number of bytes that can be copied into data.
     * @param timeoutMs The number of milliseconds before a timeout occurs. 
     * 
     * @return The number of bytes that were actually copied, or 0 if a timeout
     * occured.
     */
    ssize_t receiveTimeout(void *data, size_t len, int timeoutMs);

    /**
     * @brief Check if the socket is closed or open. Open in this case means 
     * bound and ready to send / receive.
     * 
     * @return True if the socket was not yet opened, or if it has been closed.
     */
    bool isClosed() const;

    /**
     * @brief Close the listening socket. After this, the UdpSocket can no 
     * longer be used to send or receive packets without calling bind again.
     */
    void close();

    /**
     * @brief Set the behavior for when the UdpSocket is destroyed. If autoclose
     * is enabled, the socket is closed on destruct. If autoclose is disabled, the
     * socket will not be closed automatically.
     * 
     * @param autoclose Enable or disable the autoclose functionality.
     */
    void setAutoclose(bool autoclose);

    /**
     * @brief Create a clone of this socket wrapper object. The clone and the 
     * original will share the same underlying socket and file descriptor. 
     * If one of the instances closes the socket, the socket will be closed 
     * for both. The other instance will not be notified about this, but instead
     * socket operations will just fail. Due to this, it might be a good idea to  
     * disabel autoclose and manually close the socket.
     * 
     * @return A clone of this UdpSocket that shares the same underlying socket.
     */
    UdpSocket clone();

};


} // namespace netlib

#endif // _UDPSOCKET_HPP