/* Copyright (c) 2016-2017, The Tor Project, Inc. */
/* See LICENSE for licensing information */

/**
 * \file pubsub.h
 * \brief Macros to implement publish/subscribe abstractions.
 *
 * To use these macros, call DECLARE_PUBSUB_TOPIC() with an identifier to use
 * as your topic.  Below, I'm going to assume you say DECLARE_PUBSUB_TOPIC(T).
 *
 * Doing this will declare the following types:
 *   typedef struct T_event_data_t T_event_data_t; // you define this struct
 *   typedef struct T_subscriber_data_t T_subscriber_data_t; // this one too.
 *   typedef struct T_subscriber_t T_subscriber_t; // opaque
 *   typedef int (*T_subscriber_fn_t)(T_event_data_t*, T_subscriber_data_t*);
 *
 * and it will declare the following functions:
 *     const T_subscriber_t *T_subscribe(T_subscriber_fn_t,
 *                                       T_subscriber_data_t *,
 *                                       unsigned flags,
 *                                       unsigned priority);
 *     int T_unsubscribe(const T_subscriber_t *)
 *
 * Elsewhere you can say DECLARE_NOTIFY_PUBSUB_TOPIC(static, T), which
 * declares:
 *
 *    static int T_notify(T_event_data_t *, unsigned notify_flags);
 *    static void T_clear(void);
 *
 * And in some C file, you would define these functions with:
 *    IMPLEMENT_PUBSUB_TOPIC(static, T).
 *
 * The implementations will be small typesafe wrappers over generic versions
 * of the above functions.
 *
 * To use the typesafe functions, you add any number of subscribers with
 * T_subscribe().  Each has an associated function pointer, data pointer,
 * and priority. Later, you can invoke T_notify() to declare that the
 * event has occurred. Each of the subscribers will be invoked once.
 **/

#ifndef TOR_PUBSUB_H
#define TOR_PUBSUB_H

#include "torint.h"

/**
 * Flag for T_subscribe: die with an assertion failure if the event
 * have ever been published before.  Used when a subscriber must absolutely
 * never have missed an event.
 */
#define SUBSCRIBE_ATSTART (1u<<0)

#define DECLARE_PUBSUB_STRUCT_TYPES(name)                               \
  /* You define this type. */                                           \
  typedef struct name ## _event_data_t name ## _event_data_t;           \
  /* You define this type. */                                           \
  typedef struct name ## _subscriber_data_t name ## _subscriber_data_t;

#define DECLARE_PUBSUB_TOPIC(name)                                      \
  /* This type is opaque. */                                            \
  typedef struct name ## _subscriber_t name ## _subscriber_t;           \
  /* You declare functions matching this type. */                       \
  typedef int (*name ## _subscriber_fn_t)(                              \
                                    name ## _event_data_t *data,        \
                                    name ## _subscriber_data_t *extra); \
  /* Call this function to subscribe to a topic. */                     \
  const name ## _subscriber_t *name ## _subscribe(                      \
                         name##_subscriber_fn_t subscriber,             \
                         name##_subscriber_data_t *extra_data,          \
                         unsigned flags,                                \
                         unsigned priority);                            \
  /* Call this function to unsubscribe from a topic. */                 \
  int name ## _unsubscribe(const name##_subscriber_t *s);

#define DECLARE_NOTIFY_PUBSUB_TOPIC(linkage, name)                          \
  /* Call this function to notify all subscribers. Flags not yet used. */   \
  linkage int name ## _notify(name ## _event_data_t *data, unsigned flags); \
  /* Call this function to release storage held by the topic. */            \
  linkage void name ## _clear(void);

/**
 * Type used to hold a generic function for a subscriber.
 *
 * [Yes, it is safe to cast to this, so long as we cast back to the original
 * type before calling.  From C99: "A pointer to a function of one type may be
 * converted to a pointer to a function of another type and back again; the
 * result shall compare equal to the original pointer."]
*/
typedef int (*pubsub_subscriber_fn_t)(void *, void *);

