/** 
 @file host.c
 @brief ENet host management functions
*/
#define ENET_BUILDING_LIB 1
#include <string.h>
#include <time.h>
#include "enet/enet.h"

/** @defgroup host ENet host functions
    @{
*/

/** Creates a host for communicating to peers.  

    @param address   the address at which other peers may connect to this host.  If NULL, then no peers may connect to the host.
    @param peerCount the maximum number of peers that should be allocated for the host.
    @param channelLimit the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT
    @param incomingBandwidth downstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth.
    @param outgoingBandwidth upstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth.

    @returns the host on success and NULL on failure

    @remarks ENet will strategically drop packets on specific sides of a connection between hosts
    to ensure the host's bandwidth is not overwhelmed.  The bandwidth parameters also determine
    the window size of a connection which limits the amount of reliable packets that may be in transit
    at any given time.
*/
ENetHost *
enet_host_create(const ENetAddress *address, size_t peerCount, size_t channelLimit, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth)
{
	ENetHost *host;
	ENetPeer *currentPeer;

	if (peerCount > ENET_PROTOCOL_MAXIMUM_PEER_ID)
		return NULL;

	host = (ENetHost *)enet_malloc(sizeof(ENetHost));
	if (host == NULL)
		return NULL;
	memset(host, 0, sizeof(ENetHost));

	host->peers = (ENetPeer *)enet_malloc(peerCount * sizeof(ENetPeer));
	if (host->peers == NULL)
	{
		enet_free(host);

		return NULL;
	}
	memset(host->peers, 0, peerCount * sizeof(ENetPeer));

	host->socket = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
	if (host->socket == ENET_SOCKET_NULL || (address != NULL && enet_socket_bind(host->socket, address) < 0))
	{
		if (host->socket != ENET_SOCKET_NULL)
			enet_socket_destroy(host->socket);

		enet_free(host->peers);
		enet_free(host);

		return NULL;
	}

	enet_socket_set_option(host->socket, ENET_SOCKOPT_NONBLOCK, 1);
	enet_socket_set_option(host->socket, ENET_SOCKOPT_BROADCAST, 1);
	enet_socket_set_option(host->socket, ENET_SOCKOPT_RCVBUF, ENET_HOST_RECEIVE_BUFFER_SIZE);
	enet_socket_set_option(host->socket, ENET_SOCKOPT_SNDBUF, ENET_HOST_SEND_BUFFER_SIZE);

	if (address != NULL)
		host->address = *address;

	if (!channelLimit || channelLimit > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT)
		channelLimit = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT;
	else if (channelLimit < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT)
		channelLimit = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT;

	host->randomSeed = (enet_uint32)(size_t)host;
#ifdef WIN32
	host->randomSeed += (enet_uint32)timeGetTime();
#else
	host->randomSeed += (enet_uint32)time(NULL);
#endif
	host->randomSeed = (host->randomSeed << 16) | (host->randomSeed >> 16);
	host->channelLimit = channelLimit;
	host->incomingBandwidth = incomingBandwidth;
	host->outgoingBandwidth = outgoingBandwidth;
	host->bandwidthThrottleEpoch = 0;
	host->recalculateBandwidthLimits = 0;
	host->mtu = ENET_HOST_DEFAULT_MTU;
	host->peerCount = peerCount;
	host->commandCount = 0;
	host->bufferCount = 0;
	host->checksum = NULL;
	host->receivedAddress.host = ENET_HOST_ANY;
	host->receivedAddress.port = 0;
	host->receivedData = NULL;
	host->receivedDataLength = 0;

	host->totalSentData = 0;
	host->totalSentPackets = 0;
	host->totalReceivedData = 0;
	host->totalReceivedPackets = 0;

	host->compressor.context = NULL;
	host->compressor.compress = NULL;
	host->compressor.decompress = NULL;
	host->compressor.destroy = NULL;

	host->intercept = NULL;

	enet_list_clear(&host->dispatchQueue);

	for (currentPeer = host->peers;
		 currentPeer < &host->peers[host->peerCount];
		 ++currentPeer)
	{
		currentPeer->host = host;
		currentPeer->incomingPeerID = currentPeer - host->peers;
		currentPeer->outgoingSessionID = currentPeer->incomingSessionID = 0xFF;
		currentPeer->data = NULL;

		enet_list_clear(&currentPeer->acknowledgements);
		enet_list_clear(&currentPeer->sentReliableCommands);
		enet_list_clear(&currentPeer->sentUnreliableCommands);
		enet_list_clear(&currentPeer->outgoingReliableCommands);
		enet_list_clear(&currentPeer->outgoingUnreliableCommands);
		enet_list_clear(&currentPeer->dispatchedCommands);

		enet_peer_reset(currentPeer);
	}

	return host;
}

