// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_COMMON_DEPRECATED_EVENT_SYS_INL_H_
#define CHROME_COMMON_DEPRECATED_EVENT_SYS_INL_H_
#pragma once

#include <map>

#include "base/basictypes.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/port.h"
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "chrome/common/deprecated/event_sys.h"

// How to use Channels:

// 0. Assume Bob is the name of the class from which you want to broadcast
//    events.
// 1. Choose an EventType. This could be an Enum or something more complicated.
// 2. Create an EventTraits class for your EventType. It must have
//    two members:
//
//      typedef x EventType;
//      static bool IsChannelShutdownEvent(const EventType& event);
//
// 3. Add an EventChannel<MyEventTraits>* instance and event_channel() const;
//    accessor to Bob.
//    Delete the channel ordinarily in Bob's destructor, or whenever you want.
// 4. To broadcast events, call bob->event_channel()->NotifyListeners(event).
// 5. Only call NotifyListeners from a single thread at a time.

// How to use Listeners/Hookups:

// 0. Assume you want a class called Lisa to listen to events from Bob.
// 1. Create an event handler method in Lisa. Its single argument should be of
//    your event type.
// 2. Add a EventListenerHookup* hookup_ member to Lisa.
// 3. Initialize the hookup by calling:
//
//      hookup_ = NewEventListenerHookup(bob->event_channel(),
//                                       this,
//                                       &Lisa::HandleEvent);
//
// 4. Delete hookup_ in Lisa's destructor, or anytime sooner to stop receiving
//    events.

// An Event Channel is a source, or broadcaster of events. Listeners subscribe
// by calling the AddListener() method. The owner of the channel calls the
// NotifyListeners() method.
//
// Don't inherit from this class. Just make an event_channel member and an
// event_channel() accessor.

// No reason why CallbackWaiters has to be templatized.
class CallbackWaiters {
 public:
  CallbackWaiters() : waiter_count_(0),
                      callback_done_(false),
                      condvar_(&mutex_) {
  }
  ~CallbackWaiters() {
    DCHECK_EQ(0, waiter_count_);
  }
  void WaitForCallbackToComplete(base::Lock* listeners_mutex) {
    {
      base::AutoLock lock(mutex_);
      waiter_count_ += 1;
      listeners_mutex->Release();
      while (!callback_done_)
        condvar_.Wait();
      waiter_count_ -= 1;
      if (0 != waiter_count_)
        return;
    }
    delete this;
  }

  void Signal() {
    base::AutoLock lock(mutex_);
    callback_done_ = true;
    condvar_.Broadcast();
  }

 protected:
  int waiter_count_;
  bool callback_done_;
  base::Lock mutex_;
  base::ConditionVariable condvar_;
};

template <typename EventTraitsType, typename NotifyLock,
          typename ScopedNotifyLocker>
class EventChannel {
 public:
  typedef EventTraitsType EventTraits;
  typedef typename EventTraits::EventType EventType;
  typedef EventListener<EventType> Listener;

 protected:
  typedef std::map<Listener*, bool> Listeners;

 public:
  // The shutdown event gets send in the EventChannel's destructor.
  explicit EventChannel(const EventType& shutdown_event)
    : current_listener_callback_(NULL),
      current_listener_callback_message_loop_(NULL),
      callback_waiters_(NULL),
      shutdown_event_(shutdown_event) {
  }

  ~EventChannel() {
    // Tell all the listeners that the channel is being deleted.
    NotifyListeners(shutdown_event_);

    // Make sure all the listeners have been disconnected. Otherwise, they
    // will try to call RemoveListener() at a later date.
#if defined(DEBUG)
    base::AutoLock lock(listeners_mutex_);
    for (typename Listeners::iterator i = listeners_.begin();
         i != listeners_.end(); ++i) {
      DCHECK(i->second) << "Listener not disconnected";
    }
#endif
  }

  // Never call this twice for the same listener.
  //
  // Thread safe.
  void AddListener(Listener* listener) {
    base::AutoLock lock(listeners_mutex_);
    typename Listeners::iterator found = listeners_.find(listener);
    if (found == listeners_.end()) {
      listeners_.insert(std::make_pair(listener,
                                       false));  // Not dead yet.
    } else {
      DCHECK(found->second) << "Attempted to add the same listener twice.";
      found->second = false;  // Not dead yet.
    }
  }

  // If listener's callback is currently executing, this method waits until the
  // callback completes before returning.
  //
  // Thread safe.
  void RemoveListener(Listener* listener) {
    bool wait = false;
    listeners_mutex_.Acquire();
    typename Listeners::iterator found = listeners_.find(listener);
    if (found != listeners_.end()) {
      found->second = true;  // Mark as dead.
      wait = (found->first == current_listener_callback_ &&
          (MessageLoop::current() != current_listener_callback_message_loop_));
    }
    if (!wait) {
      listeners_mutex_.Release();
      return;
    }
    if (NULL == callback_waiters_)
      callback_waiters_ = new CallbackWaiters;
    callback_waiters_->WaitForCallbackToComplete(&listeners_mutex_);
  }

  // Blocks until all listeners have been notified.
  //
  // NOT thread safe.  Must only be called by one thread at a time.
  void NotifyListeners(const EventType& event) {
    ScopedNotifyLocker lock_notify(notify_lock_);
    listeners_mutex_.Acquire();
    DCHECK(NULL == current_listener_callback_);
    current_listener_callback_message_loop_ = MessageLoop::current();
    typename Listeners::iterator i = listeners_.begin();
    while (i != listeners_.end()) {
      if (i->second) {  // Clean out dead listeners
        listeners_.erase(i++);
        continue;
      }
      current_listener_callback_ = i->first;
      listeners_mutex_.Release();

      i->first->HandleEvent(event);

      listeners_mutex_.Acquire();
      current_listener_callback_ = NULL;
      if (NULL != callback_waiters_) {
        callback_waiters_->Signal();
        callback_waiters_ = NULL;
      }

      ++i;
    }
    listeners_mutex_.Release();
  }

