#include <Inventor/SbTime.h>
#include <Inventor/C/tidbitsp.h> // for coin_atexit

#if defined(__WIN32__) || defined(_WIN32)
# include <windows.h>  // for Sleep()
#else
# include <unistd.h>  // for usleep()
#endif

#include <Inventor/cve/SoNetwork.h>
#include <Inventor/cve/SoNetworkP.h>
#include <Inventor/cve/TNet.h>
#include <Inventor/cve/SoComputer.h>
#include <Inventor/cve/SbNetListener.h>



static SbBool sonetworkInitialized = 0;
static SoDistributionGroup *defaultDistribGroup;
static SbBool deleteDistribGroup;
static SoThisComputer *thisComputer;
static SbBool processingInProgress = FALSE;



#if CVE_DEBUG
static SbBool cve_printStatistics() {
  static int debug = -1;
  if (debug == -1) {
    const char *env = coin_getenv("CVE_PRINT_STATISTICS");
    if (env)  debug = atoi(env);
    else  debug = 0;
  }
  return debug;
}
#endif


//! Initializes network.
SbBool SoNetwork::init()
{
  if (sonetworkInitialized) 
    return TRUE;

  // at exit callback:
  static SbBool alreadyExecuted = FALSE;
  if (!alreadyExecuted) {
    SoMFComputer::initClass();
    SoMFNetListener::initClass();
    SbNetMessage::initClass();
    SbNMNullMessage::initClass();
    SoDistributionGroup::initClass();
    coin_atexit(SoNetwork::cleanup, 0);
    alreadyExecuted = TRUE;
  }

  sonetworkInitialized = (tnInit()) ? TRUE : FALSE;

  // create default distribution group 
  thisComputer = new SoThisComputer;
  defaultDistribGroup = new SoDistributionGroup;
  defaultDistribGroup->ref();
  deleteDistribGroup = TRUE;

  return sonetworkInitialized;
}


//! Closes all network connections and deallocates all resources.
void SoNetwork::cleanup()
{
  if (!sonetworkInitialized)
    return;

  if (deleteDistribGroup)
    defaultDistribGroup->unref();

  delete thisComputer;

  tnCleanUp();
  sonetworkInitialized = FALSE;
}


SbBool SoNetwork::isInitialized()
{
  return sonetworkInitialized;
}


/*! Terminates all network connections and closes all listeners.
 *  It returns TRUE if all computers have been disconnected before timeout.
 *  Otherwise FALSE is returned and all connections to pending computers are closed by force.
 */
SbBool SoNetwork::shutdown(int timeout_ms)
{
  SbBool ok = disconnectAllComputers(timeout_ms, FALSE);
  if (!ok)  disconnectAllComputers(0, TRUE);
  closeAllListeners();
  SoNetwork::cleanup();
  return ok;
}

  
SbBool SoNetwork::shutdownAsync()
{
  assert(0 && "FIXME: not implemented.");
  return 0;
}


void SoNetwork::process()
{
  if (!sonetworkInitialized)
    return;

  processingInProgress = TRUE;
  SoNetworkP::numTrReceived = 0;
  SoNetworkP::numTrExecuted = 0;
  SoNetworkP::numMsgReceived = 0;
  SbTime start = SbTime::getTimeOfDay();

  // update realTime field
  // (it updates time of timeStamp generator)
  SoSFTime *realTime = (SoSFTime*)SoDB::getGlobalField(SbName("realTime"));
  assert(realTime && realTime->getTypeId().isDerivedFrom(SoSFTime::getClassTypeId()));
  realTime->setValue(start);

  // process computers - receive messages
  int i,c = SoNetworkP::compList.getLength();
  for (i=0; i<c; i++)
    SoNetworkP::compList[i]->process();

  // process distribGroups - execute transactions
  c = SoNetworkP::distribGroupList.getLength();
  for (i=0; i<c; i++)
    SoNetworkP::distribGroupList[i]->process();

  processingInProgress = FALSE;

#if CVE_DEBUG
  if (cve_printStatistics())
    if (1) {//r!=0 || e!=0 || THIS->numScheduled!=0) {
      printf("SoNetwork::process() Stats: recv: %i, exect: %i, schedl: %i, funcTime: %.2fms.\n", 
             SoNetworkP::numTrReceived, SoNetworkP::numTrExecuted, SoNetworkP::numTrScheduled, (SbTime::getTimeOfDay()-start)*1e3);
    }
#endif

  SoNetworkP::numTrScheduled = 0;
}


