#ifndef CONDITION_HPP
#define CONDITION_HPP

#include <condition_variable>
#include <list>
#include <memory>
#include <mutex>

class Condition;
using ConditionPtr = std::shared_ptr<Condition>;

class ConditionSet;
using ConditionSetPtr = std::shared_ptr<ConditionSet>;
using WeakConditionSetPtr = std::weak_ptr<ConditionSet>;

class Condition {
   public:
      /* notify all dependent condition sets */
      void notify_all();

      /* notify just one dependent condition set */
      void notify_one();

      /* invoked by condition sets that include this condition
         _and_ wait for it to become true
      */
      void enlisten(ConditionSetPtr cset);

   private:
      std::mutex mutex;
      /* condition sets that are currently waiting for us */
      std::list<WeakConditionSetPtr> dependents;
      /* common part of notify_all and notify_one */
      void notify(bool justone);
};

class ConditionSet: public std::enable_shared_from_this<ConditionSet> {
   public:
      /* include the given condition into the set */
      void include(ConditionPtr condition);

      /* wait until one of the condition variables gets notified;
         suspension and release of the lock are atomic; the lock
         is restored upon return;
         this is a no-operation if the set is empty
      */
      void wait(std::unique_lock<std::mutex>& lock_of_caller);

      /* this method is invoked by one of the included conditions
         to wake up from a call to wait();
         this is a no-operation if wait() has not been called
      */
      void notify();

   private:
      std::mutex mutex; // assure mutual exclusion
      std::list<ConditionPtr> conditions; // set of conditions
      std::condition_variable notified;
};

#endif
