#include "pch.h"
#include "Socket.h"

#pragma comment(lib, "WS2_32.lib")

using namespace TwitchInGames;

namespace {
	bool isInitialized;

	inline void ThrowIfError(int result) {
		if(result) {
			throw TwitchException(HRESULT_FROM_WIN32(result));
		}
	}

	void CheckInitialization() {
		if(!isInitialized) {
			WSADATA wsaData;
			ThrowIfError(WSAStartup(MAKEWORD(2, 2), &wsaData));
			isInitialized= true;
		}
	}
}

Socket::Socket() {
	CheckInitialization();
	value= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(value == INVALID_SOCKET) {
		throw TwitchException(HRESULT_FROM_WIN32(WSAGetLastError()));
	}
}

Socket::~Socket() {
	closesocket(value);
}

void Socket::Connect(string_t hostName, unsigned short port) {
	sockaddr_in address{};
	if(InetPton(AF_INET, hostName, &address.sin_addr) <= 0) {
		ADDRINFOT hints{};
		hints.ai_family= AF_INET;
		hints.ai_protocol= IPPROTO_TCP;
		hints.ai_socktype= SOCK_STREAM;
		ADDRINFOT* addresses;
		ThrowIfError(GetAddrInfo(hostName, nullptr, &hints, &addresses));
		ADDRINFOT const* p;
		for(p= addresses; p != nullptr; p= p->ai_next) {
			if(p->ai_family == AF_INET && p->ai_protocol == IPPROTO_TCP && p->ai_socktype == SOCK_STREAM) {
				address.sin_addr= reinterpret_cast<sockaddr_in const*>(p->ai_addr)->sin_addr;
				break;
			}
		}
		FreeAddrInfo(addresses);
		if(p == nullptr) {
			throw TwitchException(E_INVALIDARG);
		}
	}
	address.sin_family= AF_INET;
	address.sin_port= htons(port);
	ThrowIfError(connect(value, reinterpret_cast<sockaddr const*>(&address), sizeof(address)));
}

int Socket::Receive(void* buffer, size_t size) noexcept {
	int const result= recv(value, static_cast<char*>(buffer), static_cast<int>(size), 0);
	return result < 0 ? HRESULT_FROM_WIN32(WSAGetLastError()) : result;
}

int Socket::Send(void const* buffer, size_t size) noexcept {
	int const result= send(value, static_cast<char const*>(buffer), static_cast<int>(size), 0);
	return result < 0 ? HRESULT_FROM_WIN32(WSAGetLastError()) : result;
}

void Socket::Shutdown() noexcept {
	shutdown(value, SD_BOTH);
}
