/*	$Id: gbnserver.c,v 1.9 2002/04/07 17:22:17 riq Exp $	*/
/*
 * Gnome Batalla Naval
 * Servidor
 * 
 * (c) 1995-2001 Ricardo Calixto Quesada
 * mailto: 
 *	riq@core-sdi.com
 *
 * http://batnav.sourceforge.net
 *  
 * Modificaciones para adaptar cliente Win16 y bugfixes por:
 * Horacio Peņa ( horape@compendium.com.ar )
 */

/* INCLUDES */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/resource.h>
#include <sys/un.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <syslog.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>

/* Includes de la interface */
#include <config.h>
#include <gnome.h>

#include "protocol.h"			/* definicion del protocolo */
#include "server.h"
#include "check_board.h"
#include "play.h"
#include "g_interface.h"
#include "parser.h"
#include "version.h"
#include "bnwrite.h"
#include "batnavggz.h"
#include "common.h"



/* algunos defines lindos del UNP second edition volume 1 */
#define LISTENQ	50
#define SA struct sockaddr

/* VARIABLES GLOBALES y STRUCT y TYPEDEF y ... */

/* Estas, antes estaban en main */

struct st_datos usuario;

extern struct { char *nombre; } st_nombres[];

/* This describes all the arguments we understand.  */
static struct poptOption options[] =
{
	{ "port", 'p', POPT_ARG_INT, &usuario.port, 0,  N_("PORT"), N_("Port number. Default is 1995")},
#ifdef WITH_GGZ
	{ "ggz", 0, POPT_ARG_NONE, &usuario.with_ggz, 0,  NULL, N_("Enables GGZ mode")},
#endif /* WITH_GGZ */
	{ NULL, '\0',  0, NULL  }
};

void init_args( void )
{
	usuario.port = gnome_config_get_int_with_default("/gbnserver/data/port=1995",NULL);

	gnome_config_set_int("/gbnserver/data/port",usuario.port);
	gnome_config_sync();
}
/***************************************************************************
		funciones relacionadas con las tablas 
***************************************************************************/

/* write to board */
gint wtable( gint jug, gchar *str ) 
{
	gint i,x,y;

	if(strlen(str)!=100) 
		return FALSE;

	x=0;y=0;
	for(i=0;i<100;i++) {
		usuario.table[jug].p[x][y]=str[i];
		x++;
		if(x>=10) {
			x=0;
			y++;
		}
	}
	return( algoritmo(jug));
}

/* clean board */
void ctable( int jug) 
{
	int i,x,y;
	x=0;y=0;
	for(i=0;i<100;i++) {
		usuario.table[jug].p[x][y]=NOBARCO;
		x++;
		if(x>=10) {
			x=0;
			y++;
		}
	}
}

void borrar_jugador( int num_jug )
{
	if( ! usuario.with_ggz )
		gdk_input_remove( usuario.tag[num_jug] );
#ifdef WITH_GGZ
	else
		batnavggz_del_fd(usuario.nro_fd[num_jug]);
#endif /* WITH_GGZ */

	if(usuario.nro_fd[num_jug]!=-1) {
		close( usuario.nro_fd[num_jug] );
		usuario.nro_fd[num_jug]=-1;
	}
	usuario.nro_tot[num_jug]=DISCON;

	usuario.hits[num_jug]=0;
	ctable(num_jug);  
	usuario.names[num_jug][0]=0;

	say_in_clist( num_jug, C_STATUS, _(st_nombres[DISCON].nombre) );
	say_in_clist( num_jug, C_NAME, "- - -" );
	say_in_clist( num_jug, C_CLIVER, "- - -" );
	say_in_clist( num_jug, C_HOSTNAME, "- - -" );
	say_in_clist( num_jug, C_LASTTOKEN, "- - -" );

	return;
}

void salir_bien( void )
{
	int i;
	for( i=0;i<MAXPLAYER;i++) {
		if( usuario.nro_tot[i]>DISCON) {
			bnwrite(usuario.nro_fd[i],BN_DISCON"=%i",i);
			borrar_jugador( i );
		}
	}
	unlink(BATNAV_UDSOCKET);
}

