/*
===========================================================================

Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").

Doom 3 Source Code 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 3 of the License, or
(at your option) any later version.

Doom 3 Source Code 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 Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <errno.h>
#include <sys/select.h>
#include <net/if.h>
#include <ifaddrs.h>

#include "sys/platform.h"
#include "framework/Common.h"
#include "framework/CVarSystem.h"
#include "sys/sys_public.h"

#include "sys/posix/posix_public.h"

idPort clientPort, serverPort;

idCVar net_ip( "net_ip", "localhost", CVAR_SYSTEM, "local IP address" );
idCVar net_port( "net_port", "", CVAR_SYSTEM | CVAR_INTEGER, "local IP port number" );

typedef struct {
	unsigned int ip;
	unsigned int mask;
} net_interface;

#define			MAX_INTERFACES	32
int				num_interfaces = 0;
net_interface	netint[MAX_INTERFACES];

/*
=============
NetadrToSockadr
=============
*/
static void NetadrToSockadr( const netadr_t * a, struct sockaddr_in *s ) {
	memset(s, 0, sizeof(*s));

	if ( a->type == NA_BROADCAST ) {
		s->sin_family = AF_INET;

		s->sin_port = htons( (short)a->port );
		*(int *) &s->sin_addr = -1;
	} else if ( a->type == NA_IP || a->type == NA_LOOPBACK ) {
		s->sin_family = AF_INET;

		*(int *) &s->sin_addr = *(int *) &a->ip;
		s->sin_port = htons( (short)a->port );
	}
}

/*
=============
SockadrToNetadr
=============
*/
static void SockadrToNetadr(struct sockaddr_in *s, netadr_t * a) {
	unsigned int ip = *(int *)&s->sin_addr;
	*(int *)&a->ip = ip;
	a->port = ntohs( s->sin_port );
	// we store in network order, that loopback test is host order..
	ip = ntohl( ip );
	if ( ip == INADDR_LOOPBACK ) {
		a->type = NA_LOOPBACK;
	} else {
		a->type = NA_IP;
	}
}

/*
=============
ExtractPort
=============
*/
static bool ExtractPort( const char *src, char *buf, int bufsize, int *port ) {
	char *p;
	strncpy( buf, src, bufsize );
	p = buf; p += Min( bufsize - 1, (int)strlen( src ) ); *p = '\0';
	p = strchr( buf, ':' );
	if ( !p ) {
		return false;
	}
	*p = '\0';
	*port = strtol( p+1, NULL, 10 );
	if ( ( *port == 0 && errno == EINVAL ) ||
		 ( ( *port == INT_MIN || *port == INT_MAX ) && errno == ERANGE ) ) {
		return false;
	}
	return true;
}

/*
=============
StringToSockaddr
=============
*/
static bool StringToSockaddr( const char *s, struct sockaddr_in *sadr, bool doDNSResolve ) {
	struct hostent *h;
	char buf[256];
	int port;

	memset( sadr, 0, sizeof( *sadr ) );
	sadr->sin_family = AF_INET;

	sadr->sin_port = 0;

	if (s[0] >= '0' && s[0] <= '9') {
		if ( !inet_aton( s, &sadr->sin_addr ) ) {
			// check for port
			if ( !ExtractPort( s, buf, sizeof( buf ), &port ) ) {
				return false;
			}
			if ( !inet_aton( buf, &sadr->sin_addr ) ) {
				return false;
			}
			sadr->sin_port = htons( port );
		}
	} else if ( doDNSResolve ) {
		// try to remove the port first, otherwise the DNS gets confused into multiple timeouts
		// failed or not failed, buf is expected to contain the appropriate host to resolve
		if ( ExtractPort( s, buf, sizeof( buf ), &port ) ) {
			sadr->sin_port = htons( port );
		}
		if ( !( h = gethostbyname( buf ) ) ) {
			return false;
		}
		*(int *) &sadr->sin_addr =
			*(int *) h->h_addr_list[0];
	}

	return true;
}

