/*
Copyright (C) 1997-2001 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.

*/

//
// net.c
//

#include "qcommon.h"
#include "net.h"

#define	MAX_LOOPBACK	4

typedef struct {
	byte	data[MAX_PACKETLEN];
	int		datalen;
} loopmsg_t;

typedef struct {
	loopmsg_t	msgs[MAX_LOOPBACK];
	int			get;
	int			send;
} loopback_t;

static cvar_t	*net_ip;
static cvar_t	*net_port;
static cvar_t	*net_clientport;
static cvar_t	*net_dropsim;
static cvar_t	*net_log_active;
static cvar_t	*net_log_name;
static cvar_t	*net_log_flush;
static cvar_t	*net_ignore_icmp;

static loopback_t	loopbacks[NS_COUNT];
static SOCKET		sockets[NS_COUNT] = { INVALID_SOCKET, INVALID_SOCKET };

static fileHandle_t	net_logFile;
static netflag_t	net_active;

void NET_LogPacket( const netadr_t *address, qboolean send, const byte *data, int dataSize );

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

/*
===================
NET_NetadrToSockadr
===================
*/
void NET_NetadrToSockadr( const netadr_t *a, struct sockaddr *s ) {
	memset( s, 0, sizeof( *s ) );

	switch( a->type ) {
	case NA_BROADCAST:
		((struct sockaddr_in *)s)->sin_family = AF_INET;
		((struct sockaddr_in *)s)->sin_port = a->port;
		((struct sockaddr_in *)s)->sin_addr.s_addr = INADDR_BROADCAST;
		break;
	case NA_IP:
		((struct sockaddr_in *)s)->sin_family = AF_INET;
		((struct sockaddr_in *)s)->sin_addr.s_addr = *(int *)&a->ip;
		((struct sockaddr_in *)s)->sin_port = a->port;
		break;
	default:
		Com_Error( ERR_FATAL, "NetadrToSockadr: bad address type" );
		break;
	}
}

/*
===================
NET_SockadrToNetadr
===================
*/
void NET_SockadrToNetadr( const struct sockaddr *s, netadr_t *a ) {
	memset( a, 0, sizeof( *a ) );

	a->type = NA_IP;
	*(int *)&a->ip = ((struct sockaddr_in *)s)->sin_addr.s_addr;
	a->port = ((struct sockaddr_in *)s)->sin_port;

}

/*
=============
NET_StringToSockaddr

localhost
idnewt
idnewt:28000
192.246.40.70
192.246.40.70:28000
=============
*/
qboolean NET_StringToSockaddr( const char *s, struct sockaddr *sadr ) {
	struct hostent	*h;
	char	*colon;
	char	copy[MAX_QPATH];

	memset( sadr, 0, sizeof( *sadr ) );

	((struct sockaddr_in *)sadr)->sin_family = AF_INET;
	((struct sockaddr_in *)sadr)->sin_port = 0;

	Q_strncpyz( copy, s, sizeof( copy ) );
	// strip off a trailing :port if present
	for( colon = copy ; *colon ; colon++ )
		if( *colon == ':' ) {
			*colon = 0;
			((struct sockaddr_in *)sadr)->sin_port =
                htons( (u_short)atoi( colon + 1 ) );
		}

	if( copy[0] >= '0' && copy[0] <= '9' ) {
		*(int *)&((struct sockaddr_in *)sadr)->sin_addr = inet_addr( copy );
	} else {
		if( !( h = gethostbyname( copy ) ) )
			return qfalse;
		*(int *)&((struct sockaddr_in *)sadr)->sin_addr =
            *(int *)h->h_addr_list[0];
	}


	return qtrue;
}

