#include  <math.h>
#include  <stdio.h>
#include  "vrpn_Imager_Stream_Buffer.h"

vrpn_Imager_Stream_Buffer::vrpn_Imager_Stream_Buffer(const char * name, const char * imager_server_name, vrpn_Connection * c) :
vrpn_Auxiliary_Logger_Server(name, c)
, vrpn_Imager_Server(name, c, 0, 0) // Default number of rows and columns for the device.
, d_logging_thread(NULL)
, d_imager_server_name(NULL)
{
  // Copy the name of the server we are to connect to when we are logging.
  d_imager_server_name = new char[strlen(imager_server_name)+1];
  if (d_imager_server_name == NULL) {
    fprintf(stderr,"vrpn_Imager_Stream_Buffer::vrpn_Imager_Stream_Buffer: Out of memory\n");
    d_connection = NULL;
    return;
  }
  strcpy(d_imager_server_name, imager_server_name);

  // Create the logging thread but do not run it yet.
  vrpn_ThreadData td;
  td.pvUD = this;
  d_logging_thread = new vrpn_Thread(static_logging_thread_func, td);
  if (d_logging_thread == NULL) {
    fprintf(stderr,"vrpn_Imager_Stream_Buffer::vrpn_Imager_Stream_Buffer: can't create logging thread\n");
    d_connection = NULL;
    return;
  }

  // Register a handler for the got first connection message.
  got_first_connection_m_id = d_connection->register_message_type( vrpn_got_first_connection );
  if (got_first_connection_m_id == -1) {
    fprintf(stderr,"vrpn_Imager_Stream_Buffer::vrpn_Imager_Stream_Buffer: can't register got first connection type\n");
    d_connection = NULL;
    return;
  }
  if (register_autodeleted_handler(got_first_connection_m_id,
    static_handle_got_first_connection, this, vrpn_ANY_SENDER)) {
    fprintf(stderr,"vrpn_Imager_Stream_Buffer::vrpn_Imager_Stream_Buffer: can't register got first connection handler\n");
    d_connection = NULL;
  }

  // The base server class implements throttling for us, so we could just go ahead
  // and try to send the messages all the time using the normal frame begin/end and
  // region routines.  If we do this, though, we're going to have to unpack and repack
  // all of the messages. If we re-implement the throttling code, then we can just
  // watch the packets as they go by and see what types they are, discarding as
  // appropriate (but we still have to queue and watch them).
  //   If we implement the throttling
  // code down in the thread that listens to the server, we can avoid putting
  // them into the queue at all.  In that case, there can be a frame or more in
  // the queue that would drain even after the throttle message was received.
  //   We can subtract the number of frames in the buffer from the request if the
  // request is large enough, thus removing the problem, but it won't work for
  // the common case of requesting 0 or 1 frames.  This will work in the steady
  // state, where a sender requests one more each time it gets one, but there
  // will be an initial bolus of images.
  //    Nonetheless, this seems like the cleanest solution.  So, we will install
  // a handler for the throttling message that will pass it on down to the thread
  // that is baby-sitting the server object.
  if (register_autodeleted_handler(d_throttle_frames_m_id,
    static_handle_throttle_message, this, vrpn_ANY_SENDER)) {
    fprintf(stderr,"vrpn_Imager_Stream_Buffer::vrpn_Imager_Stream_Buffer: can't register throttle handler\n");
    d_connection = NULL;
  }
}

vrpn_Imager_Stream_Buffer::~vrpn_Imager_Stream_Buffer()
{
  stop_logging_thread();
  if (d_imager_server_name) {
    delete [] d_imager_server_name;
    d_imager_server_name = NULL;
  }
}