/*
=============
Sys_StringToAdr
=============
*/
bool Sys_StringToNetAdr( const char *s, netadr_t * a, bool doDNSResolve ) {
	struct sockaddr_in sadr;

	if ( !StringToSockaddr( s, &sadr, doDNSResolve ) ) {
		return false;
	}

	SockadrToNetadr( &sadr, a );
	return true;
}

/*
=============
Sys_NetAdrToString
=============
*/
const char *Sys_NetAdrToString( const netadr_t a ) {
	static char s[64];

	if ( a.type == NA_LOOPBACK ) {
		if ( a.port ) {
			idStr::snPrintf( s, sizeof(s), "localhost:%i", a.port );
		} else {
			idStr::snPrintf( s, sizeof(s), "localhost" );
		}
	} else if ( a.type == NA_IP ) {
		idStr::snPrintf( s, sizeof(s), "%i.%i.%i.%i:%i",
			a.ip[0], a.ip[1], a.ip[2], a.ip[3], a.port );
	}
	return s;
}

/*
==================
Sys_IsLANAddress
==================
*/
bool Sys_IsLANAddress( const netadr_t adr ) {
	int i;
	unsigned int ip;

#if ID_NOLANADDRESS
	common->Printf( "Sys_IsLANAddress: ID_NOLANADDRESS\n" );
	return false;
#endif

	if ( adr.type == NA_LOOPBACK ) {
		return true;
	}

	if ( adr.type != NA_IP ) {
		return false;
	}

	if ( !num_interfaces ) {
		return false;	// well, if there's no networking, there are no LAN addresses, right
	}

	for ( i = 0; i < num_interfaces; i++ ) {
		ip = ntohl( *( unsigned int *)&adr.ip );
		if( ( netint[i].ip & netint[i].mask ) == ( ip & netint[i].mask ) ) {
			return true;
		}
	}

	return false;
}

/*
===================
Sys_CompareNetAdrBase

Compares without the port
===================
*/
bool Sys_CompareNetAdrBase( const netadr_t a, const netadr_t b ) {
	if ( a.type != b.type ) {
		return false;
	}

	if ( a.type == NA_LOOPBACK ) {
		return true;
	}

	if ( a.type == NA_IP ) {
		if ( a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3] ) {
			return true;
		}
		return false;
	}

	common->Printf( "Sys_CompareNetAdrBase: bad address type\n" );
	return false;
}

/*
====================
NET_InitNetworking
====================
*/
void Sys_InitNetworking(void)
{
	unsigned int ip, mask;
	struct ifaddrs *ifap, *ifp;

	num_interfaces = 0;

	if( getifaddrs( &ifap ) < 0 ) {
		common->FatalError( "InitNetworking: SIOCGIFCONF error - %s\n", strerror( errno ) );
		return;
	}

	for( ifp = ifap; ifp; ifp = ifp->ifa_next ) {
		if ( !ifp->ifa_addr )
			continue;

		if ( ifp->ifa_addr->sa_family != AF_INET )
			continue;

		if ( !( ifp->ifa_flags & IFF_UP ) )
			continue;

		if ( !ifp->ifa_netmask )
			continue;

		common->Printf( "found interface %s - ", ifp->ifa_name);

		ip = ntohl( *( unsigned int *)&ifp->ifa_addr->sa_data[2] );
		mask = ntohl( *( unsigned int *)&ifp->ifa_netmask->sa_data[2] );

		if ( ip == INADDR_LOOPBACK ) {
			common->Printf( "loopback\n" );
		} else {
			common->Printf( "%u.%u.%u.%u/%u.%u.%u.%u\n",
							(ip >> 24) & 0xff, (ip >> 16) & 0xff,
							(ip >> 8) & 0xff, ip & 0xff,
							(mask >> 24) & 0xff, (mask >> 16) & 0xff,
							(mask >> 8) & 0xff, mask & 0xff );
		}

		netint[ num_interfaces ].ip = ip;
		netint[ num_interfaces ].mask = mask;
		num_interfaces++;

		if (num_interfaces >= MAX_INTERFACES)
			break;
	}
	freeifaddrs(ifap);
}

