#ifndef _CALLBACK_REGISTRY_TABLE_H_
#define _CALLBACK_REGISTRY_TABLE_H_

#include "callback_registry.h"
#include "debug.h"
#include "later2.h"
#include "threadutils.h"
#include <map>
#include <memory>
#include <vector>

class CallbackRegistryTable;
extern CallbackRegistryTable callbackRegistryTable;

// ============================================================================
// Callback registry table
// ============================================================================
//
// This class is used for accessing a registry by ID. The CallbackRegistries
// also have a tree structure. The global loop/registry is the root. However,
// there can also be trees that are independent of the global loop, if a loop
// is created without a parent.
//
// The operations on this class are thread-safe, because they might be used to
// from another thread.
//
class CallbackRegistryTable {

  // Basically a struct that keeps track of a registry and whether or an R loop
  // object references it.
  class RegistryHandle {
  public:
    RegistryHandle(std::shared_ptr<CallbackRegistry> registry,
                   bool r_ref_exists)
        : registry(registry), r_ref_exists(r_ref_exists) {};
    // Need to declare a copy constructor. Needed because pre-C++11 std::map
    // doesn't have an .emplace() method.
    RegistryHandle() = default;

    std::shared_ptr<CallbackRegistry> registry;
    bool r_ref_exists;
  };

public:
  CallbackRegistryTable()
      : mutex(tct_mtx_plain | tct_mtx_recursive), condvar(mutex) {}

  bool exists(int id) {
    Guard guard(&mutex);
    return (registries.find(id) != registries.end());
  }

  // Create a new CallbackRegistry. If parent_id is -1, then there is no parent.
  void create(int id, int parent_id) {
    ASSERT_MAIN_THREAD()
    Guard guard(&mutex);

    if (exists(id)) {
      cpp4r::stop("Can't create event loop %d because it already exists.", id);
    }

    // Each new registry is passed our mutex and condvar. These serve as a
    // shared lock across all CallbackRegistries and this
    // CallbackRegistryTable. If each registry had a separate lock, some
    // routines would recursively acquire a lock downward in the
    // CallbackRegistry tree, and some recursively acquire a lock upward;
    // without a shared lock, if these things happen at the same time from
    // different threads, it could deadlock.
    std::shared_ptr<CallbackRegistry> registry =
        std::make_shared<CallbackRegistry>(id, &mutex, &condvar);

    if (parent_id != -1) {
      std::shared_ptr<CallbackRegistry> parent = getRegistry(parent_id);
      if (parent == nullptr) {
        cpp4r::stop("Can't create registry. Parent with id %d does not exist.",
                    parent_id);
      }
      registry->parent = parent;
      parent->children.push_back(registry);
    }

    // Would be better to use .emplace() to avoid copy-constructor, but that
    // requires C++11.
    registries[id] = RegistryHandle(registry, true);
  }

  // Returns a shared_ptr to the registry. If the registry is not present in
  // the table, or if the target CallbackRegistry has already been deleted,
  // then the shared_ptr is empty.
  std::shared_ptr<CallbackRegistry> getRegistry(int id) {
    Guard guard(&mutex);
    if (!exists(id)) {
      return std::shared_ptr<CallbackRegistry>();
    }
    // If the target of the shared_ptr has been deleted, then this is an empty
    // shared_ptr.
    return registries[id].registry;
  }

  uint64_t scheduleCallback(void (*func)(void *), void *data, double delaySecs,
                            int loop_id) {
    // This method can be called from any thread
    Guard guard(&mutex);

    std::shared_ptr<CallbackRegistry> registry = getRegistry(loop_id);
    if (registry == nullptr) {
      return 0;
    }
    return doExecLater(registry, func, data, delaySecs, true);
  }

  // This is called when the R loop handle referring to a CallbackRegistry is
  // destroyed. Returns true if the CallbackRegistry exists and this function
  // has not previously been called on it; false otherwise.
  bool notifyRRefDeleted(int id) {
    ASSERT_MAIN_THREAD()
    Guard guard(&mutex);

    if (!exists(id)) {
      return false;
    }

    if (registries[id].r_ref_exists) {
      registries[id].r_ref_exists = false;
      this->pruneRegistries();
      return true;
    } else {
      return false;
    }
  }

  // Iterate over all registries, and remove a registry when:
  // * If the loop has a parent:
  //   * There's no R loop object referring to it, AND the registry is empty.
  // * If the loop does not have a parent:
  //   * There's no R loop object referring to it. (Dont' need the registry to
  //     be empty, because if there's no parent and no R reference to the loop,
  //     there is no way to execute callbacks in the registry.)
  void pruneRegistries() {
    ASSERT_MAIN_THREAD()
    Guard guard(&mutex);

    std::map<int, RegistryHandle>::iterator it = registries.begin();

    // Iterate over all registries. Remove under the following conditions:
    // * There are no more R loop handle references to the registry, AND
    // * The registry is empty, OR the registry has no parent.
    // This logic is equivalent to the logic describing the function, just in
    // a different order.
    //
    // std::map are sorted, and children always have a larger ID than their
    // parents. Because of this, if there is a case where initially a child does
    // not have any R refs, but the parent does have an R ref, then the parent's
    // R ref is deleted, both will removed in a single pass.
    while (it != registries.end()) {
      if (!it->second.r_ref_exists &&
          (it->second.registry->empty() ||
           it->second.registry->parent == nullptr)) {
        // Need to increment iterator before removing the registry; otherwise
        // the iterator will be invalid.
        int id = it->first;
        it++;
        remove(id);
      } else {
        it++;
      }
    }
  }

  // Remove a callback registry from the table
  bool remove(int id) {
    // Removal is always called from the main thread.
    ASSERT_MAIN_THREAD()
    Guard guard(&mutex);

    std::shared_ptr<CallbackRegistry> registry = getRegistry(id);
    if (registry == nullptr) {
      return false;
    }

    // Deregister this object from its parent. Do it here instead of the in the
    // CallbackRegistry destructor, for two reasons: One is that we can be 100%
    // sure that the deregistration happens right now (it's possible that the
    // object's destructor won't run yet, because someone else has a shared_ptr
    // to it). Second, we can't reliably use a shared_ptr to the object from
    // inside its destructor; we need to some pointer comparison, but by the
    // time the destructor runs, you can't run shared_from_this() in the object,
    // because there are no more shared_ptrs to it.
    std::shared_ptr<CallbackRegistry> parent = registry->parent;
    if (parent != nullptr) {
      // Remove this registry from the parent's list of children.
      for (std::vector<std::shared_ptr<CallbackRegistry>>::iterator it =
               parent->children.begin();
           it != parent->children.end();) {
        if (*it == registry) {
          parent->children.erase(it);
          break;
        } else {
          ++it;
        }
      }
    }

    // Tell the children that they no longer have a parent.
    for (std::vector<std::shared_ptr<CallbackRegistry>>::iterator it =
             registry->children.begin();
         it != registry->children.end(); ++it) {
      (*it)->parent.reset();
    }

    registries.erase(id);

    return true;
  }

private:
  std::map<int, RegistryHandle> registries;
  Mutex mutex;
  ConditionVariable condvar;
};

#endif
