// TCP base client with multiple socket support; Galileo, 11-30-98
#include <network.h>
#define OPEN        1
#define BLOCKED     2
class socket {
  int desc;     // Identification for the socket
  int mode;     // Mode the port was opened for 
  int state;    // Port state, OPEN/BLOCKED
  mixed *buff;  // Pending port data
} /* class client */
class server {
  int desc;                // Identification for the server
  class socket accept;     // Accepting socket
  class socket *sockets;   // Connected sockets  
} /* class server */
int close_sockets();
int close_socket(int);
private nosave class socket *sockets;
protected void mudlib_setup() {
  sockets = ({ });
} /* mudlib_setup() */
protected void dest_me() {
  close_sockets();
} /* dest_me() */
int valid_index(mixed *array, int index) {
  
  if ((index < 0) || (index >= sizeof(array)))
    return 0;
  return 1;
} /* valid_index() */
int socket_index(int fd) {
  
  for (int i = 0; i < sizeof(sockets); i++)
    if (sockets[i] -> desc == fd)
      return i;
  return -1;
} /* socket_index() */
class socket *query_sockets() { return sockets; }
class socket query_socket(int index) {
  if (!valid_index(sockets, index))
    return 0;
  return sockets[index];
} /* query_socket() */
int query_state(int index) {
  if (!valid_index(sockets, index))
    return 0;
  return sockets[index] -> mode;
} /* query_state() */
protected int set_state(int index, int mode) {
  if (!valid_index(sockets, index))
    return 0;
  return (sockets[index] -> mode = mode);
} /* set_state() */
mixed *query_buffer(int index) {
  if (!valid_index(sockets, index))
    return 0;
  return sockets[index] -> buff;
} /* query_buffer() */
protected int set_buffer(int index, mixed *buff) {
  if (!valid_index(sockets, index))
    return 0;
  sockets[index] -> buff = buff;
  return 1;
} /* set_buffer() */
protected int add_buffer(int index, mixed elem) {
  if (!valid_index(sockets, index))
    return 0;
  sockets[index] -> buff += ({ elem });
  return 1;
} /* add_buffer() */
protected int remove_buffer(int index, int elem) {
  if (!valid_index(sockets, index))
    return 0;
  if (!valid_index(sockets[index] -> buff, elem))
    return 0;
  sockets[index] -> buff = delete(sockets[index] -> buff, elem, 1);
  return 1;
} /* remove_buffer() */
protected int send_buffer(int index) {
  if (!valid_index(sockets, index))
    return 0;  
  TO -> client_write_callback(sockets[index] -> desc);
  return 1;
} /* send_buffer() */   
protected int open_socket(string host, int port, int mode) {
  class socket sock = new(class socket);
  int err;
  if ((err = socket_create(mode, "client_read_callback", 
                           "client_close_callback")) < 0)
    error("Error creating socket.\n");
  sock -> desc = err;
 
  if ((err = socket_bind(sock -> desc, 0)) != EESUCCESS) {
    socket_close(sock -> desc);
    error("Error binding socket.\n");
  }
  if ((err = socket_connect(sock -> desc, host + " " + port,
      "client_read_callback", "client_write_callback")) != EESUCCESS) {
    socket_close(sock -> desc);
    error("Error connecting socket.\n");
  }
  sock -> mode = mode;
  sock -> state = OPEN;
  sock -> buff = ({ });
  sockets += ({ sock });
  return (sizeof(sockets) - 1);
} /* open_socket() */
protected int close_sockets() {
  while (sizeof(sockets))
    if (!close_socket(0));
      return 0;
  return 1;
} /* reset_sockets() */
protected int close_socket(int index) {
  if (!valid_index(sockets, index))
    return 0;
  if (socket_close(sockets[index] -> desc) != EESUCCESS)
    return 0;
  sockets = delete(sockets, index, 1);
  return 1;
} /* close_socket() */
protected void client_close_callback(int fd) {
  socket_close(fd);
  sockets = delete(sockets, socket_index(fd), 1);
  return;
} /* close_callback() */
protected void client_read_callback(int /* fd */, mixed /* val */) {
  return;
} /* read_callback() */
protected void client_write_callback(int fd) {
  int index, err = EESUCCESS;
  if (!sizeof(sockets))
    return;
  for (index = 0; index < sizeof(sockets); index++)
    if ((sockets[index] -> desc) == fd)
      break;
  if (!sizeof(sockets[index] -> buff))
    return;
  switch (err = socket_write(sockets[index] -> desc, 
                             sockets[index] -> buff[0])) {
    case EESUCCESS:
      sockets[index] -> buff = delete(sockets[index] -> buff, 0, 1);
      sockets[index] -> state = OPEN;
      break;
    case EECALLBACK:
    case EEALREADY:
      sockets[index] -> state = BLOCKED;
      return;
    case EEWOULDBLOCK:
      sockets[index] -> state = BLOCKED;
      call_out((: client_write_callback($(fd)) :), 1);
      return;
    default:
      error("Error writing to socket.\n");
      return;
  }
  if (sizeof(sockets[index] -> buff))
    call_out((: client_write_callback($(fd)) :), 1);
  return;
} /* write_callback() */