/*
 Copyright (C) 2009-2011 id Software LLC, a ZeniMax Media company.
 Copyright (C) 2009 Id Software, Inc.
 
 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 */


/*

 Deal with all the DNS / bonjour service discovery and resolution
 
 */

#include "../doomiphone.h"

#include <dns_sd.h>
#include <netdb.h>		// for gethostbyname
#include <net/if.h>		// for if_nameindex()

DNSServiceRef	browseRef;
DNSServiceRef	clientServiceRef;
DNSServiceRef	serviceRef;
boolean			serviceRefValid;

typedef struct {
	int				interfaceIndex;
	char			browseName[1024];
	char			browseRegtype[1024];
	char			browseDomain[1024];
} service_t;

boolean			localServer;

// we can find services on both WiFi and Bluetooth interfaces
#define MAX_SERVICE_INTEFACES 4
service_t		serviceInterfaces[MAX_SERVICE_INTEFACES];

boolean			gotServerAddress;
struct sockaddr resolvedServerAddress;

static const char *serviceName = "_DoomServer._udp.";

int InterfaceIndexForName( const char *ifname );

void DNSServiceRegisterReplyCallback ( 
										  DNSServiceRef sdRef, 
										  DNSServiceFlags flags, 
										  DNSServiceErrorType errorCode, 
										  const char *name, 
										  const char *regtype, 
										  const char *domain, 
										  void *context ) {
	if ( errorCode == kDNSServiceErr_NoError ) {
		localServer = true;
	} else {
		localServer = false;
	}
}

boolean RegisterGameService() {
	DNSServiceErrorType	err = DNSServiceRegister( 
					   &serviceRef, 
					   kDNSServiceFlagsNoAutoRename,		// we want a conflict error
					   InterfaceIndexForName( "en0" ),		// pass 0 for all interfaces
					   "iPhone Doom Classic",
					   serviceName,
					   NULL,	// domain
					   NULL,	// host
					   htons( DOOM_PORT ),
					   0,		// txtLen
					   NULL,	// txtRecord
					   DNSServiceRegisterReplyCallback,
					   NULL		// context
					   );
	
	if ( err != kDNSServiceErr_NoError ) {
		printf( "DNSServiceRegister error\n" );
	} else {
		// block until we get a response, process it, and run the callback
		err = DNSServiceProcessResult( serviceRef );
		if ( err != kDNSServiceErr_NoError ) {
			printf( "DNSServiceProcessResult error\n" );
		}
	}
	return localServer;
}

void TerminateGameService() {
	if ( localServer ) {
		localServer = false;
	}
	DNSServiceRefDeallocate( serviceRef );
	memset( serviceInterfaces, 0, sizeof( serviceInterfaces ) );
}

void DNSServiceQueryRecordReplyCallback ( 
											 DNSServiceRef DNSServiceRef, 
											 DNSServiceFlags flags, 
											 uint32_t interfaceIndex, 
											 DNSServiceErrorType errorCode, 
											 const char *fullname, 
											 uint16_t rrtype, 
											 uint16_t rrclass, 
											 uint16_t rdlen, 
											 const void *rdata, 
											 uint32_t ttl, 
											 void *context ) {
	assert( rdlen == 4 );
	const byte *ip = (const byte *)rdata;
	char	interfaceName[IF_NAMESIZE];
	if_indextoname( interfaceIndex, interfaceName );
	printf( "DNSServiceQueryRecordReplyCallback: %s, interface[%i] = %s, [%i] = %i.%i.%i.%i\n", 
		   fullname, interfaceIndex, interfaceName, rdlen, ip[0], ip[1], ip[2], ip[3] );

	ReportNetworkInterfaces();
	
	memset( &resolvedServerAddress, 0, sizeof( resolvedServerAddress ) );
	struct sockaddr_in *sin = (struct sockaddr_in *)&resolvedServerAddress;
	sin->sin_len = sizeof( resolvedServerAddress );
	sin->sin_family = AF_INET;
	sin->sin_port = htons( DOOM_PORT );
	memcpy( &sin->sin_addr, ip, 4 );
	
	gotServerAddress = true;
}
	

DNSServiceFlags callbackFlags;