/** Destroys the host and all resources associated with it.
    @param host pointer to the host to destroy
*/
void enet_host_destroy(ENetHost *host)
{
	ENetPeer *currentPeer;

	if (host == NULL)
		return;

	enet_socket_destroy(host->socket);

	for (currentPeer = host->peers;
		 currentPeer < &host->peers[host->peerCount];
		 ++currentPeer)
	{
		enet_peer_reset(currentPeer);
	}

	if (host->compressor.context != NULL && host->compressor.destroy)
		(*host->compressor.destroy)(host->compressor.context);

	enet_free(host->peers);
	enet_free(host);
}

/** Initiates a connection to a foreign host.
    @param host host seeking the connection
    @param address destination for the connection
    @param channelCount number of channels to allocate
    @param data user data supplied to the receiving host 
    @returns a peer representing the foreign host on success, NULL on failure
    @remarks The peer returned will have not completed the connection until enet_host_service()
    notifies of an ENET_EVENT_TYPE_CONNECT event for the peer.
*/
ENetPeer *
enet_host_connect(ENetHost *host, const ENetAddress *address, size_t channelCount, enet_uint32 data)
{
	ENetPeer *currentPeer;
	ENetChannel *channel;
	ENetProtocol command;

	if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT)
		channelCount = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT;
	else if (channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT)
		channelCount = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT;

	for (currentPeer = host->peers;
		 currentPeer < &host->peers[host->peerCount];
		 ++currentPeer)
	{
		if (currentPeer->state == ENET_PEER_STATE_DISCONNECTED)
			break;
	}

	if (currentPeer >= &host->peers[host->peerCount])
		return NULL;

	currentPeer->channels = (ENetChannel *)enet_malloc(channelCount * sizeof(ENetChannel));
	if (currentPeer->channels == NULL)
		return NULL;
	currentPeer->channelCount = channelCount;
	currentPeer->state = ENET_PEER_STATE_CONNECTING;
	currentPeer->address = *address;
	currentPeer->connectID = ++host->randomSeed;

	if (host->outgoingBandwidth == 0)
		currentPeer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;
	else
		currentPeer->windowSize = (host->outgoingBandwidth /
								   ENET_PEER_WINDOW_SIZE_SCALE) *
								  ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;

	if (currentPeer->windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE)
		currentPeer->windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE;
	else if (currentPeer->windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE)
		currentPeer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;

	for (channel = currentPeer->channels;
		 channel < &currentPeer->channels[channelCount];
		 ++channel)
	{
		channel->outgoingReliableSequenceNumber = 0;
		channel->outgoingUnreliableSequenceNumber = 0;
		channel->incomingReliableSequenceNumber = 0;
		channel->incomingUnreliableSequenceNumber = 0;

		enet_list_clear(&channel->incomingReliableCommands);
		enet_list_clear(&channel->incomingUnreliableCommands);

		channel->usedReliableWindows = 0;
		memset(channel->reliableWindows, 0, sizeof(channel->reliableWindows));
	}

	command.header.command = ENET_PROTOCOL_COMMAND_CONNECT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE;
	command.header.channelID = 0xFF;
	command.connect.outgoingPeerID = ENET_HOST_TO_NET_16(currentPeer->incomingPeerID);
	command.connect.incomingSessionID = currentPeer->incomingSessionID;
	command.connect.outgoingSessionID = currentPeer->outgoingSessionID;
	command.connect.mtu = ENET_HOST_TO_NET_32(currentPeer->mtu);
	command.connect.windowSize = ENET_HOST_TO_NET_32(currentPeer->windowSize);
	command.connect.channelCount = ENET_HOST_TO_NET_32(channelCount);
	command.connect.incomingBandwidth = ENET_HOST_TO_NET_32(host->incomingBandwidth);
	command.connect.outgoingBandwidth = ENET_HOST_TO_NET_32(host->outgoingBandwidth);
	command.connect.packetThrottleInterval = ENET_HOST_TO_NET_32(currentPeer->packetThrottleInterval);
	command.connect.packetThrottleAcceleration = ENET_HOST_TO_NET_32(currentPeer->packetThrottleAcceleration);
	command.connect.packetThrottleDeceleration = ENET_HOST_TO_NET_32(currentPeer->packetThrottleDeceleration);
	command.connect.connectID = currentPeer->connectID;
	command.connect.data = ENET_HOST_TO_NET_32(data);

	enet_peer_queue_outgoing_command(currentPeer, &command, NULL, 0, 0);

	return currentPeer;
}

