#include <Inventor/SoInput.h>
#include <Inventor/SoOutput.h>
#include <Inventor/SoPath.h>
#include <Inventor/SbString.h>
#include <Inventor/nodes/SoGroup.h>
#include <Inventor/fields/SoField.h>
#include <Inventor/fields/SoFieldContainer.h>
#include <Inventor/fields/SoFieldData.h>
#include <Inventor/errors/SoDebugError.h>
#include <Inventor/errors/SoReadError.h>
#include <Inventor/misc/SbHash.h>
#include <string.h>

#include <Inventor/cve/SbTransaction.h>
#include <Inventor/cve/SbNetBasicP.h>
#include <Inventor/cve/SoDistributionGroup.h>
#include <Inventor/cve/SoNetworkP.h>



#define DEFAULT_STREAM_BUFFER_SIZE 4032 // 4KB without 64 bytes that are left for memory manager to be 4KB aligned



static SbHash<SbTransactionCB*, const char *> name2cb;



void SbTransaction::schedule()
{
  assert(objData.state == CONSTRUCTING && "Wrong SbTransaction usage. schedule() can be called in CONSTRUCTING state only.");
  
  if (distributionGroup->getState() != SoDistributionGroup::RUNNING ||
      distributionGroup->active[distributionGroup->getThisComputerIndex()] == FALSE) {
    this->abort();
    static SbBool first = TRUE;
    if (first) {
      first = FALSE;
      SoDebugError::postWarning("SbTransaction::scheduleToMembers",
                                "The transaction was aborted since scheduling is not allowed while "
                                "transaction's distribution group is not active or is not in the RUNNING state.\n"
                                "This message is shown only once although they may be more such transactions.");
    }
    return;
  }
  
  // set transaction state
  objData.state = SCHEDULED;

  // generate timestamps
  timeStamp = SbTimeStamp::generate(distributionGroup);

  // update readSetItem write flags
  int i,c;
  c = readSet.getLength();
  for (i=0; i<c; i++) {
    SbReadSetItem *rsi = readSet[i];
    SoCveStorage *data = rsi->field->getCveStorage();
    assert(data);
    assert(data->unscheduledReads.find(rsi) >= 0);
    data->unscheduledReads.removeItem(rsi);
    data->orderlyAppendRead(rsi);
    rsi->updateCommitPredictor();
  }

  // "execute" transaction locally,
  // just move write items from unordered "reference" list to regular ordered writeList
  c = writeSet.getLength();
  for (i=0; i<c; i++) {
    
    // move writeSetItems from unscheduledWritesList into the writeSetList
    SbWriteSetItem *wsi = writeSet[i];
    SoField *f = wsi->getField();
    SoCveStorage *data = f->getCveStorage();
    assert(data);
    data->unscheduledWrites.removeItem(wsi);
    data->orderlyAppendWrite(wsi);

    // depending on updateGraphModel, update the scene graph
    if (distributionGroup->updateGraphModel == SoDistributionGroup::LATEST_VALUE)
      f->cveSetFieldValue(wsi);
  }

  // update flags
  objData.readSetComplete = TRUE;
  updateReadSetCommitPredictor(); // cached values was initialized during read set construction
  updateAssertCommitPredictor(FALSE);
  updateCurrentCommitPredictor();
  // note: there is no need of propagation here since the transaction has the largest timestamp
  // of all transactions currently present on this computer

#if CVE_DEBUG
  int debugLevel = cve_debugTransactions();
  if (debugLevel == 2) {
    if (commitCallbackName != SoDistributionGroup::getVerifyExecutionCallbackName() &&
        commitCallbackName != SoDistributionGroup::getNullCallbackName())
      printf("Tra %s 1 SCHEDULED.\n", timeStamp.getString().getString());
  } else
  if (debugLevel >= 3) {
    if (commitCallbackName == SoDistributionGroup::getVerifyExecutionCallbackName())
      printf("VTr %s 1 SCHEDULED.\n", timeStamp.getString().getString());
    else
    if (commitCallbackName == SoDistributionGroup::getNullCallbackName())
      printf("NTr %s 1 SCHEDULED.\n", timeStamp.getString().getString());
    else
      printf("Tra %s 1 SCHEDULED (Name: %s, RSC %i, RSCP %i, ACP %i, CP %i).\n",
             timeStamp.getString().getString(), commitCallbackName.getString(),
             objData.readSetComplete, objData.readSetCommitPredictor, objData.assertCommitPredictor, getCommitPredictor() ? 1 : 0);
  }
#endif

  // broadcast the transaction
  distributionGroup->sendTransaction(this);
}


