/*****************************
File:       RemoteUDPClient.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/RemoteUDPClient.h"
#include "H3DNetworkingUtils/RemoteSync.h"
#ifndef _WIN32
#include <unistd.h>
#endif

using namespace H3D;
using namespace H3DNetworkingUtils;

const H3DFloat RemoteUDPClient::PULSE_PERIOD = 1.0; // Seconds

// ----------------------------------------------------------------------------
H3DNodeDatabase RemoteUDPClient::database(   
  "RemoteUDPClient", 
  &(newInstance<RemoteUDPClient>),
  typeid(RemoteUDPClient),
  &RemoteClient::database);

// ----------------------------------------------------------------------------
RemoteUDPClient::RemoteUDPClient() :
RemoteClient           ("UDP"),
msg_count              (0),
connect_requested      (0) {

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

  use_seq_numbers = true;
  pulse_packet.setHeartBeat();
}

// ----------------------------------------------------------------------------
RemoteUDPClient::~RemoteUDPClient() {
  setShuttingDown(true);
  while (receiveLoopRunning() || sendLoopRunning()) {
     Sleep(50);
  }
}

// ----------------------------------------------------------------------------
void RemoteUDPClient::restartClient() {
  // Called in graphics thread.

  if (!sockP) {
    UDPSock * new_sockP = new UDPSock;
    new_sockP->setTimeOut(u_int(RemoteUDPClient::PULSE_PERIOD * 2.0f * 1000.0f));
    sockP = new_sockP;
  }

  // We need a send loop even if we don't have periodic sending, so that we can
  // send pulse packets.
  if (!send_loop_threadP) {
    send_loop_threadP = new Thread((ThreadFunction)sendLoopTH, this);
  }

  // If we have not already started a receiving thread from a previous
  // connection, start one.
  if (!receive_loop_threadP) {
    receive_loop_threadP = new Thread((ThreadFunction)receivingLoopTH, this);
  }

  close_requested = false;
  if (open_comms_cb_fn) {
    (*open_comms_cb_fn)(open_comms_cb_data);
  }
  connect_requested = true;
}

// ----------------------------------------------------------------------------
void RemoteUDPClient::receiveLoop () {
  // Called in receiving thread.

  // We loop indefinately. The client may disconnect then connect again.
  int count = 0;

  setReceiveLoopRunning(true);
  while (!isShuttingDown()) {
  ///while (true) {
    // Idle if not connected.
    if (!is_connected.get()) {
      Thread::sleep(1);
      continue;
    }

    // OK we are connected...
    // Check if another thread has requested the connection to be closed.
    if (close_requested) {
      if (is_connected.get()) {
        setConnected(false);
        close_requested = false;
        if (close_comms_cb_fn) {
          (*close_comms_cb_fn)(close_comms_cb_data);
        }
      }
    }

    // Normal connected state.
    while (!sockP->dataReady()) {
       if (isShuttingDown()) {
           break;
       }
       Sleep(2);
    }
    if (isShuttingDown()) {
       break;
    }
    count = read_packet.receive(static_cast<UDPSock*>(sockP), &remote_end); // Get data from socket
    // or error if no server.
    if (count > 0) {
      if (!read_packet.isHeartBeat()) {
        readPacket(); // Interpret it.
      } 
    }
    else if (count <= 0)  {
      requestClose();  // Sets connected = false, we should have at least
      // received a pulse packet.
    }
  }
  setReceiveLoopRunning(false);
}


// ----------------------------------------------------------------------------
void RemoteUDPClient::sendLoop () {  
  // Called in sending thread.

  setSendLoopRunning(true);
  while (!isShuttingDown()) {
    if (is_connected.get()) {
      H3DTime t0 = TimeStamp::now();
      double time_since_last_try = (double)(t0 - last_try_time);
      double time_since_last_send = (double)(t0 - last_send_time);
      if ((double)time_since_last_send > PULSE_PERIOD) {
        pulse_packet.send(static_cast<UDPSock*>(sockP), &remote_end);
        last_send_time = last_try_time = t0;
      }
      if (periodicSend->getValue()) {
        // We want to delay a bit if the frame rate is too high, but we can't delay < 1 millisecond, so its a bit messy.
        double time_to_kill = 1000.0 * (period - time_since_last_try) + accumulated_time_to_kill; // Milliseconds
        if (time_to_kill > 0.0f) {
           if (time_to_kill > 1.0f) {
               accumulated_time_to_kill = (time_to_kill - floor(time_to_kill));
               Sleep((int)time_to_kill); // really millisecond sleep. Round up.
            } else {
               accumulated_time_to_kill += time_to_kill;
               Sleep(0); // Give other threads a go.
            }
        } else {
           accumulated_time_to_kill = 0.0f;
        }

        last_try_time = TimeStamp::now();

        bool sent = false;
        for (TypedMFNode<RemoteField>::const_iterator i = remoteFields->begin();
             i != remoteFields->end() ; i++) {
          RemoteField * rfP = static_cast<RemoteField *>(*i);
          if (rfP->is_enabled.get()) {
            lockWriteThread(); // Lock to avoid clash with reading thread which
            rfP->writeField();
            sendPacket();
            unlockWriteThread();      
            sent = true;
          }
        }
        if (sent) {
          last_send_time = last_try_time;
        }
      }
      else {
        Thread::sleep((u_int)PULSE_PERIOD);
      }
    } else if (autoReconnect->getValue() || connect_requested) {
      if (isShuttingDown()) {
         break;
      }
      Thread::sleep((u_int)PULSE_PERIOD);
      if (msg_count++ % 5 == 0) {
        showInfo("Trying to reconnect with UDP server\n");
        msg_count = 1;
      }
      bool success = pulse_packet.send(static_cast<UDPSock*>(sockP), &remote_end);
      if (success) {
        // Get an ack.
        while (!sockP->dataReady()) {
          if (isShuttingDown()) {
            break;
          }
          Thread::millisleep(20);
        }
        Thread::millisleep(200);
        if (isShuttingDown())  break;
        int count = read_packet.receive(static_cast<UDPSock*>(sockP), &remote_end);
        if (count > 0) {
          char tmp[80];
          sprintf(tmp, "UDP connection to server %s:%d\n", (char *)remoteHost->getValue().data(), remotePort->getValue());
          msg_count = 0;
          showInfo(tmp);
          setConnected(true);
          connect_requested = false;
          last_send_time = TimeStamp::now();
        } 
      } 
    }
  }
  setSendLoopRunning(false);
}

// ----------------------------------------------------------------------------
void RemoteUDPClient::sendThisPacket(Packet & packet) {
  // Called in sending thread.

  send_packet_lock.lock();
  bool success = packet.send(static_cast<UDPSock*>(sockP), &remote_end);
  if (!success) {
    showError("error in UDP sock write");
    requestClose();
  }
  send_packet_lock.unlock();
}

// ----------------------------------------------------------------------------
void RemoteUDPClient::requestClose() {
  if (is_connected.get()) {
    char tmp[80];
    sprintf(tmp, "UDP Server %s disconnected\n",
            (char *)remoteHost->getValue().data());
    showInfo(tmp);
    close_requested = true;
  }
}


