# define INCLUDE_FILE_IO
# include "dgd.h"
# include <sys/time.h>
# include <sys/types.h>
# include <sys/errno.h>
# include <errno.h>
# include <exec/types.h>
# include <clib/exec_protos.h>
# include "socket.h"
# include "str.h"
# include "array.h"
# include "object.h"
# include "comm.h"
# include "socket_sim.h"
# include "socket_patch.h"

# ifdef AMITCP
struct Library *SocketBase = NULL;
# endif /* AMITCP */

# ifdef AS225
#    define MAXSOCKS 50                 /* max number of socks that you want */
struct Library *SockBase = NULL;
# endif

struct _connection_ {
    int fd;                             /* file descriptor */
    struct sockaddr_in addr;            /* internet address of connection */
    struct _connection_ *next;          /* next in list */
};

static int nusers;                      /* # of users */
static connection *connections;         /* connections array */
static connection *flist;               /* list of free connections */
static int telnet;                    /* telnet port socket descriptor */
static int binary;                    /* binary port socket descriptor */
static fd_set fds;                      /* file descriptor bitmap */
static fd_set readfds;                  /* file descriptor read bitmap */
static int maxfd;                       /* largest fd opened yet */

/*
 * NAME:        conn->init()
 * DESCRIPTION: initialize connections
 */
