/*
 * The MailBoxImpl class implements the MailBox interface defined in Mail.idl.  This
 * class acts as a user's inbox and outbox.  The user can use the MailTool utility to
 * access their instance of MailBoxImpl, and use this instance to send "mail" to and
 * receive "mail" from users over the OpenFusion Notification Service.
 */

#include "MailBoxImpl.h"

/*
 * @func Constructs a MailBoxImpl object
 * @parm | orbVar | a reference to the ORB
 * @parm | pUtil | a reference to the channel utility
 * @parm | name | the name of the mail box
 * @rdesc LM_ERROR message and exit on error
 */
MailBoxImpl::MailBoxImpl (CORBA::ORB_ptr orb, ChannelUtil &util, const char *name)
  : orbVar_ (CORBA::ORB::_duplicate (orb))
  , util_ (util)
  , username_ (name)
  , poaVar_ (0)
  , oidVar_ (0)
  , inboxProxyVar_ (0)
  , outboxProxyVar_ (0)
{
  // If the IOR file exists, then the mailbox already exists.
  ACE_CString iorFileName = username_ + ACE_TEXT (".ior");
  if (util_.fileExists (iorFileName.c_str ()))
    {
      throw Mail::AccountExists ();
    }

  // Obtain a reference to a transient POA.
  ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("Obtaining default transient POA\n")));
  poaVar_ = util_.getDefaultTransientPoa ();

  // Create the mail box CORBA object.
  oidVar_ = poaVar_->activate_object (this);
  CORBA::Object_var thisRefVar = poaVar_->id_to_reference (oidVar_.in ());

  // Write out the mail box's IOR.
  util_.writeIOR (iorFileName.c_str (), thisRefVar.in ());
}

void
MailBoxImpl::init (void)
{
  try
    {
      CORBA::Object_var thisRefVar = poaVar_->id_to_reference (oidVar_.in ());

      // Obtain a reference to the channel.
      CosNotifyChannelAdmin::EventChannel_var channelVar = util_.getChannel ();

      // Structured pull consumer: obtain and connect to a structured proxy pull supplier.
      CosNotifyChannelAdmin::ClientType type = CosNotifyChannelAdmin::STRUCTURED_EVENT;
      CosNotifyChannelAdmin::ProxyID pid;

      CosNotifyChannelAdmin::ConsumerAdmin_var consAdminVar =
        channelVar->default_consumer_admin ();
      CosNotifyChannelAdmin::ProxySupplier_var psVar =
        consAdminVar->obtain_notification_pull_supplier (type, pid);
      inboxProxyVar_ =
        CosNotifyChannelAdmin::StructuredProxyPullSupplier::_narrow (psVar.in ());

      CosNotifyComm::StructuredPullConsumer_var consumerVar =
        CosNotifyComm::StructuredPullConsumer::_narrow (thisRefVar.in ());
      inboxProxyVar_->connect_structured_pull_consumer (consumerVar.in ());

      // Structured push supplier: obtain and connect to a structured proxy push consumer.
      CosNotifyChannelAdmin::SupplierAdmin_var suppAdminVar =
        channelVar->default_supplier_admin ();
      CosNotifyChannelAdmin::ProxyConsumer_var pcVar =
        suppAdminVar->obtain_notification_push_consumer (type, pid);
      outboxProxyVar_ =
        CosNotifyChannelAdmin::StructuredProxyPushConsumer::_narrow (pcVar.in ());

      CosNotifyComm::StructuredPushSupplier_var supplierVar =
        CosNotifyComm::StructuredPushSupplier::_narrow (thisRefVar.in ());
      outboxProxyVar_->connect_structured_push_supplier (supplierVar.in ());

      // Filter the "inbox" so that only events with the username in the recipients are received.
      CosNotifyFilter::FilterFactory_var factoryVar = channelVar->default_filter_factory ();

      // Create the filter.
      CosNotifyFilter::Filter_var filterVar =
        factoryVar->create_filter (ACE_TEXT ("EXTENDED_TCL"));

      // Create the constraint expression.
      ACE_CString constraint = ACE_TEXT ("\'") + username_ + ACE_TEXT ("\' in $recipients");

      // Create the constraint and add it to the filter.
      CosNotification::EventType etype;
      etype.domain_name = CORBA::string_dup (ACE_TEXT ("Mail"));
      etype.type_name = CORBA::string_dup (ACE_TEXT ("Message"));

      CosNotification::EventTypeSeq evts;
      evts.length (1);
      evts[0] = etype;

      CosNotifyFilter::ConstraintExp exp;
      exp.event_types = evts;
      exp.constraint_expr = CORBA::string_dup (constraint.c_str ());
      CosNotifyFilter::ConstraintExpSeq exps;
      exps.length (1);
      exps[0] = exp;

      CosNotifyFilter::ConstraintInfoSeq_var tmp =
        filterVar->add_constraints (exps);

      // Add the filter to the proxy.
      inboxProxyVar_->add_filter (filterVar.in ());
    }
  catch (const CORBA::Exception &ex)
    {
      poaVar_->deactivate_object (oidVar_.in ());
      ex._tao_print_exception (ACE_TEXT ("ERROR: Exception in MailBoxImpl.cpp:"));
      throw;
    }
}

