/*
   Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the
   Queen in Right of Canada (Communications Research Center Canada)
   */
/*
   This file is part of ODR-DabMux.

   ODR-DabMux is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as
   published by the Free Software Foundation, either version 3 of the
   License, or (at your option) any later version.

   ODR-DabMux is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with ODR-DabMux.  If not, see <http://www.gnu.org/licenses/>.
   */

#include "InetAddress.h"
#include <iostream>
#include <stdio.h>

#ifdef _WIN32
#else
# include <errno.h>
# include <string.h>
#endif

#ifdef TRACE_ON
# ifndef TRACE_CLASS
#  define TRACE_CLASS(clas, func) cout <<"-" <<(clas) <<"\t(" <<this <<")::" <<(func) <<endl
#  define TRACE_STATIC(clas, func) cout <<"-" <<(clas) <<"\t(static)::" <<(func) <<endl
# endif
#else
# ifndef TRACE_CLASS
#  define TRACE_CLASS(clas, func)
#  define TRACE_STATIC(clas, func)
# endif
#endif


int inetErrNo = 0;
const char *inetErrMsg = NULL;
const char *inetErrDesc = NULL;


/**
 *  Constructs an IP address.
 *  @param port The port of this address
 *  @param name The name of this address
 */
InetAddress::InetAddress(int port, const char* name) {
  TRACE_CLASS("InetAddress", "InetAddress(int, char)");
  addr.sin_family = PF_INET;
  addr.sin_addr.s_addr = htons(INADDR_ANY);
  addr.sin_port = htons(port);
  if (name)
    setAddress(name);
}


/**
 *  Constructs a copy of inet
 *  @param inet The address to be copied
 */
InetAddress::InetAddress(const InetAddress &inet) {
  TRACE_CLASS("InetAddress", "InetAddress(InetAddress)");
  memcpy(&addr, &inet.addr, sizeof(addr));
}


/// Destructor
InetAddress::~InetAddress() {
  TRACE_CLASS("InetAddress" ,"~InetAddress()");
}


/// Returns the raw IP address of this InetAddress object.
sockaddr *InetAddress::getAddress() {
  TRACE_CLASS("InetAddress", "getAddress()");
  return (sockaddr *)&addr;
}


/// Return the port of this address.
int InetAddress::getPort()
{
  TRACE_CLASS("InetAddress", "getPort()");
  return ntohs(addr.sin_port);
}


/**
 *  Returns the IP address string "%d.%d.%d.%d".
 *  @return IP address
 */
const char *InetAddress::getHostAddress() {
  TRACE_CLASS("InetAddress", "getHostAddress()");
  return inet_ntoa(addr.sin_addr);
}


/// Returns true if this address is multicast
bool InetAddress::isMulticastAddress() {
  TRACE_CLASS("InetAddress", "isMulticastAddress()");
  return IN_MULTICAST(ntohl(addr.sin_addr.s_addr));		// a modifier
}


/**
 *  Set the port number
 *  @param port The new port number
 */
void InetAddress::setPort(int port)
{
  TRACE_CLASS("InetAddress", "setPort(int)");
  addr.sin_port = htons(port);
}


/**
 *  Set the address
 *  @param name The new address name
 *  @return 0  if ok
 *          -1 if error
 */
int InetAddress::setAddress(const char *name)
{
  TRACE_CLASS("InetAddress", "setAddress(char*)");
  if (name) {
    if (atoi(name)) {   // If it start with a number
      if ((addr.sin_addr.s_addr = inet_addr(name)) == INADDR_NONE) {
	addr.sin_addr.s_addr = htons(INADDR_ANY);
	inetErrNo = 0;
	inetErrMsg = "Invalid address";
	inetErrDesc = name;
	return -1;
      }
    } else {            // Assume it's a real name
      hostent *host = gethostbyname(name);
      if (host) {
	addr.sin_addr = *(in_addr *)(host->h_addr);
      } else {
	addr.sin_addr.s_addr = htons(INADDR_ANY);
	inetErrNo = 0;
	inetErrMsg = "Could not find address";
	inetErrDesc = name;
	return -1;
      }
    }
  } else {
    addr.sin_addr.s_addr = INADDR_ANY;
  }
  return 0;
}