/*
===================
NET_IsEqualAdr
===================
*/
qboolean NET_IsEqualAdr( const netadr_t *a, const netadr_t *b ) {
	if( a->type != b->type ) {
		return qfalse;
	}

	switch( a->type ) {
	case NA_LOOPBACK:
		return qtrue;
	case NA_IP:
	case NA_BROADCAST:
		if( *( uint32 * )a->ip == *( uint32 * )b->ip &&
			a->port == b->port )
		{
			return qtrue;
		}
		return qfalse;
	default:
		Com_Error( ERR_FATAL, "NET_IsEqualAdr: bad address type: %i", a->type );
		break;
	}

	return qfalse;
}

/*
===================
NET_IsEqualBaseAdr

Compares without the port
===================
*/
qboolean NET_IsEqualBaseAdr( const netadr_t *a, const netadr_t *b ) {
	if( a->type != b->type ) {
		return qfalse;
	}

	switch( a->type ) {
	case NA_LOOPBACK:
		return qtrue;
	case NA_IP:
	case NA_BROADCAST:
		if( *( uint32 * )a->ip == *( uint32 * )b->ip ) {
			return qtrue;
		}
		return qfalse;
	default:
		Com_Error( ERR_FATAL, "NET_IsEqualBaseAdr: bad address type: %i", a->type );
		break;
	}

	return qfalse;
}

/*
===================
NET_IsLANAddress
===================
*/
qboolean NET_IsLANAddress( const netadr_t *adr ) {
	switch( adr->type ) {
	case NA_LOOPBACK:
		return qtrue;
	case NA_IP:
	case NA_BROADCAST:
		if( adr->ip[0] == 127 || adr->ip[0] == 10 ) {
			return qtrue;
		}
		if( ( adr->ip[0] == 192 && adr->ip[1] == 168 ) ||
			( adr->ip[0] == 172 && adr->ip[1] == 16 ) )
		{
			return qtrue;
		}
		return qfalse;
	default:
		Com_Error( ERR_FATAL, "NET_IsLANAddress: bad address type: %i", adr->type );
		break;
	}

	return qfalse;
}

/*
===================
NET_AdrToString
===================
*/
char *NET_AdrToString( const netadr_t *a ) {
	static	char	s[MAX_QPATH];

	switch( a->type ) {
	case NA_LOOPBACK:
		Com_sprintf( s, sizeof( s ), "loopback" );
		return s;
	case NA_IP:
	case NA_BROADCAST:
		Com_sprintf( s, sizeof( s ), "%i.%i.%i.%i:%i", a->ip[0], a->ip[1], a->ip[2], a->ip[3], ntohs( a->port ) );
		return s;
	default:
		Com_Error( ERR_FATAL, "NET_AdrToString: bad address type: %i", a->type );
		break;
	}

	return NULL;
}

/*
=============
NET_StringToAdr

localhost
idnewt
idnewt:28000
192.246.40.70
192.246.40.70:28000
=============
*/
qboolean NET_StringToAdr( const char *s, netadr_t *a ) {
	struct sockaddr sadr;
	
	if( !Q_stricmp( s, "localhost" ) ) {
		memset( a, 0, sizeof( *a ) );
		a->type = NA_LOOPBACK;
		return qtrue;
	}

	if( !NET_StringToSockaddr( s, &sadr ) )
		return qfalse;
	
	NET_SockadrToNetadr( &sadr, a );

	return qtrue;
}

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

LOOPBACK BUFFERS FOR LOCAL PLAYER

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

/*
=============
NET_GetLoopPacket
=============
*/
qboolean NET_GetLoopPacket( netsrc_t sock ) {
	loopback_t	*loop;
	loopmsg_t *loopmsg;

	loop = &loopbacks[sock];

	if( loop->send - loop->get > MAX_LOOPBACK - 1 ) {
		loop->get = loop->send - MAX_LOOPBACK + 1;
	}

	if( loop->get >= loop->send ) {
		return qfalse;
	}

	loopmsg = &loop->msgs[loop->get & (MAX_LOOPBACK-1)];
	loop->get++;

	memcpy( msg_read.data, loopmsg->data, loopmsg->datalen );
	msg_read.cursize = loopmsg->datalen;

	if( net_log_active->integer ) {
		NET_LogPacket( &net_from, qfalse, loopmsg->data, loopmsg->datalen );
	}

	return qtrue;

}


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