void DNSServiceResolveReplyCallback ( 
										 DNSServiceRef sdRef, 
										 DNSServiceFlags flags, 
										 uint32_t interfaceIndex, 
										 DNSServiceErrorType errorCode, 
										 const char *fullname, 
										 const char *hosttarget, 
										 uint16_t port, 
										 uint16_t txtLen, 
										 const unsigned char *txtRecord, 
										 void *context ) {
	char	interfaceName[IF_NAMESIZE];
	if_indextoname( interfaceIndex, interfaceName );
	printf( "Resolve: interfaceIndex [%i]=%s : %s @ %s\n", interfaceIndex, interfaceName, fullname, hosttarget );
	callbackFlags = flags;
	
#if 0	
	struct hostent * host = gethostbyname( hosttarget );
	if ( host ) {
		printf( "h_name: %s\n", host->h_name );
		if ( host->h_aliases ) {	// this can be NULL
			for ( char **list = host->h_aliases ; *list ; list++ ) {
				printf( "h_alias: %s\n", *list );
			}
		}
		printf( "h_addrtype: %i\n", host->h_addrtype );
		printf( "h_length: %i\n", host->h_length );
		if ( !host->h_addr_list ) {	// I doubt this would ever be NULL...
			return;
		}
		for ( char **list = host->h_addr_list ; *list ; list++ ) {
			printf( "addr: %i.%i.%i.%i\n", ((byte *)*list)[0], ((byte *)*list)[1], ((byte *)*list)[2], ((byte *)*list)[3] );
		}
		
		memset( &resolvedServerAddress, 0, sizeof( resolvedServerAddress ) );
		resolvedServerAddress.sin_len = sizeof( resolvedServerAddress );
		resolvedServerAddress.sin_family = host->h_addrtype;
		resolvedServerAddress.sin_port = htons( DOOM_PORT );
		assert( host->h_length == 4 );
		memcpy( &resolvedServerAddress.sin_addr, *host->h_addr_list, host->h_length );

		gotServerAddress = true;
	}	
#else
	DNSServiceRef	queryRef;
	
	// look up the name for this host
	DNSServiceErrorType err = DNSServiceQueryRecord ( 
													 &queryRef, 
													 kDNSServiceFlagsForceMulticast, 
													 interfaceIndex, 
													 hosttarget, 
													 kDNSServiceType_A,		// we want the host address
													 kDNSServiceClass_IN, 
													 DNSServiceQueryRecordReplyCallback, 
													 NULL /* may be NULL */
													 );  	
	if ( err != kDNSServiceErr_NoError ) {
		printf( "DNSServiceQueryRecord error\n" );
	} else {
		// block until we get a response, process it, and run the callback
		err = DNSServiceProcessResult( queryRef );
		if ( err != kDNSServiceErr_NoError ) {
			printf( "DNSServiceProcessResult error\n" );
		}
		DNSServiceRefDeallocate( queryRef );
	}
#endif	
}

boolean NetworkServerAvailable() {
	for ( int i = 0 ; i < MAX_SERVICE_INTEFACES ; i++ ) {
		if ( serviceInterfaces[i].interfaceIndex != 0 ) {
			return true;
		}
	}
	return false;
}

// returns "WiFi", "BlueTooth", or "" for display on the
// main menu multiplayer icon
const char *NetworkServerTransport() {
	int	count = 0;
	for ( int i = 0 ; i < MAX_SERVICE_INTEFACES ; i++ ) {
		if ( serviceInterfaces[i].interfaceIndex != 0 ) {
			count++;
		}		
	}
	
	static char	str[1024];
	
	str[0] = 0;
	for ( int i = 0 ; i < MAX_SERVICE_INTEFACES ; i++ ) {
		int	index = serviceInterfaces[i].interfaceIndex;
		if ( index == 0 ) {
			continue;
		}		
		if ( str[0] ) {
			strcat( str, "+" );
		}
		if ( index == -1 ) {
			strcat( str, "BT-NEW" );
		} else if ( index == 1 ) {
			strcat( str, "LOOP" );	// we should never see this!
		} else if ( index == 2 ) {
			strcat( str, "WiFi" );
		} else {
			strcat( str, "BT-EST" );
		}
	}
	return str;
}



