// Copyright (C) 2004, 2006 International Business Machines and others.
// All Rights Reserved.
// This code is published under the Eclipse Public License.
//
// Authors:  Carl Laird, Andreas Waechter     IBM    2004-08-13

#ifndef __IPOBSERVER_HPP__
#define __IPOBSERVER_HPP__

#include "IpUtils.hpp"
#include <vector>
#include <algorithm>

//#define IP_DEBUG_OBSERVER
#if IPOPT_CHECKLEVEL > 2
# define IP_DEBUG_OBSERVER
#endif
#ifdef IP_DEBUG_OBSERVER
# include "IpDebug.hpp"
#endif

namespace Ipopt
{
/* Forward declaration */
class Subject;

/** Slight Variation of the Observer Design Pattern.
 *
 *  This class implements the Observer class of the
 *  Observer Design Pattern. An Observer "Attach"es
 *  to a Subject, indicating that it would like to
 *  be notified of changes in the Subject.
 *  Any derived class wishing to receive notifications
 *  from a Subject should inherit off of
 *  Observer and overload the protected method,
 *  ReceiveNotification_(...).
 */
class IPOPTLIB_EXPORT Observer
{
public:
#ifdef IP_DEBUG_OBSERVER
   static const Index dbg_verbosity;
#endif

   /**@name Constructors/Destructors */
   ///@{
   /** Default Constructor */
   Observer()
   { }

   /** Destructor */
   inline
   virtual ~Observer();
   ///@}

   /** Enumeration specifying the type of notification */
   enum NotifyType
   {
      NT_All,
      NT_BeingDestroyed,
      NT_Changed
   };

protected:
   /** Derived classes should call this method
    * to request an "Attach" to a Subject.
    *
    * Do not call "Attach" explicitly on the Subject
    * since further processing is done here
    */
   inline
   void RequestAttach(
      NotifyType     notify_type,
      const Subject* subject
   );

   /** Derived classes should call this method
    * to request a "Detach" to a Subject.
    *
    * Do not call "Detach" explicitly on the Subject
    * since further processing is done here
    */
   inline
   void RequestDetach(
      NotifyType     notify_type,
      const Subject* subject
   );

   /** Derived classes should overload this method to
    * receive the requested notification from
    * attached Subjects
    */
   virtual void ReceiveNotification(
      NotifyType     notify_type,
      const Subject* subject
   ) = 0;

private:
   /**@name Default Compiler Generated Methods
    * (Hidden to avoid implicit creation/calling).
    *
    * These methods are not implemented and
    * we do not want the compiler to implement
    * them for us, so we declare them private
    * and do not define them. This ensures that
    * they will not be implicitly created/called.
    */
   ///@{
   /** Copy Constructor */
   Observer(
      const Observer&
   );

   /** Default Assignment Operator */
   void operator=(
      const Observer&
   );
   ///@}

   /** A list of the subjects currently being observed. */
   std::vector<const Subject*> subjects_;

   /** Private Method for Receiving Notification
    *  should only be called by the friend class
    *  Subject.
    *
    *  This method will, in turn, call
    *  the overloaded ReceiveNotification method
    *  for the derived class to process.
    */
   inline
   void ProcessNotification(
      NotifyType     notify_type,
      const Subject* subject
   );

   friend class Subject;
};

/** Slight Variation of the Observer Design Pattern (Subject part).
 *
 *  This class implements the Subject class of the Observer Design
 *  Pattern. An Observer "Attach"es to a Subject, indicating that it
 *  would like to be notified of changes in the Subject.  Any
 *  derived class that is to be observed has to inherit off the
 *  Subject base class.  If the subject needs to notify the
 *  Observer, it calls the Notify method.
 */
class IPOPTLIB_EXPORT Subject
{
public:
#ifdef IP_DEBUG_OBSERVER
   static const Index dbg_verbosity;
#endif

   /**@name Constructors/Destructors */
   ///@{
   /** Default Constructor */
   Subject()
   { }

   /** Destructor */
   inline
   virtual ~Subject();
   ///@}

   /**@name Methods to Add and Remove Observers.
    *
    *  Currently, the notify_type flags are not used,
    *  and Observers are attached in general and will
    *  receive all notifications (of the type requested
    *  and possibly of types not requested). It is
    *  up to the observer to ignore the types they
    *  are not interested in. The NotifyType in the
    *  parameter list is so a more efficient mechanism
    *  depending on type could be implemented later if
    *  necessary.
    */
   ///@{
   /** Attach the specified observer
    *  (i.e., begin receiving notifications). */
   inline
   void AttachObserver(
      Observer::NotifyType notify_type,
      Observer*            observer
   ) const;

   /** Detach the specified observer
    *  (i.e., no longer receive notifications). */
   inline
   void DetachObserver(
      Observer::NotifyType notify_type,
      Observer*            observer
   ) const;
   ///@}

protected:

   inline
   void Notify(
      Observer::NotifyType notify_type
   ) const;

private:
   /**@name Default Compiler Generated Methods
    * (Hidden to avoid implicit creation/calling).
    *
    * These methods are not implemented and
    * we do not want the compiler to implement
    * them for us, so we declare them private
    * and do not define them. This ensures that
    * they will not be implicitly created/called.
    */
   ///@{
   /** Copy Constructor */
   Subject(
      const Subject&
   );

   /** Default Assignment Operator */
   void operator=(
      const Subject&
   );
   ///@}