SbBool SbTransaction::read(SoInput *in)
{
  assert(objData.state == CONSTRUCTING && "SbTransaction::read can be called in CONSTRUCTING state only.");

  objData.state = SCHEDULED; // this must be done on the beginning of the function
  
  int i,c;
  SbString name;

  // timestamp
  if (!timeStamp.read(in, distributionGroup)) goto error;

  // global flag
  if (!in->read(global)) goto error;

  // update timeStamp generator (this MUST be done together with inserting transaction to
  // the scene - consistency reason: local write appears as a first record in shadow copies)
  SbTimeStamp::adjustGeneratorFloor(timeStamp);

  // update timestamps on non-active computers
  if (global && distributionGroup->getState() == SoDistributionGroup::RUNNING &&
      distributionGroup->active[distributionGroup->getThisComputerIndex()])
    SoComputer::updateLinkTimesOfNonActiveComputers(SbTimeStamp::generate(distributionGroup),
                                                    distributionGroup);

  // read set
  if (!in->read(c)) goto error;
  readSet = SbReadSetItemList(c);

  for (i=0; i<c; i++) {
    
    // create SbReadSetItem and read it from the stream
    SbReadSetItem *ri = new SbReadSetItem;
    if (!ri->read(in, distributionGroup, this)) {
      delete ri;
      goto error;
    }

    // append SbReadSetItem into the transaction and field lists
    readSet.append(ri);
    ri->field->extendCveStorageIfNecessary();
    ri->field->getCveStorage()->orderlyAppendRead(ri);
    ri->updateCommitPredictor();
  }

  // write set
  if (!in->read(c)) goto error;
  writeSet = SbWriteSetItemList(c);

  for (i=0; i<c; i++) {

    // read SbWriteSetItem    
    SbWriteSetItem *wi = SbWriteSetItem::read(in, distributionGroup, this);
    if (!wi) goto error;

    // append SbWriteSetItem into the transaction and field lists
    writeSet.append(wi);
    wi->getField()->extendCveStorageIfNecessary();
    wi->getField()->getCveStorage()->orderlyAppendWrite(wi);
  }

  // scene asserts
  if (!in->read(c)) goto error;
  assertList = SbList<SbSceneAssert*>(c);
  int index;
  for (i=0; i<c; i++) {
    if (!in->read(index)) goto error;
    assert(index >= 0 && index < distributionGroup->assertList.getLength() && 
           "Index of a scene assert out of bounds. The lists probably lost the consistency.");
    assertList.append(distributionGroup->assertList[index]);
  }

  // callback names
  if (!in->read(name)) goto error;
  commitCallbackName = name;
  if (!in->read(name)) goto error;
  abortCallbackName = name;
  
  // user data
  if (!in->read(userDataSize)) goto error;
  free(userData);
  if (userDataSize == 0)
    userData = NULL;
  else {
    userData = (uint8_t*)malloc(userDataSize);
    assert(userData != NULL && "Can not allocate memory for transaction's callback data.");
    if (!decodeBinaryData(in, userData, userDataSize)) goto error;
  }

  // update commit predictors
  updateReadSetComplete();
  updateReadSetCommitPredictor();
  updateAssertCommitPredictor(FALSE);
  updateCurrentCommitPredictor();
  propagateCommitPredictor();

#if CVE_DEBUG
  if (cve_debugTransactions() >= 3) {
    printf("Tra %s 2 READ      (", timeStamp.getString().getString());
    dumpTransactionCPs();
    printf(").\n");
  }

#endif

  // update fields values
  if (distributionGroup->updateGraphModel == SoDistributionGroup::LATEST_VALUE)
    putWriteSetValuesToPublic();

  return TRUE;

error:
  abort();
  SoReadError::post(in, "Failed to read SbTransaction.");
  getSender()->dataIntegrityFailure();
  return FALSE;
}