boolean ResolveNetworkServer( struct sockaddr *addr ) {
	if ( !NetworkServerAvailable() ) {
		return false;
	}
	
	gotServerAddress = false;
	
	DNSServiceRef	resolveRef;
	
	// An unconnected bluetooth service will report an interfaceIndex of -1, so if
	// we have a wifi link with an interfaceIndex > 0, use that
	// explicitly.
	service_t	*service = NULL;
	for ( int i = 0 ; i < MAX_SERVICE_INTEFACES ; i++ ) {
		if ( serviceInterfaces[i].interfaceIndex > 0 ) {
			service = &serviceInterfaces[i];
			char	interfaceName[IF_NAMESIZE];
			if_indextoname( service->interfaceIndex, interfaceName );
			printf( "explicitly resolving server on interface %i = %s\n", service->interfaceIndex, interfaceName );
			break;
		}
	}
	if ( !service ) {
#if 0			// don't support bluetooth now
		// settle for the unconnected bluetooth service
		for ( int i = 0 ; i < MAX_SERVICE_INTEFACES ; i++ ) {
			if ( serviceInterfaces[i].interfaceIndex != 0 ) {
				service = &serviceInterfaces[i];
				break;
			}
		}
#endif		
		if ( !service ) {
			printf( "No serviceInterface current.\n" );
			return false;
		}
	}
	
	// look up the name for this service
	
	DNSServiceErrorType err = DNSServiceResolve ( 
										 &resolveRef, 
										 kDNSServiceFlagsForceMulticast,	// always on local link
										 service->interfaceIndex > 0 ? service->interfaceIndex : 0,		// don't use -1 for bluetooth
										 service->browseName, 
										 service->browseRegtype, 
										 service->browseDomain, 
										 DNSServiceResolveReplyCallback, 
										 NULL			/* context */
										 );  

	if ( err != kDNSServiceErr_NoError ) {
		printf( "DNSServiceResolve error\n" );
	} else {
		// We can get two callbacks when both wifi and bluetooth are enabled
		callbackFlags = 0;
		do {
			err = DNSServiceProcessResult( resolveRef );
			if ( err != kDNSServiceErr_NoError ) {
				printf( "DNSServiceProcessResult error\n" );
			}
		} while ( callbackFlags & kDNSServiceFlagsMoreComing );
		DNSServiceRefDeallocate( resolveRef );
	}
	
	if ( gotServerAddress ) {
		*addr = resolvedServerAddress;
		return true;
	}
	
	
	return false;
}


void DNSServiceBrowseReplyCallback(
								   DNSServiceRef sdRef, 
								   DNSServiceFlags flags, 
								   uint32_t interfaceIndex, 
								   DNSServiceErrorType errorCode, 
								   const char *serviceName, 
								   const char *regtype, 
								   const char *replyDomain, 
								   void *context ) {
	printf( "DNSServiceBrowseReplyCallback %s: interface:%i name:%s regtype:%s domain:%s\n", 
		   (flags & kDNSServiceFlagsAdd) ? "ADD" : "REMOVE",
		   interfaceIndex, serviceName, regtype, replyDomain );
	if ( flags & kDNSServiceFlagsAdd ) {
		// add it to the list
		if ( interfaceIndex == 1 ) {
			printf( "Not adding service on loopback interface.\n" );
		} else {
			for ( int i = 0 ; i < MAX_SERVICE_INTEFACES ; i++ ) {
				service_t *service = &serviceInterfaces[i];
				if ( service->interfaceIndex == 0 ) {
					strncpy( service->browseName, serviceName, sizeof( service->browseName ) -1 );
					strncpy( service->browseRegtype, regtype, sizeof( service->browseRegtype ) -1 );
					strncpy( service->browseDomain, replyDomain, sizeof( service->browseDomain ) -1 );
					service->interfaceIndex = interfaceIndex;
					break;
				}
			}
		}
	} else {
		// remove it from the list
		for ( int i = 0 ; i < MAX_SERVICE_INTEFACES ; i++ ) {
			if ( serviceInterfaces[i].interfaceIndex == interfaceIndex ) {
				serviceInterfaces[i].interfaceIndex = 0;
			}
		}
	}
}

