#ifndef LIGHTNING_LIGHTNINGD_PLUGIN_HOOK_H
#define LIGHTNING_LIGHTNINGD_PLUGIN_HOOK_H

#include "config.h"
#include <common/json_parse_simple.h>
#include <lightningd/plugin.h>

/**
 * Plugin hooks are a way for plugins to implement custom behavior and
 * reactions to certain things in `lightningd`. `lightningd` will ask
 * plugins that have registered a hook for a given event how it'd like
 * to proceed. This allows plugins to deviate from the default
 * behavior that `lightningd` otherwise implements.
 *
 * Examples include storing an additional backup of important
 * information belonging to the wallet before committing to it, or
 * holding an incoming payment that is guaranteed to succeed for some
 * time in order to check that the delivery of goods works correctly,
 * giving the option of instantly refunding should something go wrong.
 *
 * Hooks are commonly structured into a number of converter functions
 * and a callback. The converter functions convert from an internal
 * struct representation of the method arguments to a JSON-object for
 * delivery to the plugin, and from a JSON-object to the internal
 * representation:
 *
 * - `serialize_payload` which takes a payload of type `cb_arg_type`
 *   and serializes it into the given `json_stream`. `
 * - `deserialize_cb` is called for each plugin, if it returns true the
 *   next one is called, otherwise the cb_arg_type argument is free.
 * - If all `deserialize_cb` return true, `final_cb` is called.  It must free
 *   or otherwise take ownership of the cb_arg_type argument.
 *
 * To make hook invocations easier, each hook provides a `plugin_hook_call_hookname`
 * function that performs typechecking at compile time, and makes sure
 * that all the provided functions for serialization, deserialization
 * and callback have the correct type.
 */
struct plugin_hook {
	const char *name;

	/* Returns false if we should stop iterating (and free arg). */
	bool (*deserialize_cb)(void *arg,
			       const char *buffer, const jsmntok_t *toks);
	void (*final_cb)(void *arg);

	/* To send the payload to the plugin */
	void (*serialize_payload)(void *src, struct json_stream *dest,
				  struct plugin *plugin);

	/* Type of filters we allow (JSMN_UNDEFINED means none) */
	jsmntype_t filter_type;

	/* Which plugins have registered this hook? This is a `tal_arr`
	 * initialized at creation. */
	struct hook_instance **hooks;

	/* Reference count for using the hook right now */
	size_t num_users;

	/* If someone was using the hooks while we were trying to update,
	 * we put the hook here for later use. */
	struct hook_instance **new_hooks;
};
AUTODATA_TYPE(hooks, struct plugin_hook);

/* Do not call this directly, rather use the `plugin_hook_call_name`
 * wrappers generated by the `PLUGIN_HOOK_REGISTER` macro.
 *
 * Returns true if callback called immediately, otherwise false if it's
 * still waiting on a plugin response.
 */
bool plugin_hook_call_(struct lightningd *ld,
		       struct plugin_hook *hook,
		       const char *strfilterfield TAKES,
		       u64 intfilterfield,
		       const char *cmd_id TAKES,
		       tal_t *cb_arg STEALS);

/* Generic deserialize_cb: returns true iff 'result': 'continue' */
bool plugin_hook_continue(void *arg, const char *buffer, const jsmntok_t *toks);

/* Create a small facade in from of `plugin_hook_call_` to make sure
 * arguments are of the correct type before downcasting them to `void
 * *`. Not really necessary, but nice since it also makes sure that
 * the method-name is correct for the call.
 */