   mutable std::vector<Observer*> observers_;
};

/* inline methods */
inline Observer::~Observer()
{
#ifdef IP_DEBUG_OBSERVER
   DBG_START_METH("Observer::~Observer", dbg_verbosity);
   if (DBG_VERBOSITY() >= 1)
   {
      for (Index i = 0; i < (Index)subjects_.size(); i++)
      {
         DBG_PRINT((1, "subjects_[%d] = 0x%x\n", i, subjects_[i]));
      }
   }
#endif
   // Detach all subjects
   for( Int i = (Int) (subjects_.size() - 1); i >= 0; i-- )
   {
#ifdef IP_DEBUG_OBSERVER
      DBG_PRINT((1, "About to detach subjects_[%d] = 0x%x\n", i, subjects_[i]));
#endif

      RequestDetach(NT_All, subjects_[i]);
   }
}

inline
void Observer::RequestAttach(
   NotifyType     notify_type,
   const Subject* subject
)
{
#ifdef IP_DEBUG_OBSERVER
   DBG_START_METH("Observer::RequestAttach", dbg_verbosity);

   // Add the subject to the list if it does not already exist
   std::vector<const Subject*>::iterator attached_subject;
   attached_subject = std::find(subjects_.begin(), subjects_.end(), subject);
   DBG_ASSERT(attached_subject == subjects_.end());
   DBG_ASSERT(subject);
#endif

   // add the subject to the list
   subjects_.push_back(subject);
   // Attach the observer to the subject
   subject->AttachObserver(notify_type, this);
}

inline
void Observer::RequestDetach(
   NotifyType     notify_type,
   const Subject* subject
)
{
#ifdef IP_DEBUG_OBSERVER
   DBG_START_METH("Observer::RequestDetach", dbg_verbosity);
   DBG_PRINT((1, "Requesting detach of subject: 0x%x\n", subject));
   DBG_ASSERT(subject);
#endif

   if( subject )
   {
      std::vector<const Subject*>::iterator attached_subject;
      attached_subject = std::find(subjects_.begin(), subjects_.end(), subject);
#ifdef IP_DEBUG_OBSERVER

      DBG_ASSERT(attached_subject != subjects_.end());
#endif

      if( attached_subject != subjects_.end() )
      {
#ifdef IP_DEBUG_OBSERVER
         DBG_PRINT((1, "Removing subject: 0x%x from the list\n", subject));
#endif

         subjects_.erase(attached_subject);
      }

      // Detach the observer from the subject
      subject->DetachObserver(notify_type, this);
   }
}

inline
void Observer::ProcessNotification(
   NotifyType     notify_type,
   const Subject* subject
)
{
#ifdef IP_DEBUG_OBSERVER
   DBG_START_METH("Observer::ProcessNotification", dbg_verbosity);
   DBG_ASSERT(subject);
#endif

   if( subject )
   {
      std::vector<const Subject*>::iterator attached_subject;
      attached_subject = std::find(subjects_.begin(), subjects_.end(), subject);

      // We must be processing a notification for a
      // subject that was previously attached.
#ifdef IP_DEBUG_OBSERVER

      DBG_ASSERT(attached_subject != subjects_.end());
#endif

      this->ReceiveNotification(notify_type, subject);

      if( notify_type == NT_BeingDestroyed )
      {
         // the subject is going away, remove it from our list
         subjects_.erase(attached_subject);
      }
   }
}

inline Subject::~Subject()
{
#ifdef IP_DEBUG_OBSERVER
   DBG_START_METH("Subject::~Subject", dbg_verbosity);
#endif

   std::vector<Observer*>::iterator iter;
   for( iter = observers_.begin(); iter != observers_.end(); iter++ )
   {
      (*iter)->ProcessNotification(Observer::NT_BeingDestroyed, this);
   }
}

inline
void Subject::AttachObserver(
   Observer::NotifyType /*notify_type*/,
   Observer*            observer
) const
{
#ifdef IP_DEBUG_OBSERVER
   DBG_START_METH("Subject::AttachObserver", dbg_verbosity);
   // current implementation notifies all observers of everything
   // they must filter the notifications that they are not interested
   // in (i.e. a hub, not a router)
   DBG_ASSERT(observer);

   std::vector<Observer*>::iterator attached_observer;
   attached_observer = std::find(observers_.begin(), observers_.end(), observer);
   DBG_ASSERT(attached_observer == observers_.end());

   DBG_ASSERT(observer);
#endif

   observers_.push_back(observer);
}

inline
void Subject::DetachObserver(
   Observer::NotifyType /*notify_type*/,
   Observer*            observer
) const
{
#ifdef IP_DEBUG_OBSERVER
   DBG_START_METH("Subject::DetachObserver", dbg_verbosity);
   DBG_ASSERT(observer);
#endif

   if( observer )
   {
      std::vector<Observer*>::iterator attached_observer;
      attached_observer = std::find(observers_.begin(), observers_.end(), observer);
#ifdef IP_DEBUG_OBSERVER

      DBG_ASSERT(attached_observer != observers_.end());
#endif

      if( attached_observer != observers_.end() )
      {
         observers_.erase(attached_observer);
      }
   }
}

inline
void Subject::Notify(
   Observer::NotifyType notify_type
) const
{
#ifdef IP_DEBUG_OBSERVER
   DBG_START_METH("Subject::Notify", dbg_verbosity);
#endif

   std::vector<Observer*>::iterator iter;
   for( iter = observers_.begin(); iter != observers_.end(); iter++ )
   {
      (*iter)->ProcessNotification(notify_type, this);
   }
}

} // namespace Ipopt

#endif
