/*
  mars_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/auto_buffer.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 mars_boost {} namespace boost = mars_boost; namespace mars_boost
{
  namespace signals2
  {
    inline void null_deleter(const void*) {}
    namespace detail
    {
      // This lock maintains a list of shared_ptr<void>
      // which will be destroyed only after the lock
      // has released its mutex.  Used to garbage
      // collect disconnected slots
      template<typename Mutex>
      class garbage_collecting_lock: public noncopyable
      {
      public:
        garbage_collecting_lock(Mutex &m):
          lock(m)
        {}
        void add_trash(const shared_ptr<void> &piece_of_trash)
        {
          garbage.push_back(piece_of_trash);
        }
      private:
        // garbage must be declared before lock
        // to insure it is destroyed after lock is
        // destroyed.
        auto_buffer<shared_ptr<void>, store_n_objects<10> > garbage;
        unique_lock<Mutex> lock;
      };
      
      class connection_body_base
      {
      public:
        connection_body_base():
          _connected(true), m_slot_refcount(1)
        {
        }
        virtual ~connection_body_base() {}
        void disconnect()
        {
          garbage_collecting_lock<connection_body_base> local_lock(*this);
          nolock_disconnect(local_lock);
        }
        template<typename Mutex>
        void nolock_disconnect(garbage_collecting_lock<Mutex> &lock_arg) const
        {
          if(_connected)
          {
            _connected = false;
            dec_slot_refcount(lock_arg);
          }
        }
        virtual bool connected() const = 0;
        shared_ptr<void> get_blocker()
        {
          unique_lock<connection_body_base> local_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;

        // Slot refcount should be incremented while
        // a signal invocation is using the slot, in order
        // to prevent slot from being destroyed mid-invocation.
        // garbage_collecting_lock parameter enforces 
        // the existance of a lock before this
        // method is called
        template<typename Mutex>
        void inc_slot_refcount(const garbage_collecting_lock<Mutex> &)
        {
          BOOST_ASSERT(m_slot_refcount != 0);
          ++m_slot_refcount;
        }
        // if slot refcount decrements to zero due to this call, 
        // it puts a
        // shared_ptr to the slot in the garbage collecting lock,
        // which will destroy the slot only after it unlocks.
        template<typename Mutex>
        void dec_slot_refcount(garbage_collecting_lock<Mutex> &lock_arg) const
        {
          BOOST_ASSERT(m_slot_refcount != 0);
          if(--m_slot_refcount == 0)
          {
            lock_arg.add_trash(release_slot());
          }
        }

      protected:
        virtual shared_ptr<void> release_slot() const = 0;

        weak_ptr<void> _weak_blocker;
      private:
        mutable bool _connected;
        mutable unsigned m_slot_refcount;
      };

      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, const mars_boost::shared_ptr<mutex_type> &signal_mutex):
          m_slot(new SlotType(slot_in)), _mutex(signal_mutex)
        {
        }
        virtual ~connection_body() {}
        virtual bool connected() const
        {
          garbage_collecting_lock<mutex_type> local_lock(*_mutex);
          nolock_grab_tracked_objects(local_lock, 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;}
        template<typename M>
        void disconnect_expired_slot(garbage_collecting_lock<M> &lock_arg)
        {
          if(!m_slot) return;
          bool expired = slot().expired();
          if(expired == true)
          {
            nolock_disconnect(lock_arg);
          }
        }
        template<typename M, typename OutputIterator>
        void nolock_grab_tracked_objects(garbage_collecting_lock<M> &lock_arg,
          OutputIterator inserter) const
        {
          if(!m_slot) return;
          slot_base::tracked_container_type::const_iterator it;
          for(it = slot().tracked_objects().begin();
            it != slot().tracked_objects().end();
            ++it)
          {
            void_shared_ptr_variant locked_object
            (
              apply_visitor
              (
                detail::lock_weak_ptr_visitor(),
                *it
              )
            );
            if(apply_visitor(detail::expired_weak_ptr_visitor(), *it))
            {
              nolock_disconnect(lock_arg);
              return;
            }
            *inserter++ = locked_object;
          }
        }
        // expose Lockable concept of mutex
        virtual void lock()
        {
          _mutex->lock();
        }
        virtual void unlock()
        {
          _mutex->unlock();
        }
        SlotType &slot()
        {
          return *m_slot;
        }
        const SlotType &slot() const
        {
          return *m_slot;
        }
      protected:
        virtual shared_ptr<void> release_slot() const
        {
          
          shared_ptr<void> released_slot = m_slot;
          m_slot.reset();
          return released_slot;
        }
      private:
        mutable mars_boost::shared_ptr<SlotType> m_slot;
        const mars_boost::shared_ptr<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 mars_boost::weak_ptr<detail::connection_body_base> &connectionBody):
        _weak_connection_body(connectionBody)
      {}
      
      // move support
#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
      connection(connection && other): _weak_connection_body(std::move(other._weak_connection_body))
      {
        // make sure other is reset, in case it is a scoped_connection (so it
        // won't disconnect on destruction after being moved away from).
        other._weak_connection_body.reset();
      }
      connection & operator=(connection && other)
      {
        if(&other == this) return *this;
        _weak_connection_body = std::move(other._weak_connection_body);
        // make sure other is reset, in case it is a scoped_connection (so it
        // won't disconnect on destruction after being moved away from).
        other._weak_connection_body.reset();
        return *this;
      }
#endif // !defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
      connection & operator=(const connection & other)
      {
        if(&other == this) return *this;
        _weak_connection_body = other._weak_connection_body;
        return *this;
      }

      ~connection() {}
      void disconnect() const
      {
        mars_boost::shared_ptr<detail::connection_body_base> connectionBody(_weak_connection_body.lock());
        if(connectionBody == 0) return;
        connectionBody->disconnect();
      }
      bool connected() const
      {
        mars_boost::shared_ptr<detail::connection_body_base> connectionBody(_weak_connection_body.lock());
        if(connectionBody == 0) return false;
        return connectionBody->connected();
      }
      bool blocked() const
      {
        mars_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
      {
        mars_boost::shared_ptr<detail::connection_body_base> connectionBody(_weak_connection_body.lock());
        mars_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
      {
        mars_boost::shared_ptr<detail::connection_body_base> connectionBody(_weak_connection_body.lock());
        mars_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:

      mars_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;
      }

      // move support
#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
      scoped_connection(scoped_connection && other): connection(std::move(other))
      {
      }
      scoped_connection(connection && other): connection(std::move(other))
      {
      }
      scoped_connection & operator=(scoped_connection && other)
      {
        if(&other == this) return *this;
        disconnect();
        connection::operator=(std::move(other));
        return *this;
      }
      scoped_connection & operator=(connection && other)
      {
        if(&other == this) return *this;
        disconnect();
        connection::operator=(std::move(other));
        return *this;
      }
#endif // !defined(BOOST_NO_CXX11_RVALUE_REFERENCES)

      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