void conn_init(maxusers, telnet_port, binary_port)
int maxusers;
unsigned short telnet_port, binary_port;
{
    struct sockaddr_in sin;
    struct hostent *host;
    register int n;
    register connection *conn;
    char buffer[256];
    int on, tmp;

#ifdef AS225
    if ((SockBase = OpenLibrary ("socket.library", 1L)) == NULL)
        P_message("  Can't open 'socket.library'"
                   " - using simulated sockets instead.\n");
    else {
        setup_sockets (MAXSOCKS, &errno);
        P_message("  socket.library found and accessed.\n");
    }
#endif
#ifdef AMITCP
    {
        int rc;

# ifdef __SASC
        rc = _STIopenSockets();
# else
        rc = _openSockets();
# endif
        if (rc) {
            P_message("  Can't access AmiTCP-2 because ");
            switch (rc) {
              case 1: P_message("of wrong OS version"); break;
              case 2: P_message("outdated net.lib was used."); break;
              case 3: P_message("OpenLibrary() failed"); break;
              default: P_message("of an unknown failure"); break;
            }
            P_message(".\n  Socket simulation is used instead.\n");
        }
        else
            P_message("  AmiTCP found and accessed.\n");
    }
#endif
    sim_init();

    gethostname(buffer, sizeof(buffer));
    host = gethostbyname(buffer);
    if (host == (struct hostent *) NULL) {
        perror("gethostbyname");
        exit(2);
    }

    telnet = socket(host->h_addrtype, SOCK_STREAM, 0);
    binary = socket(host->h_addrtype, SOCK_STREAM, 0);
    if (telnet < 0 || binary < 0) {
        perror("socket");
        exit(2);
    }
    on = 1;
    if (setsockopt(telnet, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
        perror("setsockopt");
        exit(2);
    }
    on = 1;
    if (setsockopt(binary, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
      perror("setsockopt");
      exit(2);
    }

    memset(&sin, '\0', sizeof(sin));
    memcpy(&sin.sin_addr, host->h_addr, host->h_length);
    sin.sin_port = htons(telnet_port);
    sin.sin_family = host->h_addrtype;
    sin.sin_addr.s_addr = INADDR_ANY;
    if (bind(telnet, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
        perror("bind");
        exit(2);
    }
    sin.sin_port = htons(binary_port);
    if (bind(binary, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
      perror("bind");
      exit(2);
    }

    if (listen(telnet, 5) < 0 || listen(binary, 5) < 0) {
        perror("listen");
        exit(2);
    }

# ifdef AMIGA
    if (socket_ioctl(telnet, FIONBIO, &tmp) == -1 ||
        socket_ioctl(binary, FIONBIO, &tmp) == -1) {
        perror("ioctl socket FIONBIO");
        exit(2);
    }
# else
    if (fcntl(telnet, F_SETFL, FNDELAY) < 0 ||
        fcntl(binary, F_SETFL, FNDELAY) < 0) {
        perror("fcntl");
        exit(2);
    }
# endif

    connections = ALLOC(connection, nusers = maxusers);
    for (n = nusers, conn = connections; n > 0; --n, conn++) {
        conn->fd = -1;
        conn->next = flist;
        flist = conn;
    }

    FD_ZERO(&fds);
}

/*
 * NAME:        conn->finish()
 * DESCRIPTION: terminate connections
 */
void conn_finish()
{
    socket_close(telnet);
    socket_close(binary);

# ifdef AS225
    if (SockBase != NULL) {
        cleanup_sockets();
        CloseLibrary (SockBase);
        SockBase = NULL;
    }
# endif

# ifdef AMITCP
#  ifdef __SASC
    _STDcloseSockets();
#  else
    _closeSockets();
#  endif
# endif

    sim_close_all();
}

/*
 * NAME:      conn->tnew()
 * DESCRIPTION:       accept a new telnet connection
 */
connection *conn_tnew()
{
    int fd, len;
    struct sockaddr_in sin;
    register connection *conn;

    len = sizeof(sin);
    fd = accept(telnet, (struct sockaddr *) &sin, &len);
    if (fd < 0) {
      return (connection *) NULL;
    }

    conn = flist;
    flist = conn->next;
    conn->fd = fd;
    memcpy(&conn->addr, (char *) &sin, len);
    FD_SET(fd, &fds);
    if (fd > maxfd) {
      maxfd = fd;
    }

    return conn;
}

/*
 * NAME:        conn->bnew()
 * DESCRIPTION: accept a new binary connection
 */
connection *conn_bnew()
{
    int fd, len;
    struct sockaddr_in sin;
    register connection *conn;

    len = sizeof(sin);
    fd = accept(binary, (struct sockaddr *) &sin, &len);
    if (fd < 0) {
        return (connection *) NULL;
    }

    conn = flist;
    flist = conn->next;
    conn->fd = fd;
    memcpy(&conn->addr, (char *) &sin, len);
    FD_SET(fd, &fds);
    if (fd > maxfd) {
        maxfd = fd;
    }

    return conn;
}

/*
 * NAME:        conn->del()
 * DESCRIPTION: delete a connection
 */
void conn_del(conn)
register connection *conn;
{
    if (conn->fd >= 0) {
        shutdown(conn->fd, 2);
        socket_close(conn->fd);
        FD_CLR(conn->fd, &fds);
        conn->fd = -1;
    }
    conn->next = flist;
    flist = conn;
}

/*
 * NAME:        conn->select()
 * DESCRIPTION: wait for input from connections
 */
int conn_select(wait)
bool wait;
{
    struct timeval timeout;

    memcpy(&readfds, &fds, sizeof(fd_set));
    timeout.tv_sec = (int) wait;
    timeout.tv_usec = 0;
    return socket_select(maxfd + 1, &readfds, (fd_set *) NULL, (fd_set *) NULL,
                        &timeout);
}

/*
 * NAME:        conn->read()
 * DESCRIPTION: read from a connection
 */
int conn_read(conn, buf, size)
connection *conn;
char *buf;
int size;
{
    if (conn->fd < 0) {
        return -1;
    }
    if (!FD_ISSET(conn->fd, &readfds)) {
        return 0;
    }
    size = socket_read(conn->fd, buf, size);
    return (size == 0) ? -1 : size;
}

/*
 * NAME:        conn->write()
 * DESCRIPTION: write to a connection
 */
void conn_write(conn, buf, size)
connection *conn;
char *buf;
register int size;
{
    if (conn->fd >= 0) {
        if (socket_write(conn->fd, buf, size) < 0 && errno != EWOULDBLOCK) {
            socket_close(conn->fd);
            conn->fd = -1;
        }
    }
}

/*
 * NAME:        conn->ipnum()
 * DESCRIPTION: return the ip number of a connection
 */
char *conn_ipnum(conn)
connection *conn;
{
    return inet_ntoa(conn->addr.sin_addr);
}