void vrpn_Imager_Stream_Buffer::mainloop(void)
{
  // Required from all servers
  server_mainloop();

  // See if we have a new image description from a logging thread.  If so,
  // fill in our values and send a description to any attached clients.
  const char *channelBuffer = NULL;
  if (d_shared_state.get_imager_description(d_nRows, d_nCols, d_nDepth, d_nChannels, &channelBuffer)) {
    int i;
    const char *bufptr = channelBuffer;
    for (i = 0; i < d_nChannels; i++) {
      d_channels[i].unbuffer(&bufptr);
    }
    delete [] const_cast<char *>(channelBuffer);
    send_description();
  }

  // See if we have any messages waiting in the queue from the logging thread.
  // If we do, get an initial count and then send that many messages to the
  // client.  Don't go looking again this iteration or we may never return --
  // the server is quite possibly packing frames faster than we can send them.
  // Note that the messages in the queue have already been transcoded for our
  // and sender ID.
  unsigned count = d_shared_state.get_logger_to_client_queue_size();
  if (count) {
    unsigned i;
    for (i = 0; i < count; i++) {
      // Read the next message from the queue.
      vrpn_HANDLERPARAM p;
      if (!d_shared_state.retrieve_logger_to_client_message(&p)) {
        fprintf(stderr, "vrpn_Imager_Stream_Buffer::mainloop(): Could not retrieve message from queue\n");
        break;
      }

      // Decrement the in-buffer frame count whenever we see a begin_frame
      // message.  This will un-block the way for later frames to be buffered.
      if (p.type == d_begin_frame_m_id) {
        d_shared_state.decrement_frames_in_queue();
      }

      // Pack and send the message to the client, then delete the buffer
      // associated with the message.  Send them all reliably.  Send them
      // all using our sender ID.
      if (d_connection->pack_message(p.payload_len, p.msg_time, p.type, d_sender_id, p.buffer,
        vrpn_CONNECTION_RELIABLE) != 0) {
        fprintf(stderr, "vrpn_Imager_Stream_Buffer::mainloop(): Could not pack message\n");
        break;
      }
      delete [] const_cast<char *>(p.buffer);
    }
  }
}

/* static */
// This method just passes the call on to the virtual function.
int vrpn_Imager_Stream_Buffer::static_handle_got_first_connection(void *userdata,
	vrpn_HANDLERPARAM p)
{
  vrpn_Imager_Stream_Buffer *me = static_cast<vrpn_Imager_Stream_Buffer *>(userdata);
  me->handle_got_first_connection();
  return 0;
}

// Handle got first connection request by (having the second thread) create
// a connection to the server and waiting until we get a description message
// from the imager server we're listening to.  Timeout after a while if the
// connection cannot be made or the server does not respond.

void vrpn_Imager_Stream_Buffer::handle_got_first_connection(void)
{
  // There should be no thread in existence when this call is made.
  // If there is, kill it and complain.
  if (d_logging_thread->running()) {
    struct timeval now;
    vrpn_gettimeofday(&now, NULL);
    send_text_message("handle_got_first_connection: Thread running when it should not be", now, vrpn_TEXT_ERROR);
    d_logging_thread->kill();
    return;
  }

  // Reset the shared state before starting the thread running.
  d_shared_state.init();

  // Create a thread whose userdata points at the object that
  // created it.  Then call the start function on that thread and wait
  // for its vrpn_Imager_Remote to receive the info from the remote server
  // it has connected to.  We time out after a few seconds if we don't
  // get the response, leaving us with a presumably broken connection
  // to the server.
  if (!d_logging_thread->go()) {
    struct timeval now;
    vrpn_gettimeofday(&now, NULL);
    send_text_message("handle_got_first_connection: Failed to start logging thread", now, vrpn_TEXT_ERROR);
    delete d_logging_thread;
    d_logging_thread = NULL;
    return;
  }
  struct timeval start, now;
  vrpn_gettimeofday(&start, NULL);
  do {
    const char *channelBuffer = NULL;
    if (d_shared_state.get_imager_description(d_nRows, d_nCols, d_nDepth, d_nChannels, &channelBuffer)) {
      int i;
      const char *bufptr = channelBuffer;
      for (i = 0; i < d_nChannels; i++) {
        d_channels[i].unbuffer(&bufptr);
      }
      delete [] const_cast<char *>(channelBuffer);
      return;
    }

    vrpn_SleepMsecs(1);
    vrpn_gettimeofday(&now, NULL);
  } while ( vrpn_TimevalDiff(now, start).tv_sec < 3 );

  // Timed out, so we won't be hearing from the server!
  vrpn_gettimeofday(&now, NULL);
  send_text_message("handle_got_first_connection: Didn't hear from server.", now, vrpn_TEXT_WARNING);
}

// Handle dropped last connection on our primary connection by shutting down the
// connection to the imager server (killing the logging thread).
void vrpn_Imager_Stream_Buffer::handle_dropped_last_connection(void)
{
  if (!stop_logging_thread()) {
    fprintf(stderr, "vrpn_Imager_Stream_Buffer::handle_dropped_last_connection(): Had to kill logging thread\n");
  }
}