/* FIXME: Find a way to avoid back-to-back declaration and definition */
#define PLUGIN_HOOK_CALL_DEF_NOFILTER(name, cb_arg_type)				       \
	UNNEEDED static inline bool plugin_hook_call_##name(                   \
		struct lightningd *ld, const char *cmd_id TAKES, cb_arg_type cb_arg STEALS) \
	{                                                                      \
		return plugin_hook_call_(ld, &name##_hook_gen, NULL, 0, cmd_id, cb_arg); \
	}

#define PLUGIN_HOOK_CALL_DEF_STRFILTER(name, cb_arg_type)				       \
	UNNEEDED static inline bool plugin_hook_call_##name(                   \
		struct lightningd *ld, const char *strfilterfield, const char *cmd_id TAKES, cb_arg_type cb_arg STEALS) \
	{                                                                      \
		return plugin_hook_call_(ld, &name##_hook_gen, strfilterfield, 0, cmd_id, cb_arg); \
	}

#define PLUGIN_HOOK_CALL_DEF_INTFILTER(name, cb_arg_type)				       \
	UNNEEDED static inline bool plugin_hook_call_##name(                   \
		struct lightningd *ld, u64 intfilterfield, const char *cmd_id TAKES, cb_arg_type cb_arg STEALS) \
	{                                                                      \
		return plugin_hook_call_(ld, &name##_hook_gen, NULL, intfilterfield, cmd_id, cb_arg); \
	}

/* Typechecked registration of a plugin hook. We check that the
 * serialize_payload function converts an object of type payload_type
 * to a json_stream (.params object in the JSON-RPC request), that the
 * deserialize_response function converts from the JSON-RPC response
 * json_stream to an object of type response_type and that the
 * response_cb function accepts the deserialized response format and
 * an arbitrary extra argument used to maintain context.
 */
#define REGISTER_PLUGIN_HOOK2(name, filter_type, deserialize_cb, final_cb,     \
			     serialize_payload, cb_arg_type)                   \
	struct plugin_hook name##_hook_gen = {                                 \
	    stringify(name),                                                   \
	    typesafe_cb_cast(                                                  \
		bool (*)(void *, const char *, const jsmntok_t *),             \
		bool (*)(cb_arg_type, const char *, const jsmntok_t *),        \
		deserialize_cb),                                               \
	    typesafe_cb_cast(void (*)(void *STEALS),                           \
			     void (*)(cb_arg_type STEALS), final_cb),          \
	    typesafe_cb_cast(                                                  \
		void (*)(void *, struct json_stream *, struct plugin *),       \
		void (*)(cb_arg_type, struct json_stream *, struct plugin *),  \
		serialize_payload),                                            \
	    (filter_type),						       \
 	    NULL, /* .plugins */                                               \
	};                                                                     \
	AUTODATA(hooks, &name##_hook_gen)

#define REGISTER_PLUGIN_HOOK(name, deserialize_cb, final_cb,		\
			      serialize_payload, cb_arg_type)		\
	REGISTER_PLUGIN_HOOK2(name, JSMN_UNDEFINED, deserialize_cb, final_cb, serialize_payload, cb_arg_type); \
	PLUGIN_HOOK_CALL_DEF_NOFILTER(name, cb_arg_type)

#define REGISTER_PLUGIN_HOOK_STRFILTER(name, deserialize_cb, final_cb,		\
				       serialize_payload, cb_arg_type) \
	REGISTER_PLUGIN_HOOK2(name, JSMN_STRING, deserialize_cb, final_cb, serialize_payload, cb_arg_type) \
	PLUGIN_HOOK_CALL_DEF_STRFILTER(name, cb_arg_type)

#define REGISTER_PLUGIN_HOOK_INTFILTER(name, deserialize_cb, final_cb,		\
				       serialize_payload, cb_arg_type) \
	REGISTER_PLUGIN_HOOK2(name, JSMN_PRIMITIVE, deserialize_cb, final_cb, serialize_payload, cb_arg_type) \
	PLUGIN_HOOK_CALL_DEF_INTFILTER(name, cb_arg_type)

/* Returns the error, or NULL and populates *plugin_hook */
const char *plugin_hook_register(struct plugin *plugin,
				 const char *method,
				 const char *buf, const jsmntok_t *filterstok,
				 struct plugin_hook **plugin_hook);

/* Special sync plugin hook for db. */
void plugin_hook_db_sync(struct db *db);

/* Add dependencies for this hook. */
void plugin_hook_add_deps(struct plugin_hook *hook,
			  struct plugin *plugin,
			  const char *buffer,
			  const jsmntok_t *before,
			  const jsmntok_t *after);

/* Returns array of plugins which cannot be ordered (empty on success) */
struct plugin **plugin_hooks_make_ordered(const tal_t *ctx,
					  struct logger *log);

#endif /* LIGHTNING_LIGHTNINGD_PLUGIN_HOOK_H */
