#include "cdefs.h"
#include "csock.h"
#include <errno.h>
#include <stdlib.h>

cstring csock_error_message(){
  cint err;
#ifdef _WIN32
  err = WSAGetLastError();
  switch (err){
    case 0: return "No error";
    case WSAEINTR: return "Interrupted system call";
    case WSAEBADF: return "Bad file number";
    case WSAEACCES: return "Permission denied";
    case WSAEFAULT: return "Bad address";
    case WSAEINVAL: return "Invalid argument";
    case WSAEMFILE: return "File table overflow";
    case WSAEWOULDBLOCK: return "Would block";
    case WSAEINPROGRESS: return "Blocking call in progress";
    case WSAEALREADY: return "Operation already in progress";
    case WSAENOTSOCK: return "Socket operation on non-socket";
    case WSAEDESTADDRREQ: return "Destination address required";
    case WSAEMSGSIZE: return "Message too long";
    case WSAEPROTOTYPE: return "Protocol type wrong for socket";
    case WSAENOPROTOOPT: return "Protocol not available";
    case WSAEPROTONOSUPPORT: return "Protocol not supported";
    case WSAESOCKTNOSUPPORT: return "Socket type not supported";
    case WSAEOPNOTSUPP: return "Operation not supported on socket";
    case WSAEPFNOSUPPORT: return "Protocol family not supported";
    case WSAEAFNOSUPPORT: return "Address family not supported by protocol family";
    case WSAEADDRINUSE: return "Address already in use";
    case WSAEADDRNOTAVAIL: return "Can't assign requested address because of operational problems";
    case WSAENETDOWN: return "Network is down";
    case WSAENETUNREACH: return "Network is unreachable";
    case WSAENETRESET: return "Network dropped connection because of reset";
    case WSAECONNABORTED: return "Software caused connection abort";
    case WSAECONNRESET: return "Connection reset by peer";
    case WSAENOBUFS: return "No buffer space available";
    case WSAEISCONN: return "Socket is already connected";
    case WSAENOTCONN: return "Socket is not connected";
    case WSAESHUTDOWN: return "Can't send after socket shutdown";
    case WSAETOOMANYREFS: return "Too many references: can't splice";
    case WSAETIMEDOUT: return "Connection timed out";
    case WSAECONNREFUSED: return "Connection refused";
    case WSAELOOP: return "Symbolic link loop";
    case WSAENAMETOOLONG: return "Path name is too long";
    case WSAEHOSTDOWN: return "Host is down";
    case WSAEHOSTUNREACH: return "No route to host";
    case WSASYSNOTREADY: return "Network subsystem not ready";
    case WSAVERNOTSUPPORTED: return "Required Winsock version not supported";
    case WSANOTINITIALISED: return "Winsock not initialized";
    case WSAHOST_NOT_FOUND: return "Host not found";
    case WSATRY_AGAIN: return "Try again";
    case WSANO_RECOVERY: return "No recovery";
    case WSANO_DATA: return "No data";
  }
  return "Unknown error";
#else
  err = errno;
  return strerror(err);
#endif
}

cint csock_finalize(){
#ifdef _WIN32
  WSACleanup();
#endif
}
cint csock_host_address_to_name(cstring addr, cstring hname){
  struct hostent *hp; unsigned long net_addr;
  
  net_addr = inet_addr(addr);
  if ((hp = gethostbyaddr(&net_addr,4,AF_INET)) == NULL) return -1;
  strcpy(hname, hp->h_name);
  return 0;
}

cint csock_host_name_to_address(cstring hname, cstring addr){
  struct hostent *hp; struct in_addr ia;
  
  if ((hp = gethostbyname(hname)) == NULL) return -1;
  ia = *((struct in_addr *)(hp->h_addr_list[0]));
  strcpy(addr, inet_ntoa(ia));
  return 0;
}

cint csock_initialize(){
#ifdef _WIN32
  WORD wVersionRequested; WSADATA wsaData; int err;
  
  wVersionRequested = MAKEWORD(1, 1);
  if (WSAStartup(wVersionRequested, &wsaData)) return -1;
  if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) return -1;
  return 0;
#endif
}
 
cint csock_local_address(cint s, cstring addr){
  struct sockaddr_in sa; cint namelen;
  
  namelen = sizeof (sa);
  if (getsockname(s, &sa, &namelen)) return -1;
  strcpy(addr, inet_ntoa(sa.sin_addr));
  return 0;
}

