/* Blinker
   Copyright 2011-2019 Stefan Schuermans <stefan@blinkenarea.org>
   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
   a blinkenarea.org project */

#include <winsock2.h>
#include <string>

#include "Io.h"
#include "Udp4Addr.h"
#include "Udp4Sock.h"

namespace Blinker {

/// constructor
Udp4Sock::Udp4Sock()
{
  // create socket
  m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if (m_socket == INVALID_SOCKET)
    return;

  // switch to nonblocking mode
  u_long nonblock = 1;
  if (ioctlsocket(m_socket, FIONBIO, &nonblock) != 0) {
    closesocket(m_socket);
    m_socket = INVALID_SOCKET;
    return;
  }

  // enable address re-use
  BOOL opt = TRUE;
  if (setsockopt(m_socket, SOL_SOCKET, SO_REUSEADDR,
                 (char *)&opt, sizeof(opt)) != 0) {
    closesocket(m_socket);
    m_socket = INVALID_SOCKET;
    return;
  }
}

/// destructor
Udp4Sock::~Udp4Sock()
{
  // exit if not initialized
  if (m_socket == INVALID_SOCKET)
    return;

  // shutdown and close socket
  shutdown(m_socket, SD_BOTH);
  closesocket(m_socket);
  m_socket = INVALID_SOCKET;
}

/**
 * @brief get address the socket is bound to
 * @param[out] addr local addess the socket is bound to
 * @return if local address could be obtained
 */
bool Udp4Sock::getAddr(Udp4Addr &addr)
{
  struct sockaddr * sockaddr;
  int socklen;

  // exit with error if not initialized
  if (m_socket == INVALID_SOCKET)
    return false;

  // get local name of socket
  sockaddr = (struct sockaddr *)&addr.m_addr;
  socklen = sizeof(addr.m_addr);
  if (getsockname(m_socket, sockaddr, &socklen) != 0)
    return false;
  return socklen <= (int)sizeof(addr.m_addr); // check if not truncated
}

/**
 * @brief bind socket
 * @param[in] addr local address to bind to
 * @return if binding succeeded
 */
bool Udp4Sock::bind(const Udp4Addr &addr)

{
  const struct sockaddr * sockaddr;
  int socklen;

  // exit with error if not initialized
  if (m_socket == INVALID_SOCKET)
    return false;

  // bind
  sockaddr = (const struct sockaddr *)&addr.m_addr;
  socklen = sizeof(addr.m_addr);
  return ::bind(m_socket, sockaddr, socklen) == 0;
}

/**
 * @brief send to socket
 * @param[in] data data to send in message
 * @param[in] addr address the message should be sent to
 * @return if the message could be sent
 */
bool Udp4Sock::send(const std::string &data, const Udp4Addr &addr)
{
  const struct sockaddr * sockaddr;
  int socklen;
  ssize_t len;

  // exit with error if not initialized
  if (m_socket == INVALID_SOCKET)
    return false;

  // send message
  sockaddr = (const struct sockaddr *)&addr.m_addr;
  socklen = sizeof(addr.m_addr);
  len = sendto(m_socket, data.c_str(), data.size(), 0, sockaddr, socklen);
  return len == (ssize_t)data.size();
}

/**
 * @brief receive from socket
 * @param[out] data data in received message
 * @param[out] addr address the message has been received from
 * @return if a message has been received
 */
bool Udp4Sock::recv(std::string &data, Udp4Addr &addr)
{
  char buf[65536];
  struct sockaddr * sockaddr;
  int socklen;
  ssize_t len;

  // exit with error if not initialized
  if (m_socket == INVALID_SOCKET)
    return false;

  // receive message
  sockaddr = (struct sockaddr *)&addr.m_addr;
  socklen = sizeof(addr.m_addr);
  len = recvfrom(m_socket, buf, sizeof(buf), 0, sockaddr, &socklen);
  if (len < 0 || socklen > (int)sizeof(addr.m_addr)) // error or truncated
    return false;
  data.assign(buf, len);
  return true;
}

} // namespace Blinker

