
/*
	highscore-server.c -- The hiscore server using TCP/IP stream.
	Created on: 2008/12/05 18:56:11
	~yas/dsys/highscore/tcp/highscore-server.c
*/

#include <stdio.h>	/* stderr, fprintf() */
#include <stdlib.h>	/* strtol() */
#include <string.h>	/* memcpy() */
#include <sys/types.h>	/* socket() */
#include <sys/socket.h>	/* socket() */
#include <netinet/in.h>	/* struct sockaddr_in, INADDR_ANY */
#include <netdb.h>	/* getnameinfo() */
#include "highscore-tcp.h"
#include "marshaling-burffer.h"

/* From Coins System Program */
extern int tcp_acc_port( int portno );
extern void tcp_peeraddr_print( int com );
extern void sockaddr_print( struct sockaddr *addrp, socklen_t addr_len );
extern int tcp_acc_port( int portno );

/* Hiscore data in memory */
static score_record_t hiscore_records[HIGHSCORE_MAX_RECORDS];
static int	      hiscore_nelements;

static void hiscore_server( int portno );
static void hiscore_request_reply( int com );
static void print_my_host_port( int portno );
static int insert_score( score_record_t records[], int len, int nelement,
			 int score, char *user );
static int find_posision( score_record_t records[], int len, int nelement,
			  int score );

static void usage( char *comname ) {
	fprintf(stderr,"Usage: %% %s portno\n", comname);
	exit( 1 );
}

main( int argc, char *argv[], char *envp[] ) {
    int portno;
	if( argc != 2 )
	    usage( argv[0] );
	portno = strtol( argv[1], 0, 10 );
	hiscore_server( portno );
}

static void hiscore_server( int portno ) {
    int acc,com ;
	acc = tcp_acc_port( portno );
	if( acc<0 )
	    exit( -1 );
	print_my_host_port( portno );
	printf("(To stop this server, Press ^C)\n");
	while( 1 ) {
	    if( (com = accept( acc,0,0 )) < 0 ) {
		perror("accept");
		exit( -1 );
	    }
	    tcp_peeraddr_print( com );
	    hiscore_request_reply( com );
	}
}

static void hiscore_request_reply( int com ) {
    marbuf_t request, reply;
    int cmd;
	marbuf_init( &request,HISCORE_PROTO_MAX_MESSAGE_SIZE );
	marbuf_init( &reply,HISCORE_PROTO_MAX_MESSAGE_SIZE );
	if( marbuf_receive_message( &request, com ) < 0 ) {
	    perror("read");
	    goto error0;
	}
	
	if( !marbuf_unmarshal_int( &request, &cmd ) ) {
	    perror("request_msg cmd");
	    goto error0;
	}
	switch( cmd ) {
	case HISCORE_PROTO_PUT_SCORE:
	{
	    int score ; char name[HIGHSCORE_NAME_LEN];
	    if( !marbuf_unmarshal_int( &request, &score ) )
		goto error1;
	    if( !marbuf_unmarshal_byte_array( &request, name, HIGHSCORE_NAME_LEN ) )
		goto error1;
	    hiscore_nelements = insert_score( hiscore_records, HIGHSCORE_MAX_RECORDS,
					      hiscore_nelements, score, name );
	    if( !marbuf_marshal_int( &reply, HISCORE_PROTO_OK ) )
		goto error1;
	    break;
	}
	case HISCORE_PROTO_GET_HISCORE:
	{
	    int len,i,n ; 
	    if( !marbuf_unmarshal_int( &request, &len ) )
		goto error1;
	    if( len > HIGHSCORE_MAX_RECORDS )
		len = HIGHSCORE_MAX_RECORDS;
	    n = len < hiscore_nelements ? len : hiscore_nelements ;
	    /* return n and hiscore_records[0..n-1] to client. */
	    if( !marbuf_marshal_int( &reply, n ) )
		goto error1;
	    for( i=0 ; i<n; i++ )
	    {
	        if( !marbuf_marshal_int( &reply,hiscore_records[i].score ) )
		    goto error1;
	        if( !marbuf_marshal_byte_array(
			&reply,hiscore_records[i].name,HIGHSCORE_NAME_LEN ) )
		    goto error1;
	    }
	     break;
	 }
	default:
	    marbuf_marshal_int( &reply, HISCORE_PROTO_NO_COMMAND );
	    break;
	}
	marbuf_send_message( &reply, com );

error0:	marbuf_final( &request );
	marbuf_final( &reply );
	close( com );
	return;

error1:	marbuf_marshal_int( &reply, HISCORE_PROTO_MARSHAL_ERROR );
	marbuf_send_message( &reply, com );
	goto error0;
}

static void print_my_host_port( int portno ) {
    char hostname[100] ;
	gethostname( hostname,sizeof(hostname) );
	hostname[99] = 0 ;
	printf("run client %s %d \n",hostname, portno );
}

static int insert_score( score_record_t records[], int len, int nelement,
			 int score, char *user ) {
    int pos, copy_records;
    	pos  = find_posision( records, len, nelement, score );
	if( pos < 0 )
	    return( -1 );
	if( nelement == len )
	    copy_records = nelement - pos - 1;
	else
	    copy_records = nelement - pos ;
	memcpy( &records[pos+1], &records[pos],
		copy_records*sizeof(score_record_t) );
	memset( &records[pos], 0, sizeof(score_record_t) );
	records[pos].score = score ;
	snprintf( records[pos].name, HIGHSCORE_NAME_LEN, "%s", user );
	return( nelement == len ? nelement : nelement+1 );
}

static int find_posision( score_record_t records[], int len, int nelement,
			  int score ) {
    score_record_t *pos;
    int i;
    	for( i=0; i<nelement; i++ ) {
	    if( records[i].score <= score )
		return( i );
	}
	return( len == nelement ? -1 : nelement );
}

/* Coins System Program */
#define	BUFFERSIZE	1024

void tcp_peeraddr_print( int com ) {
    struct sockaddr_storage addr ;
    socklen_t addr_len ; /* MacOSX: __uint32_t */
	addr_len = sizeof( addr );
    	if( getpeername( com, (struct sockaddr *)&addr, &addr_len  )<0 ) {
	    perror("tcp_peeraddr_print");
	    return;
	}
    	printf("[%d] connection (fd==%d) from ",getpid(),com );
	sockaddr_print( (struct sockaddr *)&addr, addr_len );
	printf("\n");
}

void sockaddr_print( struct sockaddr *addrp, socklen_t addr_len ) {
    char host[BUFFERSIZE] ;
    char port[BUFFERSIZE] ;
	if( getnameinfo(addrp, addr_len, host, sizeof(host),
			port, sizeof(port), NI_NUMERICHOST|NI_NUMERICSERV)<0 )
	    return;
    	printf("%s:%s", host, port );
}

int tcp_acc_port( int portno ) {
    struct sockaddr_in addr ;
    int addr_len ;
    int s ;
	if( (s = socket(PF_INET, SOCK_STREAM, 0)) < 0 ) {
	    perror("socket");
	    return( -1 );
	}
	memset( &addr, 0, sizeof(addr) );
	addr.sin_family = AF_INET ;
	addr.sin_addr.s_addr = INADDR_ANY ;
	addr.sin_port = htons( portno );

	if( bind(s,(struct sockaddr *)&addr,sizeof(addr)) < 0 ) {
	    perror("bind");
	    fprintf(stderr,"port number %d is already used. wait a moment or kill another program.\n", portno );
	    return( -1 );
	}
	if( listen( s, 5 ) < 0 ) {
	    perror("listen");
	    close( s );
	    return( -1 );
	}
	return( s );
}
