/*
  boost::signals2::connection provides a handle to a signal/slot connection.

  Author: Frank Mori Hess <fmhess@users.sourceforge.net>
  Begin: 2007-01-23
*/
// Copyright Frank Mori Hess 2007-2008.
// Distributed under the Boost Software License, Version
// 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// See http://www.boost.org/libs/signals2 for library home page.

#ifndef BOOST_SIGNALS2_CONNECTION_HPP
#define BOOST_SIGNALS2_CONNECTION_HPP

#include <boost/function.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/signals2/detail/null_output_iterator.hpp>
#include <boost/signals2/detail/unique_lock.hpp>
#include <boost/signals2/slot.hpp>
#include <boost/weak_ptr.hpp>

namespace boost
{
  namespace signals2
  {
    extern inline void null_deleter(const void*) {}
    namespace detail
    {
      class connection_body_base
      {
      public:
        connection_body_base():
          _connected(true)
        {
        }
        virtual ~connection_body_base() {}
        void disconnect()
        {
          unique_lock<connection_body_base> lock(*this);
          nolock_disconnect();
        }
        void nolock_disconnect()
        {
          _connected = false;
        }
        virtual bool connected() const = 0;
        shared_ptr<void> get_blocker()
        {
          unique_lock<connection_body_base> lock(*this);
          shared_ptr<void> blocker = _weak_blocker.lock();
          if(blocker == shared_ptr<void>())
          {
            blocker.reset(this, &null_deleter);
            _weak_blocker = blocker;
          }
          return blocker;
        }
        bool blocked() const
        {
          return !_weak_blocker.expired();
        }
        bool nolock_nograb_blocked() const
        {
          return nolock_nograb_connected() == false || blocked();
        }
        bool nolock_nograb_connected() const {return _connected;}
        // expose part of Lockable concept of mutex
        virtual void lock() = 0;
        virtual void unlock() = 0;

      protected:

        mutable bool _connected;
        weak_ptr<void> _weak_blocker;
      };

      template<typename GroupKey, typename SlotType, typename Mutex>
      class connection_body: public connection_body_base
      {
      public:
        typedef Mutex mutex_type;
        connection_body(const SlotType &slot_in):
          slot(slot_in)
        {
        }
        virtual ~connection_body() {}
        virtual bool connected() const
        {
          unique_lock<mutex_type> lock(_mutex);
          nolock_grab_tracked_objects(detail::null_output_iterator());
          return nolock_nograb_connected();
        }
        const GroupKey& group_key() const {return _group_key;}
        void set_group_key(const GroupKey &key) {_group_key = key;}
        bool nolock_slot_expired() const
        {
          bool expired = slot.expired();
          if(expired == true)
          {
            _connected = false;
          }
          return expired;
        }
        template<typename OutputIterator>
          void nolock_grab_tracked_objects(OutputIterator inserter) const
        {
            slot_base::tracked_container_type::const_iterator it;
            for(it = slot.tracked_objects().begin();
              it != slot.tracked_objects().end();
              ++it)
            {
              boost::shared_ptr<void> locked_object = it->lock();
              boost::shared_ptr<void> empty;
              if(!(empty < locked_object) && !(locked_object < empty))
              {
                _connected = false;
                return;
              }
              *inserter++ = locked_object;
          }
        }
        // expose Lockable concept of mutex
        virtual void lock()
        {
          _mutex.lock();
        }
        virtual void unlock()
        {
          _mutex.unlock();
        }
        SlotType slot;
      private:
        mutable mutex_type _mutex;
        GroupKey _group_key;
      };
    }

    class shared_connection_block;

    class connection
    {
    public:
      friend class shared_connection_block;

      connection() {}
      connection(const connection &other): _weak_connection_body(other._weak_connection_body)
      {}
      connection(const boost::weak_ptr<detail::connection_body_base> &connectionBody):
        _weak_connection_body(connectionBody)
      {}
      ~connection() {}
      void disconnect() const
      {
        boost::shared_ptr<detail::connection_body_base> connectionBody(_weak_connection_body.lock());
        if(connectionBody == 0) return;
        connectionBody->disconnect();
      }
      bool connected() const
      {
        boost::shared_ptr<detail::connection_body_base> connectionBody(_weak_connection_body.lock());
        if(connectionBody == 0) return false;
        return connectionBody->connected();
      }
      bool blocked() const
      {
        boost::shared_ptr<detail::connection_body_base> connectionBody(_weak_connection_body.lock());
        if(connectionBody == 0) return true;
        return connectionBody->blocked();
      }
      bool operator==(const connection& other) const
      {
        boost::shared_ptr<detail::connection_body_base> connectionBody(_weak_connection_body.lock());
        boost::shared_ptr<detail::connection_body_base> otherConnectionBody(other._weak_connection_body.lock());
        return connectionBody == otherConnectionBody;
      }
      bool operator!=(const connection& other) const
      {
        return !(*this == other);
      }
      bool operator<(const connection& other) const
      {
        boost::shared_ptr<detail::connection_body_base> connectionBody(_weak_connection_body.lock());
        boost::shared_ptr<detail::connection_body_base> otherConnectionBody(other._weak_connection_body.lock());
        return connectionBody < otherConnectionBody;
      }
      void swap(connection &other)
      {
        using std::swap;
        swap(_weak_connection_body, other._weak_connection_body);
      }
    protected:

      boost::weak_ptr<detail::connection_body_base> _weak_connection_body;
    };
    inline void swap(connection &conn1, connection &conn2)
    {
      conn1.swap(conn2);
    }

    class scoped_connection: public connection
    {
    public:
      scoped_connection() {}
      scoped_connection(const connection &other):
        connection(other)
      {}
      ~scoped_connection()
      {
        disconnect();
      }
      scoped_connection& operator=(const connection &rhs)
      {
        disconnect();
        connection::operator=(rhs);
        return *this;
      }
      connection release()
      {
        connection conn(_weak_connection_body);
        _weak_connection_body.reset();
        return conn;
      }
    private:
      scoped_connection(const scoped_connection &other);
      scoped_connection& operator=(const scoped_connection &rhs);
    };
    // Sun 5.9 compiler doesn't find the swap for base connection class when
    // arguments are scoped_connection, so we provide this explicitly.
    inline void swap(scoped_connection &conn1, scoped_connection &conn2)
    {
      conn1.swap(conn2);
    }
  }
}

#endif  // BOOST_SIGNALS2_CONNECTION_HPP