// Handles a throttle request by passing it on down to the non-blocking
// thread to deal with.
int vrpn_Imager_Stream_Buffer::static_handle_throttle_message(void *userdata,
	vrpn_HANDLERPARAM p)
{
  const char *bufptr = p.buffer;
  vrpn_Imager_Stream_Buffer *me = static_cast<vrpn_Imager_Stream_Buffer *>(userdata);

  // Get the requested number of frames from the buffer
  vrpn_int32  frames_to_send;
  if (vrpn_unbuffer(&bufptr, &frames_to_send)) {
    return -1;
  }

  me->d_shared_state.set_throttle_request(frames_to_send);
  return 0;
}

// The function that is called to become the logging thread.  It is passed
// a pointer to "this" so that it can acces the object that created it.
// The static function basically just unpacks the "this" pointer and
// then calls the non-static function.
void vrpn_Imager_Stream_Buffer::static_logging_thread_func(vrpn_ThreadData &threadData)
{
    vrpn_Imager_Stream_Buffer *me = static_cast<vrpn_Imager_Stream_Buffer *>(threadData.pvUD);
    me->logging_thread_func();
}

// Note that it must use semaphores to get at the data that will be shared
// between the main thread and itself.
// This function does all the work of the logging thread, including all
// interactions with the vrpn_Imager_Server connection(s) and the client
// object; it forwards information both ways to the main thread that is
// communicating with the end-user client connection.
// DO NOT CALL VRPN message sends on the client object's connection from
// this function or those it calls, because we're not the thread that is
// connected to the client object's connection and such calls are not thread-safe.
// Instead, pass the data needed to make the calls to the initial thread.
void vrpn_Imager_Stream_Buffer::logging_thread_func(void)
{
  // Initialize everything to a clean state.
  d_log_connection = NULL;
  d_imager_remote = NULL;
  d_server_dropped_due_to_throttle = 0; // None dropped yet!
  d_server_frames_to_send = -1; // Send as many as you get

  // Open a connection to the server object, not asking it to log anything.
  // (Logging will be started later if we receive a message from our client.)
  // Create a vrpn_Imager_Remote object and set its callbacks to fill things
  // into the shared data structures.
  d_log_connection = open_new_log_connection("","","","");
  if (d_log_connection == NULL) {
    fprintf(stderr,"vrpn_Imager_Stream_Buffer::logging_thread_func(): Cannot open connection\n");
    return;
  }
  if (!setup_handlers_for_logging_connection(d_log_connection)) {
    fprintf(stderr,"vrpn_Imager_Stream_Buffer::logging_thread_func(): Cannot set up handlers\n");
    return;
  }

  // Keep doing mainloop() on the client object(s) and checking
  // for commands that we're supposed to issue until we're
  // told that we're supposed to die.  Sleep a little between iterations
  // to avoid eating CPU time.
  while (!d_shared_state.time_to_exit()) {
    // Check to see if the client has sent a new throttling message.
    // If so, then adjust our state accordingly.  Note that we are duplicating
    // the effort of the vrpn_Imager_Server base class here; it will still be
    // keeping its own shadow copy of these values, but will not be doing anything
    // with them because we'll never be calling its send routines.  This duplicates
    // the code in the handle_throttle_message() in the vrpn_Imager_Server base
    // class.
    vrpn_int32 frames_to_send;
    if (d_shared_state.get_throttle_request(&frames_to_send)) {
      // If the requested number of frames is negative, then we set
      // for unbounded sending.  The next time a begin_frame message
      // arrives, it will start the process going again.
      if (frames_to_send < 0) {
        d_server_frames_to_send = -1;

      // If we were sending continuously, store the number of frames
      // to send.  Decrement by the number of frames already in the
      // outgoing buffer, but don't let the number go below zero.
      } else if (d_server_frames_to_send == -1) {
        int frames_in_queue = d_shared_state.get_frames_in_queue();
        if (frames_to_send >= frames_in_queue) {
          frames_to_send -= frames_in_queue;
        }
        d_server_frames_to_send = frames_to_send;

      // If we already had a throttle limit set, then increment it
      // by the count.
      } else {
        d_server_frames_to_send += frames_to_send;
      }
    }

    // Check to see if we've been asked to create new log files.  If we have,
    // then attempt to do so.  If that works, pass back the names of the files
    // created to the initial thread.  If it did not work, return empty log-file
    // names.
    char *lil, *lol, *ril, *rol;
    if ( d_shared_state.get_logfile_request(&lil, &lol, &ril, &rol) ) {
      if (make_new_logging_connection(lil, lol, ril, rol)) {
        d_shared_state.set_logfile_result(lil, lol, ril, rol);
      } else {
        d_shared_state.set_logfile_result("","","","");
      }
      // Delete the allocated space only if there were return values.
      delete [] lil;
      delete [] lol;
      delete [] ril;
      delete [] rol;
    }

    // Handle all of the messages coming from the server.
    if (d_imager_remote) {
      d_imager_remote->mainloop();
    }
    if (d_log_connection) {
      d_log_connection->mainloop();
      d_log_connection->save_log_so_far();
    }

    vrpn_SleepMsecs(1);
  }

  // Now that we've been told to die, clean up everything and return.
  if (d_imager_remote) {
    delete d_imager_remote;
    d_imager_remote = NULL;
  }
  if (d_log_connection) {
    d_log_connection->removeReference();
    d_log_connection = NULL;
  }
}