void ProcessDNSMessages() {
	static boolean initialized;
	
	if ( !initialized ) {
		initialized = true;
		DNSServiceErrorType err = DNSServiceBrowse ( 
													&browseRef, 
													0,					/* flags */
													0,					/* interface */
													serviceName, 
													NULL,				/* domain */
													DNSServiceBrowseReplyCallback, 
													NULL				/* context */
													);  
		if ( err != kDNSServiceErr_NoError ) {
			printf( "DNSServiceBrowse error\n" );
			return;
		}		
	}
	
	// poll the socket for updates
	int	socket = DNSServiceRefSockFD( browseRef );
	if ( socket <= 0 ) {
		return;
	}
	fd_set	set;
	FD_ZERO( &set );
	FD_SET( socket, &set );
	
	struct timeval tv;
	memset( &tv, 0, sizeof( tv ) );
	if ( select( socket+1, &set, NULL, NULL, &tv ) > 0 ) {
		DNSServiceProcessResult( browseRef );
	}	
}

void ReportNetworkInterfaces() {
	struct ifaddrs *ifap;
	printf( "getifaddrs():\n" );
	if ( getifaddrs( &ifap ) == -1 ) {
		perror( "getifaddrs(): " );
	} else {
		for ( struct ifaddrs *ifa = ifap ; ifa ; ifa = ifa->ifa_next ) {
			struct sockaddr_in *ina = (struct sockaddr_in *)ifa->ifa_addr;
			if ( ina->sin_family == AF_INET ) {
				byte *ip = (byte *)&ina->sin_addr;
				printf( "ifa_name: %s ifa_flags: %i sa_family: %i=AF_INET ip: %i.%i.%i.%i\n", ifa->ifa_name, ifa->ifa_flags,
					   ina->sin_family, ip[0], ip[1], ip[2], ip[3]  );
			} else if ( ina->sin_family == AF_LINK ) {
				struct if_data *data = (struct if_data *)ifa->ifa_data;
				printf( "ifa_name: %s ifa_flags: %i sa_family: %i=AF_LINK ifi_ipackets: %i\n", ifa->ifa_name, ifa->ifa_flags,
					   ina->sin_family, data->ifi_ipackets );
			} else {
				printf( "ifa_name: %s ifa_flags: %i sa_family: %i=???\n", ifa->ifa_name, ifa->ifa_flags,
					   ina->sin_family );
			}
		}
		freeifaddrs( ifap );
	}
	
	printf( "if_nameindex():\n" );
	struct if_nameindex *ifnames = if_nameindex();
	if ( !ifnames ) {
		perror( "if_ameindex():" );
	} else {
		for ( int i = 0 ; ifnames[i].if_index != 0 ; i++ ) {
			printf( "%i : %s\n", ifnames[i].if_index, ifnames[i].if_name );
		}
		if_freenameindex( ifnames );
	}
	
}

boolean NetworkAvailable() {
	struct ifaddrs *ifap;
	if ( getifaddrs( &ifap ) == -1 ) {
		return false;
	}
	
	// We can't tell if bluetooth is available from here, because
	// the interface doesn't appear until after the service is found,
	// but I decided not to support bluetooth for now due to the poor performance.
	boolean	goodInterface = false;
	
	for ( struct ifaddrs *ifa = ifap ; ifa ; ifa = ifa->ifa_next ) {
		struct sockaddr_in *ina = (struct sockaddr_in *)ifa->ifa_addr;
		if ( ina->sin_family == AF_INET ) {
			if ( !strcmp( ifa->ifa_name, "en0" ) ) {
				goodInterface = true;
			}
		}
	}
	freeifaddrs( ifap );
	
	return goodInterface;
}

int InterfaceIndexForAddress( struct sockaddr_in *adr ) {
	// FIXME: compare against getifaddrs 
	return 0;
}

struct sockaddr_in AddressForInterfaceName( const char *ifname ) {	
	struct sockaddr_in s;
	memset( &s, 0, sizeof( s ) );
	
	struct ifaddrs *ifap;
	if ( getifaddrs( &ifap ) == -1 ) {
		perror( "getifaddrs()" );
		return s;
	}
	