void SbTransaction::write(SoOutput *out) const
{
  // timeStamp
  int i,c;
  timeStamp.write(out,distributionGroup);
  SO_OUTPUT_LN(out);

  // global flag
  out->write(global);
  SO_OUTPUT_LN(out);

  // read set
  c = readSet.getLength();
  out->write(c);
  SO_OUTPUT_LN(out);
  for (i=0; i<c; i++) {
    readSet[i]->write(out,distributionGroup);
    SO_OUTPUT_LN(out);
  }

  // write set
  c = writeSet.getLength();
  out->write(c);
  for (i=0; i<c; i++) {
    SO_OUTPUT_LN(out);
    writeSet[i]->write(out);
  }

  // asserts
  SO_OUTPUT_LN(out);
  c = assertList.getLength();
  out->write(c);
  for (i=0; i<c; i++) {
    SO_OUTPUT_SPACE(out);
    int index = distributionGroup->assertList.find(assertList[i]);
    out->write(index);
  }

  // callbacks and user data
  SO_OUTPUT_LN(out);
  out->write(commitCallbackName);
  SO_OUTPUT_LN(out);
  out->write(abortCallbackName);
  
  // user data
  SO_OUTPUT_LN(out);
  out->write(userDataSize);
  SO_OUTPUT_SPACE(out);
  if (userDataSize != 0)
    encodeBinaryData(out, userData, userDataSize);
}


SbBool SbTransaction::execute()
{
  // verify that the transaction should be received by this computer
  assert(global || (distributionGroup->getComputerIndex(getSender()) != -1 &&
         distributionGroup->active[distributionGroup->getComputerIndex(getSender())])
         && "Transaction should not be sent to this computer.");

#if CVE_DEBUG
  if (!((objData.currentCommitPredictor &&
        (objData.readSetComplete && objData.readSetCommitPredictor && objData.assertCommitPredictor)) ||
        (!objData.currentCommitPredictor &&
        (!objData.readSetComplete || !objData.readSetCommitPredictor || !objData.assertCommitPredictor)))) {
    printf("Tra %s 3 EXEC_FAIL (", timeStamp.getString().getString());
    dumpTransactionCPs();
    assert(0 && "Execution failed. Determinism reasons.");
  }
#endif

  // update assertCommitPredictor
  if (updateAssertCommitPredictor(TRUE))
    if (updateCurrentCommitPredictor())
      propagateCommitPredictor();

#if CVE_DEBUG // debugging of commit predictor propagation
  if (cve_debugTransactions() >= 3) {
    if (commitCallbackName != SoDistributionGroup::getVerifyExecutionCallbackName() &&
        commitCallbackName != SoDistributionGroup::getNullCallbackName()) {
      printf("Tra %s 3 EXEC      (", timeStamp.getString().getString());
      dumpTransactionCPs();
      printf(").\n");
    }
  }
#endif

  if (getCommitPredictor())
    // if everythink is ok, commit
    return commit();
  else
    // abort transaction
    return abort();
}


void SbTransaction::dumpTransactionCPs() const
{
  printf("S:%i, E:%i, RSC %i, RSCP %i, ACP %i, CP %i,  readSet:",
         timeStamp.getComputerIdentity().id, this->distributionGroup->getThisComputerIdentity().id,
         objData.readSetComplete, objData.readSetCommitPredictor, objData.assertCommitPredictor, getCommitPredictor());
  int c = readSet.getLength();
  int i;
  for (i=0; i<c; i++) {
    SbReadSetItem *ri = readSet[i];
    printf(" 0x%x(we: %i, wcp: %i, ts: %s),", uint32_t(ri->field), ri->flags.writeArrived, ri->flags.commitPredictor, 
           ri->timeStamp.getString().getString());
  }
}