/* static */
int VRPN_CALLBACK vrpn_Imager_Stream_Buffer::static_handle_server_messages(void *pvISB, vrpn_HANDLERPARAM p)
{
  vrpn_Imager_Stream_Buffer *me = static_cast<vrpn_Imager_Stream_Buffer *>(pvISB);
  return me->handle_server_messages(p);
}

int vrpn_Imager_Stream_Buffer::handle_server_messages(const vrpn_HANDLERPARAM &p)
{
  // Handle begin_frame message very specially, because it has all sorts
  // of interactions with throttling and missed-frame reporting.
  if (p.type == d_server_begin_frame_m_id) {
    // This duplicates code from the send_begin_frame() method in
    // the vrpn_Imager_Server base class that handles throttling.
    // It further adds code to handle throttling when the queue to
    // the initial thread is too full.

    // If we are throttling frames and the frame count has gone to zero,
    // then increment the number of frames missed and do not add this
    // message to the queue.
    if (d_server_frames_to_send == 0) {
      d_server_dropped_due_to_throttle++;
      return 0;
    }

    // If there are too many frames in the queue already,
    // add one to the number lost due to throttling (which
    // will prevent region and end-of-frame messages until the next
    // begin_frame message) and break without forwarding the message.
    if (d_shared_state.get_frames_in_queue() >= 2) {
      d_server_dropped_due_to_throttle++;
      return 0;
    }

    // If we missed some frames due to throttling, but are now
    // sending frames again, report how many we lost due to
    // throttling.  This is incremented both for client-requested
    // throttling and to queue-overflow throttling.
    if (d_server_dropped_due_to_throttle > 0) {
      // We create a new message header and body, using the server's
      // type IDs, and then transcode and send the message through
      // the initial connection.
      vrpn_HANDLERPARAM tp = p;
      vrpn_float64 fbuf [vrpn_CONNECTION_TCP_BUFLEN/sizeof(vrpn_float64)];
      char	  *msgbuf = (char *) fbuf;
      int	  buflen = sizeof(fbuf);
      tp.type = d_server_discarded_frames_m_id;

      if (vrpn_buffer(&msgbuf, &buflen, d_server_dropped_due_to_throttle)) {
        fprintf(stderr,"vrpn_Imager_Stream_Buffer::handle_server_messages: Can't pack count\n");
        return -1;
      }
      tp.buffer = static_cast<char*>(static_cast<void*>(fbuf));
      tp.payload_len = sizeof(fbuf) - buflen;

      if (!transcode_and_send(tp)) {
        fprintf(stderr,"vrpn_Imager_Stream_Buffer::handle_server_messages: Can't send discarded frames count\n");
        return -1;
      }

      d_server_dropped_due_to_throttle = 0;
    }

    // If we are throttling, then decrement the number of frames
    // left to send.
    if (d_server_frames_to_send > 0) {
      d_server_frames_to_send--;
    }

    // No throttling going on, so add the message to the outgoing queue and
    // also increment the count of how many outstanding frames are in the
    // queue.
    if (!transcode_and_send(p)) {
      fprintf(stderr,"vrpn_Imager_Stream_Buffer::handle_server_messages: Can't transcode and send\n");
      return -1;
    }
    d_shared_state.increment_frames_in_queue();

  // Handle the end_frame and all of the region messages in a similar manner,
  // dropping them if throttling is going on and passing them on if not.
  // This duplicates code from the send_end_frame() and the region
  // send methods in the vrpn_Imager_Server base class that handles
  // throttling.
  } else if ( (p.type == d_server_end_frame_m_id) ||
              (p.type == d_server_regionu8_m_id) ||
              (p.type == d_server_regionu12in16_m_id) ||
              (p.type == d_server_regionu16_m_id) ||
              (p.type == d_server_regionf32_m_id) ) {

    // If we are discarding frames, do not queue this message.
    if (d_server_dropped_due_to_throttle > 0) {
      return 0;
    }

    // No throttling going on, so add this message to the outgoing queue.
    if (!transcode_and_send(p)) {
      fprintf(stderr,"vrpn_Imager_Stream_Buffer::handle_server_messages: Can't transcode and send\n");
      return -1;
    }

  // Send these messages on without modification
  // (Currently, these are description messages and discarded-frame
  // messages.  It also includes the generic pong response and any
  // text messages.)
  } else if ( (p.type == d_server_description_m_id) ||
              (p.type == d_server_discarded_frames_m_id) ||
              (p.type == d_server_text_m_id) ||
              (p.type == d_server_pong_m_id) ) {
    if (!transcode_and_send(p)) {
      fprintf(stderr,"vrpn_Imager_Stream_Buffer::handle_server_messages: Can't transcode and send\n");
      return -1;
    }

  // Ignore these messages without passing them on.
  } else if ( (p.type == d_server_ping_m_id) ) {
    return 0;

  // We need to throw a warning here on unexpected types so that we get some
  // warning if additional messages are added.  This code is fragile because it
  // relies on us knowing the types of base-level and imager messages and catching
  // them all.  This way, at least we'll know if we miss one.
  } else {
    // We create a new message header and body, using the server's
    // type IDs, and then transcode and send the message through
    // the initial connection.  This is a text message saying that we
    // got a message of unknown type.
    vrpn_HANDLERPARAM tp = p;
    char buffer[2 * sizeof(vrpn_int32) + vrpn_MAX_TEXT_LEN];
    char msg[vrpn_MAX_TEXT_LEN];
    tp.type = d_server_text_m_id;
    tp.buffer = buffer;
    tp.payload_len = sizeof(buffer);
    sprintf(msg, "Unknown message type from server: %d", static_cast<int>(p.type));
    encode_text_message_to_buffer(buffer, vrpn_TEXT_ERROR, 0, msg);
    if (!transcode_and_send(tp)) {
      fprintf(stderr,"vrpn_Imager_Stream_Buffer::handle_server_messages: Can't transcode text message\n");
      return -1;
    }
  }

  return 0;
}

