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

using namespace H3D;
using namespace H3DNetworkingUtils;

// ----------------------------------------------------------------------------
H3DNodeDatabase RemoteUDPServer::database(   
  "RemoteUDPServer", 
  &(newInstance<RemoteUDPServer>),
  typeid(RemoteUDPServer),
  &RemoteServer::database);

// ----------------------------------------------------------------------------
RemoteUDPServer::RemoteUDPServer() :
pulse_packet() {

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

  use_seq_numbers = true;
  pulse_packet.setHeartBeat();
}

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

// ----------------------------------------------------------------------------
void RemoteUDPServer::startServer(int port_number) {
  // Called in graphics thread.

  close_requested = false;
  // If we have not already started a receiving thread from a previous
  // connection, start one.
  if (open_comms_cb_fn) {
    (*open_comms_cb_fn)(open_comms_cb_data);
  }

  InetAddr * myAddr = new InetAddr;
  char str[80];
  sprintf(str, "%d", port_number);
  myAddr->scanPort(str);

  UDPSock * udp_sockP = new UDPSock;
  udp_sockP->setTimeOut(u_int(RemoteUDPClient::PULSE_PERIOD * 2.0f * 1000.0f));
  FailErr(udp_sockP->bind(myAddr, 4), "Cannot serve on that port");

  sockP = udp_sockP;

  char tmp[80];
  sprintf(tmp, "Listening using UDP on %d\n", port_number);
  showInfo(tmp);
  ///  showInfo(tmp);

  // Note: we are not connected yet.
  if (!receive_loop_threadP){
    receive_loop_threadP = new Thread((ThreadFunction)receivingLoopTH, this);
  }
  if (!send_loop_threadP) {
    send_loop_threadP = new Thread((ThreadFunction)sendLoopTH, this);
  }
}


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

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

  setReceiveLoopRunning(true);
  do {
    // Check if another thread has requested the connection to be closed.
    if (close_requested) {
      if (is_connected.get()) {
        showInfo("UDP Closing\n");
        setConnected(false);
        close_requested = false;
        if (close_comms_cb_fn) {
          (*close_comms_cb_fn)(close_comms_cb_data);
        }
      }
    }

    while (!sockP->dataReady()) {
       if (isShuttingDown()) {
         break;
       }
       Sleep(1); // Win32 Sleep is millisleep   
    }
    if (isShuttingDown()) {
       break;
    }
    count = read_packet.receive(static_cast<UDPSock*>(sockP), &remote_end);
    if (count <= 0) {
      if (is_connected.get()) {
        // Timeout.
        char tmp[80];
        sprintf(tmp, "UDP Client %s disconnected\n", remote_end.text());
        showInfo(tmp);
        if (close_comms_cb_fn) {
          (*close_comms_cb_fn)(close_comms_cb_data);
        }
        setConnected(false);
      }
    }
    else { // count > 0
      if (!is_connected.get()) {
        // Send a pulse as a response
        bool success = pulse_packet.send(static_cast<UDPSock*>(sockP), &remote_end);
        if (success) {
          cout << 'S' << endl;
          char tmp[80];
          sprintf(tmp, "UDP connection from client %s\n", remote_end.text());
          showInfo(tmp);
          setConnected(true);
        }
      }
      if (initialized && !read_packet.isHeartBeat()) {
        readPacket(); // Interpret it.
      } 
    }
//  } while (count >= 0);
  ///} while (true);
  } while (!isShuttingDown());
  setReceiveLoopRunning(false);
}

// ----------------------------------------------------------------------------
void RemoteUDPServer::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 > RemoteUDPClient::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)RemoteUDPClient::PULSE_PERIOD);
      }
    }
    else { // ! connected
      Thread::sleep(1);
    }
  }
   setSendLoopRunning(false);
}

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

  send_packet_lock.lock();
  bool success = packet.send(static_cast<UDPSock*>(sockP), &remote_end);
  if (!success) {
    requestClose();
  }
  send_packet_lock.unlock();
}