SbBool SbTransaction::commit()
{
  assertState(SCHEDULED, "Transaction can be commited in SCHEDULED state only.");

#if CVE_DEBUG
  int debugLevel = cve_debugTransactions();
  if (debugLevel == 2) {
    if (commitCallbackName != SoDistributionGroup::getVerifyExecutionCallbackName() &&
        commitCallbackName != SoDistributionGroup::getNullCallbackName())
      printf("Tra %s 3 COMMITTED.\n", timeStamp.getString().getString());
  } else
  if (debugLevel >= 3) {
    if (commitCallbackName == SoDistributionGroup::getVerifyExecutionCallbackName())
      printf("VTr %s 3 COMMITTED.\n", timeStamp.getString().getString());
    else
    if (commitCallbackName == SoDistributionGroup::getNullCallbackName())
      printf("NTr %s 1 COMMITTED.\n", timeStamp.getString().getString());
    else {
      printf("Tra %s 3 COMMITTED (Name: %s, ", timeStamp.getString().getString(), commitCallbackName.getString());
      dumpTransactionCPs();
      printf(").\n");
      if (readSet.getLength() > 0) {
        if (readSet[0]->timeStamp != readSet[0]->field->getCveStorage()->commitTime) {
          printf("Tra %s 5 WARNING: ", timeStamp.getString().getString());
          printf("expected TS: %s, present TS: %s\n", readSet[0]->timeStamp.getString().getString(),
                 readSet[0]->field->getCveStorage()->commitTime.getString().getString());
        }
      }
    }
  }
#endif

  // read set
  int i,c;
  c = readSet.getLength();
  for (i=0; i<c; i++) {
    SbReadSetItem *ri = readSet[i];
    SoCveStorage *data = ri->field->getCveStorage();
    assert(data && "Field has no cveData allocated.");
    data->readList.removeItem(ri);
    delete ri;
  }
  readSet.truncate(0);

  // write set
  c = writeSet.getLength();
  for (i=0; i<c; i++) {
    
    // get SoCveStorage
    SbWriteSetItem *wsi = writeSet[i];
    SoCveStorage *data = wsi->getField()->getCveStorage();
    assert(data && "Field has no cveData allocated.");
    
    // remove writeSetItem from field's lists
    data->writeList.removeItem(wsi);
    
    // update commitedValue
    delete data->committedValue;
    data->committedValue = wsi;
    data->commitTime = this->timeStamp;
    data->committedValue->makeCommitted();
    
    // update fields value
    if (distributionGroup->updateGraphModel == SoDistributionGroup::COMMITTED_VALUE)
      wsi->getField()->cveSetFieldValue(wsi);
  }
  writeSet.truncate(0);

  objData.state = COMMITTED;
  distributionGroup->onTransactionDelete(this);

  // perform commit callback
  // note: this is done after all commit processing
  if (commitCallbackName.getLength() != 0) {
    SbTransactionCB *cb = SbTransaction::getRegisteredCallbackFunc(commitCallbackName.getString());
    if (!cb) {
      SoDebugError::post("SbTransaction::commit", "Commit callback \"%s\" is not registered.",
                         commitCallbackName.getString());
      // break the processing since data consistency problem
      distributionGroup->dataIntegrityFailure(); // FIXME: this is just a quick hack
                                                 // This also aborts all uncommited transactions including this one.
      return FALSE;
    } else
      cb(this, commitCallbackName, (uint8_t*)userData, userDataSize);
  }

  delete this;
  return TRUE;
}


