#include "CNet.h"

CNet::CNet (CBot *b, int socket)
{
  bot = b;
  sock = socket;
  bufpos = bytesin = bytesout = 0;
  time_read = time_write = bot->time_now;
  bufread = (char *)my_malloc (MSG_SIZE+1);
}

CNet::~CNet (void)
{
  my_free (bufread);
  closesock ();
}

ostream &
CNet::operator<< (c_char str)
{
  if (bot->debug)
    cout << "write: " << sock << ": " << str << endl;
  stm << str;
  return stm;
}

void
CNet::writeln (const char *format, ...)
{
  if (sock == -1)
    return;
  char bufwrite[MSG_SIZE+1];
  va_list args;
  va_start (args, format);
  vsnprintf (bufwrite, MSG_SIZE-1, format, args);
  va_end (args);
  if (bot->debug)
    cout << "write: " << sock << ": " << bufwrite << endl;
  size_t len = strlen (bufwrite);
  bytesout += len + 2;		// +2 because a '\r\n' will be appended
  my_strncpy (bufwrite+len, "\r\n", 3);
  send (sock, bufwrite, len+2, 0);
  time_write = bot->time_now;
}

// read as many bytes as the socket has, until a \n or bufpos has max size
// return -1 on error, 0 to ignore, 1 if a new string is ready
int
CNet::readln (void)
{
  if (sock == -1)
    return 0;
  int i = readok (sock);
  while (i == 1)		// while there's something to read
    {
      i = recv (sock, bufread + bufpos, 1, 0);
      if (i == 1)					// success?
        {
          bytesin++;
          if (bufread[bufpos] == '\n' || bufpos == MSG_SIZE)	// end msg
            {
              bufread[bufpos] = 0;
              strip_crlf (bufread);
              if (bot->debug)
                cout << "read: " << sock << ": " << bufread << endl;
              bufpos = 0;
              time_read = bot->time_now;
              return 1;
            }
          bufpos++;			// next char in buffer
        }
      else if (i == 0)
        return 0;
      else
        {
#ifdef WINDOZE
          errno = WSAGetLastError ();
		  if (errno == WSAEWOULDBLOCK)
#else
          if (i == -1 && errno == EAGAIN)	// non-blocking socket
#endif
            return 0;
          else		// -1 on error, 0 when the socket dies
            return -1;
        }
    }
  return i;
}

// prepare sock and sa to openhost()
// return 0 on error
bool
CNet::create_tcp (int port, bool block)
{
  sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (sock == -1)
    return 0;
  sa.sin_family = AF_INET;
  sa.sin_port = htons (port);
  int parm = 1;
  setsockopt (sock, IPPROTO_TCP, TCP_NODELAY, (c_char)&parm, sizeof (int));
  if (!block)
    blocksock (0);
  connecting = 1;
  return 1;
}

// perform blocking connect() on dest:port
// return 0 on error
bool
CNet::openhost (void)
{
  if (connect (sock, (struct sockaddr *)&sa, sizeof sa) == -1)
    {
      if (errno != EISCONN)
        {
          if (errno != EINPROGRESS && errno != EALREADY)
            {
              close (sock);
              connecting = 0;
              return 0;
            }
          return 1;
        }
    }
  // we're connected
  fcntl (sock, F_SETFL, 0);
  sock_linger (sock, 60 * 100); // wait up to one minute in close()
  connected = 1;
  connecting = 0;
  stm.attach (sock);
  return 1;
}

// read from sock to buf until a '\n' or buf_size bytes are read
// return read bytes, -1 on error
int
CNet::readsock (int sock, char *buf, size_t buf_size)
{
  int i = -1;
  do
    {
      i++;
      if (read(sock,buf+i,1) <= 0)
        return -1;
      if (buf[i] == '\r')
        i--;
    }
  while (buf[i] != '\n' && i < (int)buf_size);
  buf[++i] = 0;
  return i;
}