static void NetLogFile_Close( void ) {
	if( !net_logFile ) {
		return;
	}

	Com_Printf( "Closing %s\n", FS_GetFileName( net_logFile ) );

	FS_FCloseFile( net_logFile );
	net_logFile = 0;
}

static void NetLogFile_Open( void ) {
	uint32	mode;

	mode = net_log_active->integer > 1 ? FS_MODE_APPEND : FS_MODE_WRITE;

	if( net_log_flush->integer ) {
		mode |= FS_FLUSH_SYNC;
	}

	FS_FOpenFile( net_log_name->string, &net_logFile, mode );

	if( !net_logFile ) {
		Com_WPrintf( "Couldn't open %s\n", net_log_name->string );
		Cvar_SetInteger( "net_log_active", 0 );
		return;
	}

	Com_Printf( "Logging network packets to %s\n", net_log_name->string );
}

static void NetLogFileActive_OnChange( cvar_t *self, void *arg ) {
	if( !self->integer ) {
		NetLogFile_Close();
	} else {
		NetLogFile_Open();
	}	
}	

static void NetLogFileParam_OnChange( cvar_t *self, void *arg ) {
	if( net_log_active->integer ) {
		NetLogFile_Close();
		NetLogFile_Open();
	}	
}

/*
=============
NET_LogPacket
=============
*/
void NET_LogPacket( const netadr_t *address, qboolean send, const byte *data, int dataSize ) {
	int numRows;
	int i, j, c;

	if( !net_logFile ) {
		return;
	}

	FS_FPrintf( net_logFile, "%s : %s\n", send ? "send" : "recv", NET_AdrToString( address ) );

	numRows = ( dataSize + 15 ) / 16;
	for( i = 0; i < numRows; i++ ) {
		FS_FPrintf( net_logFile, "%04x : ", i * 16 );
		for( j = 0; j < 16; j++ ) {
			if( i * 16 + j < dataSize ) {
				FS_FPrintf( net_logFile, "%02x ", data[i * 16 + j] );
			} else {
				FS_FPrintf( net_logFile, "   " );
			}
		}
		FS_FPrintf( net_logFile, ": ", i );
		for( j = 0; j < 16; j++ ) {
			if( i * 16 + j < dataSize ) {
				c = data[i * 16 + j];
				FS_FPrintf( net_logFile, "%c", ( c < 32 || c > 127 ) ? '.' : c );
			} else {
				FS_FPrintf( net_logFile, " " );
			}
		}
		FS_FPrintf( net_logFile, "\n" );
	}

	FS_FPrintf( net_logFile, "\n" );

}