const SoComputerList& SoNetwork::getComputerList()
{
  return SoNetworkP::compList;
}


const SoDistributionGroupList& SoNetwork::getDistributionGroupList()
{
  return SoNetworkP::distribGroupList;
}


SbBool SoNetwork::isProcessing()
{
  return processingInProgress;
}


/*! Returns TRUE if all computers have been disconnected before timeout.
 *  Otherwise FALSE is returned and all connections to pending computers are closed.
 */
SbBool SoNetwork::disconnectAllComputers(int timeout_ms, SbBool dropAfterTimeout)
{
  int i,c;
  c = SoNetworkP::compList.getLength();
  for (i=0; i<c; i++)
    SoNetworkP::compList[i]->disconnect();

  SbTime stopTime = SbTime::getTimeOfDay() + double(timeout_ms)*1e-3;
  do {
  
    SbBool done = TRUE;
    for (i=0; i<c; i++)
      if (!SoNetworkP::compList[i]->isDisconnected())
        done = FALSE;

    if (done) return TRUE;
    if (SbTime::getTimeOfDay() >= stopTime) break;

    SoNetwork::sleep(10);
  } while (TRUE);

  if (dropAfterTimeout) {
    for (i=0; i<c; i++)
      SoNetworkP::compList[i]->dropConnection();
  }
  return FALSE;
}


SbBool SoNetwork::disconnectAllComputersAsync()
{
  assert(0 && "FIXME: not implemented.");
  return 0;
}


void SoNetwork::closeAllListeners()
{
  int i,c;
  c = SoNetworkP::listenerList.getLength();
  for (i=0; i<c; i++)
    SoNetworkP::listenerList[i]->close();
}


void SoNetwork::sleep(int ms)
{
#if defined(_WIN32)  || defined(__WIN32__)
  Sleep(ms); // value in ms
#else
  usleep(ms*1000); // value in us
#endif
}


SoDistributionGroup* SoNetwork::getDefaultDistributionGroup()
{
  return defaultDistribGroup;
}


void SoNetwork::setDefaultDistributionGroup(SoDistributionGroup *dg)
{
  if (deleteDistribGroup) {
    defaultDistribGroup->unref();
    deleteDistribGroup = FALSE;
  }
  
  defaultDistribGroup = dg;
}


//! Returns list of IP that are available on this computer (for computers with more network interfaces).
void SoNetwork::getIPAddresses(SbInetIPList &list)
{
  SbInetIP *ipList = tnGetLocalIPAddresses();
  list.truncate(0);
  if (!ipList)
    return;

  for (int i=0;; i++) {
    if (ipList[i] == 0) break;
    list.append(ipList[i]);
  }
  tnFreeMem(ipList);
}


/*! Returns new instance of SoComputer and performs connect operation for it.
 *  In the time of return, computer can be in three states - still connecting,
 *  connected or in error state if connection attempt has not been successful.
 *  Connection may be established through any computer interface.
 */
SoComputer* SoNetwork::connect(const SbInetAddr &addr)
{
  return SoNetwork::connect(addr, SbInetAddr(0,0));
}


/*! Returns new instance of SoComputer and performs connect operation for it.
 *  In the time of return, computer can be in three states - still connecting,
 *  connected or in error state if connection attempt has not been successful.
 *  Connection is established through the computer interface given by the localAddr parameter.
 */
SoComputer* SoNetwork::connect(const SbInetAddr &addr, const SbInetAddr &localAddr)
{
  SoComputer *comp = new SoComputer;
  comp->connect(addr, localAddr);
  return comp;
}


SoComputer* SoNetwork::getThisComputer()
{
  return thisComputer;
}
