#ifndef LIGHTNING_LIGHTNINGD_PLUGIN_HOOK_H
#define LIGHTNING_LIGHTNINGD_PLUGIN_HOOK_H

#include "config.h"
#include <ccan/autodata/autodata.h>
#include <ccan/tal/tal.h>
#include <ccan/typesafe_cb/typesafe_cb.h>
#include <common/json_stream.h>
#include <lightningd/lightningd.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`. `
 *
 * For single-plugin hooks:
 * - `single_response_cb` is called once the plugin has responded (or with
 *   buffer == NULL if there's no plugin).  In addition an arbitrary
 *   additional argument of type `cb_arg_type` can be passed along
 *   that may contain any additional context necessary.  It must free
 *   or otherwise take ownership of the cb_arg_type argument.
 *
 * For chained-plugin hooks:
 * - `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.
 */

enum plugin_hook_type {
	PLUGIN_HOOK_SINGLE,
	PLUGIN_HOOK_CHAIN,
};

struct plugin_hook {
	const char *name;

	/* Which type of plugin is this? It'll determine how many plugins can
	 * register this hook, and how the hooks are called. */
	enum plugin_hook_type type;

	/* For PLUGIN_HOOK_SINGLE hooks */
	void (*single_response_cb)(void *arg,
				   const char *buffer, const jsmntok_t *toks);

	/* For PLUGIN_HOOK_CHAIN hooks: */
	/* 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);

	/* Which plugins have registered this hook? This is a `tal_arr`
	 * initialized at creation. */
	struct plugin **plugins;
};
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, const struct plugin_hook *hook,
		       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(name, cb_arg_type)				       \
	UNNEEDED static inline bool plugin_hook_call_##name(                   \
	    struct lightningd *ld, cb_arg_type cb_arg STEALS)                  \
	{                                                                      \
		return plugin_hook_call_(ld, &name##_hook_gen, 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_SINGLE_PLUGIN_HOOK(name, response_cb,                         \
				    serialize_payload, cb_arg_type)            \
	struct plugin_hook name##_hook_gen = {                                 \
	    stringify(name),                                                   \
	    PLUGIN_HOOK_SINGLE,                                                \
	    typesafe_cb_cast(                                                  \
		void (*)(void *STEALS, const char *, const jsmntok_t *),       \
		void (*)(cb_arg_type STEALS, const char *, const jsmntok_t *), \
		response_cb),                                                  \
	    NULL, NULL,                                                        \
	    typesafe_cb_cast(void (*)(void *, struct json_stream *),           \
			     void (*)(cb_arg_type, struct json_stream *),      \
			     serialize_payload),                               \
	    NULL, /* .plugins */                                               \
	};                                                                     \
	AUTODATA(hooks, &name##_hook_gen);                                     \
	PLUGIN_HOOK_CALL_DEF(name, cb_arg_type)


#define REGISTER_PLUGIN_HOOK(name, deserialize_cb, final_cb,                   \
			     serialize_payload, cb_arg_type)		       \
	struct plugin_hook name##_hook_gen = {                                 \
	    stringify(name),                                                   \
	    PLUGIN_HOOK_CHAIN,                                                 \
            NULL,                                                              \
	    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 *),           \
			     void (*)(cb_arg_type, struct json_stream *),      \
			     serialize_payload),                               \
	    NULL, /* .plugins */                                               \
	};                                                                     \
	AUTODATA(hooks, &name##_hook_gen);                                     \
	PLUGIN_HOOK_CALL_DEF(name, cb_arg_type)

bool plugin_hook_register(struct plugin *plugin, const char *method);

/* Unregister a hook a plugin has registered for */
bool plugin_hook_unregister(struct plugin *plugin, const char *method);

/* Unregister all hooks a plugin has registered for */
void plugin_hook_unregister_all(struct plugin *plugin);

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

#endif /* LIGHTNING_LIGHTNINGD_PLUGIN_HOOK_H */