// Transcode the sender and type fields from the logging server connection to
// the initial client connection and pack the resulting message into the queue
// from the logging thread to the initial thread.  The data buffer is copied;
// this space is allocated by the logging thread and must be freed by the initial thread.
// Returns true on success and false on failure.  The sender is set to the
// d_sender_id of our server object.
bool vrpn_Imager_Stream_Buffer::transcode_and_send(const vrpn_HANDLERPARAM &p)
{
  // Copy the contents of the buffer to a newly-allocated one that will be
  // passed to the initial thread.
  char *newbuf = new char[p.payload_len];
  if (newbuf == NULL) {
    fprintf(stderr,"vrpn_Imager_Stream_Buffer::transcode_and_send(): Out of memory\n");
    return false;
  }
  memcpy(newbuf, p.buffer, p.payload_len);

  // Copy the contents of the handlerparam to a newly-allocated one that will
  // be passed to the initial thread.  Change the sender to match ours, set the
  // buffer pointer to the new buffer, and transcode the type.
  vrpn_HANDLERPARAM newp = p;
  newp.buffer = newbuf;
  newp.sender = d_sender_id;
  newp.type = transcode_type(p.type);
  if (newp.type == -1) {
    fprintf(stderr,"vrpn_Imager_Stream_Buffer::transcode_and_send(): Unknown type (%d)\n",
      static_cast<int>(p.type));
    delete [] newbuf;
    return false;
  }

  // Add the message to the queue of messages going to the initial thread.
  if (!d_shared_state.insert_logger_to_client_message(newp)) {
    fprintf(stderr,"vrpn_Imager_Stream_Buffer::transcode_and_send(): Can't queue message\n");
    return false;
  }

  return true;
}