/** Queues a packet to be sent to all peers associated with the host.
    @param host host on which to broadcast the packet
    @param channelID channel on which to broadcast
    @param packet packet to broadcast
*/
void enet_host_broadcast(ENetHost *host, enet_uint8 channelID, ENetPacket *packet)
{
	ENetPeer *currentPeer;

	for (currentPeer = host->peers;
		 currentPeer < &host->peers[host->peerCount];
		 ++currentPeer)
	{
		if (currentPeer->state != ENET_PEER_STATE_CONNECTED)
			continue;

		enet_peer_send(currentPeer, channelID, packet);
	}

	if (packet->referenceCount == 0)
		enet_packet_destroy(packet);
}

/** Sets the packet compressor the host should use to compress and decompress packets.
    @param host host to enable or disable compression for
    @param compressor callbacks for for the packet compressor; if NULL, then compression is disabled
*/
void enet_host_compress(ENetHost *host, const ENetCompressor *compressor)
{
	if (host->compressor.context != NULL && host->compressor.destroy)
		(*host->compressor.destroy)(host->compressor.context);

	if (compressor)
		host->compressor = *compressor;
	else
		host->compressor.context = NULL;
}

/** Limits the maximum allowed channels of future incoming connections.
    @param host host to limit
    @param channelLimit the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT
*/
void enet_host_channel_limit(ENetHost *host, size_t channelLimit)
{
	if (!channelLimit || channelLimit > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT)
		channelLimit = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT;
	else if (channelLimit < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT)
		channelLimit = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT;

	host->channelLimit = channelLimit;
}

/** Adjusts the bandwidth limits of a host.
    @param host host to adjust
    @param incomingBandwidth new incoming bandwidth
    @param outgoingBandwidth new outgoing bandwidth
    @remarks the incoming and outgoing bandwidth parameters are identical in function to those
    specified in enet_host_create().
*/
void enet_host_bandwidth_limit(ENetHost *host, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth)
{
	host->incomingBandwidth = incomingBandwidth;
	host->outgoingBandwidth = outgoingBandwidth;
	host->recalculateBandwidthLimits = 1;
}