/*
 * @func Destroy this object
 */
MailBoxImpl::~MailBoxImpl (void)
{
  ACE_CString iorFileName = username_ + ACE_TEXT (".ior");
  util_.deleteFile (iorFileName.c_str ());
}

/*
 * @func Sends a message
 * @parm message | the message to send
 * @parm to | string sequence of recipients
 * @rdesc LM_ERROR message on error
 */
void
MailBoxImpl::sendMessage (const char *message, const Mail::StringSeq &to)
{
  ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("Send called\n")));

  // Header.
  CosNotification::EventType type;
  type.domain_name = CORBA::string_dup (ACE_TEXT ("Mail"));
  type.type_name = CORBA::string_dup (ACE_TEXT ("Message"));

  CosNotification::FixedEventHeader fixedHeader;
  fixedHeader.event_type = type;
  fixedHeader.event_name = CORBA::string_dup (ACE_TEXT (""));

  CosNotification::PropertySeq variableHeader;
  variableHeader.length (0);
  CosNotification::EventHeader header;
  header.fixed_header = fixedHeader;
  header.variable_header = variableHeader;

  // Filterable data.
  // 1: list of recipients.  This allows filtering to be performed, so that
  //    only the requested recipients receive a given message.
  // 2: the username of the sending mailbox.
  CosNotification::PropertySeq filterable;
  filterable.length (2);
  filterable[0].name = CORBA::string_dup (ACE_TEXT ("recipients"));
  filterable[0].value <<= to;
  filterable[1].name = CORBA::string_dup (ACE_TEXT ("sender"));
  filterable[1].value <<= CORBA::String_var (CORBA::string_dup (username_.c_str ()));

  CosNotification::StructuredEvent event;
  event.header = header;
  event.filterable_data = filterable;
  event.remainder_of_body <<= CORBA::String_var (CORBA::string_dup (message));

  // Push the event
  try
    {
      outboxProxyVar_->push_structured_event (event);

      ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("Successfully sent event\n")));
    }
  catch (const CosEventComm::Disconnected &)
    {
      ACE_ERROR ((LM_ERROR,
                  ACE_TEXT ("Failed to send event: user is disconnected from proxy\n")));
      throw;
    }
}

/*
 * @func Receives a message, if one is waiting for this user
 * @rdesc true or false indicating if a message was received: if true, sender and message
 *        are populated with the appropriate information
 */
CORBA::Boolean
MailBoxImpl::receiveMessage (CORBA::String_out sender, CORBA::String_out message)
{
  ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("Receive called\n")));

  // Try to pull an event from the inbox proxy.
  CosNotification::StructuredEvent_var event;
  try
    {
      CORBA::Boolean hasEvent = 0;

      event = inboxProxyVar_->try_pull_structured_event (hasEvent);

      // Return if there was no event to be pulled.
      if (!hasEvent)
        {
          return hasEvent;
        }
    }
  catch (const CosEventComm::Disconnected &)
    {
      ACE_ERROR ((LM_ERROR,
                  ACE_TEXT ("Could not receive events: mailbox is not connected to the proxy\n")));
      return 0;
    }

  // Extract the sender information.
  const char *val = 0;
  CosNotification::PropertySeq filt = event->filterable_data;
  for (CORBA::ULong i = 0; i < filt.length () ;++i)
    {
      if (ACE_OS::strcmp (filt[i].name, ACE_TEXT ("sender")) == 0)
        {
          filt[i].value >>= val;
          sender = CORBA::string_dup (val);
          break;
        }
    }

  // Extract the message information.
  event->remainder_of_body >>= val;
  message = CORBA::string_dup (val);

  return 1;
}

/*
 * @func Destroys this mailbox
 * @rdesc LM_ERROR message if an exception occurs
 */
void
MailBoxImpl::destroy (void)
{
  // Disconnect from the proxies.
  ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("Disconnecting the mailbox from the proxies\n")));
  inboxProxyVar_->disconnect_structured_pull_supplier ();
  outboxProxyVar_->disconnect_structured_push_consumer ();

  // Delete the IOR file for this mailbox.
  ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("Deleting the mailbox IOR file\n")));
  ACE_CString filename = username_ + ACE_TEXT (".ior");
  util_.deleteFile (filename.c_str ());

  // Destroy the CORBA object for this mailbox.
  ACE_DEBUG ((LM_DEBUG, ACE_TEXT ("Deactivating the mailbox's CORBA object\n")));
  poaVar_->deactivate_object (oidVar_.in ());

  ACE_DEBUG ((LM_DEBUG,
              ACE_TEXT ("Successfully deleted account for %s\n"),
              username_.c_str ()));
}

void
MailBoxImpl::offer_change (const CosNotification::EventTypeSeq &/*added*/,
                           const CosNotification::EventTypeSeq &/*removed*/)
{
}

void
MailBoxImpl::subscription_change (const CosNotification::EventTypeSeq &/*added*/,
                                  const CosNotification::EventTypeSeq &/*removed*/)
{
}

void
MailBoxImpl::disconnect_structured_pull_consumer (void)
{
}

void
MailBoxImpl::disconnect_structured_push_supplier (void)
{
}
