/*****************************
File:       RemoteField.CPP
Language:   C++ (header)
Project:    H3DNetworkingUtils
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/

Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
License for the specific language governing rights and limitations
under the License.

The Original Code is H3DNetworkingUtils v1.0.

The Initial Developer of the Original Code is CSIRO.
Portions created by the Initial Developer are Copyright (C) 1009 CSIRO. All Rights Reserved.
Contributor(s):
    Chris Gunn  <Chris.Gunn@csiro.au> <ChrisJGunn@gmail.com>
***************************/
#include "H3DNetworkingUtils/RemoteField.h"
#include "H3DNetworkingUtils/RemoteConnection.h"
#include "H3DNetworkingUtils/RemoteSync.h"
#include "H3DNetworkingUtils/systypes.h"

using namespace H3D;
using namespace H3DNetworkingUtils;

H3DNodeDatabase RemoteField::database(   
  "RemoteField", 
  NULL,
  typeid(RemoteField),
  &X3DChildNode::database);

namespace RemoteFieldInternals {   
  FIELDDB_ELEMENT( RemoteField, fieldId,                    INITIALIZE_ONLY );
  FIELDDB_ELEMENT( RemoteField, printPeriod,                INITIALIZE_ONLY );
  FIELDDB_ELEMENT( RemoteField, sendOnceOnConnect,          INPUT_OUTPUT );
  FIELDDB_ELEMENT( RemoteField, enabled,                    INPUT_OUTPUT );
  FIELDDB_ELEMENT( RemoteField, bufferReceivedVals,         INPUT_OUTPUT );
  FIELDDB_ELEMENT( RemoteField, bufferStrategy,             INITIALIZE_ONLY );
  FIELDDB_ELEMENT( RemoteField, remoteTimestamp,            OUTPUT_ONLY );
  FIELDDB_ELEMENT( RemoteField, timestamps,                 INPUT_OUTPUT );
  FIELDDB_ELEMENT( RemoteField, isHapticField,              INPUT_OUTPUT );
}

// ----------------------------------------------------------------------------
RemoteField::RemoteField( Inst<SFInt32> _fieldId,
                          Inst<SFFloat> _printPeriod,
                          Inst<SFBool> _sendOnceOnConnect,
                          Inst<Enabled> _enabled,
                          Inst<SFBool> _bufferReceivedVals,
                          Inst<SFString> _bufferStrategy,
                          Inst<SFTime> _remoteTimestamp,
                          Inst<SFBool> _timestamps,
                          Inst<SFBool> _isHapticField):
fieldId                    ( _fieldId ),
printPeriod                (_printPeriod ),
sendOnceOnConnect          (_sendOnceOnConnect),
enabled                    (_enabled),
bufferReceivedVals         (_bufferReceivedVals),
bufferStrategy             (_bufferStrategy),
remoteTimestamp            (_remoteTimestamp ),
timestamps                 (_timestamps),
isHapticField              (_isHapticField),
conP                       (NULL) ,
stats_printer              ( new StatsPrinter ),
collecting_stats           (false),
received_data              (false),
debug (false),
is_enabled                 (true) {

   type_name = "RemoteField";
   database.initFields( this );

   fieldId->setValue( 0, id );
   printPeriod->setValue( 0.0 );
   stats_printer->remote_fld = this;
   sendOnceOnConnect->setValue(false);
   enabled->setValue(true, id);
   bufferReceivedVals->setValue(false);
   bufferStrategy->setValue("NONE");
   timestamps->setValue(false);
   remoteTimestamp->setValue(0, id);
   isHapticField->setValue(false);
}

// ----------------------------------------------------------------------------
void RemoteField::initialize() {
   X3DChildNode::initialize();
   
   //  string s1 = "Field #";
   //  int temp = fieldId->getValue();
   //  string s2 = itoa( temp );  // no itoa() function!!!
   //  string s3 = " update rate: ";
   //  str = s1 + s2 + s3;
   if ( printPeriod->getValue() > 0.0 ) {
      char str[40];
      sprintf( str, "Field #%d statistics: ", fieldId->getValue() );
      stats_printer->msg = str;
      collecting_stats = true;
   }
}

