#include <Inventor/cve/SbNetMessage.h>


SoType SbNetMessage::classTypeId;

SB_NETMESSAGE_SRC(SbNMUserMessage, SbNetMessage);
SB_NETMESSAGE_SRC(SbNMNullMessage, SbNetMessage);


SbNetMessage::SbNetMessage()
{
  localData = NULL;
}


SbNetMessage::~SbNetMessage()
{
}


void SbNetMessage::setLocalData(void *data)
{
  localData = data;
}


void* SbNetMessage::getLocalData()
{
  return localData;
}


void SbNetMessage::createStream(void *&buffer, size_t &size)
{
  // initialize SoOutput
  SoOutput out;
  void *buf = malloc(INITIAL_TRANSACTION_STREAM_SIZE);
  out.setBuffer(buf, INITIAL_TRANSACTION_STREAM_SIZE, realloc, 8);

  // write transaction
  out.write(this->getFileFormatName());
  SO_OUTPUT_SPACE(&out);
  this->write(&out);

  // write header ("MSG" and size)
  out.getBuffer(buf, size);
  memcpy(buf, "MSG", 4);
  ((uint32_t*)buf)[1] = size;

  buffer = buf; 
}


void SbNetMessage::sendTo(SoComputer *comp, SbNetMessageFailureCB *cb)
{
  void *buf;
  size_t size;
  createStream(buf, size);

  // broadcast stream
  comp->sendData(buf, size);

  // free memory
  free(buf);
  delete this;
}


void SbNetMessage::sendToComputers(const SoComputerList &list, SbNetMessageFailureCB *cb)
{
  if (list.getLength() == 0)
    return;

  void *buf;
  size_t size;
  createStream(buf, size);

  // broadcast stream
  int i,c = list.getLength();
  for (i=0; i<c; i++)
    list[i]->sendData(buf, size);

  // free memory
  free(buf);
  delete this;
}


void SbNetMessage::sendToNonActiveComputers(SoDistributionGroup *dg, SbNetMessageFailureCB *cb)
{
  SoComputerList list;
  int i,c = dg->computer.getNum();
  for (i=0; i<c; i++)
    if (dg->active[i] == FALSE)
      list.append(dg->computer[i]);

  sendToComputers(list, cb);
}


void SbNetMessage::sendToAnyActiveComputers(SoDistributionGroup *dg, SbNetMessageFailureCB *cb)
{
  int i,c = dg->computer.getNum();
  for (i=0; i<c; i++)
    if (dg->active[i] == TRUE) {
      sendTo(dg->computer[i], cb);
      return;
    }
}


void SbNetMessage::sendToAllActiveComputers(SoDistributionGroup *dg, SbNetMessageFailureCB *cb)
{
  SoComputerList list;
  int i,c = dg->computer.getNum();
  for (i=0; i<c; i++)
    if (dg->active[i] == TRUE)
      list.append(dg->computer[i]);

  sendToComputers(list, cb);
}


void SbNetMessage::sendToAllComputers(SoDistributionGroup *dg, SbNetMessageFailureCB *cb)
{
  SoComputerList list;
  int i,c = dg->computer.getNum();
  for (i=0; i<c; i++)
    list.append(dg->computer[i]);

  sendToComputers(list, cb);
}


SbNetMessage* SbNetMessage::readMessage(const char *type, SoInput *in,
                                        SoComputer *sender)
{
  SbNetMessage *msg;
  if (strcmp(&type[0], "TRA") == 0)
    msg = new SbTransaction(sender->getCurrentDistributionGroup());
  else if (strcmp(&type[0], "MSG") == 0 ) {
    
    // read type
    SbName typeName;
    if (!in->read(typeName)) {
      sender->dataIntegrityFailure();
      SoDebugError::post("SbNetMessage::readMessage",
                         "Can not read message from the stream.");
      return NULL;
    }

    // create msg
    SoType type = SoType::fromName(typeName);
    if (type == SoType::badType() ||
       (msg=(SbNetMessage*)type.createInstance()) == NULL) {
      sender->dataIntegrityFailure();
      SoDebugError::post("SbNetMessage::readMessage",
                         "Can not create message %s since its class is not registered.", typeName.getString());
      return NULL;
    }
  } else
    assert(0);

  msg->sender = sender;

  if (!msg->read(in)) {
    sender->dataIntegrityFailure();
    delete msg;
    return NULL;
  }

  return msg;
}


