/*****************************
File:      RemoteNodeField.h
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>
***************************/

/// \file RemoteNodeField.h
/// \brief Header file for RemoteNodeField, sends updates of a given MField of a given Node across
/// a network.

#ifndef RemoteNodeField_H
#define RemoteNodeField_H

#include "H3DNetworkingUtils/RemoteField.h"
#include <H3D/Node.h>
//#include "H3DNetworkingUtils/lockwrap.h"
#include "H3DNetworkingUtils/RemoteConnection.h"
#include "H3DNetworkingUtils/Config.h"
#include <H3DUtil/Threads.h>
#include <H3D/MFInt32.h>
#include <H3D/SFInt32.h>
#include <H3D/MFVec3f.h>
#include <H3D/MFBool.h>


namespace H3D {
  H3D_VALUE_EXCEPTION( int, InvalidNodeFieldFlag );
}

namespace H3DNetworkingUtils {

/// \class RemoteNodeField
/// The RemoteNodeField is an abstract base class which provides the ability to 
/// can send updates of a given MField of a given Node across
/// a network. It differs from classes such as RemoteSFVec3f in that it does
/// not remotely route a field to a field, but instead holds a reference to a 
/// particular node that contains the field to be modified. The reason for this
/// is to make it efficient for multi valued (array) fields, such as MFVec3f.
/// To be efficient, it needs to be able to send only the values that have 
/// changed. A simple routing of the field only passes the information that 
/// something in the value array has changed, not which particular values.
///
/// There are 3 mechanisms which trigger a send:
/// 1. Each time the indexToBeSent gets an event, it sends that index and the
///    corresponding value from the Node's field.
/// 2. Each time the indiciesToBeSent gets an event, it sends those indicies
///    and the corresponding values from the Node's field.
/// 3. If sendAll gets an event, it sends the whole Nodes's field.
///    This mechanism can be used if all values need to be sent , or if the size of
///    the field has changed.
///
/// Each time an index and value is received, it sets the indexed value on the
///    Node's field.
/// Note that the X3D interface is created in inheriting classes.  \n
/// <b>Examples:</b>
///   - <a href="../../examples/RemoteNodeFieldServer.x3d">RemoteNodeFieldServer.x3d</a>
///   - <a href="../../examples/RemoteNodeFieldClient.x3d">RemoteNodeFieldClient.x3d</a>

template< class TheNode, class MultiFieldType, auto_ptr<MultiFieldType> TheNode::*fld >
class H3D_NETWORKING_UTILS_DLL_SPEC  RemoteNodeField : public RemoteField {
public:
  static MultiFieldType *theField( H3D::Node *node ) {
    return (static_cast< TheNode* >( node )->*fld).get();
  }

  /// Constructor
  RemoteNodeField();
  
  /// Takes the index of the value in the field and sends the index plus the associated value.
  class H3D_NETWORKING_UTILS_DLL_SPEC SFSender : public H3D::AutoUpdate< H3D::SFInt32 >  {
  public:
    virtual void update(  ) {
      RemoteNodeField * rem_node_fieldP = static_cast<RemoteNodeField *>(getOwner());
      H3D::SFInt32 * f = static_cast<H3D::SFInt32 *>(event.ptr);
      value = f->getValue();
      if (rem_node_fieldP->conP->isConnected->getValue()) {
        rem_node_fieldP->conP->lockWriteThread();
        rem_node_fieldP->writeSField(value);
        rem_node_fieldP->sendPacket();
        rem_node_fieldP->conP->unlockWriteThread();
      }
    }
    virtual void setValue( const int & val, int id = 0 ) {
      RemoteNodeField * rem_node_fieldP = static_cast<RemoteNodeField *>(getOwner());
      H3D::SFInt32::setValue(val, id);
      if (rem_node_fieldP->conP->isConnected->getValue()) {
        rem_node_fieldP->conP->lockWriteThread();
        rem_node_fieldP->writeSField(val);
        rem_node_fieldP->sendPacket();
        rem_node_fieldP->conP->unlockWriteThread();
      }
    }
  };
    