  // A map iterator remains valid until the element it points to gets removed
  // from the map, so a map is perfect for our needs.
  //
  // Map value is a bool, true means the Listener is dead.
  Listeners listeners_;
  // NULL means no callback is currently being called.
  Listener* current_listener_callback_;
  // Only valid when current_listener is not NULL.
  // The thread on which the callback is executing.
  MessageLoop* current_listener_callback_message_loop_;
  // Win32 Event that is usually NULL. Only created when another thread calls
  // Remove while in callback. Owned and closed by the thread calling Remove().
  CallbackWaiters* callback_waiters_;

  base::Lock listeners_mutex_;  // Protects all members above.
  const EventType shutdown_event_;
  NotifyLock notify_lock_;

  DISALLOW_COPY_AND_ASSIGN(EventChannel);
};

// An EventListenerHookup hooks up a method in your class to an EventChannel.
// Deleting the hookup disconnects from the EventChannel.
//
// Contains complexity of inheriting from Listener class and managing lifetimes.
//
// Create using NewEventListenerHookup() to avoid explicit template arguments.
class EventListenerHookup {
 public:
  virtual ~EventListenerHookup() { }
};

template <typename EventChannel, typename EventTraits,
          class Derived>
class EventListenerHookupImpl : public EventListenerHookup,
public EventListener<typename EventTraits::EventType> {
 public:
  explicit EventListenerHookupImpl(EventChannel* channel)
    : channel_(channel), deleted_(NULL) {
    channel->AddListener(this);
    connected_ = true;
  }

  ~EventListenerHookupImpl() {
    if (NULL != deleted_)
      *deleted_ = true;
    if (connected_)
      channel_->RemoveListener(this);
  }

  typedef typename EventTraits::EventType EventType;
  virtual void HandleEvent(const EventType& event) {
    DCHECK(connected_);
    bool deleted = false;
    deleted_ = &deleted;
    static_cast<Derived*>(this)->Callback(event);
    if (deleted)  // The callback (legally) deleted this.
      return;  // The only safe thing to do.
    deleted_ = NULL;
    if (EventTraits::IsChannelShutdownEvent(event)) {
      channel_->RemoveListener(this);
      connected_ = false;
    }
  }

 protected:
  EventChannel* const channel_;
  bool connected_;
  bool* deleted_;  // Allows the handler to delete the hookup.
};

// SimpleHookup just passes the event to the callback message.
template <typename EventChannel, typename EventTraits,
          typename CallbackObject, typename CallbackMethod>
class SimpleHookup
    : public EventListenerHookupImpl<EventChannel, EventTraits,
                                     SimpleHookup<EventChannel,
                                                  EventTraits,
                                                  CallbackObject,
                                                  CallbackMethod> > {
 public:
  SimpleHookup(EventChannel* channel, CallbackObject* cbobject,
               CallbackMethod cbmethod)
    : EventListenerHookupImpl<EventChannel, EventTraits,
                              SimpleHookup>(channel), cbobject_(cbobject),
    cbmethod_(cbmethod) { }

  typedef typename EventTraits::EventType EventType;
  void Callback(const EventType& event) {
    (cbobject_->*cbmethod_)(event);
  }
  CallbackObject* const cbobject_;
  CallbackMethod const cbmethod_;
};

// ArgHookup also passes an additional arg to the callback method.
template <typename EventChannel, typename EventTraits,
          typename CallbackObject, typename CallbackMethod,
          typename CallbackArg0>
class ArgHookup
    : public EventListenerHookupImpl<EventChannel, EventTraits,
                                     ArgHookup<EventChannel, EventTraits,
                                               CallbackObject,
                                               CallbackMethod,
                                               CallbackArg0> > {
 public:
  ArgHookup(EventChannel* channel, CallbackObject* cbobject,
            CallbackMethod cbmethod, CallbackArg0 arg0)
    : EventListenerHookupImpl<EventChannel, EventTraits,
                              ArgHookup>(channel), cbobject_(cbobject),
      cbmethod_(cbmethod), arg0_(arg0) { }

  typedef typename EventTraits::EventType EventType;
  void Callback(const EventType& event) {
    (cbobject_->*cbmethod_)(arg0_, event);
  }
  CallbackObject* const cbobject_;
  CallbackMethod const cbmethod_;
  CallbackArg0 const arg0_;
};


template <typename EventChannel, typename CallbackObject,
          typename CallbackMethod>
EventListenerHookup* NewEventListenerHookup(EventChannel* channel,
                                            CallbackObject* cbobject,
                                            CallbackMethod cbmethod) {
  return new SimpleHookup<EventChannel,
    typename EventChannel::EventTraits,
    CallbackObject, CallbackMethod>(channel, cbobject, cbmethod);
}

template <typename EventChannel, typename CallbackObject,
          typename CallbackMethod, typename CallbackArg0>
EventListenerHookup* NewEventListenerHookup(EventChannel* channel,
                                            CallbackObject* cbobject,
                                            CallbackMethod cbmethod,
                                            CallbackArg0 arg0) {
  return new ArgHookup<EventChannel,
    typename EventChannel::EventTraits,
    CallbackObject, CallbackMethod, CallbackArg0>(channel, cbobject,
                                                  cbmethod, arg0);
}

#endif  // CHROME_COMMON_DEPRECATED_EVENT_SYS_INL_H_