void enet_host_bandwidth_throttle(ENetHost *host)
{
	enet_uint32 timeCurrent = enet_time_get(),
				elapsedTime = timeCurrent - host->bandwidthThrottleEpoch,
				peersTotal = 0,
				dataTotal = 0,
				peersRemaining,
				bandwidth,
				throttle = 0,
				bandwidthLimit = 0;
	int needsAdjustment;
	ENetPeer *peer;
	ENetProtocol command;

	if (elapsedTime < ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL)
		return;

	for (peer = host->peers;
		 peer < &host->peers[host->peerCount];
		 ++peer)
	{
		if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER)
			continue;

		++peersTotal;
		dataTotal += peer->outgoingDataTotal;
	}

	if (peersTotal == 0)
		return;

	peersRemaining = peersTotal;
	needsAdjustment = 1;

	if (host->outgoingBandwidth == 0)
		bandwidth = ~0;
	else
		bandwidth = (host->outgoingBandwidth * elapsedTime) / 1000;

	while (peersRemaining > 0 && needsAdjustment != 0)
	{
		needsAdjustment = 0;

		if (dataTotal < bandwidth)
			throttle = ENET_PEER_PACKET_THROTTLE_SCALE;
		else
			throttle = (bandwidth * ENET_PEER_PACKET_THROTTLE_SCALE) / dataTotal;

		for (peer = host->peers;
			 peer < &host->peers[host->peerCount];
			 ++peer)
		{
			enet_uint32 peerBandwidth;

			if ((peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) ||
				peer->incomingBandwidth == 0 ||
				peer->outgoingBandwidthThrottleEpoch == timeCurrent)
				continue;

			peerBandwidth = (peer->incomingBandwidth * elapsedTime) / 1000;
			if ((throttle * peer->outgoingDataTotal) / ENET_PEER_PACKET_THROTTLE_SCALE <= peerBandwidth)
				continue;

			peer->packetThrottleLimit = (peerBandwidth *
										 ENET_PEER_PACKET_THROTTLE_SCALE) /
										peer->outgoingDataTotal;

			if (peer->packetThrottleLimit == 0)
				peer->packetThrottleLimit = 1;

			if (peer->packetThrottle > peer->packetThrottleLimit)
				peer->packetThrottle = peer->packetThrottleLimit;

			peer->outgoingBandwidthThrottleEpoch = timeCurrent;

			needsAdjustment = 1;
			--peersRemaining;
			bandwidth -= peerBandwidth;
			dataTotal -= peerBandwidth;
		}
	}

	if (peersRemaining > 0)
		for (peer = host->peers;
			 peer < &host->peers[host->peerCount];
			 ++peer)
		{
			if ((peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) ||
				peer->outgoingBandwidthThrottleEpoch == timeCurrent)
				continue;

			peer->packetThrottleLimit = throttle;

			if (peer->packetThrottle > peer->packetThrottleLimit)
				peer->packetThrottle = peer->packetThrottleLimit;
		}

	if (host->recalculateBandwidthLimits)
	{
		host->recalculateBandwidthLimits = 0;

		peersRemaining = peersTotal;
		bandwidth = host->incomingBandwidth;
		needsAdjustment = 1;

		if (bandwidth == 0)
			bandwidthLimit = 0;
		else
			while (peersRemaining > 0 && needsAdjustment != 0)
			{
				needsAdjustment = 0;
				bandwidthLimit = bandwidth / peersRemaining;

				for (peer = host->peers;
					 peer < &host->peers[host->peerCount];
					 ++peer)
				{
					if ((peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) ||
						peer->incomingBandwidthThrottleEpoch == timeCurrent)
						continue;

					if (peer->outgoingBandwidth > 0 &&
						peer->outgoingBandwidth >= bandwidthLimit)
						continue;

					peer->incomingBandwidthThrottleEpoch = timeCurrent;

					needsAdjustment = 1;
					--peersRemaining;
					bandwidth -= peer->outgoingBandwidth;
				}
			}

		for (peer = host->peers;
			 peer < &host->peers[host->peerCount];
			 ++peer)
		{
			if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER)
				continue;

			command.header.command = ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE;
			command.header.channelID = 0xFF;
			command.bandwidthLimit.outgoingBandwidth = ENET_HOST_TO_NET_32(host->outgoingBandwidth);

			if (peer->incomingBandwidthThrottleEpoch == timeCurrent)
				command.bandwidthLimit.incomingBandwidth = ENET_HOST_TO_NET_32(peer->outgoingBandwidth);
			else
				command.bandwidthLimit.incomingBandwidth = ENET_HOST_TO_NET_32(bandwidthLimit);

			enet_peer_queue_outgoing_command(peer, &command, NULL, 0, 0);
		}
	}

	host->bandwidthThrottleEpoch = timeCurrent;

	for (peer = host->peers;
		 peer < &host->peers[host->peerCount];
		 ++peer)
	{
		peer->incomingDataTotal = 0;
		peer->outgoingDataTotal = 0;
	}
}

/** @} */