/**
 * Helper type to implement pubsub abstraction. Don't use this directly.
 * It represents a subscriber.
 */
typedef struct pubsub_subscriber_t {
  /** Function to invoke when the event triggers. */
  pubsub_subscriber_fn_t fn;
  /** Data associated with this subscriber. */
  void *subscriber_data;
  /** Priority for this subscriber. Low priorities happen first. */
  unsigned priority;
  /** Flags set on this subscriber. Not yet used.*/
  unsigned subscriber_flags;
} pubsub_subscriber_t;

/**
 * Helper type to implement pubsub abstraction. Don't use this directly.
 * It represents a topic, and keeps a record of subscribers.
 */
typedef struct pubsub_topic_t {
  /** List of subscribers to this topic. May be NULL. */
  struct smartlist_t *subscribers;
  /** Total number of times that pubsub_notify_() has ever been called on this
   * topic. */
  uint64_t n_events_fired;
  /** True iff we're running 'notify' on this topic, and shouldn't allow
   * any concurrent modifications or events. */
  unsigned locked;
} pubsub_topic_t;

const pubsub_subscriber_t *pubsub_subscribe_(pubsub_topic_t *topic,
                                             pubsub_subscriber_fn_t fn,
                                             void *subscriber_data,
                                             unsigned subscribe_flags,
                                             unsigned priority);
int pubsub_unsubscribe_(pubsub_topic_t *topic, const pubsub_subscriber_t *sub);
void pubsub_clear_(pubsub_topic_t *topic);
typedef int (*pubsub_notify_fn_t)(pubsub_subscriber_t *subscriber,
                                  void *notify_data);
int pubsub_notify_(pubsub_topic_t *topic, pubsub_notify_fn_t notify_fn,
                   void *notify_data, unsigned notify_flags);

#define IMPLEMENT_PUBSUB_TOPIC(notify_linkage, name)                    \
  static pubsub_topic_t name ## _topic_ = { NULL, 0, 0 };               \
  const name ## _subscriber_t *                                         \
  name ## _subscribe(name##_subscriber_fn_t subscriber,                 \
                     name##_subscriber_data_t *extra_data,              \
                     unsigned flags,                                    \
                     unsigned priority)                                 \
  {                                                                     \
    const pubsub_subscriber_t *s;                                       \
    s = pubsub_subscribe_(&name##_topic_,                               \
                          (pubsub_subscriber_fn_t)subscriber,           \
                          extra_data,                                   \
                          flags,                                        \
                          priority);                                    \
    return (const name##_subscriber_t *)s;                              \
  }                                                                     \
  int                                                                   \
  name ## _unsubscribe(const name##_subscriber_t *subscriber)           \
  {                                                                     \
    return pubsub_unsubscribe_(&name##_topic_,                          \
                               (const pubsub_subscriber_t *)subscriber); \
  }                                                                     \
  static int                                                            \
  name##_call_the_notify_fn_(pubsub_subscriber_t *subscriber,           \
                             void *notify_data)                         \
  {                                                                     \
    name ## _subscriber_fn_t fn;                                        \
    fn = (name ## _subscriber_fn_t) subscriber->fn;                     \
    return fn(notify_data, subscriber->subscriber_data);                \
  }                                                                     \
  notify_linkage int                                                    \
  name ## _notify(name ## _event_data_t *event_data, unsigned flags)    \
  {                                                                     \
    return pubsub_notify_(&name##_topic_,                               \
                          name##_call_the_notify_fn_,                   \
                          event_data,                                   \
                          flags);                                       \
  }                                                                     \
  notify_linkage void                                                   \
  name ## _clear(void)                                                  \
  {                                                                     \
    pubsub_clear_(&name##_topic_);                                      \
  }

#endif /* TOR_PUBSUB_H */