// Transcode the type from the logging thread's connection type to
// the initial thread's connection type.  Return -1 if we don't
// recognize the type.
vrpn_int32 vrpn_Imager_Stream_Buffer::transcode_type(vrpn_int32 type)
{
  if (type == d_server_description_m_id) {
    return d_description_m_id;
  } else if (type == d_server_begin_frame_m_id) {
    return d_begin_frame_m_id;
  } else if (type == d_server_end_frame_m_id) {
    return d_end_frame_m_id;
  } else if (type == d_server_discarded_frames_m_id) {
    return d_discarded_frames_m_id;
  } else if (type == d_server_regionu8_m_id) {
    return d_regionu8_m_id;
  } else if (type == d_server_regionu12in16_m_id) {
    return d_regionu12in16_m_id;
  } else if (type == d_server_regionu16_m_id) {
    return d_regionu16_m_id;
  } else if (type == d_server_regionf32_m_id) {
    return d_regionf32_m_id;
  } else if (type == d_server_text_m_id) {
    return d_text_message_id;
  } else if (type == d_server_ping_m_id) {
    return d_ping_message_id;
  } else if (type == d_server_pong_m_id) {
    return d_pong_message_id;
  } else {
    return -1;
  }
}

vrpn_Connection *vrpn_Imager_Stream_Buffer::open_new_log_connection(
                  const char *local_in_logfile_name,
                  const char *local_out_logfile_name,
                  const char *remote_in_logfile_name,
                  const char *remote_out_logfile_name)
{
  vrpn_Connection *ret = NULL;

  // Find the relevant part of the name (skip past last '@'
  // if there is one); also find the port number.
  const char *cname = d_imager_server_name;
  const char *where_at;	// Part of name past last '@'
  if ( (where_at = strrchr(cname, '@')) != NULL) {
	  cname = where_at+1;	// Chop off the front of the name
  }

  // Pass "true" to force_connection so that it will open a new
  // connection even if we already have one with that name.
  ret = vrpn_get_connection_by_name(where_at,
    local_in_logfile_name, local_out_logfile_name, remote_in_logfile_name, remote_out_logfile_name,
    NULL, true);
  if ( !ret || !ret->doing_okay() ) {
    struct timeval now;
    vrpn_gettimeofday(&now, NULL);
    fprintf(stderr, "vrpn_Imager_Stream_Buffer::open_new_log_connection: Could not create connection (files already exist?)");
    if (ret) { delete ret; return NULL; }
  }

  return ret;
}

bool vrpn_Imager_Stream_Buffer::setup_handlers_for_logging_connection(vrpn_Connection *c)
{
  // Create a vrpn_Imager_Remote on this connection and set its callbacks so
  // that they will do what needs doing; the callbacks point to the
  // Imager_Stream_Buffer object, not to the imager_remote object; access it
  // through the member variable pointer.
  d_imager_remote = new vrpn_Imager_Remote(d_imager_server_name, c);
  if (d_imager_remote == NULL) {
    fprintf(stderr,"vrpn_Imager_Stream_Buffer::setup_handlers_for_logging_connection(): Cannot create vrpn_Imager_Remote\n");
    return false;
  }
  d_imager_remote->register_description_handler(this, handle_image_description);

  // Figure out the remote type IDs from the server for the messages we want
  // to forward.  This is really dangerous, because we need to make sure to
  // explicitly list all the ones we might need.  If we forget an important
  // one (or it gets added later to either the base class or the imager class)
  // then it won't get forwarded.
  d_server_description_m_id = c->register_message_type("vrpn_Imager Description");
  d_server_begin_frame_m_id = c->register_message_type("vrpn_Imager Begin_Frame");
  d_server_end_frame_m_id = c->register_message_type("vrpn_Imager End_Frame");
  d_server_discarded_frames_m_id = c->register_message_type("vrpn_Imager Discarded_Frames");
  d_server_regionu8_m_id = c->register_message_type("vrpn_Imager Regionu8");
  d_server_regionu16_m_id = c->register_message_type("vrpn_Imager Regionu16");
  d_server_regionu12in16_m_id = c->register_message_type("vrpn_Imager Regionu12in16");
  d_server_regionf32_m_id = c->register_message_type("vrpn_Imager Regionf32");
  d_server_text_m_id = c->register_message_type("vrpn_Base text_message");
  d_server_ping_m_id = c->register_message_type("vrpn_Base ping_message");
  d_server_pong_m_id = c->register_message_type("vrpn_Base pong_message");

  // Set up handlers for the other messages from the server so that they can
  // be passed on up to the initial thread and on to the client as appropriate.
  // Be sure to strip the "@" part off the device name before registering the sender
  // so that it is the same as the one used by the d_imager_remote.
  c->register_handler(vrpn_ANY_TYPE, static_handle_server_messages, this, c->register_sender(vrpn_copy_service_name(d_imager_server_name)));

  return true;
}

