// $Id$

#define ACE_BUILD_DLL
#include "ace/Synch_T.h"
#include "ace/SOCK_Acceptor.h"
#include "ace/SOCK_Connector.h"
#include "ace/Reactor.h"
#include "ace/XtReactor.h"

#if defined (ACE_HAS_XT)

struct ACE_XtReactorID 
{
  XtInputId id_;
  int good_id_;
};

ACE_ALLOC_HOOK_DEFINE (ACE_XtReactor)

// Must be called with lock held
ACE_XtReactor::ACE_XtReactor (XtAppContext context,
			      size_t size, 
			      int restart, 
			      ACE_Sig_Handler *h)
  : ACE_Select_Reactor (size, restart, h),
    context_ (context),
    ids_ (0),
    id_len_ (0),
    timeout_ (0)
{
  // When the ACE_Select_Reactor is constructed it creates the notify
  // pipe and registers it with the register_handler_i() method. The
  // XtReactor overloads this method BUT because the
  // register_handler_i occurs when constructing the base class
  // ACE_Select_Reactor, the ACE_Select_Reactor register_handler_i()
  // is called not the XtReactor register_handler_i().  This means
  // that the notify pipe is registered with the ACE_Select_Reactor
  // event handling code not the XtReactor and so notfications don't
  // work.  To get around this we simply close and re-opened the
  // notification handler in the constructor of the XtReactor.

#if defined (ACE_MT_SAFE) && (ACE_MT_SAFE != 0)
  this->notify_handler_.close ();
  this->notify_handler_.open (this, 0);
  // @@ The notify pipe is always enabled, check this...
#endif /* ACE_MT_SAFE */
}

ACE_XtReactor::~ACE_XtReactor (void)
{
  delete [] this->ids_;
}

// This is just the wait_for_multiple_events from ace/Reactor.cpp but
// we use the Xt functions to wait for an event, not select ()

int 
ACE_XtReactor::wait_for_multiple_events (ACE_Select_Reactor_Handle_Set &handle_set,
					 ACE_Time_Value *max_wait_time)
{
  ACE_TRACE ("ACE_XtReactor::wait_for_multiple_events");
  int nfound;

  do 
    {
      max_wait_time = this->timer_queue_->calculate_timeout (max_wait_time);
      
      size_t width = this->handler_rep_.max_handlep1 ();
      handle_set.rd_mask_ = this->wait_set_.rd_mask_;
      handle_set.wr_mask_ = this->wait_set_.wr_mask_;
      handle_set.ex_mask_ = this->wait_set_.ex_mask_;
      nfound = XtWaitForMultipleEvents (width, 
					handle_set,
					max_wait_time);

    } while (nfound == -1 && this->handle_error () > 0);

  if (nfound > 0)
    {
#if !defined (ACE_WIN32)
      handle_set.rd_mask_.sync (this->handler_rep_.max_handlep1 ());
      handle_set.wr_mask_.sync (this->handler_rep_.max_handlep1 ());
      handle_set.ex_mask_.sync (this->handler_rep_.max_handlep1 ());
#endif /* ACE_WIN32 */
    }
  return nfound; // Timed out or input available
}

void 
ACE_XtReactor::TimerCallbackProc (XtPointer closure, XtIntervalId *id)
{
  ACE_XtReactor *self = (ACE_XtReactor *) closure;
  self->timeout_ = 0;

  ACE_DEBUG ((LM_DEBUG,  ASYS_TEXT ("ACE_XtReactor::Timer on id %d\n"), (int) *id));

  // Deal with any timer events
  ACE_Select_Reactor_Handle_Set handle_set;
  self->dispatch (0, handle_set);
  self->reset_timeout ();
}

// This could be made shorter if we know which *kind* of event we were
// about to get.  Here we use select () to find out which one might be
// available.