void broadcast( char *fmt, ...)
{
	int i;

	char t[5000];
	va_list args;
	va_start( args, fmt );

	vsprintf(t,fmt,args);
	va_end(args);

	for(i=0;i<MAXPLAYER;i++) {
		if( usuario.nro_tot[i]>DISCON && usuario.nro_fd[i]!=-1 )  {

			bnwrite(usuario.nro_fd[i],"%s",t);
		}
	}
	return;
}

/*****************************************************************************
		el barco esta hundido ? y todo tipo de funciones
*****************************************************************************/
int quebarco( int i,int x, int y) 
{
	if( (x<0) || (x>9) || (y<0) || (y>9) )
		return NOBARCO;
	return( usuario.table[i].p[x][y]);
}

/* Se fija si el barco es hundido */
int r_eshundido(int i,	/* Numero de Jugador */
		    int x,	/* X del barco */
		    int y,	/* Y del barco */
		    int xx,	/* Direccion X del barco */
		    int yy	/* Direccion Y del barco */
		    )
{
	if(quebarco(i,x,y)<=NOBARCO)
		  return 0;
	if(quebarco(i,x,y)==TOCADO)
		  return r_eshundido(i,x+xx,y+yy,xx,yy);
	return 1;	/* No es hundido */
}

/* Pinta al barco hundido */
void r_pihundido(int i,	/* Numero de Jugador */
		    int x,	/* X del barco */
		    int y,	/* Y del barco */
		    int xx,	/* Direccion X del barco */
		    int yy	/* Direccion Y del barco */
		    )
{
	if(quebarco(i,x,y)<=NOBARCO)
		  return;
	if(quebarco(i,x,y)==TOCADO) {
		usuario.table[i].p[x][y]=HUNDIDO;
		bnwrite(usuario.nro_fd[i],BN_FIRE"=%i,%i,%i",x,y,HUNDIDO);

		return r_pihundido(i,x+xx,y+yy,xx,yy);
	}
	printf("server: gbnserver.c: r_pihundido error\n");
}

int eshundido(int i,int x,int y) 
{
	gint a;

	if(quebarco(i,x,y)<=NOBARCO)
		return FALSE;
	if(quebarco(i,x,y)==HUNDIDO)
		  return TRUE;

	usuario.table[i].p[x][y]=TOCADO;	/* Tocado por ahora */
	a =	r_eshundido(i,x-1,y,-1,0) + 
		r_eshundido(i,x+1,y,+1,0) + 
		r_eshundido(i,x,y-1,0,-1) + 
		r_eshundido(i,x,y+1,0,+1);
	if(a==0) {
		r_pihundido(i,x,y,-1,0);
		r_pihundido(i,x+1,y,+1,0);
		r_pihundido(i,x,y-1,0,-1);
		r_pihundido(i,x,y+1,0,+1);

		usuario.hits[i]++;
		if(usuario.hits[i]==10) { /* con 10 barcos hundidos uno pierde */
			broadcast(BN_LOST"=%i",i);
			usuario.nro_tot[i]=PERDIO;
			say_in_clist( i, C_STATUS, _(st_nombres[PERDIO].nombre) );
		}
		return TRUE;
	}
	return FALSE;
}


int accept_new_player( int fd )
{
	int j;

	/* El servidor esta lleno ? */
	for(j=0;j<MAXPLAYER;j++) {
		if(usuario.nro_tot[j]==DISCON )
			break;
	}
	
	if(j>=MAXPLAYER) {
		bnwrite(fd,BN_SER_FULL);
		close(fd);
		return -1;
	}

	usuario.nro_tot[j]=CONNEC;
	usuario.nro_fd[j]=fd;

	return j;
}

/***************************************************************************** 
			funciones control de child y juego
***************************************************************************/