bool vrpn_Imager_Stream_Buffer::teardown_handlers_for_logging_connection(vrpn_Connection *c)
{
  if (!d_imager_remote) {
    fprintf(stderr,"vrpn_Imager_Stream_Buffer::teardown_handlers_for_logging_connection(): No imager remote\n");
    return false;
  }
  if (d_imager_remote->unregister_description_handler(this, handle_image_description) != 0) {
    fprintf(stderr,"vrpn_Imager_Stream_Buffer::teardown_handlers_for_logging_connection(): Cannot unregister handler\n");
    return false;
  }

  // Tear down handlers for the other messages from the server.
  c->unregister_handler(vrpn_ANY_TYPE, static_handle_server_messages, this, c->register_sender(vrpn_copy_service_name(d_imager_server_name)));

  delete d_imager_remote;
  d_imager_remote = NULL;
  return true;
}

bool vrpn_Imager_Stream_Buffer::make_new_logging_connection(const char *local_in_logfile_name,
                                    const char *local_out_logfile_name,
                                    const char *remote_in_logfile_name,
                                    const char *remote_out_logfile_name)
{
  // Open a new connection to do logging on before deleting the old one so
  // that we keep at least one connection open to the server at all time.
  // This will prevent it from doing its "dropped last connection" things
  // which will include resetting the imager server.
  vrpn_Connection *new_log_connection = open_new_log_connection(
    local_in_logfile_name, local_out_logfile_name,
    remote_in_logfile_name, remote_out_logfile_name);
  if (new_log_connection == NULL) {
    fprintf(stderr,"vrpn_Imager_Stream_Buffer::make_new_logging_connection(): Cannot open connection\n");
    return false;
  }

  // Unhook the callbacks from the existing logging connection so that
  // we don't end up with two callbacks for each message.
  if (!teardown_handlers_for_logging_connection(d_log_connection)) {
    fprintf(stderr,"vrpn_Imager_Stream_Buffer::make_new_logging_connection(): Cannot teardown connection\n");
    return false;
  }

  // Hook the callbacks up to the new connection so that we will get reports
  // from the server.
  if (!setup_handlers_for_logging_connection(new_log_connection)) {
    fprintf(stderr,"vrpn_Imager_Stream_Buffer::make_new_logging_connection(): Cannot setup connection\n");
    return false;
  }

  // Mainloop the new connection object until it becomes connected or we
  // time out.  If we time out, then put things back on the old connection
  // and tell the thread it is time to self-destruct.  The way we check
  // for connected cannot be just that the connection's connected() method
  // returns true (because our end can be marked connected before the other
  // end decides it has complete the connection.  Rather, we check to see
  // that we've got a new description report from the server -- indicating
  // that it has seen the new report.  This also lets us know that the old
  // log file will have accumulated all images up to the new report, so we
  // can shut it off without losing any images in the switch to the new
  // log file (there may be duplicates, but not losses).
  struct timeval start, now;
  vrpn_gettimeofday(&start, NULL);
  now = start;
  d_ready_to_drop_old_connection = false;
  while ( !d_ready_to_drop_old_connection && (vrpn_TimevalDiff(now, start).tv_sec < 3)) {
    new_log_connection->mainloop();   // Enable connection set-up to occur
    new_log_connection->save_log_so_far();
    d_log_connection->mainloop();     // Eat up (and log) any incoming messages
    d_log_connection->save_log_so_far();
    vrpn_gettimeofday(&now, NULL);
    vrpn_SleepMsecs(1);
  };
  if (!d_ready_to_drop_old_connection) {
    fprintf(stderr,"vrpn_Imager_Stream_Buffer::make_new_logging_connection(): Could not connect new logging connection\n");
    teardown_handlers_for_logging_connection(new_log_connection);
    setup_handlers_for_logging_connection(d_log_connection);
    new_log_connection->removeReference();
    d_shared_state.time_to_exit(true);
    return false;
  }

  // Delete the old connection object by reducing its reference count.
  d_log_connection->removeReference();

  // Set up to use the new connection
  d_log_connection = new_log_connection;
  return true;
}