SbBool SbNetMessage::read(SoInput *in)
{
  return TRUE;
}


void SbNetMessage::write(SoOutput *out) const
{
}


void SbNetMessage::process()
{
}



void SbNetMessage::initClass()
{
  classTypeId = SoType::createType(SoType::badType(), "SbNetMessage", createInstance);
}


SoType SbNetMessage::getTypeId() const
{
  return SbNetMessage::classTypeId;
}


SbBool SbNetMessage::isOfType(SoType type) const
{
  return this->getTypeId().isDerivedFrom(type);
}


SoType SbNetMessage::getClassTypeId()
{
  return SbNetMessage::classTypeId;
}


const char* SbNetMessage::getFileFormatName() const
{
  return SO__QUOTE(SbNetMessage);
}


void* SbNetMessage::createInstance()
{
  return new SbNetMessage;
}



SbNMUserMessage::SbNMUserMessage()
{
  msg = NULL;
  size = 0;
}


SbNMUserMessage::SbNMUserMessage(const SoOutput *out)
{
  msg = NULL;
  size = 0;
  
  void *buf;
  size_t s;
  SbBool ok = out->getBuffer(buf, s);
  if (!ok) {
    SoDebugError::post("SbNetMessage::SbNetMessage",
                       "SoOutput must be writing into the memory to be able to get its buffer.");
    assert(0);
  }
  
  setData(buf, s, COPY);
}


SbNMUserMessage::SbNMUserMessage(void *buf, size_t size)
{
  this->msg = NULL;
  this->size = 0;
  
  setData(buf, size, COPY);
}


SbNMUserMessage::~SbNMUserMessage()
{
  setData(NULL, 0);
}


void SbNMUserMessage::setData(void *msg, size_t size, CopyPolicy policy)
{
  // deallocate previous data
  if (this->msg) {
    switch (this->copyPolicy) {
    case COPY: free(this->msg); break;
    case NO_COPY: break;
    case NO_COPY_AND_DELETE: delete[] this->msg; break;
    case NO_COPY_AND_FREE: free(this->msg); break;
    default:
      assert(0);
    }
  }

  // handle invalid input data
  if (msg == NULL || size == 0) {
    this->msg = NULL;
    this->size = 0;
    return;
  }

  // allocate new data
  if (policy == COPY) {
    this->msg = malloc(size);
    memcpy(this->msg, msg, size);
  } else
    this->msg = msg;

  this->size = size;
  this->copyPolicy = policy;
}


void SbNMUserMessage::getData(void *&msg, size_t &size)
{
  msg = this->msg;
  size = this->size;
}


SbBool SbNMUserMessage::read(SoInput *in)
{
  size_t newSize;
  void *buf;

  if (!in->read(newSize)) {
    SoReadError::post(in, "Couldn't read SbNetMessage.");
    return FALSE;
  }

  buf = malloc(newSize);
  SbBool ok;
  if (in->isBinary())
    ok = in->readBinaryArray((unsigned char*)buf, newSize);
  else
    ok = decodeBinaryData(in, buf, newSize);

  if (!ok) {
    SoReadError::post(in, "Couldn't read SbNetMessage.");
    return FALSE;
  }

  setData(buf, newSize, NO_COPY_AND_FREE);
  return TRUE;
}


void SbNMUserMessage::write(SoOutput *out) const
{
  if (out->isBinary()) {
    out->write(size);
    out->writeBinaryArray((unsigned char*)msg, size);
  } else {
    out->write(size);
    SO_OUTPUT_SPACE(out);
    encodeBinaryData(out, msg, size);
  }
}


SbBool SbNMNullMessage::read(SoInput *in)
{
  SoDistributionGroup *dg = SoNetwork::getDefaultDistributionGroup();

  // read message
  if (!timeStamp.read(in,dg)) {
    SoDebugError::post("SbNMNullMessage::read",
                       "Wrong data in SbNMNullMessage info message.");
    getSender()->dataIntegrityFailure();
    return FALSE;
  }
  
  return TRUE;
}


void SbNMNullMessage::write(SoOutput *out) const
{
  SoDistributionGroup *dg = SoNetwork::getDefaultDistributionGroup();
  timeStamp.write(out,dg);
}


void SbNMNullMessage::process()
{
  assert(getSender()->getLargestTimeStamp() < timeStamp);
  getSender()->updateLargestTimeStamp(timeStamp);
}
