#include <Inventor/SoDB.h>
#include <Inventor/errors/SoReadError.h>
#include <Inventor/fields/SoSFTime.h>
#include <Inventor/sensors/SoFieldSensor.h>
#include <Inventor/C/tidbitsp.h> // coin_atexit

#include <Inventor/cve/SbTimeStamp.h>
#include <Inventor/cve/SbNetBasicP.h>


// global values that are used by an application
// to generate unique timestamps
static double appTime = -1.; // time that is gained from realTime field (makes increasing monotonic sequence of numbers)
static double timeAdjustment = 0.f;
static double lowestMultiplier;
static uint32_t appCounter;  // logical counter that is incremented for each new timestamp,
                             // it is set to zero each time when appTime is updated


static SoFieldSensor *realTimeSensor;
static SoSFTime* realTimeField;
static const SbTimeStamp zeroTimeStamp(0., SbComputerIdentity(NULL, -1), 0);

static void realTimeCb(void *data, SoSensor *sensor);



SbTimeStamp SbTimeStamp::generate(SoDistributionGroup *dg)
{
  if (appTime == -1.) {

    realTimeField = (SoSFTime*)SoDB::getGlobalField("realTime");
    assert(realTimeField && (realTimeField->getTypeId() == SoSFTime::getClassTypeId()) &&
           "Something is wrong with realTime field.");

    realTimeSensor = new SoFieldSensor(realTimeCb, NULL);
    realTimeSensor->attach(realTimeField);
    realTimeSensor->setPriority(0);
    realTimeCb(NULL, realTimeSensor);

#if 0 // Following code for computing lowest multiplier does not work with g++
      // (maybe some computing optimizations that elimiates desired result).
    double tmp = 2.;
    do {
      lowestMultiplier = tmp;
      tmp = (tmp-1.)/2. + 1.;
    } while (tmp != 1. && tmp != lowestMultiplier);
    lowestMultiplier = lowestMultiplier*4. - 4.; // to avoid problems with possible floating point non-precise calculations
#else
    lowestMultiplier = 1.000000000000000444; // this is double 1. increased two times by minimal double addition
#endif
    assert(lowestMultiplier > 1. && "Something is wrong with lowestMultiplier.");
  
    coin_atexit(SbTimeStamp::cleanup, 1);
  }

  return SbTimeStamp(appTime, SoNetwork::getThisComputer()->getComputerIdentity(dg), appCounter++);
}


SbBool SbTimeStamp::adjustGeneratorFloor(const SbTimeStamp &ts)
{
  if (ts.getTime() < appTime)
    // ts is less, no need to adjust time stamp generator
    return FALSE;

  // make sure timeStamp generator is initialized
  if (appTime == -1.)
    SbTimeStamp::generate(ts.getComputerIdentity().dg);

  double delta = ts.getTime() - appTime;
  double oldTime = appTime;
  appTime = ts.getTime() * lowestMultiplier; // multiplying by lowestMultiplier will force 
                                      // timestamps to be _always_ greater then parameter ts
  assert(appTime > ts.getTime() && "Multiplying by lowest multiplier did not incremented the value.");

  timeAdjustment += appTime-oldTime;

  return TRUE;
}


const SbTimeStamp& SbTimeStamp::zero()
{
  return zeroTimeStamp;
}


SbBool SbTimeStamp::isZero() const
{
  return time == 0.;
}


static void realTimeCb(void *data, SoSensor *sensor)
{
  double ct = realTimeField->getValue().getValue();
  assert(ct+timeAdjustment > appTime && "Time is going backward or is not increasing.");
  appTime = ct + timeAdjustment;
  appCounter = 0;
}


void SbTimeStamp::cleanup()
{
  delete realTimeSensor;
  appTime = -1;
}


SbTimeStamp::SbTimeStamp()
{
  compId.dg = NULL;
}


SbTimeStamp::SbTimeStamp(const SbTimeStamp &ts)
{
  time = ts.time;
  compId = ts.compId;
  counter = ts.counter;
}


SbTimeStamp::SbTimeStamp(const double time, const SbComputerIdentity &compId, const uint32_t counter)
{
  this->time = time;
  this->compId = compId;
  this->counter = counter;
}


SbTimeStamp::~SbTimeStamp()
{
}


const double& SbTimeStamp::getTime() const
{
  return time;
}


SoComputer* SbTimeStamp::getComputer() const
{
  if (compId.dg == NULL) {
    assert(compId.id == -1);
    return SoNetwork::getThisComputer();
  }

  assert(compId.id != -1);
  return compId.dg->computer[compId.id];
}


const SbComputerIdentity& SbTimeStamp::getComputerIdentity() const
{
  return compId;
}


int SbTimeStamp::getCounter() const
{
  return counter;
}


SbString SbTimeStamp::getString() const
{
  SbString r;
  if (compId.isValid())
    // note: .7 is used for time to make sure, the last significant bit is printed.
    // The hack was implemented because VC6 does not preserve least significant bit sometimes.
    // This quick hack shall work for all time values greater than 1e9.
    r.sprintf("%.7f,%i,%i", time, compId.id, counter); 
  else
    r.sprintf("%.7f,<not valid>,%i", time, compId.id, counter);
  return r;
}