SbBool SbTransaction::abort()
{
  // FIXME: make alternatives for non-field items

  assertAlive();

#if CVE_DEBUG
  int debugLevel = cve_debugTransactions();
  if (debugLevel == 2)
    printf("Tra %s 4 ABORTED.\n", timeStamp.getString().getString());
  else
  if (debugLevel >= 3) {
    printf("Tra %s 4 ABORTED   (", timeStamp.getString().getString());
    dumpTransactionCPs();
    printf(").\n");
  }
#endif

  // read set
  int i,c;
  c = readSet.getLength();
  for (i=0; i<c; i++) {

    // get SoCveStorage
    SbReadSetItem *ri = readSet[i];
    SoCveStorage *data = ri->field->getCveStorage();
    assert(data && "Field has no cveData allocated.");
    
    // remove readSetItem from field's lists
    if (objData.state == CONSTRUCTING)
      data->unscheduledReads.removeItem(ri);
    else
      data->readList.removeItem(ri);
    
    delete ri;
  }
  readSet.truncate(0);

  // write set
  c = writeSet.getLength();
  for (i=0; i<c; i++) {
    
    // get SoCveStorage
    SbWriteSetItem *wi = writeSet[i];
    SoCveStorage *data = wi->getField()->getCveStorage();
    assert(data && "Field has no cveData allocated.");
    
    // remove writeSetItem from field's lists
    if (objData.state == CONSTRUCTING) 
      data->unscheduledWrites.removeItem(wi);
    else 
      data->writeList.removeItem(wi);
    
    delete wi;
  }
  writeSet.truncate(0);

  // FIXME: restore original value if wi->field->cveSetValue(wi) has been called for some writeSetItems
  // and their values are still in the field. This happens in NEWEST_VALUE updateGraphModel.
  
  if (objData.state == SCHEDULED) {
    objData.state = ABORTED;
    distributionGroup->onTransactionDelete(this);
  } else
    objData.state = ABORTED;

  // perform abort callback
  // note: this is done after all commit processing
  if (abortCallbackName.getLength() != 0) {
    SbTransactionCB *cb = SbTransaction::getRegisteredCallbackFunc(abortCallbackName.getString());
    if (!cb) {
      SoDebugError::post("SbTransaction::abort", "Abort callback \"%s\" is not registered.",
                         abortCallbackName.getString());
      // break the processing since data consistency problem
      distributionGroup->dataIntegrityFailure(); // FIXME: this is just a quick hack
                                                 // This also aborts all uncommited transactions including this one.
      return FALSE;
    } else
      cb(this, abortCallbackName, (uint8_t*)userData, userDataSize);
  }

  delete this;
  return TRUE;
}


void SbTransaction::appendAssert(SbSceneAssert *sa)
{
  if (objData.state != CONSTRUCTING) {
    assert(0 && "It is not possible to append scene asserts to the transaction that has been scheduled.");
    return;
  }
  assertList.append(sa);
}


SbSceneAssert* SbTransaction::getSceneAssert(int index) const  { return assertList[index]; }
int SbTransaction::getNumSceneAsserts() const  { return assertList.getLength(); }

  
SbBool SbTransaction::checkAsserts(SbBool checkForCommit) const
{
  int i, c = assertList.getLength();
  for (i=0; i<c; i++) {
    if (!assertList[i]->check(this, checkForCommit)) {
      return FALSE;
    }
  }
  return TRUE;
}

  
void SbTransaction::setGlobal(SbBool value)
{
  assert(objData.state == CONSTRUCTING && "Global state can be changed in CONSTRUCTING state only.");
  global = value;
}


SbBool SbTransaction::isGlobal() const
{
  return global;
}


SbBool SbTransaction::updateReadSetComplete()
{
  if (!objData.readSetComplete) {
    const int c = readSet.getLength();
    int i;
    for (i=0; i<c; i++)
      if (readSet[i]->flags.writeArrived == FALSE)
        return FALSE; // the readSetComplete value has NOT been changed => FALSE

    objData.readSetComplete = TRUE;
    return TRUE; // the readSetComplete value has been changed => TRUE
  }
  return FALSE; // the readSetComplete value has NOT been changed => FALSE
}


