#include "dns.h"

/**
 * Resolves a domainname to an ip address
 * It makes use of net_gethostbyname from libogc, which in turn makes use of a Wii BIOS function
 * Just like the net_gethostbyname function this function is NOT threadsafe!
 *
 * @param char* The domain name to resolve
 * @return u32 The ipaddress represented by four bytes inside an u32 (in network order)
 */
u32 getipbyname(char *domain)
{
	//Care should be taken when using net_gethostbyname,
	//it returns a static buffer which makes it not threadsafe
	//TODO: implement some locking mechanism to make below code atomic
	struct hostent *host = net_gethostbyname(domain);

	if (host == NULL)
	{
		return 0;
	}

	u32 *ip = (u32*) host->h_addr_list[0];
	return *ip;
}

//Defines how many DNS entries should be cached by getipbynamecached()
#define MAX_DNS_CACHE_ENTRIES 20

//The cache is defined as a linked list,
//The last resolved domain name will always be at the front
//This will allow heavily used domainnames to always stay cached
struct dnsentry
{
	char *domain;
	u32 ip;
	struct dnsentry *nextnode;
};

static struct dnsentry *firstdnsentry = NULL;
static int dnsentrycount = 0;

/**
 * Performs the same function as getipbyname(),
 * except that it will prevent extremely expensive net_gethostbyname() calls by caching the result
 */
u32 getipbynamecached(char *domain)
{
	//Search if this domainname is already cached
	struct dnsentry *node = firstdnsentry;
	struct dnsentry *previousnode = NULL;

	while (node != NULL)
	{
		if (strcmp(node->domain, domain) == 0)
		{
			//DNS node found in the cache, move it to the front of the list
			if (previousnode != NULL) previousnode->nextnode = node->nextnode;

			if (node != firstdnsentry) node->nextnode = firstdnsentry;
			firstdnsentry = node;

			return node->ip;
		}
		//Go to the next element in the list
		previousnode = node;
		node = node->nextnode;
	}
	u32 ip = getipbyname(domain);

	//No cache of this domain could be found, create a cache node and add it to the front of the cache
	struct dnsentry *newnode = malloc(sizeof(struct dnsentry));
	if (newnode == NULL)
	{
		return ip;
	}

	newnode->ip = ip;
	newnode->domain = malloc(strlen(domain) + 1);
	if (newnode->domain == NULL)
	{
		free(newnode);
		return ip;
	}
	strcpy(newnode->domain, domain);

	newnode->nextnode = firstdnsentry;
	firstdnsentry = newnode;
	dnsentrycount++;

	//If the cache grows too big delete the last (and probably least important) node of the list
	if (dnsentrycount > MAX_DNS_CACHE_ENTRIES)
	{
		struct dnsentry *node = firstdnsentry;
		struct dnsentry *previousnode = NULL;

		//Fetch the last two elements of the list
		while (node->nextnode != NULL)
		{
			previousnode = node;
			node = node->nextnode;
		}

		if (node == NULL)
		{
			printf("Configuration error, MAX_DNS_ENTRIES reached while the list is empty\n");
			exit(1);
		}
		else if (previousnode == NULL)
		{
			firstdnsentry = NULL;
		}
		else
		{
			previousnode->nextnode = NULL;
		}

		free(node->domain);
		free(node);
		dnsentrycount--;
	}

	return newnode->ip;
}