/*
====================
IPSocket
====================
*/
static int IPSocket( const char *net_interface, int port, netadr_t *bound_to = NULL ) {
	int newsocket;
	struct sockaddr_in address;
	int i = 1;

	if ( net_interface ) {
		common->Printf( "Opening IP socket: %s:%i\n", net_interface, port );
	} else {
		common->Printf( "Opening IP socket: localhost:%i\n", port );
	}

	if ( ( newsocket = socket( PF_INET, SOCK_DGRAM, IPPROTO_UDP ) ) == -1 ) {
		common->Printf( "ERROR: IPSocket: socket: %s", strerror( errno ) );
		return 0;
	}
	// make it non-blocking
#if defined(__GENODE__)
	int  flags = fcntl(newsocket, F_GETFL, 0);
	if (fcntl(newsocket, F_SETFL, flags | O_NONBLOCK) != 0) {
		common->Printf( "ERROR: IPSocket: O_NONBLOCK:%s\n",
		                strerror( errno ) );
		return 0;
	}
#else
	int on = 1;
	if ( ioctl( newsocket, FIONBIO, &on ) == -1 ) {
		common->Printf( "ERROR: IPSocket: ioctl FIONBIO:%s\n",
				   strerror( errno ) );
		return 0;
	}

	// make it broadcast capable
	if ( setsockopt( newsocket, SOL_SOCKET, SO_BROADCAST, (char *) &i, sizeof(i) ) == -1 ) {
		common->Printf( "ERROR: IPSocket: setsockopt SO_BROADCAST:%s\n", strerror( errno ) );
		return 0;
	}
#endif
	if ( !net_interface || !net_interface[ 0 ]
		|| !idStr::Icmp( net_interface, "localhost" ) ) {
		address.sin_addr.s_addr = INADDR_ANY;
	} else {
		StringToSockaddr( net_interface, &address, true );
	}

	if ( port == PORT_ANY ) {
		address.sin_port = 0;
	} else {
		address.sin_port = htons((short) port);
	}

	address.sin_family = AF_INET;

	if ( bind( newsocket, (const struct sockaddr *)&address, sizeof( address ) ) == -1 ) {
		common->Printf( "ERROR: IPSocket: bind: %s\n", strerror( errno ) );
		close( newsocket );
		return 0;
	}

	if ( bound_to ) {
		unsigned int len = sizeof( address );
		if ( (unsigned int)(getsockname( newsocket, (struct sockaddr *)&address, (socklen_t*)&len )) == -1 ) {
			common->Printf( "ERROR: IPSocket: getsockname: %s\n", strerror( errno ) );
			close( newsocket );
			return 0;
		}
		SockadrToNetadr( &address, bound_to );
	}

	return newsocket;
}

/*
==================
idPort::idPort
==================
*/
idPort::idPort() {
	netSocket = 0;
	memset( &bound_to, 0, sizeof( bound_to ) );
}

/*
==================
idPort::~idPort
==================
*/
idPort::~idPort() {
	Close();
}

/*
==================
idPort::Close
==================
*/
void idPort::Close() {
	if ( netSocket ) {
		close(netSocket);
		netSocket = 0;
		memset( &bound_to, 0, sizeof( bound_to ) );
	}
}

/*
==================
idPort::GetPacket
==================
*/
bool idPort::GetPacket( netadr_t &net_from, void *data, int &size, int maxSize ) {
	int ret;
	struct sockaddr_in from;
	int fromlen;

	if ( !netSocket ) {
		return false;
	}

	fromlen = sizeof( from );
	ret = recvfrom( netSocket, data, maxSize, 0, (struct sockaddr *) &from, (socklen_t *) &fromlen );

	if ( ret == -1 ) {
		if (errno == EWOULDBLOCK || errno == ECONNREFUSED) {
			// those commonly happen, don't verbose
			return false;
		}
		common->DPrintf( "idPort::GetPacket recvfrom(): %s\n", strerror( errno ) );
		return false;
	}

	assert( ret < maxSize );

	SockadrToNetadr( &from, &net_from );
	size = ret;
	return true;
}