SbBool SbTransaction::updateReadSetCommitPredictor()
{
  // save old predictor's value
  SbBool oldPredictor = objData.readSetCommitPredictor;

  // find out transaction's predictor
  const int c = readSet.getLength();
  int i;
  for (i=0; i<c; i++)
    if (readSet[i]->flags.commitPredictor == FALSE) {
      objData.readSetCommitPredictor = FALSE;
      return objData.readSetCommitPredictor != oldPredictor; // return whether the readSetCommitPredictor was changed
    }

  objData.readSetCommitPredictor = TRUE;
  return objData.readSetCommitPredictor != oldPredictor; // return whether the readSetCommitPredictor was changed
}


// This function is used by internal propagation mechanism.
void SbTransaction::notifyReadSetCommitPredictorChanged(SbBool newValue)
{
#if CVE_DEBUG
  if (cve_debugTransactions() >= 3) {
    printf("Tra %s 3 NOTIFY_ENTER(", timeStamp.getString().getString(), newValue);
    dumpTransactionCPs();
    printf(").\n");
  }
#endif

  if (newValue == FALSE) {
    // commit predictor can be set to FALSE directly
    objData.readSetCommitPredictor = FALSE;

    // propagate the change
    if (updateCurrentCommitPredictor()) {
      propagateCommitPredictor();

#if CVE_DEBUG
      if (cve_debugTransactions() >= 3) {
        printf("Tra %s 3 NOTIFY_%i   (", timeStamp.getString().getString(), newValue ? 1 : 0);
        dumpTransactionCPs();
        printf(").\n");
      }
#endif
    }
  
  } else {
    // commit predictor should be re-evaluated
    if (updateReadSetCommitPredictor()) {
      // propagate the change
      if (updateCurrentCommitPredictor()) {
        propagateCommitPredictor();

#if CVE_DEBUG
        if (cve_debugTransactions() >= 3) {
          printf("Tra %s 3 NOTIFY_%i   (", timeStamp.getString().getString(), newValue ? 1 : 0);
          dumpTransactionCPs();
          printf(").\n");
        }
#endif
      }
    }
  }
}


SbBool SbTransaction::updateAssertCommitPredictor(SbBool checkForCommit)
{
  SbBool oldPredictor = objData.assertCommitPredictor;

  objData.assertCommitPredictor = checkAsserts(checkForCommit);
  
  return objData.assertCommitPredictor != oldPredictor;
}


SbBool SbTransaction::updateCurrentCommitPredictor()
{
  SbBool oldPredictor = objData.currentCommitPredictor;

  objData.currentCommitPredictor = objData.readSetCommitPredictor && objData.assertCommitPredictor;
  
  return objData.currentCommitPredictor != oldPredictor;
}


void SbTransaction::propagateCommitPredictor()
{
#if CVE_DEBUG
  if (cve_debugTransactions() >= 3) {
    printf("Tra %s 2 PROPGATING(", timeStamp.getString().getString());
    dumpTransactionCPs();
    printf(").\n");
  }
#endif

  SbWriteSetItem::propagateCommitPredictor(this);
}


SbBool SbTransaction::getCommitPredictor() const
{
  return objData.currentCommitPredictor;
}


SbTransaction::SbTransaction()
{
  objData.state = CONSTRUCTING;
  objData.readSetComplete = FALSE;
  distributionGroup = SoNetwork::getDefaultDistributionGroup();
  global = FALSE;
  userData = NULL;
  userDataSize = 0;
}


SbTransaction::SbTransaction(SoDistributionGroup *dg)
{
  objData.state = CONSTRUCTING;
  objData.readSetComplete = FALSE;
  distributionGroup = dg;
  global = FALSE;
  userData = NULL;
  userDataSize = 0;
}


SbTransaction::~SbTransaction()
{
  // this releases all read and write set items
  assert(objData.state == COMMITTED || objData.state == ABORTED && 
         "SbTransaction's destructor must be called in COMMITED or ABORTED state.");
  free(userData);
  objData.state = SbTransaction::State(-1);
}


