#include "common/network/udp_socket.hpp"

#if defined(__unix__)
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#endif

#include <cstring>

#include "common/network/network_packet.hpp"

using namespace polygun::network;

UDPSocket::UDPSocket() :
	m_fd(socket(AF_INET, SOCK_DGRAM, 0)),
	m_addr()
{
	if(m_fd==-1)
		throw std::string("Failed to open UDPSocket");
	memset((void*)&m_addr, 0, sizeof(m_addr));
}

UDPSocket::~UDPSocket() {
	close();
}

void UDPSocket::connect(const std::string& ip, unsigned short port) {
	m_addr.sin_family = AF_INET;
	m_addr.sin_port = htons(port);
	m_addr.sin_addr.s_addr = inet_addr(ip.c_str());
}

void UDPSocket::bind(unsigned short port) {
	m_addr.sin_family = AF_INET;
	m_addr.sin_addr.s_addr = INADDR_ANY;
	m_addr.sin_port = port;
	if(::bind(m_fd, (const struct sockaddr*)&m_addr, sizeof(m_addr))<0)
		throw std::string("Failed to bind to address polygun://localhost:"+std::to_string(port));
}

bool UDPSocket::send(const UDPSocket& target, const NetworkPacket& packet) const {
	return sendto(m_fd, packet.get_data().data(), packet.get_data().size(), MSG_CONFIRM, (const struct sockaddr*)&target.m_addr, sizeof(target.m_addr))>0;
}

bool UDPSocket::recv(const UDPSocket& from, NetworkPacket& packet) {
	uint8_t data[65536];
	socklen_t len=sizeof(from.m_addr);
	ssize_t received = recvfrom(m_fd, data, 65536, MSG_WAITALL, (struct sockaddr*)&from.m_addr, &len);
	if(received>0)
		return packet.load(data, received);
	return false;
}

bool UDPSocket::has_incoming_data() const {
	fd_set fds;
	struct timeval tm;

	FD_ZERO(&fds);
	FD_SET(0, &fds);

	tm.tv_sec = 0;
	tm.tv_usec = 100;

	int result = select(1, &fds, 0, 0, &tm);

	if(result==-1)
		return false;
	return result && FD_ISSET(0, &fds);
}

void UDPSocket::close() {
	::close(m_fd);
}