static int compareIdentities(const SbComputerIdentity &i1, const SbComputerIdentity &i2) 
{
  if (i1.dg == i2.dg) {
    if (i1.dg != NULL)
      /* identities are equal and non-NULL */
      return i1.id < i2.id ? -1 :
             i1.id == i2.id ? 0 : 1;
    else {
      /* both identities are NULL */
      assert(i1.id==-1 && i2.id==-1);
      return 0;
    }
  }

  if (i1.dg!=NULL && i2.dg!=NULL) {
    /* identities are not comparable */
    SoDebugError::postWarning("SbTimeStamp::compareIdentities",
                              "The TimeStamps are not comparable. Their time is equal\n"
                              "but they belong to different distribution groups. Handling by returning 0\n"
                              "(it means equal), but there is something seriously wrong.");
    return 0;
  }

  int i;
  if (i1.dg == NULL) {
    /* only i1 is NULL */
    i = i2.dg->getThisComputerIndex();
    return i < i2.id ? -1 :
           i == i2.id ? 0 : 1;
  } else {
    /* only i2 is NULL */
    i = i1.dg->getThisComputerIndex();
    return i < i1.id ? -1 :
           i == i1.id ? 0 : 1;
  }
}


int SbTimeStamp::operator== (const SbTimeStamp &ts) const
{
  if (time == ts.time) {
    if (time == 0.)
      return TRUE;
    else {
      if (compareIdentities(compId, ts.compId) != 0)
        return FALSE;
      else
        return counter==ts.counter;
    }
  }
  return FALSE;
}


int SbTimeStamp::operator!= (const SbTimeStamp &ts) const
{
  return !operator==(ts);
}


#define OPERATION(_op1_,_op2_,_zeroRet_,_lastop_) \
  if (time _op1_ ts.time)  return TRUE; \
  if (time _op2_ ts.time)  return FALSE; \
\
  if (time == 0.) \
    return _zeroRet_; \
  else { \
    int i = compareIdentities(compId, ts.compId); \
    if (i _op1_ 0)  return TRUE; \
    if (i _op2_ 0)  return FALSE; \
\
    if (counter _lastop_ ts.counter) \
      return TRUE; \
    else \
      return FALSE; \
  }


SbBool SbTimeStamp::operator<  (const SbTimeStamp &ts) const
{
  OPERATION(<, >, FALSE, <);
}


SbBool SbTimeStamp::operator>  (const SbTimeStamp &ts) const
{
  OPERATION(>, <, FALSE, >);
}


SbBool SbTimeStamp::operator<= (const SbTimeStamp &ts) const
{
  OPERATION(<, >, TRUE, <=);
}


SbBool SbTimeStamp::operator>= (const SbTimeStamp &ts) const
{
  OPERATION(>, <, TRUE, >=);
}

#undef OPERATION


void SbTimeStamp::write(SoOutput *out, SoDistributionGroup *dg) const
{
  assert((compId.dg!=NULL && compId.id!=-1) || (compId.dg==NULL && compId.id==-1));

  int id = (compId.id == -1) ? dg->getThisComputerIndex() : compId.id;

  // write time as binary dump to transmit double exactly as it is
  // (Coin does not preserve least significant bits through SoOutput/SoInput transfer.
  // SoInput_FileInfo::readReal() looks like probable source of the problem.) PCJohn-2006-02-28
  encodeBinaryData(out, (void*)&time, sizeof(time));

  SO_OUTPUT_SPACE(out);
  out->write(id);
  SO_OUTPUT_SPACE(out);
  out->write(counter);
}


SbBool SbTimeStamp::read(SoInput *in, SoDistributionGroup *dg)
{
  if (!decodeBinaryData(in, (void*)&time, sizeof(time))) {
    SoReadError::post(in, "Couldn't read SbTimeStamp."); 
    return FALSE;
  }

  if (!in->read(compId.id) ||
      !in->read(counter)) {
    SoReadError::post(in, "Couldn't read SbTimeStamp.");
    return FALSE;
  }

  compId.dg = dg;
  if (compId.id == -1) {
    SoDebugError::postWarning("SbTimeStamp::read",
                              "TimeStamp that was read from the stream has no identity.\n"
                              "Using identity of this computer.\n"
                              "This is potential source of future problems.");
    compId.id = dg->getThisComputerIndex();
  }
  return TRUE;
}


SbString SbTimeStamp::get(SoDistributionGroup *dg) const
{
  SoOutput out;
  out.setBuffer(malloc(40), 40, realloc);

  // skip header
  out.write("");
  void *p;
  size_t offset;
  out.getBuffer(p, offset);

  // write data
  write(&out, dg);
  size_t size;
  out.getBuffer(p, size);
  SbString str(((char*)p)+offset);
  free(p);
  return str;
}


SbBool SbTimeStamp::set(const char *string, SoDistributionGroup *dg)
{
  SoInput in;
  in.setBuffer((void*)string, strlen(string));
  return read(&in, dg);
}