/*
=============
NET_GetPacket

Fills current read buffer with packet contents
=============
*/
int NET_GetPacket( netsrc_t sock, netadr_t *fromAddress ) {
	byte	buffer[MAX_PACKETLEN + 10];
	int 	ret;
	struct sockaddr from;
	uint32	fromlen;
	int		err;

	if( sock >= NS_COUNT ) {
		Com_Error( ERR_FATAL, "NET_GetPacket: bad socket" );
	}
	if( sockets[sock] == INVALID_SOCKET ) {
		return 0;
	}

	fromlen = sizeof( from );
	ret = recvfrom( sockets[sock], buffer, sizeof( buffer ), 0,
		( struct sockaddr * )&from, &fromlen );

	NET_SockadrToNetadr( &from, fromAddress );

	if( ret == -1 ) {
#ifdef WIN32
		err = WSAGetLastError();

		switch( err ) {
		case WSAEWOULDBLOCK:
			// wouldblock is silent
			break;
		case WSAECONNRESET:
			if( !net_ignore_icmp->integer ) {
				return -1;
			}
			break;
		case WSAEMSGSIZE:
			Com_WPrintf( "NET_GetPacket: oversize packet from %s\n",
				NET_AdrToString( fromAddress ) );
			break;
		default:
			Com_EPrintf( "NET_GetPacket: %s from %s\n",
				Sys_NetErrorString(), NET_AdrToString( fromAddress ) );
			break;
		}
#else
		err = errno;

		switch( err ) {
		case EWOULDBLOCK:
			// wouldblock is silent
			break;
		case ECONNREFUSED:
			if( !net_ignore_icmp->integer ) {
				return -1;
			}
			break;
		default:
			Com_EPrintf( "NET_GetPacket: %s from %s\n",
				Sys_NetErrorString(), NET_AdrToString( fromAddress ) );
			break;
		}
#endif
		return 0;
	}

	if( net_log_active->integer ) {
		NET_LogPacket( fromAddress, qfalse, buffer, ret );
	}
	
	if( ret > msg_read.maxsize ) {
		Com_WPrintf( "NET_GetPacket: oversize packet from %s\n",
			NET_AdrToString( fromAddress ) );
		return 0;
	}

	memcpy( msg_read.data, buffer, ret );
	msg_read.cursize = ret;

	

	return 1;

}

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

/*
=============
NET_SendPacket
=============
*/
int NET_SendPacket( netsrc_t sock, const netadr_t *to, int length, const byte *data ) {
	int		ret;
	struct sockaddr	addr;
    int    err;

	if( sock >= NS_COUNT ) {
		Com_Error( ERR_FATAL, "NET_GetPacket: bad socket" );
	}
	if( length < 0 ) {
		Com_Error( ERR_FATAL, "NET_SendPacket: bad length: %d\n", length );
	}
	if( length > MAX_PACKETLEN ) {
		Com_WPrintf( "NET_SendPacket: oversize packet: %d\n", length );
		return 0;
	}

	switch( to->type ) {
	case NA_LOOPBACK: {
			loopback_t	*loop;
			loopmsg_t *msg;

			if( net_dropsim->integer > 0 && ( rand() % 100 ) < net_dropsim->integer ) {
				return 0;
			}
	
			loop = &loopbacks[sock ^ 1];

			msg = &loop->msgs[loop->send & ( MAX_LOOPBACK - 1 )];
			loop->send++;
			
			memcpy( msg->data, data, length );
			msg->datalen = length;

			if( net_log_active->integer ) {
				NET_LogPacket( to, qtrue, data, length );
			}
		}
		return 1;
	case NA_IP:
	case NA_BROADCAST:
		break;
	default:
		Com_Error( ERR_FATAL, "NET_SendPacket: bad address type: %i", to->type );
		break;
	}	

	if( sockets[sock] == INVALID_SOCKET ) {
		return 0;
	}

	NET_NetadrToSockadr( to, &addr );

	ret = sendto( sockets[sock], data, length, 0, &addr, sizeof( addr ) );
	if( net_log_active->integer ) {
		NET_LogPacket( to, qtrue, data, length );
	}

	if( ret == SOCKET_ERROR ) {
#ifdef WIN32
		err = WSAGetLastError();

		switch( err ) {
		case WSAEWOULDBLOCK:
		case WSAEINTR:
			// wouldblock is silent
			break;
		case WSAECONNRESET:
		case WSAEHOSTUNREACH:
			if( !net_ignore_icmp->integer ) {
				return -1;
			}
			break;
		case WSAEADDRNOTAVAIL:
			// some PPP links do not allow broadcasts
			if( to->type == NA_BROADCAST ) {
				break;
			}
			// intentional fallthrough
		default:
			Com_EPrintf( "NET_SendPacket: %s to %s\n",
				Sys_NetErrorString(), NET_AdrToString( to ) );
			break;
		}
#else
		err = errno;

		switch( err ) {
		case EWOULDBLOCK:
			// wouldblock is silent
			break;
		case ECONNRESET:
        case EHOSTUNREACH:
        case ENETUNREACH:
        case ENETDOWN:
            if( !net_ignore_icmp->integer ) {
                return -1;
            }
			break;
		default:
			Com_EPrintf( "NET_SendPacket: %s (%d) to %s\n",
				Sys_NetErrorString(), err, NET_AdrToString( to ) );
			break;
		}
#endif

		return 0;
		
	}

	return 1;
}


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