void vrpn_Imager_Stream_Buffer::handle_request_logging(const char *local_in_logfile_name,
                                    const char *local_out_logfile_name,
                                    const char *remote_in_logfile_name,
                                    const char *remote_out_logfile_name)
{
  // Request that the logging thread start new logs.
  d_shared_state.set_logfile_request(local_in_logfile_name,
    local_out_logfile_name, remote_in_logfile_name, remote_out_logfile_name);

  // Wait until we hear back from the logging thread or time out;
  // return empty if timeout and the strings we got back if not.
  // Remember to deallocated the memory if we got a response.
  struct timeval start, now;
  vrpn_gettimeofday(&start, NULL);
  do {
    char *lil, *lol, *ril, *rol;
    if (d_shared_state.get_logfile_result(&lil, &lol, &ril, &rol)) {
      send_report_logging(lil, lol, ril, rol);
      delete [] lil;
      delete [] lol;
      delete [] ril;
      delete [] rol;
      return;
    }
    vrpn_SleepMsecs(1);
    vrpn_gettimeofday(&now, NULL);
  } while (vrpn_TimevalDiff(now, start).tv_sec < 2);

  // Timeout, report failure of logging by saying that there are empty log file names.
  send_report_logging("","","","");
}


void vrpn_Imager_Stream_Buffer::
handle_request_logging_status()
{
	char* local_in;
	char* local_out;
	char* remote_in;
	char* remote_out;
	d_shared_state.get_logfile_names( &local_in, &local_out, &remote_in, &remote_out );
	send_report_logging( local_in, local_out, remote_in, remote_out );
	if( local_in ) delete [] local_in;
	if( local_out ) delete [] local_out;
	if( remote_in ) delete [] remote_in;
	if( remote_out ) delete [] remote_out;
}


/* Static */
// We've gotten a new imager description, so fill it into the shared data structure
// so that the parent object can hear about it.
void vrpn_Imager_Stream_Buffer::handle_image_description(void *pvISB, const struct timeval msg_time)
{
  vrpn_Imager_Stream_Buffer *me = static_cast<vrpn_Imager_Stream_Buffer *>(pvISB);

  // Pack up description messages for all of the channels into a buffer that is at
  // least large enough to hold them all.
  // msgbuf must be float64-aligned!
  vrpn_float64 *fbuf = new vrpn_float64[vrpn_CONNECTION_TCP_BUFLEN/sizeof(vrpn_float64)];
  char *buffer = static_cast<char *>(static_cast<void*>(fbuf));
  if (buffer == NULL) {
    fprintf(stderr, "vrpn_Imager_Stream_Buffer::handle_image_description(): Out of memory\n");
    me->d_shared_state.time_to_exit(true);
    return;
  }
  int i;
  char *bufptr = buffer;
  vrpn_int32 buflen = sizeof(vrpn_float64) * vrpn_CONNECTION_TCP_BUFLEN/sizeof(vrpn_float64);
  for (i = 0; i < me->d_imager_remote->nChannels(); i++) {
    me->d_imager_remote->channel(i)->buffer(&bufptr, &buflen);
  }

  me->d_shared_state.set_imager_description(
    me->d_imager_remote->nRows(),
    me->d_imager_remote->nCols(),
    me->d_imager_remote->nDepth(),
    me->d_imager_remote->nChannels(),
    buffer);

  // We've gotten a description report on the new connection, so we're ready
  // to drop the old connection.
  me->d_ready_to_drop_old_connection = true;
}

// Stop the logging thread function, cleanly if possible.  Returns true if
// the function stopped cleanly, false if it had to be killed.
bool vrpn_Imager_Stream_Buffer::stop_logging_thread(void)
{
  // Set the flag telling the logging thread to stop.
  d_shared_state.time_to_exit(true);

  // Wait for up to three seconds for the logging thread to die a clean death.
  // If it does, return true.
  struct timeval start, now;
  vrpn_gettimeofday(&start, NULL);
  do {
    if (!d_logging_thread->running()) {
      return true;
    }
    vrpn_SleepMsecs(1);
    vrpn_gettimeofday(&now, NULL);
  } while (vrpn_TimevalDiff(now, start).tv_sec < 3);

  d_logging_thread->kill();
  return false;
}
