#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/tcp.h>
#include<netdb.h>
#include<sys/time.h>
#include<errno.h>

#include"protocol.h"
#include"server.h"

#define TCP_PROTO 6   /* TCPのプロトコルナンバー */


unsigned char Send_buf[128];
unsigned char *Send_write;

static int syserr( char *msg )
{
  fprintf( stderr,"%s: ",Myname );
  perror( msg );
}


static void quit_connection( void )
{
  static unsigned char msg[2] = {2,MSG_QUIT};

  write( P[0].sd,msg,2 );
  write( P[1].sd,msg,2 );
  close( P[0].sd );
  close( P[1].sd );
}



int init_network( int port )
{
  struct sockaddr_in sin;
  static int on = 1;

  if( (Sock = socket(AF_INET,SOCK_STREAM,0)) < 0 ){
    syserr( "socket" );
    return -1;
  }

  if( setsockopt( Sock,SOL_SOCKET,SO_REUSEADDR,(char*)&on,sizeof(on) ) < 0 ){
    syserr("setsockopt" );
    return -1;
  }
  memset( &sin,0,sizeof(struct sockaddr_in) );
  sin.sin_family = AF_INET;
  sin.sin_port = htons( port );

  if( bind(Sock,(struct sockaddr *)&sin,sizeof(sin)) < 0 ){
    syserr( "bind" );
    return -1;
  }

  if( listen( Sock,5 ) < 0 ){
    syserr( "listen" );
    return -1;
  }

  return 0;

}

static int read_buf( int p )
{
  int i;

  if( P[p].recv_len >= 128 )
    return 1;

 restart:
  if( (i=read(P[p].sd,P[p].recv_buf+P[p].recv_len,128-P[p].recv_len))<0 ){
    if( errno == EINTR )
      goto restart;

    syserr( "read" );
    quit_connection();
    exit(1);
  }
  if( i == 0 )
    return 0;

  P[p].recv_len += i;
  return 1;
}

void fill_buf( void )
{
  int p,i;
  fd_set fds;

  FD_ZERO( &fds );

  if( P[0].recv_len < 128 )
    FD_SET( P[0].sd,&fds );

  if( P[1].recv_len < 128 )
    FD_SET( P[1].sd,&fds );

  if( select( 31,&fds,NULL,NULL,NULL ) < 0 ){
    syserr( "select" );
    quit_connection();
    exit(1);
  }

  for( p = 0 ; p < 2 ; p++ ){
    if( FD_ISSET( P[p].sd,&fds ) )
      if( !read_buf( p ) ){
	quit_connection();
	exit( 0 );
      }
  }
}


#define ST_PRE  0
#define ST_WAIT 1