/*
====================
NET_OpenServerIP
====================
*/
static void NET_OpenServerIP( void ) {
	int port;

	for( port = 0; port < PORT_MAX_SEARCH; port++ ) {
		sockets[NS_SERVER] = Sys_IPSocket( net_ip->string, net_port->integer + port );
		if( sockets[NS_SERVER] != INVALID_SOCKET ) {
			if( port ) {
				Cvar_SetInteger( "net_port", net_port->integer + port );
			}
			// set this for compatibility with game mods
			Cvar_SetInteger( "port", net_port->integer + port );
			return;
		}
	}

	if( dedicated->integer ) {
		Com_Error( ERR_FATAL, "Couldn't allocate dedicated server IP port" );
	}

	Com_Error( ERR_DROP, "Couldn't allocate server IP port" );

}

/*
====================
NET_OpenClientIP
====================
*/
static void NET_OpenClientIP( void ) {
	sockets[NS_CLIENT] = Sys_IPSocket( net_ip->string, net_clientport->integer );
	if( sockets[NS_CLIENT] != INVALID_SOCKET ) {
		return;
	}

	if( net_clientport->integer != PORT_ANY ) {
		sockets[NS_CLIENT] = Sys_IPSocket( net_ip->string, PORT_ANY );
		if( sockets[NS_CLIENT] != INVALID_SOCKET ) {
			Cvar_SetInteger( "net_clientport", PORT_ANY );
			return;
		}
	}

	Com_Error( ERR_DROP, "Couldn't allocate client IP port" );

}

/*
====================
NET_Sleep

sleeps msec or until net socket is ready
====================
*/
void NET_Sleep( int msec ) {
    struct timeval timeout;
	fd_set	fdset;
	SOCKET i;

	FD_ZERO( &fdset );
	i = 0;
	FD_SET( 0, &fdset ); // stdin
	if( sockets[NS_SERVER] != INVALID_SOCKET ) {
		FD_SET( sockets[NS_SERVER], &fdset ); // network socket
		i = sockets[NS_SERVER];
	}
	timeout.tv_sec = msec / 1000;
	timeout.tv_usec = ( msec % 1000 ) * 1000;
	select( i + 1, &fdset, NULL, NULL, &timeout );
}

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

/*
====================
NET_DumpHostInfo
====================
*/
static void NET_DumpHostInfo( struct hostent *h ) {
	byte **list;
	int i;

	Com_Printf( "Hostname: %s\n", h->h_name );

	list = (byte **)h->h_aliases;
	for( i=0 ; list[i] ; i++ ) {
		Com_Printf( "Alias   : %s\n", list[i] );
	}

	list = (byte **)h->h_addr_list;
	for( i=0 ; list[i] ; i++ ) {
		Com_Printf( "IP      : %d.%d.%d.%d\n",
			list[i][0] & 255,
			list[i][1] & 255,
			list[i][2] & 255,
			list[i][3] & 255 );
	}
}

/*
====================
NET_ShowIP_f
====================
*/
static void NET_ShowIP_f( void ) {
	char buffer[256];
	struct hostent *h;

	if( gethostname( buffer, sizeof( buffer ) ) == SOCKET_ERROR ) {
		Com_Printf( "Couldn't get machine hostname\n" );
		return;
	}

	if( !( h = gethostbyname( buffer ) ) ) {
		Com_Printf( "Couldn't get machine hostname\n" );
		return;
	}

	NET_DumpHostInfo( h );
}