  /// Sends all values in input routed field
  class H3D_NETWORKING_UTILS_DLL_SPEC MFSender : public H3D::AutoUpdate< H3D::MFInt32 >  {
  public:
    virtual void update() {
      H3D::MFInt32 * f = static_cast<H3D::MFInt32 *>(event.ptr);
      RemoteNodeField * rem_node_fieldP = static_cast<RemoteNodeField *>(getOwner());
      value = f->getValue();
      if (rem_node_fieldP->conP->isConnected->getValue()) {
        rem_node_fieldP->conP->lockWriteThread();
        rem_node_fieldP->writeMField(value);
        rem_node_fieldP->sendPacket();
        rem_node_fieldP->conP->unlockWriteThread();
      } 
    }
    virtual void setValue( const std::vector<int> & val, int id = 0 ) {
      RemoteNodeField * rem_node_fieldP = static_cast<RemoteNodeField *>(getOwner());
      H3D::MFInt32::setValue(val, id);
      if (rem_node_fieldP->conP->isConnected->getValue()) {
        rem_node_fieldP->conP->lockWriteThread();
        rem_node_fieldP->writeMField(val);
        rem_node_fieldP->sendPacket();
        rem_node_fieldP->conP->unlockWriteThread();
      }
    }
    virtual void setValue( H3D::MFInt32::size_type index, const int & val, int id = 0 ) {
      // Can't do anything here, as the sending clears out the values,
      // so the index into the array will not be valid.
    }
  };
    
  /// Sends all values
  class SFSendAll : public H3D::AutoUpdate< H3D::Field >  {
  public:
    virtual void update( ) {
      RemoteNodeField * rem_node_fieldP = static_cast<RemoteNodeField *>(getOwner());
      //H3D::SFTime * t = static_cast<H3D::SFTime *>(event.ptr);
      //value = t->getValue();
      if (rem_node_fieldP->conP->isConnected->getValue()) {
        rem_node_fieldP->conP->lockWriteThread();
        rem_node_fieldP->writeWholeArray();
        rem_node_fieldP->sendPacket();
        rem_node_fieldP->conP->unlockWriteThread();
      }
    }
  };
        
  typedef H3D::TypedSFNode<TheNode> SFTheNode;
  
  // Public fields

  /// The node to which values are applied \n
  /// access type: inputOutput \n
  /// basic type: SFNode \n
  /// default value: 
  auto_ptr< SFTheNode             > theNode; 

  /// Supplies the index of a single value to be sent \n
  /// access type: inputOutput \n
  /// basic type: SFInt32 \n
  /// default value: 0.01
  auto_ptr< SFSender              > indexToBeSent;

  /// Supplies the indicies of a multiple values to be sent \n
  /// access type: inputOutput \n
  /// basic type: SFFloat \n
  /// default value: 0.01
  auto_ptr< MFSender              > indiciesToBeSent;

  /// Triggers all values to be sent
  /// access type: inputOutput \n
  /// basic type: SFFloat \n
  /// default value: 0.01
  auto_ptr< SFSendAll             > sendAll; 
  
  /// X3D interface
  static H3D::H3DNodeDatabase database;

protected:
  /// This class always sends on change.
  virtual void setSendOnChange(bool val) {}

  /// Write a indexToBeSent index field to socket.
  virtual void writeField () {writeSField(indexToBeSent->getValue());}

  /// Write a single index field to socket.
  virtual void writeSField (unsigned int index);  

  /// Write an array index field to socket.
  virtual void writeMField (const std::vector<int> & indicies);

  /// Write array field to socket.
  virtual void writeWholeArray ();

  /// Write value to socket.
  virtual void writeValue (typename MultiFieldType::value_type const & val);
  
  /// Read value from socket.
  virtual typename MultiFieldType::value_type readVal ();
  
  /// read value(s) in from socket to temporary storage.
  virtual void readValue();
  
  virtual void readMField();
  
  virtual void readSField();
  
  /// Called from the collider in the graphics loop to check for any changes
  /// in the received field and to update the field appropriately.
  virtual void checkForChange();
  
  /// Indicates to send whole array
  static const int WHOLE_ARRAY;

  /// Indicates to send single value
  static const int SINGLE_VALUE;

  /// Indicates to several values from array
  static const int MULTI_VALUE;
  
private:
  struct NodeUpdateData {
    std::vector<int> index;
    std::vector<typename MultiFieldType::value_type> val;
  };
  
  NodeUpdateData data;
  bool received_whole_array;
  bool data_waiting;
//  Mutex change_lock;
  H3D::MutexLock change_lock;

};

}

#endif
