#if HAVE_CONFIG_H
#include <ntm_conf.h>
#endif

#include <stdlib.h>
#include <stdio.h>                      /* fprintf() stderr */
#include <sys/types.h>                  /* just on GP */
#include <sys/socket.h>                 /* socket() bind() listen() accept() */
#include <netinet/in.h>                 /* htons() struct sockaddr_in struct in_addr */
#include <arpa/inet.h>                  /* old inet_ntoa()
#include <memory.h>                     /* memset() */
#include <netdb.h>                      /* gethostbyname() struct hostent */
#include <signal.h>                     /* signal() SIGNAL */
#include <string.h>                     /* strlen() strcpy() */
#include "../types/types.h"             /* Bit32 */
#include "../wattcp/tcp.h"              /* i/f for this module */


#if !NF_OCX_BGP

int the_tcp_mode = TCP_MODE_BINARY;
longword resolve( char *hostname )
{
    struct hostent *my_entry;

    /* yes, this works for dotted-decimal as well as names
     */
    my_entry = gethostbyname( hostname );
    if( !my_entry )
        return inet_addr( hostname );

    /* is this right?
     * or should I get the data pointed to by h_addr?
     */
    return ntohl( * (longword *) my_entry->h_addr );
}



void sock_init( void )
{
#if 1
    signal( SIGPIPE, SIG_IGN );
#endif
}



/* returns nonzero if the socket is still open for both read and write
 */
int tcp_tick( void *s )
{
    int result;
    int my_socket = * (int *) s;
    char my_data;

    /* read() will return -1 and set errno==EBADF if socket was
     * half-closed by the other side and we read all their data
     */
    result = read( my_socket, &my_data, 0 );
    if( result == -1 )
    {
/*      perror( "tcp_tick() read()" ); */
        return 0;
    }

    /* write() will return -1 and set errno==EBADF if socket was
     * half-closed by us
     */
    result = write( my_socket, &my_data, 0 );
    if( result == -1 )
    {
/*      perror( "tcp_tick() write()" ); */
        return 0;
    }

/*  printf( "tcp_tick() succeeded!\n" ); */
    return 1;
}



int tcp_open( void *s, word lport )
{
    int result;
    int bound_socket;
    struct sockaddr_in addr;
    int reuse_option;

    /* create a socket
     */
    bound_socket = socket( PF_INET, SOCK_STREAM, 0 );
    if( bound_socket < 0 )
    {
        perror( "tcp_listen() socket()" );
        return 0;
    }

    /* make sure the socket will not stay around after this program exits,
     * so we can be run again immediately afterwards, and use the same
     * well-known port number
     */
    reuse_option = 1;
    result = setsockopt( bound_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse_option, sizeof(reuse_option) );
    if( result < 0 )
    {
        perror( "tcp_listen() setsockopt(SO_REUSEADDR)" );
        close( bound_socket );
        return 0;
    }

    /* bind the socket to any of our local addresses on the desired local port
     */
    memset( &addr, 0, sizeof(addr) );
    addr.sin_family = AF_INET;
    addr.sin_port = htons( lport );
    addr.sin_addr.s_addr = INADDR_ANY;
    result = bind( bound_socket, (struct sockaddr *)&addr, sizeof(addr) );
    if( result == -1 )
    {
        perror( "tcp_listen() bind()" );
        close( bound_socket );
        return 0;
    }

    /* success!
     */
    * (int *) s = bound_socket;
    return 1;
}



int tcp_listen( void *s, longword ina, word port, int (*datahandler)(), word timeout )
{
    int result;
    int bound_socket, accepted_socket;
    struct sockaddr_in addr;
    int addr_length;

    bound_socket = * (int *) s;

    /* allow only one connection to exist at a time
     */
    result = listen( bound_socket, 1 );
    if( result != 0 )
    {
        perror( "tcp_listen() listen()" );
        close( bound_socket );
        return 0;
    }

    /* wait for a connection to come in on the bound_socket
     * from the desired host and port (zeroes are wildcards)
     *
     * if we had set the bound_socket to be nonblocking
     * (using fcntl(O_NDELAY) ) this call would
     * return right away with a failure when nobody is trying
     * to reach us, and we could use that to implement the
     * timeout parameter of this function
     *
     * but that isn't implemented yet
     */
    memset( &addr, 0, sizeof(addr) );
    addr.sin_family = AF_INET;
    addr.sin_port = htons( port );
    addr.sin_addr.s_addr = htonl( ina );
    addr_length = sizeof( addr );
    accepted_socket= accept( bound_socket, (struct sockaddr *)&addr, &addr_length );
    if( accepted_socket == -1 )
    {
        perror( "tcp_listen() accept()" );
        close( bound_socket );
        return 0;
    }

    /* success!
     */
    close( bound_socket );
    * (int *) s = accepted_socket;
    return 1;
}