/*
==================
idPort::GetPacketBlocking
==================
*/
bool idPort::GetPacketBlocking( netadr_t &net_from, void *data, int &size, int maxSize, int timeout ) {
	fd_set				set;
	struct timeval		tv;
	int					ret;

	if ( !netSocket ) {
		return false;
	}

	if ( timeout < 0 ) {
		return GetPacket( net_from, data, size, maxSize );
	}

	FD_ZERO( &set );
	FD_SET( netSocket, &set );

	tv.tv_sec = timeout / 1000;
	tv.tv_usec = ( timeout % 1000 ) * 1000;
	ret = select( netSocket+1, &set, NULL, NULL, &tv );
	if ( ret == -1 ) {
		if ( errno == EINTR ) {
			common->DPrintf( "idPort::GetPacketBlocking: select EINTR\n" );
			return false;
		} else {
			common->Error( "idPort::GetPacketBlocking: select failed: %s\n", strerror( errno ) );
		}
	}

	if ( ret == 0 ) {
		// timed out
		return false;
	}
	struct sockaddr_in from;
	int fromlen;
	fromlen = sizeof( from );
	ret = recvfrom( netSocket, data, maxSize, 0, (struct sockaddr *)&from, (socklen_t *)&fromlen );
	if ( ret == -1 ) {
		// there should be no blocking errors once select declares things are good
		common->DPrintf( "idPort::GetPacketBlocking: %s\n", strerror( errno ) );
		return false;
	}
	assert( ret < maxSize );
	SockadrToNetadr( &from, &net_from );
	size = ret;
	return true;
}

/*
==================
idPort::SendPacket
==================
*/
void idPort::SendPacket( const netadr_t to, const void *data, int size ) {
	int ret;
	struct sockaddr_in addr;

	if ( to.type == NA_BAD ) {
		common->Warning( "idPort::SendPacket: bad address type NA_BAD - ignored" );
		return;
	}

	if ( !netSocket ) {
		return;
	}

	NetadrToSockadr( &to, &addr );

	ret = sendto( netSocket, data, size, 0, (struct sockaddr *) &addr, sizeof(addr) );
	if ( ret == -1 ) {
		common->Printf( "idPort::SendPacket ERROR: to %s: %s\n", Sys_NetAdrToString( to ), strerror( errno ) );
	}
}

/*
==================
idPort::InitForPort
==================
*/
bool idPort::InitForPort( int portNumber ) {
	netSocket = IPSocket( net_ip.GetString(), portNumber, &bound_to );
	if ( netSocket <= 0 ) {
		netSocket = 0;
		memset( &bound_to, 0, sizeof( bound_to ) );
		return false;
	}
	return true;
}

//=============================================================================

/*
==================
idTCP::idTCP
==================
*/
idTCP::idTCP() {
	fd = 0;
	memset(&address, 0, sizeof(address));
}

/*
==================
idTCP::~idTCP
==================
*/
idTCP::~idTCP() {
	Close();
}