cstring csock_local_address_family(cint s){
  struct sockaddr sa; cint namelen;
  
  namelen = sizeof (sa);
  if (getsockname(s, &sa, &namelen)) return -1;
  switch (sa.sa_family){
    case AF_UNSPEC: return "Unspecified";
    case AF_INET: return "Internet";
    case AF_NETBIOS: return "Netbios";
  }
  return "Unknown";
}

cint csock_local_port_number(cint s){
  struct sockaddr_in sa; cint namelen;
  
  namelen = sizeof (sa);
  if (getsockname(s, &sa, &namelen)) return -1;
  return ntohs(sa.sin_port);
}

cint csock_make_socket_addr(cstring hname, cint pnum, cint atype){
  struct sockaddr_in *sa; struct hostent *hp; cint i;
  
  if ((hp = gethostbyname(hname)) == NULL) return -1;
  if (atype != hp->h_addrtype) return -1;
  sa = mem_malloc(sizeof (struct sockaddr_in));
  for (i = 0; i < sizeof (struct sockaddr_in); i++)((unsigned char*)sa)[i] = 0;
  for (i = 0; i < hp->h_length; i++)
    ((unsigned char*)&(sa->sin_addr))[i] = ((unsigned char*)(hp->h_addr_list[0]))[i];
  sa->sin_family = hp->h_addrtype;
  sa->sin_port = htons((u_short)pnum);
  return (cint)sa;
}

cint csock_peer_address(cint s, cstring addr){
  struct sockaddr_in sa; cint namelen;
  
  namelen = sizeof (sa);
  if (getpeername(s, &sa, &namelen)) return -1;
  strcpy(addr, inet_ntoa(sa.sin_addr));
  return 0;
}

cstring csock_peer_address_family(cint s){
  struct sockaddr sa; cint namelen;
  
  namelen = sizeof (sa);
  if (getpeername(s, &sa, &namelen)) return -1;
  switch (sa.sa_family){
    case AF_UNSPEC: return "Unspecified";
    case AF_INET: return "Internet";
    case AF_NETBIOS: return "Netbios";
  }
  return "Unknown";
}

cint csock_peer_port_number(cint s){
  struct sockaddr_in sa; cint namelen;
  
  namelen = sizeof (sa);
  if (getpeername(s, &sa, &namelen)) return -1;
  return ntohs(sa.sin_port);
}

cint csock_port_number_for(cstring sname){
  struct servent *sp; u_short result;
  
  if ((sp = getservbyname(sname, NULL)) == NULL) return -1;
  result = ntohs(sp->s_port);
  return result;
}

cint csock_port_number_to_name(cint portnum, cstring servname){
  struct servent *sp;
  
  if ((sp = getservbyport(portnum, NULL)) == NULL) return -1;
  strcpy(servname, sp->s_name);
  return 0;
}

cint csock_readable(cint *list_s, cint timewait){
  fd_set set_s; cint i; struct timeval timeout; cint result;
  
  FD_ZERO(&set_s);
  for (i = 0; list_s[i] != NULL; i++) FD_SET(list_s[i], &set_s);

  timeout.tv_sec = timewait / 1000000; 
  timeout.tv_usec = timewait % 1000000;
  result = select (FD_SETSIZE, &set_s, NULL, NULL, &timeout);

  if (result == 0){	/* timeout expired */
    for (i = 0; list_s[i] != NULL; i++)
      if (FD_ISSET(list_s[i], &set_s)) return 1;
  }; 
  return result; 	
}

cint csock_sizeof_sockaddr(cint af){
  switch (af){
    case AF_INET: return sizeof (struct sockaddr_in);
  }
  return sizeof (struct sockaddr);
}

cint csock_udp_read(cint s, cstring msg, cstring send_a, cint *send_p){
  struct sockaddr_in sa; cint addr_size, i;
  
  addr_size = sizeof(struct sockaddr_in);
  i = recvfrom(s, msg, 2048, 0, &sa, &addr_size);
  if (i < 0) return i;
  strcpy(send_a, inet_ntoa(sa.sin_addr));
  *send_p = ntohs(sa.sin_port);
  return i;
}

cint csock_writeable(cint *list_s, cint timewait){
  fd_set set_s; cint i; struct timeval timeout; cint result;
  
  FD_ZERO(&set_s);
  for (i = 0; list_s[i] != NULL; i++) FD_SET(list_s[i], &set_s);
 
  timeout.tv_sec = timewait / 1000000;
  timeout.tv_usec = timewait % 1000000;
  result = select (FD_SETSIZE, NULL, &set_s, NULL, &timeout);

  if (result == 0){	/* timeout expired */
    for (i = 0; list_s[i] != NULL; i++)
      if (FD_ISSET(list_s[i], &set_s)) return 1;
  };
  return result; 	
}