void SbTransaction::assertAlive()
{
#if !defined(NDEBUG)
  if (objData.state == SbTransaction::State(-1)) {
    SoDebugError::post("SbTransaction::assertAlive",
                       "Trying to access dead transaction.");
    assert(FALSE && "assertAlive() failed.");
  }
#endif
}


#if !defined(NDEBUG)
static const char* state2str(SbTransaction::State s)
{
  switch (s) {
  case SbTransaction::CONSTRUCTING: return "CONSTRUCTING";
  case SbTransaction::SCHEDULED:    return "SCHEDULED";
  case SbTransaction::COMMITTED:    return "COMMITTED";
  case SbTransaction::ABORTED:      return "ABORTED";
  case SbTransaction::State(-1):    return "(dead object)";
  default: return "(invalid state)";
  }
}
#endif


void SbTransaction::assertState(State s, const char *msg)
{
#if !defined(NDEBUG)
  if (objData.state != s) {
    SoDebugError::post("SbTransaction::assertState",
                       "Transaction state differs - is: %s, should be: %s.\n%s",
                       state2str(objData.state), state2str(s), msg);
    assert(FALSE && "assertAlive() failed.");
  }
#endif
}


SoDistributionGroup* SbTransaction::getDistributionGroup() const
{
  return distributionGroup;
}


const SbTimeStamp& SbTransaction::getTimeStamp() const
{
  if (objData.state == CONSTRUCTING) {
    static SbBool first = TRUE;
    if (first) {
      SoDebugError::postWarning("SbTransaction::getTimeStamp()",
                                "Attempt to read transaction's timestamp before transaction was "
                                "scheduled (undefined value will be returned).\n"
                                "This message is shown only once, but read attempt may happen more times.");
      first = FALSE;
    }
  }
                                
  return timeStamp;
}


SbTransaction::State SbTransaction::getState() const
{
  return objData.state;
}


void SbTransaction::appendReadSetItem(SbReadSetItem *ri)
{
  readSet.append(ri);
}


void SbTransaction::appendWriteSetItem(SbWriteSetItem *wi)
{
  writeSet.append(wi);
}


void SbTransaction::putWriteSetValuesToPublic()
{
  // set fields values to values in writeSetItems of this transaction
  int i,c;
  c = writeSet.getLength();
  for (i=0; i<c; i++) {
    writeSet[i]->getField()->cveSetFieldValue(writeSet[i]);
  }
}


SoComputer* SbTransaction::getSender() const
{
  return timeStamp.getComputer();
}


int SbTransaction::getSenderIndex() const
{
  return timeStamp.getComputerIdentity().id;
}


SbBool SbTransaction::isSenderLocalhost() const
{
  return getSender() == SoNetwork::getThisComputer();
}


void SbTransaction::setCommitCallback(const SbName &name)
{
  assertState(CONSTRUCTING, "Transaction callback can be set in CONSTRUCTING state only.");
  commitCallbackName = name;
}


SbName SbTransaction::getCommitCallback() const
{
  return commitCallbackName;
}


void SbTransaction::setAbortCallback(const SbName &name)
{
  assertState(CONSTRUCTING, "Transaction callback can be set in CONSTRUCTING state only.");
  abortCallbackName = name;
}


SbName SbTransaction::getAbortCallback() const
{
  return abortCallbackName;
}


void SbTransaction::registerCallback(const SbName &name, SbTransactionCB *cb)
{
  name2cb.put(name.getString(), cb);
}


SbTransactionCB* SbTransaction::getRegisteredCallbackFunc(const SbName &name)
{
  SbTransactionCB *cb;
  if (!name2cb.get(name.getString(), cb))
    return NULL;
  return cb;
}


void SbTransaction::setUserData(const void *data, size_t datasize)
{
  if (userData) {
    free(userData);
    userData = NULL;
  }

  userDataSize = datasize;

  if (data) {
    userData = malloc(datasize);
    memcpy(userData, data, datasize);
  }
}


void SbTransaction::getUserData(void *&data, size_t &datasize)
{
  data = userData;
  datasize = userDataSize;
}