void 
ACE_XtReactor::InputCallbackProc (XtPointer closure, 
				  int *source,
				  XtInputId *)
{
  ACE_XtReactor *self = (ACE_XtReactor *) closure;
  ACE_HANDLE handle = (ACE_HANDLE) *source;

  ACE_DEBUG ((LM_DEBUG,  ASYS_TEXT ("ACE_XtReactor::Input on fd %d\n"), *source));

  ACE_Time_Value zero = ACE_Time_Value::zero; // my copy isn't const

  ACE_Select_Reactor_Handle_Set wait_set;

  // Deal with one file event
    
  // - read which kind of event
  if (self->wait_set_.rd_mask_.is_set (handle))
    wait_set.rd_mask_.set_bit (handle);
  if (self->wait_set_.wr_mask_.is_set (handle))
    wait_set.wr_mask_.set_bit (handle);
  if (self->wait_set_.ex_mask_.is_set (handle))
    wait_set.ex_mask_.set_bit (handle);

  int result = ACE_OS::select (int (handle) + 1,
			       wait_set.rd_mask_,
			       wait_set.wr_mask_,
			       wait_set.ex_mask_, &zero);

  ACE_Select_Reactor_Handle_Set dispatch_set;

  // - Use only that one file event (removes events for other files)
  if (result > 0)
    {
      if (wait_set.rd_mask_.is_set (handle))
	dispatch_set.rd_mask_.set_bit (handle);
      if (wait_set.wr_mask_.is_set (handle))
	dispatch_set.wr_mask_.set_bit (handle);
      if (wait_set.ex_mask_.is_set (handle))
	dispatch_set.ex_mask_.set_bit (handle);

      self->dispatch (1, dispatch_set);
    }
}

int 
ACE_XtReactor::XtWaitForMultipleEvents (int width, 
					ACE_Select_Reactor_Handle_Set &wait_set,
					ACE_Time_Value *)
{
  // Check to make sure our handle's are all usable.

  ACE_Select_Reactor_Handle_Set temp_set = wait_set;

  if (ACE_OS::select (width, 
		      temp_set.rd_mask_, 
		      temp_set.wr_mask_, 
		      temp_set.ex_mask_, 
		      (ACE_Time_Value *) &ACE_Time_Value::zero) == -1)
    return -1; // Bad file arguments...

  // Instead of waiting using select, just use the Xt mechanism to wait
  // for a single event.

  // Wait for something to happen.
  ::XtAppProcessEvent (context_, XtIMAll);

  // Now actually read the result needed by the Select_Reactor using select.
  return ACE_OS::select (width,
			 wait_set.rd_mask_,  
			 wait_set.wr_mask_,  
			 wait_set.ex_mask_,  
			 (ACE_Time_Value *) &ACE_Time_Value::zero);
}

XtAppContext 
ACE_XtReactor::context (void) 
{
  return this->context_; 
}

int
ACE_XtReactor::register_handler_i (ACE_HANDLE handle, 
				   ACE_Event_Handler *handler,
				   ACE_Reactor_Mask mask)
{
  ACE_TRACE ("ACE_XtReactor::register_handler_i");
#if defined (ACE_WIN32)
  ACE_NOTSUP_RETURN (-1);
#else
  ACE_DEBUG ((LM_DEBUG,  ASYS_TEXT ("+++%d\n"), handle));
  int result = ACE_Select_Reactor::register_handler_i (handle, handler, mask);

  if (result == -1)
    return -1;

  // Ensure the list of InputId's is big enough
  if (this->ids_ == 0 || this->id_len_ < handle + 1)
    {
      ACE_XtReactorID *more;
      ACE_NEW_RETURN (more, ACE_XtReactorID[handle + 1], -1);

      int i;

      for (i = 0; i < this->id_len_; i++)
	more[i] = ids_[i];

      for (i = this->id_len_; i < handle + 1; i++)
	more[i].good_id_ = 0;

      id_len_ = handle + 1;
      delete [] this->ids_;
      ids_ = more;
    }

  int condition = 0;

  if (ACE_BIT_ENABLED (mask, ACE_Event_Handler::READ_MASK))
    ACE_SET_BITS (condition, XtInputReadMask);
  if (ACE_BIT_ENABLED (mask, ACE_Event_Handler::WRITE_MASK))
    ACE_SET_BITS (condition, XtInputWriteMask);
  if (ACE_BIT_ENABLED (mask, ACE_Event_Handler::EXCEPT_MASK))
    ACE_SET_BITS (condition, XtInputExceptMask);
  if (ACE_BIT_ENABLED (mask, ACE_Event_Handler::ACCEPT_MASK))
    ACE_SET_BITS (condition, XtInputReadMask);
  if (ACE_BIT_ENABLED (mask, ACE_Event_Handler::CONNECT_MASK))
    {
      ACE_SET_BITS (condition, XtInputWriteMask); // connected, you may write
      ACE_SET_BITS (condition, XtInputReadMask);  // connected, you have data/err
    }

  if (condition != 0)
    {
      if (ids_[handle].good_id_)
	::XtRemoveInput (ids_[handle].id_);

      ids_[handle].id_ = ::XtAppAddInput (context_, 
					  handle, 
					  (XtPointer) condition, 
					  InputCallbackProc, 
					  (XtPointer) this);
      ids_[handle].good_id_ = 1;
    }
  return 0;
#endif /* ACE_WIN32 */
}

