/*
** JNetLib
** Copyright (C) 2000-2007 Nullsoft, Inc.
** Author: Justin Frankel
** File: asyncdns.cpp - JNL portable asynchronous DNS implementation
** License: see jnetlib.h
*/

#include "netinc.h"
#include "util.h"

#include "wac_network_dns.h"

#include <time.h>

#ifdef _WIN32
#include <strsafe.h>
#endif

enum
{
	MODE_RESOLVE = 0,
	MODE_REVERSE = 1,
};


struct  cache_entry
{
	time_t          last_used; // timestamp.
	bool            resolved;
	int             mode;      // 1=reverse
	unsigned short  port;
	char            hostname[ 256 ];
	addrinfo *addr;
	int             sockettype;
};

wa::Components::WAC_Network_AsyncDNS::WAC_Network_AsyncDNS( int max_cache_entries )
{
	m_thread_kill = 1;
	m_thread = 0;
	m_cache_size = max_cache_entries;
	m_cache = (cache_entry *)malloc( sizeof( cache_entry ) * m_cache_size );

	if ( m_cache )
		memset( m_cache, 0, sizeof( cache_entry ) * m_cache_size );
	else
		m_cache_size = 0;
}

wa::Components::WAC_Network_AsyncDNS::~WAC_Network_AsyncDNS()
{
	m_thread_kill = 1;

#ifdef _WIN32
	if ( m_thread )
	{
		WaitForSingleObject( m_thread, INFINITE );
		CloseHandle( m_thread );
	}
#else
	if ( m_thread )
	{
		void *p;
		pthread_join( m_thread, &p );
	}
#endif//!_WIN32

	// free all the addrinfo stuff
	for ( int x = 0; x < m_cache_size; x++ )
	{
		if ( m_cache[ x ].addr )
			freeaddrinfo( m_cache[ x ].addr );
	}

	free( m_cache );
}

int wa::Components::WAC_Network_AsyncDNS::resolvenow( const char *hostname, unsigned short port, addrinfo **addr, int sockettype )
{
	addrinfo hints;
	memset( &hints, 0, sizeof( hints ) );
	hints.ai_family = PF_UNSPEC;

	if ( hostname )
		hints.ai_flags = AI_NUMERICHOST;
	else
		hints.ai_flags = AI_NUMERICHOST | AI_PASSIVE;

	hints.ai_socktype = sockettype;

	char portString[ 32 ] = { 0 };
	sprintf( portString, "%u", (unsigned int)port );

	if ( getaddrinfo( hostname, portString, &hints, addr ) == 0 )
	{
		return 0;
	}
	else
	{
		hints.ai_flags = 0;
		if ( getaddrinfo( hostname, portString, &hints, addr ) == 0 )
			return 0;
		else
			return -1;
	}
}

#ifdef _WIN32
unsigned long WINAPI wa::Components::WAC_Network_AsyncDNS::_threadfunc( LPVOID _d )
#else
unsigned int WAC_Network_AsyncDNS::_threadfunc( void *_d )
#endif
{
	int nowinsock = JNL::open_socketlib();
	wa::Components::WAC_Network_AsyncDNS *_this = (WAC_Network_AsyncDNS *)_d;
	int x;
	for ( x = 0; x < _this->m_cache_size && !_this->m_thread_kill; x++ )
	{
		if ( _this->m_cache[ x ].last_used && !_this->m_cache[ x ].resolved )
		{
			if ( !nowinsock )
			{
				if ( _this->m_cache[ x ].mode == 0 )
				{
					addrinfo *res = 0;
					if ( resolvenow( _this->m_cache[ x ].hostname, _this->m_cache[ x ].port, &res, _this->m_cache[ x ].sockettype ) == 0 )
					{
						_this->m_cache[ x ].addr = res;
					}
					else
					{
						_this->m_cache[ x ].addr = 0; //INADDR_NONE;
					}
				}
				else if ( _this->m_cache[ x ].mode == 1 )
				{
					/*
					hostent *ent;
					// TODO: replace with getnameinfo for IPv6
					ent=gethostbyaddr((const char *)&_this->m_cache[x].addr,4,AF_INET);
					if (ent)
						lstrcpyn(_this->m_cache[x].hostname, ent->h_name, 256);
					else
						_this->m_cache[x].hostname[0]=0;
					*/
				}

				_this->m_cache[ x ].resolved = true;
			}
			else
			{
				if ( _this->m_cache[ x ].mode == 0 )
				{
					_this->m_cache[ x ].addr = 0;//INADDR_NONE;
					_this->m_cache[ x ].resolved = true;
				}
				else if ( _this->m_cache[ x ].mode == 1 )
				{
					_this->m_cache[ x ].hostname[ 0 ] = 0;
					_this->m_cache[ x ].resolved = true;
				}
			}
		}
	}

	if ( !nowinsock )
		JNL::close_socketlib();

	_this->m_thread_kill = 1;

	return 0;
}

