/*****************************
File:      RemoteConnection.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 RemoteConnection.h
/// \brief Header file for RemoteConnection, a base class for networking clients and servers.

#ifndef RemoteConnection_H
#define RemoteConnection_H


#include "H3DNetworkingUtils/Config.h"
#include <H3D/Group.h>
#include <H3DUtil/Threads.h>
#include "H3DNetworkingUtils/RemoteField.h"
#include "H3DNetworkingUtils/sockwrap.h"
#include "H3DNetworkingUtils/lockwrap.h"
#include "H3DNetworkingUtils/threadwrap.h"
#include "H3DNetworkingUtils/PacketSequenceChecker.h"
#include "H3DNetworkingUtils/Packet.h"
#include <queue>
#include "H3DNetworkingUtils/BufferedSField.h"
#include "H3DNetworkingUtils/RemoteSync.h"
#include "H3DNetworkingUtils/ThreadSafeBool.h"

namespace H3D {
  H3D_VALUE_EXCEPTION( int, RemoteFieldIDTooLarge );
}

namespace H3DNetworkingUtils {

/// \class RemoteConnection
/// Network connections involve client software conecting across a network to server software.
/// The RemoteConnection class handles both TCP and UDP sockets, which is determined by the
/// inheriting class.
/// Sets up communications to and from another RemoteConnection node
/// running on another application.
/// This node operates in stand-alone mode, needing no other supporting objects
/// in the scene-graph.  In other words, it handles the connection and
/// communication itself, regardless of what other communication is happening.
/// It is, however, an abstract class.  In order to work, the inheriting
/// classes, RemoteServer or RemoteClient, RemoteUDPServer or RemoteUDPClient need to
/// be instantiated.
/// During normal operation, there is no difference in the way the server and
/// client behave:  both act as equal peers. It is only during connection and
/// disconnection that there needs to be a difference.
/// Note that operation of this class can be completely handled via the fields. 
/// The public functions are provided so that other classes in the library can call them.
///
/// Note : in this class "read" means read from a buffer or packet. "Receive"
///          means receive from a socket. "write" means write to a buffer or
///          packet. "Send" means send to a socket.
///
/// A word of warning...
/// The order of the remoteFields is important. If two fields are set in a certain order on a
/// remote machine and you expect them to be received in the same order, that will not necessarily be so.
/// They will be received in the order they appear in the remoteFields list.
/// This is because they are received in a reading thread (not the graphics thread). The graphics thread
/// then extracts the values (if changed) during its collide phase, in the order of the remoteFields list.

class H3D_NETWORKING_UTILS_DLL_SPEC RemoteConnection : public H3D::Group {
public:

   static H3D::H3DNodeDatabase database;

   virtual ~RemoteConnection();
   virtual void initialize();

   class H3D_NETWORKING_UTILS_DLL_SPEC MFRemoteField : public H3D::TypedMFNode< RemoteField >{
   public:
      virtual void onAdd(H3D::Node * nodeP);
      RemoteConnection * conP;
   };

   typedef H3D::TypedSFNode<RemoteSync> SFRemoteSync;

   // Public fields

   /// Gets set if an error has occured - this can be routed to a
   /// message display \n
   /// access type: outputOnly \n
   /// basic type: SFString \n
   /// default value: ""
   auto_ptr<H3D::SFString      >      errorMessage;

   /// Gets set if some info has occured that may be of interest to the
   /// user (such as the remote end disconnectiing) - this can be routed to a
   /// message display \n
   /// access type: outputOnly \n
   /// basic type: SFString \n
   /// default value: ""
   auto_ptr<H3D::SFString      >      infoMessage;
   
   /// The simulated latency can be used to simulate a latency
   /// of a network \n
   /// The units are seconds It has a precision down to 1 mSec \n
   /// Note that simulated latency currently only works for
   /// periodicSend == TRUE.
   /// access type: inputOutput \n
   /// basic type: SFFloat \n
   /// default value: "0"
   auto_ptr<H3D::SFFloat       >      simulatedLatency;

   auto_ptr<MFRemoteField      >      remoteFields;

   /// If true, values are sent periodically from a looping 'send' thread \n
   /// If false they are sent when their values changes (typically from the
   /// graphics thread) \n
   /// When periodicSend is true, you can route to the field from
   /// something that is updated from a thread other than the graphics
   /// thread (eg haptics thread)
   /// access type: initializeOnly \n
   /// basic type: SFBool \n
   /// default value: FALSE
   auto_ptr<H3D::SFBool        >      periodicSend;

   /// The rate (sends per second) the values are sent if periodicSend is true \n
   /// access type: initializeOnly \n
   /// basic type: SFFloat \n
   /// default value: 100
   auto_ptr<H3D::SFFloat       >      periodicSendRate;

   /// An optional RemoteSync node that can be added (see RemoteSync.h) \n
   /// access type: initializeOnly \n
   /// basic type: SFNode \n
   /// default value: 
   auto_ptr<SFRemoteSync       >      remoteSync;

   /// Returns true if the connnection is established \n
   /// access type: outputOnly \n
   /// basic type: SFBool \n
   /// default value: false
   /// This can get set in any thread but can only be read in the graphics thread becasue it is part of the field mechanism
   /// Use the is_connected ThreadSafeBoolean in other threads
   auto_ptr<BufferedSField<H3D::SFBool> >      isConnected;


   typedef void (* CallbackFn)(void *);

   /// Set a function to be called when the communication channel is opened.
   void setOpenCommsCallbackFn(CallbackFn fn, void * client_data) {
      open_comms_cb_fn = fn;
      open_comms_cb_data = client_data;
   }

   /// Set a function to be called when the communication channel is closed.
   void setCloseCommsCallbackFn(CallbackFn fn, void * client_data) {
      close_comms_cb_fn = fn;
      close_comms_cb_data = client_data;
   }

   /// Request closing the connection.
   virtual void requestClose();

   /// A close of the connection has been requested.
   bool closeRequested() const {return close_requested;}

   /// reads from packet.
   void read(char * data, int num_bytes) {read_packet.read(data, num_bytes);}

   /// Write to the packet \n
   /// Note: nothing gets written to socket until write_packet.send() is called.
   void write(const char * data, int num_bytes) {write_packet.write((void*)data, num_bytes);}

   /// Returns true if a socket has been created
   bool hasSock() const {return sockP != 0;}

   /// Sets the error message field.
   /// This can be routed to an error showing mechanism like
   /// LocalNodes::OKDialogBox.
   virtual void showError(const char * msg);

   /// Clears the error message field.
   /// This can be routed to an error showing mechanism like
   /// LocalNodes::OKDialogBox.
   virtual void clearError();

   /// Sets the info message field \n
   /// This can be routed to a string display mechanism 
   virtual void showInfo(const char * msg);

   /// Sets the info message field \n
   /// This can be routed to a string display mechanism
   virtual void showInfo(const string msg)   {showInfo(msg.data());}

   /// Write the id and time stamp to the write packet.
   void writeFieldHeader(int fld_id, bool with_timestamp);

   /// Read a double from the read packet.
   double readDouble();

   /// Write a double to the write packet.
   void writeDouble(const double & val);

   /// Read a float from the read packet.
   float readFloat();

   /// Write a float to the write packet.
   void writeFloat(const float & val);

   /// Read a float from the read packet.
   H3D::H3DFloat readH3DFloat() {return (H3D::H3DFloat)readFloat();}

   /// Write a H3DFloat to the write packet.
   void writeH3DFloat(const H3D::H3DFloat & val) {writeFloat(val);}

   /// Write the 'write_packet' packet of data to socket \n
   /// If simulated latency is true, it may buffer the write_packet and send the
   /// next one off the buffer \n
   /// This is not virtual as it is not designed to be overridden \n
   /// Override sendPacket(Packet&) instead.
   virtual void sendPacket();

   /// Lock the writing thread to avoid clashes with the reading
   /// thread when it occasionally needs to write.
   void lockWriteThread() {write_lock.lock();}

   /// Unlock the writing thread .
   void unlockWriteThread() {write_lock.unlock();}

   ThreadSafeBool is_connected;
   ThreadSafeBool is_connecting;


protected:

   // traverseSG traverses the RemoteFields in turn.
   virtual void traverseSG( H3D::TraverseInfo &ti );

   RemoteConnection( H3D::Inst<H3D::SFString       >     _errorMessage = 0,
                     H3D::Inst<H3D::SFString       >     _infoMessage = 0,
                     H3D::Inst<H3D::SFFloat        >     _simulatedLatency = 0,
                     H3D::Inst<MFRemoteField       >     _remoteFields = 0,
                     H3D::Inst<H3D::SFBool         >     _periodicSend = 0,
                     H3D::Inst<H3D::SFFloat        >     _periodicSendRate = 0,
                     H3D::Inst<SFRemoteSync        >     _remoteSync = 0,
                     H3D::Inst<BufferedSField<H3D::SFBool>  > _isConnected = 0);
   // Constructor is protected so that it can not explicitely be called:
   // rather it should be implicely called from an inheriting class's
   // constructor.
   // This is since it is an abstract class.

   static void receivingLoopTH(void * client_data) {
      static_cast<RemoteConnection*>(client_data)->receiveLoop();
   }

   virtual void receiveLoop();
   // Wait and receive packets.

   virtual bool doClosedActions() {return false;}
   // Do any actions that are required while the connection is closed.
   // This is different for servers and clients, so must be overridden.
   // The return value tells the receive loop whether to break out of the
   // loop.
   // NOTE Make sure you only call this from the receiving thread.

   virtual bool receivePacket();
   // read a single packet of data from socket
   // Returns false on failure, true on success..

   virtual void sendThisPacket(Packet & packet);
   // Write a single packet of data to socket, given the packet

   virtual void readPacket();
   // get data from the packet

   virtual void readField(int field_id);
   // Read the field with the given id.

   static void sendLoopTH(void * client_data) {
      RemoteConnection * objP = static_cast<RemoteConnection* >(client_data);
      objP->sendLoop();
   }
   virtual void sendLoop();
   // The send loop thread starts here, if we are doing periodic sending.

   void setConnected(bool val) {
      is_connected = val; // Local thread safe boolean
      isConnected->setValue(val, id); // Field
      for (unsigned int i = 0; i < remoteFields->size(); i++) {
         remoteFields->getValueByIndex(i)->is_connected = val;
      }
   }

   virtual void setSendTimeout(InetSock * sockP, int millisecs);
   // Set the timeout on send operations.

   virtual void sendFields(bool first_connection);
   // Sends all fields once only.
   // If first connection ios true, it will send only those fields that have
   // sendOnConnect true.
   // if first_connection is false, it will send all fields.

   virtual void doGraphicsLoopActions();
   // Do any actions required periodically at graphics loop rate.
   // Note this does NOT handle the periodic send option, that happens in
   // another thread.

   void requestOpenComms() {
      comms_lock.lock();
      comms_open_requested = true;
      comms_lock.unlock();
   }

   bool commsOpenRequested() const {
      comms_lock.lock();
      bool ret = comms_open_requested;
      comms_lock.unlock();
      return ret;
   }

   virtual void close();
   // Close the connection.

   // Request comms to be opened.
   // If periodicSend, a new thread to send periodically will be created.

   bool isShuttingDown();
   void setShuttingDown(bool v);

   bool receiveLoopRunning();
   void setReceiveLoopRunning(bool v);

   bool sendLoopRunning();
   void setSendLoopRunning(bool v);

   InetSock * sockP;
   bool close_requested;
   Thread * receive_loop_threadP;
   Thread * send_loop_threadP;
   bool use_seq_numbers;
   Packet write_packet;
   Packet read_packet;
   bool initialized;
   double period;
   double last_send_time;
   double last_try_time;
   CallbackFn open_comms_cb_fn;
   void * open_comms_cb_data;
   CallbackFn close_comms_cb_fn;
   void * close_comms_cb_data;
   Mutex sock_lock;
   Semaphore connected_sem;
   InetAddr remote_end;

private:
   bool comms_open_requested;
   bool connecting;
   mutable Mutex comms_lock;
   mutable Mutex connecting_lock;
   mutable Mutex msg_lock;
   Mutex write_lock;
   PacketSequenceChecker::u_int16 seq_number;
   PacketSequenceChecker seq_checker;
   static const int MAX_PACKET_SIZE;
   std::queue<Packet*> sim_latency_buffer;
   bool sim_latency;
   unsigned int max_sim_latency_buf_size;
   bool little_endian;
   static const int MAX_ID;
   bool error_msg_waiting;
   bool info_msg_waiting;
   char * error_msg;
   char * info_msg;
   char * remote_client_addr;
   
   bool shutting_down;  // This is accessed by several threads. Use isShuttingDown() for safe access.
   Mutex shutdown_lock;
   bool receive_loop_running;  // This is accessed by several threads. Use receiveLoopRunning() for safe access.
   Mutex receive_loop_lock;
   bool send_loop_running;  // This is accessed by several threads. Use sendLoopRunning() for safe access.
   Mutex send_loop_lock;
};

}

#endif