int connection( int private, int auto_exit )
{
  fd_set fds;
  int sa;
  int msg,bits;
  int p,users;

  static int on = 1;
  static unsigned char quit[2] = {2,MSG_QUIT };

  users = 0;

  for(;;){
                                    /* クライアントからのメッセージを処理 */
    bits = (1 << users) - 1;
    while( bits )
      for( p = 0 ; p < users ; p++ ){
	switch( recv_msg( p ) ){
	case 0:
	  bits &= ~( 1 << p );
	  break;

	case MSG_QUIT:
	  close( P[p].sd );
	  bits &= ~( 1 << p );
	  if( p == 0 && users == 2 ){
	    memmove( &P[0],&P[1],sizeof(player_t) );
	    bits >>= 1;
	  }
	  users--;
	  if( users == 0 ){
	    Waiting = 0;
	    if( Children == 0 && auto_exit ){
	      close( Sock );
	      exit( 0 );
	    }
	  }
	  break;

	case MSG_LGON:
	  P[p].mode = ST_WAIT;
	  if( users == 2 && P[1-p].mode == ST_WAIT ){
	    Waiting = 0;
	    if( private ){
	      close( Sock );
	      return 0;
	    }
	  restart1:
	    if( (p = fork()) < 0 ){
	      if( errno == EINTR )
		goto restart1;
	      syserr("fork");
	      goto error;
	    }
	    if( p ){
	      close( P[0].sd );
	      close( P[1].sd );
	      users = 0;
	      bits = 0;
	      Children++;
	    }else
	      return 0;
	  }
	  break;
	}
      }


    FD_ZERO( &fds );  /* fd_set構造体をセット */

  restart2:

    switch( users ){
    case 0:
      FD_SET( Sock,&fds );
      break;

    case 1:
      FD_SET( Sock,&fds );
      FD_SET( P[0].sd,&fds );
      break;

    case 2:
      FD_SET( P[0].sd,&fds );
      FD_SET( P[1].sd,&fds );
      break;
    }

    if( select( 31,&fds,NULL,NULL,NULL ) < 0 ){
      if( errno == EINTR )
	goto restart2;

      syserr( "select" );
      goto error;
    }

    if( FD_ISSET( Sock,&fds ) ){      /* accept()できる */
    restart3:
      if( ( sa = accept( Sock,NULL,NULL ) ) < 0 ){
	if( errno == EINTR )
	  goto restart3;

	syserr( "accept" );
	goto error;
      }

    restart4:
      if( setsockopt( sa,TCP_PROTO,TCP_NODELAY,(char*)&on,sizeof(on)) < 0 ){
	if( errno == EINTR )
	  goto restart4;
	syserr( "setsockopt" );
	goto error;
      }
      P[users].sd = sa;
      P[users].recv_len = 0;
      P[users].recv_next = 0;
      P[users].mode = ST_PRE;

      send_init( MSG_VRSN );
      send_snum( VERSION_MAJ );
      send_snum( VERSION_MIN );
      send_msg( users );

      users++;
      Waiting = 1;
    }

    for( p = 0 ; p < users ; p++ )
      if( FD_ISSET( P[p].sd,&fds ) && !read_buf( p ) ){
	close( P[p].sd );
	if( p == 0 && users == 2 )
	  memmove( &P[0],&P[1],sizeof(player_t) );
	users--;
	if( users == 0 ){
	  Waiting = 0;
	  if( Children == 0 && auto_exit ){
	    close( Sock );
	    exit(0);
	  }
	}
      }
  }

 error:
  for( p = 0 ; p < users ; p++ ){
    write( P[p].sd,quit,2 );
    close( P[p].sd );
  }
  return -1;
}


int recv_msg( int p )
{
  int l;

  if( P[p].recv_next ){
    if( P[p].recv_len > P[p].recv_next )
      memmove( P[p].recv_buf,P[p].recv_buf+P[p].recv_next,
	      P[p].recv_len - P[p].recv_next );
    P[p].recv_len -= P[p].recv_next;
    P[p].recv_next = 0;
  }

  if( P[p].recv_len == 0 )
    return 0;

  l = P[p].recv_buf[0] - 32;
  if( l > P[p].recv_len )
    return 0;

  P[p].recv_next = l;
  P[p].recv_read = P[p].recv_buf + 2;

  return P[p].recv_buf[1];
}


int recv_snum( int p )
{
  return *P[p].recv_read++ - 32;
}


int recv_lnum( int p )
{
  int i;

  i = ( P[p].recv_read[0] - 32 ) + ( P[p].recv_read[1] - 32 ) * 64;
  P[p].recv_read += 2;

  return i;
}


void recv_str( int p, unsigned char *buf, int siz )
{
  int len;

  for( siz--, len = *P[p].recv_read++ - 32 ; len > 0 && siz > 0 ;len--, siz-- )
    *buf++ = *P[p].recv_read++;

  *buf = '\0';
}


void send_init( int msg )
{
  Send_buf[1] = msg;
  Send_write = Send_buf+2;
}


void send_snum( int num )
{
  *Send_write++ = num+32;
}


void send_lnum( int num )
{
  if( num > 4095 )
    num = 4095;

  *Send_write++ = ( num & 63 ) + 32;
  *Send_write++ = ( num / 64 ) + 32;
}


void send_str( char *str )
{
  unsigned char *sizp;

  sizp = Send_write++;

  while( *str )
    if( iscntrl( *str ) )
      str++;
    else
      *Send_write++ = *str++;

  *sizp = Send_write - sizp - 1 + 32;
}


void send_msg( int p )
{
  int i,l,w;

  l = Send_write - Send_buf;
  Send_buf[0] = l + 32;

  w = 0;
  while( w < l ){
    if( (i = write( P[p].sd,Send_buf+w,l-w )) < 0 ){
      syserr( "write" );
      quit_connection();
      exit(1);
    }
    w += i;
  }
}
