#include <Inventor/SbString.h>
#include <stdlib.h>
#include <string.h>

#include <Inventor/cve/SbNetBasic.h>
#include <Inventor/cve/SoNetwork.h>
#include <Inventor/cve/TNet.h>

#if !defined(__WIN32__) && !defined(_WIN32)
#include <errno.h>
#endif

#if defined(__WIN32__) || defined(_WIN32)
# include <process.h> // for _getpid()
# define getpid() _getpid()
#else
# include <unistd.h> // for getpid()
#endif


SbInetAddr::SbInetAddr(const char *s)
{
  if (!setString(s)) {
    ip = 0;
    port = 0;
  }
}


void SbInetAddr::setIP(const SbInetIP v)
{
  ip = v;
}


void SbInetAddr::setPort(const SbInetPort v)
{
  port = v;
}


/*! Returns ip:port string.
 *  Note that "unspecified" address (for multi-homed purposes, known as INADDR_ANY)
 *  is returned as 0.0.0.0. Also, "unspecified" port is returned as 0.
 */
SbString SbInetAddr::getString() const
{
  SbString result = ipToString(ip);
  result += ":";
  result += portToString(port);
  return result;
}


/*! Sets the address from the string. The string given by parameter s must contain
 *  ip:port pair or ip only.
 *  If only ip is specified, zero is assigned to the port.
 *  On error, FALSE is returned and no changes to the object are made.
 */
SbBool SbInetAddr::setString(const char *s)
{
  // FIXME: merge this code with other functions. PCJohn 2005-05-06

  // NULL handling
  if (s == NULL)
    return FALSE;
  
  // find ':'
  char *colonChar = strstr(s, ":");
  int colonPos = (colonChar==NULL) ? -1 : colonChar - s;
  SbString sip;
  SbString sport;

  // generate sip and sport strings
  if (colonPos != -1) {
    sip = SbString(s, 0, colonPos-1);
    if (int(strlen(s)) <= colonPos+1)
      return FALSE;
    sport = SbString(s, colonPos+1, -1);
  } else {
    sip = s;
    sport = "0";
  }

  // parse sip and sport strings
  SbInetIP tmpIP;
  unsigned long tmpPort;
  if (!tnStr2IP(sip.getString(), &tmpIP))
    return FALSE;
  const char *nptr = sport.getString();
  char *endptr;
  tmpPort = strtoul(nptr, &endptr, 10);
  if (endptr != (nptr+sport.getLength()) || tmpPort > 65535 || (errno == ERANGE))
    return FALSE;

  // if everything went right, update object
  ip = tmpIP;
  port = SbInetPort(tmpPort);
  return TRUE;
}


/*! Sets the address from the string given by the ptr parameter.
 *  The address can be ip or ip:port pair and they may be followed a string.
 *  In the case of error, the object state is not changed and FALSE is returned.
 *  If the string was successfully read and if endptr parameter is not NULL,
 *  the endptr parameter is updated to point at the first character after the address string.
 *  If there is no string after the address, it points to the terminating zero character.
 */
SbBool SbInetAddr::setString(const char *ptr, char **endptr)
{
  // NULL handling
  if (ptr == NULL)
    return FALSE;
  
  // FIXME: merge this code with other functions. PCJohn 2005-05-06
  const char *tmpPtr = ptr;
  SbInetIP tmpIP;

  if (!tnReadIP(&tmpPtr, &tmpIP))
    return FALSE;

  if (*tmpPtr != ':') {
    ip = tmpIP;
    port = 0;
    if (endptr != NULL)
      (*endptr) = (char*)tmpPtr;
    return TRUE;
  }

  tmpPtr++;
  char *tmpEnd;
  unsigned long tmpPort = strtoul(tmpPtr, &tmpEnd, 10);

  if (tmpPtr == tmpEnd || tmpPort > 65535 || errno == ERANGE)
    return FALSE;

  ip = tmpIP;
  port = SbInetPort(tmpPort);
  if (endptr != NULL)
    (*endptr) = tmpEnd;
  return TRUE;
}


SbString SbInetAddr::ipToString(SbInetIP ip)
{
  char sip[16];
  if (!tnIP2Str(ip, sip, sizeof(sip))) {
    assert(FALSE && "Something is wrong with ip addresses. (Trailing zeros?)");
    return SbString(); // This should never happen.
  }
  
  return SbString(sip);
}


SbBool SbInetAddr::stringToIP(const char *s, SbInetIP &ip, const char **endptr)
{
  SbInetIP tmpIP;

  if (!tnReadIP(&s, &tmpIP))
    return FALSE;

  ip = tmpIP;
  if (endptr != NULL)
    (*endptr) = (char*)s;
  
  return TRUE;
}


SbString SbInetAddr::portToString(SbInetPort port)
{
  char sport[6];
  char *p = &sport[5];
  *p = 0;
  do {
    *(--p) = (port % 10) + '0';
    port /= 10;
  } while (port != 0);
  assert(p>=&sport[0] && "Buffer overrun detected. This should never happen.");

  return SbString(p);
}


SbBool SbInetAddr::stringToPort(const char *s, SbInetPort &port, const char **endptr)
{
  char *tmpEnd;
  unsigned long tmpPort = strtoul(s, &tmpEnd, 10);

  if (endptr != NULL)
    (*endptr) = (char*)tmpEnd;

  if (s == tmpEnd || tmpPort > 65535 || errno == ERANGE)
    return FALSE;
  
  port = (SbInetPort)tmpPort;
  return TRUE;
}


//! Returns TRUE if ip and port attributes are equal.
int SbInetAddr::operator== (const SbInetAddr &ia) const
{
  return this->ip==ia.ip && this->port==ia.port;
}


//! Returns TRUE if ip or port differs.
int SbInetAddr::operator!= (const SbInetAddr &ia) const
{
  return this->ip!=ia.ip || this->port!=ia.port;
}


SbInetAddr SbInetAddr::invalid()
{
  return SbInetAddr(0,0);
}


SbBool SbInetAddr::isValid() const
{
  return ip != 0 || port != 0;
}


void SbInetAddr::makeInvalid()
{
  ip = 0;
  port = 0;
}


SbComputerIdentity::SbComputerIdentity(SoDistributionGroup * const dg, const int32_t id)
{
  this->dg = dg;
  this->id = id;
}


SbBool SbComputerIdentity::isValid() const
{
  return dg != NULL && id != -1;
}


SbBool operator== (const SbComputerIdentity &ci1, const SbComputerIdentity &ci2)
{
  // handle invalid identities
  if (!ci1.isValid())
    return ci2.isValid() == FALSE;

  if (!ci2.isValid())
    return FALSE;

  // handle identities in the same replication group
  if (ci1.dg == ci2.dg)
    return ci1.id == ci2.id;

  assert(0 && "Comparing identities from different replication groups.\n"
              "This is an dangerous operation, therefore it is forbidden.");

#if 0 // forbidden, see the assert above

  // try to find computer1 in ci2 replicationGroup
  SbComputerIndentity alternate1 = ci2.dg->getComputerIdentity(ci1);
  if (alternate1.isValid())
    return alternate1.id == ci2.id;

  // try to find computer2 in ci1 replicationGroup
  SbComputerIndentity alternate2 = ci1.dg->getComputerIdentity(ci2);
  if (alternate2.isValid())
    return alternate2.id == ci1.id;
#endif

  // identities can not be compared
  return FALSE;
}


SbBool operator!= (const SbComputerIdentity &ci1, const SbComputerIdentity &ci2)
{
  return !operator== (ci1, ci2);
}