/*
==================
idTCP::Init
==================
*/
bool idTCP::Init( const char *host, short port ) {
	struct sockaddr_in sadr;
	if ( !Sys_StringToNetAdr( host, &address, true ) ) {
		common->Printf( "Couldn't resolve server name \"%s\"\n", host );
		return false;
	}
	address.type = NA_IP;
	if (!address.port) {
		address.port = port;
	}
	common->Printf( "\"%s\" resolved to %i.%i.%i.%i:%i\n", host,
		address.ip[0], address.ip[1], address.ip[2], address.ip[3],  address.port );
	NetadrToSockadr(&address, &sadr);

	if (fd) {
		common->Warning("idTCP::Init: already initialized?\n");
	}

	if ((fd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
		fd = 0;
		common->Printf("ERROR: idTCP::Init: socket: %s\n", strerror(errno));
		return false;
	}

	if ( connect( fd, (const sockaddr *)&sadr, sizeof( sadr ) ) == -1 ) {
		common->Printf( "ERROR: idTCP::Init: connect: %s\n", strerror( errno ) );
		close( fd );
		fd = 0;
		return false;
	}

	int status;
	if ((status = fcntl(fd, F_GETFL, 0)) != -1) {
		status |= O_NONBLOCK; /* POSIX */
		status = fcntl(fd, F_SETFL, status);
	}
	if (status == -1) {
		common->Printf("ERROR: idTCP::Init: fcntl / O_NONBLOCK: %s\n", strerror(errno));
		close(fd);
		fd = 0;
		return false;
	}

	common->DPrintf("Opened TCP connection\n");
	return true;
}

/*
==================
idTCP::Close
==================
*/
void idTCP::Close() {
	if (fd) {
		close(fd);
	}
	fd = 0;
}

/*
==================
idTCP::Read
==================
*/
int idTCP::Read(void *data, int size) {
	int nbytes;

	if (!fd) {
		common->Printf("idTCP::Read: not initialized\n");
		return -1;
	}

#if defined(_GNU_SOURCE) && defined(TEMP_FAILURE_RETRY)
	// handle EINTR interrupted system call with TEMP_FAILURE_RETRY -  this is probably GNU libc specific
	if ( ( nbytes = TEMP_FAILURE_RETRY( read( fd, data, size ) ) ) == -1 ) {
#else
	do {
	  nbytes = read( fd, data, size );
	} while ( nbytes == -1 && errno == EINTR );
	if ( nbytes == -1 ) {
#endif
		if (errno == EAGAIN) {
			return 0;
		}
		common->Printf("ERROR: idTCP::Read: %s\n", strerror(errno));
		Close();
		return -1;
	}

	// a successful read of 0 bytes indicates remote has closed the connection
	if ( nbytes == 0 ) {
		common->DPrintf( "idTCP::Read: read 0 bytes - assume connection closed\n" );
		return -1;
	}

	return nbytes;
}

/*
==================
idTCP::Write
==================
*/

static void got_SIGPIPE( int signum ) {
	common->Printf( "idTCP: SIGPIPE\n" );
}

int	idTCP::Write(void *data, int size) {
	int nbytes;

	if ( !fd ) {
		common->Printf( "idTCP::Write: not initialized\n");
		return -1;
	}

	struct sigaction bak_action;
	struct sigaction action;

	action.sa_handler = got_SIGPIPE;
	sigemptyset( &action.sa_mask );
	action.sa_flags = 0;

	if ( sigaction( SIGPIPE, &action, &bak_action ) != 0 ) {
		common->Printf( "ERROR: idTCP::Write: failed to set temporary SIGPIPE handler\n" );
		Close();
		return -1;
	}

#if defined(_GNU_SOURCE) && defined(TEMP_FAILURE_RETRY)
	// handle EINTR interrupted system call with TEMP_FAILURE_RETRY -  this is probably GNU libc specific
	if ( ( nbytes = TEMP_FAILURE_RETRY ( write( fd, data, size ) ) ) == -1 ) {
#else
	  do {
		nbytes = write( fd, data, size );
	  } while ( nbytes == -1 && errno == EINTR );
	  if ( nbytes == -1 ) {
#endif
		common->Printf( "ERROR: idTCP::Write: %s\n", strerror( errno ) );
		Close();
		return -1;
	}

	if ( sigaction( SIGPIPE, &bak_action, NULL ) != 0 ) {
		common->Printf( "ERROR: idTCP::Write: failed to reset SIGPIPE handler\n" );
		Close();
		return -1;
	}

	return nbytes;
}