int
ACE_XtReactor::register_handler_i (const ACE_Handle_Set &handles, 
				   ACE_Event_Handler *handler, 
				   ACE_Reactor_Mask mask)
{
  return ACE_Select_Reactor::register_handler_i (handles, handler, mask);
}

int
ACE_XtReactor::remove_handler_i (ACE_HANDLE handle, 
				 ACE_Reactor_Mask mask)
{
  ACE_TRACE ("ACE_XtReactor::remove_handler_i");

#if defined (ACE_WIN32)
  ACE_NOTSUP_RETURN (-1);
#else
  ACE_DEBUG ((LM_DEBUG,  ASYS_TEXT ("---%d\n"), handle));
  int result = ACE_Select_Reactor::remove_handler_i (handle, mask);

  if (handle <= id_len_)
    { 
      if (ids_[handle].good_id_)
	::XtRemoveInput (ids_[handle].id_);
      else
	ACE_DEBUG ((LM_DEBUG,  ASYS_TEXT ("Handle id is not good %d\n"), handle));
      ids_[handle].good_id_ = 0;
    }
  else 
    ACE_DEBUG ((LM_DEBUG,  ASYS_TEXT ("Handle out of range %d\n"), handle));

  if (result == -1)
    return result;
  else
    return 0;
#endif /* ACE_WIN32 */
}

int 
ACE_XtReactor::remove_handler_i (const ACE_Handle_Set &handles, 
				 ACE_Reactor_Mask mask)
{
  return ACE_Select_Reactor::remove_handler_i (handles, 
					       mask);
}

// The following functions ensure that there is an Xt timeout for the
// first timeout in the Reactor's Timer_Queue.

void 
ACE_XtReactor::reset_timeout (void)
{
  if (timeout_)
    ::XtRemoveTimeOut (timeout_);
  timeout_ = 0;

  ACE_Time_Value *max_wait_time = 
    this->timer_queue_->calculate_timeout (0);

  if (max_wait_time)
    {
      ACE_DEBUG ((LM_DEBUG,  ASYS_TEXT ("       %ld\n"), max_wait_time->msec ()));

      timeout_ = ::XtAppAddTimeOut (context_, 
				    max_wait_time->msec (), 
				    TimerCallbackProc, 
				    (XtPointer) this);
    }
}

long
ACE_XtReactor::schedule_timer (ACE_Event_Handler *handler, 
			       const void *arg,
			       const ACE_Time_Value &delta_time, 
			       const ACE_Time_Value &interval)
{
  ACE_TRACE ("ACE_XtReactor::schedule_timer");
  ACE_MT (ACE_GUARD_RETURN (ACE_SELECT_REACTOR_MUTEX, ace_mon, this->token_, -1));

  long result = 
    ACE_Select_Reactor::schedule_timer (handler, arg, delta_time, interval);

  if (result == -1)
    return -1;
  else
    {
      this->reset_timeout ();
      return result;
    }
}

int
ACE_XtReactor::cancel_timer (ACE_Event_Handler *handler,
			     int dont_call_handle_close)
{
  ACE_TRACE ("ACE_XtReactor::cancel_timer");

  if (ACE_Select_Reactor::cancel_timer (handler, 
					dont_call_handle_close) == -1)
    return -1;
  else
    {
      this->reset_timeout ();
      return 0;
    }
}

int
ACE_XtReactor::cancel_timer (long timer_id, 
			     const void **arg,
			     int dont_call_handle_close)
{
  ACE_TRACE ("ACE_XtReactor::cancel_timer");

  if (ACE_Select_Reactor::cancel_timer (timer_id, 
					arg,
					dont_call_handle_close) == -1)
    return -1;
  else
    {
      this->reset_timeout ();
      return 0;
    }
}

#endif /* ACE_HAS_XT */