#include "blitz/network/TCPListener.h"

#include <fcntl.h>

#ifndef _WIN32
#define closesocket close
#define SD_BOTH SHUT_RDWR
#define ioctlsocket ioctl
#endif

namespace blitz {
namespace network {

TCPListener::TCPListener() {}

TCPListener::~TCPListener() {
	Destroy();
}

bool TCPListener::Listen(uint16_t port, int maxConnections) {
	if ((m_Handle = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
		return false;

	struct sockaddr_in address;
	address.sin_family = AF_INET;
	address.sin_addr.s_addr = INADDR_ANY;
	address.sin_port = htons(port);

	if (::bind(m_Handle, reinterpret_cast<sockaddr*>(&address), sizeof(address)) < 0)
		return false;

	if (::listen(m_Handle, maxConnections) < 0)
		return false;

	socklen_t len = sizeof(address);
	if (::getsockname(m_Handle, reinterpret_cast<sockaddr*>(&address), &len) < 0)
		return false;

	m_Port = ntohs(address.sin_port);
	m_MaxConnections = maxConnections;

	return true;
}

bool TCPListener::Accept(TCPSocket& newSocket) {
	int addrlen = sizeof(newSocket.m_RemoteAddr);
	if ((newSocket.m_Handle =
				::accept(m_Handle, reinterpret_cast<sockaddr*>(&newSocket.m_RemoteAddr), reinterpret_cast<socklen_t*>(&addrlen))) < 0)
		return false;
	newSocket.SetStatus(TCPSocket::Status::Connected);
	newSocket.SetBlocking(false);
	return true;
}

void TCPListener::Destroy() {
	if (m_Handle < 0)
		::closesocket(m_Handle);
}

bool TCPListener::Close() {
	if (::shutdown(m_Handle, SD_BOTH) == 0)
		return true;
	return false;
}

bool TCPListener::SetBlocking(bool blocking) {
	unsigned long mode = !blocking;

	if (::ioctlsocket(m_Handle, FIONBIO, &mode) < 0) {
		return false;
	}
	return true;
}

}  // namespace network
}  // namespace blitz