/*
====================
NET_Dns_f
====================
*/
static void NET_Dns_f( void ) {
	char buffer[MAX_QPATH];
	char *p;
	struct hostent *h;
	u_long address;

	if( Cmd_Argc() != 2 ) {
		Com_Printf( "Usage: %s <address>\n", Cmd_Argv( 0 ) );
		return;
	}

	Cmd_ArgvBuffer( 1, buffer, sizeof( buffer ) );

	if( ( p = strchr( buffer, ':' ) ) != NULL ) {
		*p = 0;
	}

	if( ( address = inet_addr( buffer ) ) != INADDR_NONE ) {
		h = gethostbyaddr( (const char *)&address, sizeof( address ), AF_INET );
	} else {
		h = gethostbyname( buffer );
	}

	if( !h ) {
		Com_Printf( "Couldn't resolve %s\n", buffer );
		return;
	}

	NET_DumpHostInfo( h );
	
}

/*
====================
NET_Restart_f
====================
*/
static void NET_Restart_f( void ) {
	netflag_t flag = net_active;

	NET_Config( NET_NONE );
	NET_Config( flag );
}

/*
====================
NET_Config
====================
*/
void NET_Config( netflag_t flag ) {
	netsrc_t sock;

	if( flag == net_active ) {
		return;
	}

	if( flag == NET_NONE ) {
		// shut down any existing sockets
		for( sock = 0; sock < NS_COUNT; sock++ ) {
			if( sockets[sock] != INVALID_SOCKET ) {
				Sys_CloseSocket( sockets[sock] );
				sockets[sock] = INVALID_SOCKET;
			}
		}
		net_active = NET_NONE;
		return;
	}

	if( flag & NET_CLIENT ) {
		if( sockets[NS_CLIENT] == INVALID_SOCKET ) {
			NET_OpenClientIP();
		}
	}

	if( flag & NET_SERVER ) {
		if( sockets[NS_SERVER] == INVALID_SOCKET ) {
			NET_OpenServerIP();
		}
	}

	net_active |= flag;
}

/*
====================
NET_Init
====================
*/
void NET_Init( void ) {
	if( !Sys_InitNetworking() ) {
		return;
	}

	Cvar_Subsystem( CVAR_SYSTEM_NET );

	net_ip = Cvar_Get( "net_ip", "localhost", 0 );
	net_port = Cvar_Get( "net_port", va( "%i", PORT_SERVER ), 0 );
	net_clientport = Cvar_Get( "net_clientport", va( "%i", PORT_ANY ), 0 );
	net_dropsim = Cvar_Get( "net_dropsim", "0", 0 );
	net_log_active = Cvar_Get( "net_log_active", "0", 0 );
	net_log_active->changedFunc = NetLogFileActive_OnChange;
	net_log_name = Cvar_Get( "net_log_name", "qnetwork.log", 0 );
	net_log_name->changedFunc = NetLogFileParam_OnChange;
	net_log_flush = Cvar_Get( "net_log_flush", "0", 0 );
	net_log_flush->changedFunc = NetLogFileParam_OnChange;
	net_ignore_icmp = Cvar_Get( "net_ignore_icmp", "0", 0 );

	Cvar_Subsystem( CVAR_SYSTEM_GENERIC );

	Cmd_AddCommand( "net_restart", NET_Restart_f );
	Cmd_AddCommand( "showip", NET_ShowIP_f );
	Cmd_AddCommand( "dns", NET_Dns_f );

	NET_ShowIP_f();
}

/*
====================
NET_Shutdown
====================
*/
void NET_Shutdown( void ) {
	if( net_logFile ) {
		FS_FCloseFile( net_logFile );
		net_logFile = 0;
	}

	NET_Config( NET_NONE );

	Sys_ShutdownNetworking();

	Cmd_RemoveCommand( "net_restart" );
	Cmd_RemoveCommand( "showip" );
	Cmd_RemoveCommand( "dns" );
}