	struct ifaddrs *ifa;
	for ( ifa = ifap ; ifa ; ifa = ifa->ifa_next ) {
		struct sockaddr_in *ina = (struct sockaddr_in *)ifa->ifa_addr;
		if ( ina->sin_family == AF_INET && !strcmp( ifa->ifa_name, ifname ) ) {
			byte *ip = (byte *)&ina->sin_addr;
			printf( "AddressForInterfaceName( %s ) = ifa_name: %s ifa_flags: %i sa_family: %i=AF_INET ip: %i.%i.%i.%i\n", 
				   ifname, ifa->ifa_name, ifa->ifa_flags,
				   ina->sin_family, ip[0], ip[1], ip[2], ip[3]  );
			freeifaddrs( ifap );
			return *ina;
		}
	}
	freeifaddrs( ifap );
	printf( "AddressForInterfaceName( %s ): Couldn't find IP address\n", ifname );
	return s;
}

int InterfaceIndexForName( const char *ifname ) {
	struct if_nameindex *ifnames = if_nameindex();
	if ( !ifnames ) {
		perror( "if_nameindex()" );
		return 0;
	}
	for ( int i = 0 ; ifnames[i].if_index != 0 ; i++ ) {
		if ( !strcmp( ifname, ifnames[i].if_name ) ) {
			int	index = ifnames[i].if_index;
			if_freenameindex( ifnames );
			return index;
		}
	}	
	printf( "InterfaceIndexForName( %s ): Couldn't find interface\n", ifname );
	if_freenameindex( ifnames );
	return 0;
}

struct sockaddr_in AddressForInterfaceIndex( int interfaceIndex ) {
	struct sockaddr_in addr;

	memset( &addr, 0, sizeof( addr ) );
	addr.sin_len = sizeof( addr );
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;

	if ( interfaceIndex == 0 ) {
		return addr;
	}
		
	struct if_nameindex *ifnames = if_nameindex();
	if ( !ifnames ) {
		perror( "if_ameindex()" );
		return addr;
	}
	for ( int i = 0 ; ifnames[i].if_index != 0 ; i++ ) {
		if ( ifnames[i].if_index == interfaceIndex ) {
			addr = AddressForInterfaceName( ifnames[i].if_name );
			if_freenameindex( ifnames );
			return addr;
		}
	}	
	printf( "AddressForInterfaceIndex( %i ): Couldn't find interface\n", interfaceIndex );
	if_freenameindex( ifnames );
	return addr;
}

struct sockaddr_in gameSocketAddress;

int	UDPSocket( const char *interfaceName, int portnum ) {
	int udpSocket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
	if ( udpSocket == -1 ) {
		Com_Printf( "UDP socket failed: %s\n", strerror( errno ) );
		return -1;
	}
	struct sockaddr_in addr = AddressForInterfaceName( interfaceName );
	addr.sin_port = htons( portnum );
	byte *ip = (byte *)&addr.sin_addr;
	gameSocketAddress = addr;
	Com_Printf( "UDPSocket( %s, %i ) = %i.%i.%i.%i\n", interfaceName, portnum,
			   ip[0], ip[1], ip[2], ip[3] );	
	if ( bind( udpSocket, (struct sockaddr *)&addr, sizeof( addr ) ) == -1 ) {
		Com_Printf( "UDP bind failed: %s\n", strerror( errno ) );
		close( udpSocket );
		return -1;
	}
	
#if 0	
	// enable broadcast
	int	on = 1;
	if ( setsockopt( udpSocket, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on) ) == -1 ) {
		Com_Printf( "UDP setsockopt failed: %s\n", strerror( errno ) );
		close( udpSocket );
		return -1;
	}
#endif

#if 0	
	// set the type-of-service, in hopes that the link level drivers use it
	// to stop buffering huge amounts of data when there are line errors
	int tos = 0x10; /* IPTOS_LOWDELAY; */       /* see <netinet/in.h> */
	if ( setsockopt( udpSocket, IPPROTO_IP, IP_TOS, &tos, sizeof(tos) ) == -1 ) {
		Com_Printf( "setsockopt IP_TOS failed: %s\n", strerror( errno ) );
	}
#endif
	
	// enable non-blocking IO
	if ( fcntl( udpSocket, F_SETFL, O_NONBLOCK ) == -1 ) {
		Com_Printf( "UDP fcntl failed: %s\n", strerror( errno ) );
		close( udpSocket );
		return -1;
	}
	
	return udpSocket;
}