BATNAV_STATUS find_inaddr( int fd, char *name, int namelen )
{
	struct sockaddr *sa;
	socklen_t slen = 128;


	if( fd <= 0)
		return BATNAV_STATUS_ERROR;

	if( (sa=malloc(slen)) == NULL )
		return BATNAV_STATUS_ERROR;

	if( getpeername( fd, sa, &slen ) == -1) {
		strncpy(name, _("Unknown"), namelen);
		name[namelen]=0;

		free(sa);
		return BATNAV_STATUS_ERROR;
	}

	switch(sa->sa_family) {
	case AF_INET: {
		struct sockaddr_in *sin = (struct sockaddr_in*) sa;
		inet_ntop( AF_INET, &sin->sin_addr,name, namelen);
		break;
		}
	case AF_INET6: {
		struct sockaddr_in6 *sin6 = (struct sockaddr_in6*) sa;
		inet_ntop( AF_INET6, &sin6->sin6_addr,name, namelen);
		break;
		}
	case AF_UNIX: {
		struct sockaddr_un *unp = (struct sockaddr_un *) sa;
		if(unp->sun_path[0]==0)
			strncpy(name,_("Unknow"),namelen);
		else
			snprintf(name, namelen, "%s", unp->sun_path);
		break;
		}
	default:
		strncpy(name,_("Unknow"),namelen);
		break;
	}

	name[namelen]=0;
	free(sa);

	return BATNAV_STATUS_SUCCESS;
}

void ex_loop( gpointer data, gint que_sock, GdkInputCondition GDK_INPUT_READ)
{
	int i,j,k,client_len;
        char str[150];
        struct sockaddr client;

	client_len = sizeof(client);
	k = que_sock;
	
	if( que_sock==usuario.sock ) { /* AF_INET */
		if( (que_sock=accept(usuario.sock, (struct sockaddr *) &client,&client_len)) < 0 ) {
			perror("accept error");
			return;
		} 
	}

	if( (j=accept_new_player(que_sock)) < 0)
		return;

	i = gdk_input_add( que_sock, GDK_INPUT_READ, play_batnav, NULL );
	if(i<0) {
		usuario.nro_fd[j]=-1;
		usuario.nro_tot[j]=DISCON;
		close(que_sock);
		return;
	}

	usuario.tag[j]=i;

        find_inaddr(que_sock,str,sizeof(str)-1);

	say_in_clist( j, C_HOSTNAME, str);
	say_in_clist( j, C_STATUS, _(st_nombres[CONNEC].nombre) );
	return;
}

/**
 * @fn void main_loop()
 */
void main_loop()
{
        usuario.sock = net_listen(NULL,usuario.port);
	
	init_screen();

	gdk_input_add( usuario.sock, GDK_INPUT_READ, ex_loop, NULL );

	gtk_main();
}

/*****************
 * main function *
 *****************/
int main(int argc, char *argv[]) 
{
	int i ;
	poptContext ctx=NULL;
	
	dont_run_as_root();

	bindtextdomain (PACKAGE, GNOMELOCALEDIR);
	textdomain (PACKAGE);

	init_args();

	ctx = poptGetContext(NULL,argc,argv,options,0);
	if(ctx) {
		while( poptGetNextOpt(ctx) != -1);
		poptFreeContext(ctx);
	}

	if( ! usuario.with_ggz)
		gnome_init_with_popt_table("gbnserver", BNVERSION, argc, argv, options,0, NULL);

	gethostname(usuario.server_name,PROT_MAX_LEN);        
   
	printf( "\n"
		BNVERSION"\n" 
		"(c) 1995,2001 by Ricardo Quesada (riq@core-sdi.com)\n"
		"Port Number:%i\n"
		"Server Name:%s\n"
		"Max Players:%i\n"
		,usuario.port,usuario.server_name,MAXPLAYER);
   
	/* Set default parameters */
	for(i=0;i<MAXPLAYER;i++) {
		/* init server */
		usuario.nro_tot[i]=DISCON;
		say_in_clist( i, C_STATUS, _(st_nombres[DISCON].nombre) );
		usuario.nro_fd[i]=-1;
		ctable(i);
	}
 
#ifdef WITH_GGZ
	if( usuario.with_ggz )
		batnavggz_main_loop();
	else
#endif /* WITH_GGZ */
		main_loop();

	return 0;
}