void setInetError(const char* description)
{
  inetErrNo = 0;
#ifdef _WIN32
  inetErrNo = WSAGetLastError();
  switch (inetErrNo) {
  case WSANOTINITIALISED:
    inetErrMsg = "WSANOTINITIALISED A successful WSAStartup must occur before using this function.";
    break;
  case WSAENETDOWN:
    inetErrMsg = "WSAENETDOWN The network subsystem has failed.";
    break;
  case WSAEFAULT:
    inetErrMsg = "WSAEFAULT The buf or from parameters are not part of the user address space, or the fromlen parameter is too small to accommodate the peer address.";
    break;
  case WSAEINTR:
    inetErrMsg = "WSAEINTR The (blocking) call was canceled through WSACancelBlockingCall.";
    break;
  case WSAEINPROGRESS:
    inetErrMsg = "WSAEINPROGRESS A blocking Windows Sockets 1.1 call is in progress, or the service provider is still processing a callback function.";
    break;
  case WSAEINVAL:
    inetErrMsg = "WSAEINVAL The socket has not been bound with bind, or an unknown flag was specified, or MSG_OOB was specified for a socket with SO_OOBINLINE enabled, or (for byte stream-style sockets only) len was zero or negative.";
    break;
  case WSAEISCONN:
    inetErrMsg = "WSAEISCONN The socket is connected. This function is not permitted with a connected socket, whether the socket is connection-oriented or connectionless.";
    break;
  case WSAENETRESET:
    inetErrMsg = "WSAENETRESET The connection has been broken due to the \"keep-alive\" activity detecting a failure while the operation was in progress.";
    break;
  case WSAENOTSOCK:
    inetErrMsg = "WSAENOTSOCK The descriptor is not a socket.";
    break;
  case WSAEOPNOTSUPP:
    inetErrMsg = "WSAEOPNOTSUPP MSG_OOB was specified, but the socket is not stream-style such as type SOCK_STREAM, out-of-band data is not supported in the communication domain associated with this socket, or the socket is unidirectional and supports only send operations.";
    break;
  case WSAESHUTDOWN:
    inetErrMsg = "WSAESHUTDOWN The socket has been shut down; it is not possible to recvfrom on a socket after shutdown has been invoked with how set to SD_RECEIVE or SD_BOTH.";
    break;
  case WSAEWOULDBLOCK:
    inetErrMsg = "WSAEWOULDBLOCK The socket is marked as nonblocking and the recvfrom operation would block.";
    break;
  case WSAEMSGSIZE:
    inetErrMsg = "WSAEMSGSIZE The message was too large to fit into the specified buffer and was truncated.";
    break;
  case WSAETIMEDOUT:
    inetErrMsg = "WSAETIMEDOUT The connection has been dropped, because of a network failure or because the system on the other end went down without notice.";
    break;
  case WSAECONNRESET:
    inetErrMsg = "WSAECONNRESET";
    break;
  case WSAEACCES:
    inetErrMsg = "WSAEACCES The requested address is a broadcast address, but the appropriate flag was not set. Call setsockopt with the SO_BROADCAST parameter to allow the use of the broadcast address.";
    break;
  case WSAENOBUFS:
    inetErrMsg = "WSAENOBUFS No buffer space is available.";
    break;
  case WSAENOTCONN:
    inetErrMsg = "WSAENOTCONN The socket is not connected (connection-oriented sockets only)";
    break;
  case WSAEHOSTUNREACH:
    inetErrMsg = "WSAEHOSTUNREACH The remote host cannot be reached from this host at this time.";
    break;
  case WSAECONNABORTED:
    inetErrMsg = "WSAECONNABORTED The virtual circuit was terminated due to a time-out or other failure. The application should close the socket as it is no longer usable.";
    break;
  case WSAEADDRNOTAVAIL:
    inetErrMsg = "WSAEADDRNOTAVAIL The remote address is not a valid address, for example, ADDR_ANY.";
    break;
  case WSAEAFNOSUPPORT:
    inetErrMsg = "WSAEAFNOSUPPORT Addresses in the specified family cannot be used with this socket.";
    break;
  case WSAEDESTADDRREQ:
    inetErrMsg = "WSAEDESTADDRREQ A destination address is required.";
    break;
  case WSAENETUNREACH:
    inetErrMsg = "WSAENETUNREACH The network cannot be reached from this host at this time.";
    break;
  case WSAEMFILE:
	inetErrMsg = "No more socket descriptors are available.";
	break;
  case WSAEPROTONOSUPPORT:
	inetErrMsg = "The specified protocol is not supported.";
	break;
  case WSAEPROTOTYPE:
	inetErrMsg = "The specified protocol is the wrong type for this socket.";
	break;
  case WSAESOCKTNOSUPPORT:
	inetErrMsg = "The specified socket type is not supported in this address family.";
    break;
  default:
    inetErrMsg = "Unknown";
  };
#else
  inetErrNo = errno;
  inetErrMsg = strerror(inetErrNo);
#endif
  inetErrDesc = description;
}