int tcp_connect( void *s, longword ina, word port, int (*datahandler)() )
{
    int result;
    int my_socket;
    struct sockaddr_in addr;

    my_socket = * (int *) s;

    /* actively try to connect to the desired host and port (zeroes are wildcards)
     */
    memset( &addr, 0, sizeof(addr) );
    addr.sin_family = AF_INET;
    addr.sin_port = htons( port );
    addr.sin_addr.s_addr = htonl( ina );
    result = connect( my_socket, (struct sockaddr *)&addr, sizeof(addr) );
    if( result == -1 )
    {
        perror( "tcp_open() connect()" );
        close( my_socket );
        * (int *) s = 0;
        return 0;
    }

    /* success!
     */
    return 1;
}



longword sock_set_rx_buf( void *s, byte *dp, longword len )
{
    int result;
    int my_socket = * (int *) s;
    int old_len;
    int len_of_old_len;

#if 0
    free( dp );
#endif

    if( !dp )
        len = 0;

    len_of_old_len = sizeof( old_len );
    result = getsockopt( my_socket, SOL_SOCKET, SO_RCVBUF, (char *)&old_len, &len_of_old_len );
    if( result < 0 )
    {
        perror( "sock_set_rx_buf() getsockopt(SO_RCVBUF)" );
        return 0;
    }
    fprintf( stderr, "old socket %d RX buffering was %d\n", my_socket, old_len );

    /* Solaris seems to want to return 0 when default was in effect,
       but we don't want our caller to think there was an error, so
       force it to a 1 in that case
    */
    if( old_len == 0 )
        old_len = 1;

#if 1
    result = setsockopt( my_socket, SOL_SOCKET, SO_RCVBUF, (char *)&len, sizeof(len) );
    if( result < 0 )
    {
        perror( "sock_set_rx_buf() setsockopt(SO_RCVBUF)" );
        return 0;
    }
#endif

    return old_len;
}



longword sock_set_tx_buf( void *s, byte *dp, longword len )
{
    int result;
    int my_socket = * (int *) s;
    int old_len;
    int len_of_old_len;

#if 0
    free( dp );
#endif

    if( !dp )
        len = 0;

    len_of_old_len = sizeof( old_len );
    result = getsockopt( my_socket, SOL_SOCKET, SO_SNDBUF, (char *)&old_len, &len_of_old_len );
    if( result < 0 )
    {
        perror( "sock_set_tx_buf() getsockopt(SO_SNDBUF)" );
        return 0;
    }
    fprintf( stderr, "old socket %d TX buffering was %d\n", my_socket, old_len );

    /* Solaris seems to want to return 0 when default was in effect,
       but we don't want our caller to think there was an error, so
       force it to a 1 in that case
    */
    if( old_len == 0 )
        old_len = 1;

    result = setsockopt( my_socket, SOL_SOCKET, SO_SNDBUF, (char *)&len, sizeof(len) );
    if( result < 0 )
    {
        perror( "sock_set_tx_buf() setsockopt(SO_SNDBUF)" );
        return 0;
    }

    return old_len;
}



/* uses blocking I/O so the caller can skip calling sock_dataready()
 *
 * assumes that carriage return and linefeed always come in pairs,
 * with the carriage return first
 */
longword sock_gets( void *s, byte *dp, longword n )
{
    int count = 0;

    while( 1 )
    {
        if( sock_read( (int *)s, dp, 1 ) < 1 )
            return 0;

        if( *dp == '\r' )
            continue;

        if( *dp == '\n' )
        {
            if( count != 0 )
                break;
        }
        else
        {
            count++;
            dp++;
        }
    }

    /* need that terminating NUL
     */
    *dp = '\0';

    return count;
}



longword sock_puts( void *s, byte *dp )
{
    int result;
    int len = strlen( dp );

    result = sock_write( (int *)s, dp, len );
    if( result < len )
        return 0;

    if( the_tcp_mode == TCP_MODE_ASCII )
        return sock_write( (int *)s, "\r\n", 2 );
}

#endif  /* NF_OCX_BGP */

char *inet_ntoa_safe( char *temp, longword ip )
{
    struct in_addr parm;
    char *result;

    parm.s_addr = ip;
#undef inet_ntoa
    result = inet_ntoa( parm );
    strcpy( temp, result );
    return temp;
}



