#include <malloc.h>
#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;
}