#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<sys/errno.h>

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

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


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

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


static void quit_connection( void )
{
  static char msg[16] = {MSG_QUIT};

  write( P[0].sd,msg,16 );
  write( P[1].sd,msg,16 );
  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;

}


int connection( int private, int auto_exit )
{
  fd_set fds;
  int s,sd[2];
  int nfd;
  int i,l;
  unsigned char buf[8];
  static int on = 1;
  nfd = 0;
  
  for(;;){
  
  restart0:
  
    FD_ZERO( &fds );
    FD_SET( Sock,&fds );

    if( nfd )
      FD_SET( sd[0],&fds );

    if( (i = select( 31,&fds,NULL,NULL,NULL )) < 0 ){
      if( errno == EINTR )
	goto restart0;
      syserr( "select" );
      return -1;
    }

    if( nfd  && FD_ISSET( sd[0],&fds ) ){
      if( (i = read( sd[0],buf,8 )) < 0 ){
	if( errno == EINTR )
	  goto restart0;
	syserr( "read" );
	return -1;
      }

      if( i == 0 || buf[0] == MSG_QUIT ){
      restart1:
	if( close( sd[0] ) < 0 ){
	  if( errno == EINTR )
	    goto restart1;
	  syserr( "close" );
	  return -1;
	}
	nfd=0;
	if( auto_exit ){
	  close( Sock );
	  exit(0);
	}
      }
    }
    
    if( FD_ISSET( Sock,&fds ) ){
      if( (s = accept( Sock,NULL,NULL)) < 0 ){
	if( errno == EINTR )
	  goto restart0;
	syserr( "accept" );
	return -1;
      }
    restart2:
      if( setsockopt( s,TCP_PROTO,TCP_NODELAY,(char*)&on,sizeof(on)) < 0 ){
	if( errno == EINTR )
	  goto restart2;
	syserr( "setsockopt" );
	return -1;
      }

      sd[nfd++] = s;

      if( nfd == 2 ){
	if( private ){
	  P[0].sd = sd[0];
	  P[1].sd = sd[1];
	  close( Sock );
	  return 0;
	}
      restart3:
	if( (i = fork()) < 0 ){
	  if( errno == EINTR )
	    goto restart3;
	  syserr("fork");
	  return -1;
	}
	if( i == 0 ){
	  P[0].sd = sd[0];
	  P[1].sd = sd[1];
	  close( Sock );
	  return 0;
	}else{
	  Children++;
	restart4:
	  if( close( sd[0] ) < 0 ){
	    if( errno == EINTR )
	      goto restart4;
	    syserr( "close" );
	    return -1;
	  }
	restart5:
	  if( close( sd[1] ) < 0 ){
	    if( errno == EINTR )
	      goto restart5;
	    syserr( "close" );
	    return -1;
	  }
	  nfd = 0;
	}
      }
    }
  }
}


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

  FD_ZERO( &fds );

  if( P[0].len < 64 )
    FD_SET( P[0].sd,&fds );

  if( P[1].len < 64 )
    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( (i = read( P[p].sd,P[p].buf+P[p].len,64-P[p].len )) < 0 ){
	syserr( "read" );
	quit_connection();
	exit(1);
      }
      if( i == 0 ){
	quit_connection();
	exit(0);
      }
      P[p].len += i;
    }
  }
}


int recv_msg( int p )
{
  int l;

  l = P[p].len - P[p].read;

  if( l < 8 ){
    if( l > 0 )
      memmove( P[p].buf,P[p].buf+P[p].read,l );
    P[p].len = l;
    P[p].read = 0;
    return 0;
  }
  P[p].msg = P[p].buf+P[p].read;
  P[p].read += 8;

  return *P[p].msg++;
}


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


int recv_lnum( int p )
{
  int i;

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

  return i;
}


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


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_msg( int p )
{
  int i,l;

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