// ----------------------------------------------------------------------------
void RemoteField::readField () {
   received_data = true;
   if (timestamps->getValue()) {
      remoteTimestamp->setValue(conP->readDouble(), id);
   }
   readValue();
   if (collecting_stats) {
      stats_printer->increment();
   }
}

//// ----------------------------------------------------------------------------
//Time const &  RemoteField::getRemoteSyncClockOffsetValue() const {
  //  return conP->remote_sync->clock_offset->getValue();
//}

// ----------------------------------------------------------------------------
void RemoteField::StatsPrinter::update() {
   if ( ( remote_fld->printPeriod->getValue() != 0.0 )
        && (remote_fld->is_connected == true) ) {
       H3D::H3DTime now = Scene::time->getValue();
       if ( ( now - start ) > remote_fld->printPeriod->getValue() ) {
          std::cout << msg << "update rate: " << std::setprecision(4)
                   << ( counter / ( now - start ) );
          H3D::H3DTime sum = 0.0;
          std::vector< H3D::H3DTime >::iterator i;
          for( i = latencies.begin();
               i != latencies.end();
               i++ ) {
              sum = sum + *i;
          }
          std::cout << " latency: " << ( sum / counter ) << std::endl;
          start = now;
          counter = 0;
          latencies.clear();
      }
   }
}

// ----------------------------------------------------------------------------
void RemoteField::StatsPrinter::increment() {
   counter++;
   H3D::H3DTime now = Scene::time->getValue();
   H3D::H3DTime latency = ( now - remote_fld->remoteTimestamp->getValue()) +
               remote_fld->conP->remoteSync->getValue()->clockOffset->getValue() * 1000;
   latencies.push_back( latency );
}

// ----------------------------------------------------------------------------
void RemoteField::sendPacket() {conP->sendPacket();}


// ----------------------------------------------------------------------------
void RemoteField::writeInt32(int const & val) {
   // use a union so that we can easily access the bytes within an int
   union {
      int v;
      char sz[4];
   } enc_int;

   // Convert to network format
   enc_int.v = hton32(val);

   // send 4 bytes
   if (conP->hasSock()) {
      conP->write(&enc_int.sz[0], 4);
   }
}

// ----------------------------------------------------------------------------
void RemoteField::writeBool(bool const & val) {
   // use a union so that we can easily access the bytes within a bool
   union {
      bool v;
      char sz;
   } enc_bool;

   enc_bool.v = val;

   // send 1 byte
   if (conP->hasSock()) {
      conP->write(&enc_bool.sz, 1);
   }
}

// ----------------------------------------------------------------------------
void RemoteField::writeString(std::string const & str) {
   // send bytes
   const char * s = str.data();
   if (conP->hasSock()) {
      conP->write(s, strlen(s) + 1);
   }
}


// ----------------------------------------------------------------------------
// readInt:
//    Extract 32 bit int from buffer_stream and return it.
//
int RemoteField::readInt32() {
   // use a union so that we can easily access the bytes within a float
   union {
      int v;
      char sz[4];
   } decoded_int;

   // read 4 bytes
   conP->read(&decoded_int.sz[0], 4);

   return ntoh32(decoded_int.v);
}

// ----------------------------------------------------------------------------
// readBool:
//    Extract 8 bit bool from buffer_stream and return it.
//
bool RemoteField::readBool() {
   // use a union so that we can easily access the bytes within a bool
   union {
      bool v;
      char sz;
   } decoded_bool;

   // read 1 bytes
   conP->read(&decoded_bool.sz, 1);

   return decoded_bool.v;
}

// ----------------------------------------------------------------------------
// readString:
//    Extract a null terminated string from buffer_stream and return it.
//
std::string RemoteField::readString() {
   string str;
   char ch;
   // read bytes until null received
   do {
      conP->read(&ch, 1);
      str += ch;
   } while (ch != '\0');

   return str;
}

// ----------------------------------------------------------------------------
void RemoteField::traverseSG(H3D::TraverseInfo & ti) {
   H3D::X3DChildNode::traverseSG(ti);
   if (isHapticField->getValue()) {
      // We add a dummy force effect which will check for a change in value on each haptics cycle.
      DummyForce * dummyP = new DummyForce(this);
      ///cout << (int)this << " adding" << endl;
      ti.addForceEffect( 0, dummyP ); 
   } else {
      checkForChange();
   }
}