// return 1 if there's new data, 0 if not, -1 if failed
int
CNet::readok (int sock2)
{
  fd_set rfds;
  struct timeval tv;
  FD_ZERO (&rfds);
  FD_SET ((unsigned)sock2, &rfds);
  tv.tv_sec = 0;
  tv.tv_usec = 2;
  int i = select (sock2+1, &rfds, NULL, &rfds, &tv);
  FD_ZERO (&rfds);
  return i;
}

// return 1 if there's new data, 0 if not, -1 if failed
int
CNet::writeok (int sock)
{
  fd_set wfds;
  struct timeval tv;
  FD_ZERO (&wfds);
  FD_SET ((unsigned)sock, &wfds);
  tv.tv_sec = 0;
  tv.tv_usec = 1;
  int i = select (sock+1, NULL, &wfds, &wfds, &tv);
  FD_ZERO (&wfds);
  return i;
}

// force the socket to flush buffers on a close()
void
CNet::sock_linger (int sock, int timeout)
{
  struct linger ling;
  ling.l_onoff = 1;
  ling.l_linger = timeout;
  setsockopt (sock, SOL_SOCKET, SO_LINGER, (c_char)&ling, sizeof (&ling));
}

// create a nonblocking socket and bind it to the specified port
// if port=0, use any free one. return 0 on error.
bool
CNet::bindsock (int port)
{
  struct sockaddr_in local;

  memset (&local, 0, sizeof (local));
  local.sin_family = AF_INET;
  local.sin_port = htons (port);
  local.sin_addr.s_addr = INADDR_ANY;
  memset (&(local.sin_zero), 0, 8);
  sock = socket (AF_INET, SOCK_STREAM, 0);
  if (sock == -1)
    return 0;
  int parm = 1;
  setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, (c_char)&parm, sizeof (int));
  setsockopt (sock, IPPROTO_TCP, TCP_NODELAY, (c_char)&parm, sizeof (int));
  blocksock (0);

  if (bind (sock, (struct sockaddr *)&local, sizeof (struct sockaddr)) == -1)
    {
      closesock ();
      return 0;
    }
  if (listen (sock, 1) == -1)
    {
      closesock ();
      return 0;
    }
  return 1;
}

void
CNet::blocksock (bool block)
{
#ifdef WINDOZE
  unsigned long opt = !block;
  ioctlsocket (sock, FIONBIO, &opt);
#else
  fcntl (sock, F_SETFL, block ? 0 : O_NONBLOCK);
#endif          // !WINDOZE
}

u_short
CNet::sock2port (int sock)
{
  struct sockaddr_in addr;
  socklen_t size = sizeof (struct sockaddr);

  memset (&addr, 0, sizeof (struct sockaddr_in));
  getsockname (sock, (struct sockaddr *)&addr, &size);

  return ntohs (addr.sin_port);
}

u_long
CNet::sock2addr (int sock)
{
  struct sockaddr_in addr;
  socklen_t size = sizeof (struct sockaddr);

  memset (&addr, 0, sizeof (struct sockaddr_in));
  getsockname (sock, (struct sockaddr *)&addr, &size);

  return ntohl (addr.sin_addr.s_addr);
}

bool
CNet::resolvehost (c_char name)
{
  struct hostent *he;
  if ((he = gethostbyname (name)) == NULL)
    {
      sa.sin_addr.s_addr = inet_addr (name);
      if (sa.sin_addr.s_addr == (unsigned int)-1)
        return 0;
    }
  else
    memcpy (&sa.sin_addr.s_addr, he->h_addr_list[0], he->h_length);
  return 1;
}

// close the socket, 0 if not open
bool
CNet::closesock (void)
{
  if (sock == -1)
    return 0;
#ifdef WINDOZE
  closesocket (sock);
#else
  close (sock);
#endif
  stm.close ();
  sock = -1;
  return 1;
}