int wa::Components::WAC_Network_AsyncDNS::resolve( const char *hostname, unsigned short port, addrinfo **addr, int sockettype )
{
	// return 0 on success, 1 on wait, -1 on unresolvable
	int x;

	for ( x = 0; x < m_cache_size; x++ )
	{
		if ( !strcasecmp( m_cache[ x ].hostname, hostname ) && port == m_cache[ x ].port && m_cache[ x ].mode == 0 && m_cache[ x ].sockettype == sockettype )
		{
			m_cache[ x ].last_used = time( 0 );
			if ( m_cache[ x ].resolved )
			{
				if ( m_cache[ x ].addr == 0 )//INADDR_NONE)
				{
					return DNS_RESOLVE_UNRESOLVABLE;
				}

				*addr = m_cache[ x ].addr;
				return DNS_RESOLVE_SUCCESS;
			}

			makesurethreadisrunning();
			return DNS_RESOLVE_WAIT;
		}
	}

	// add to resolve list
	int oi = -1;
	for ( x = 0; x < m_cache_size; x++ )
	{
		if ( !m_cache[ x ].last_used )
		{
			oi = x;
			break;
		}

		if ( ( oi == -1 || m_cache[ x ].last_used < m_cache[ oi ].last_used ) && m_cache[ x ].resolved )
		{
			oi = x;
		}
	}

	if ( oi == -1 )
	{
		return DNS_RESOLVE_UNRESOLVABLE;
	}

#ifdef _WIN32
	StringCchCopyA( m_cache[ oi ].hostname, 256, hostname );
#elif defined(__APPLE__)
	strlcpy( m_cache[ oi ].hostname, hostname, 255 );
#else
	strncpy( m_cache[ oi ].hostname, hostname, 255 );
	m_cache[ oi ].hostname[ 255 ] = 0;
#endif

	m_cache[ oi ].port       = port;
	m_cache[ oi ].mode       = 0;
	m_cache[ oi ].addr       = 0;//INADDR_NONE;
	m_cache[ oi ].resolved   = false;
	m_cache[ oi ].last_used  = time( 0 );
	m_cache[ oi ].sockettype = sockettype;

	makesurethreadisrunning();

	return DNS_RESOLVE_WAIT;
}

void wa::Components::WAC_Network_AsyncDNS::makesurethreadisrunning( void )
{
	if ( m_thread_kill )
	{
#ifdef _WIN32
		if ( m_thread )
		{
			WaitForSingleObject( m_thread, INFINITE );
			CloseHandle( m_thread );
		}
		DWORD id;
		m_thread_kill = 0;
		m_thread = CreateThread( NULL, 0, _threadfunc, (LPVOID)this, 0, &id );
		if ( !m_thread )
		{
#else
		if ( m_thread )
		{
			void *p;
			pthread_join( m_thread, &p );
		}
		m_thread_kill = 0;
		if ( pthread_create( &m_thread, NULL, ( void *( * ) ( void * ) )_threadfunc, (void *)this ) != 0 )
		{
#endif
			m_thread_kill = 1;
		}
		}
	}

#define CBCLASS wa::Components::WAC_Network_AsyncDNS
START_DISPATCH;
CB( API_DNS_RESOLVE, resolve );
END_DISPATCH;
#undef CBCLASS