#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 <lightningd/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 `payload_type`
 *   and serializes it into the given `json_stream`. `
 *
 * - `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.
 *
 *
 * To make hook invocations easier, each hook registered with
 * `REGISTER_PLUGIN_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;
	void (*response_cb)(void *arg, const char *buffer, const jsmntok_t *toks);
	void (*serialize_payload)(void *src, struct json_stream *dest);

	/* Which plugin has registered this hook? */
	struct plugin *plugin;
};
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.
 */
void plugin_hook_call_(struct lightningd *ld, const struct plugin_hook *hook,
		       void *payload, void *cb_arg);


/* 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, payload_type, response_cb_arg_type)         \
	UNNEEDED static inline void plugin_hook_call_##name(                   \
	    struct lightningd *ld, payload_type payload,                       \
	    response_cb_arg_type cb_arg)                                       \
	{                                                                      \
		plugin_hook_call_(ld, &name##_hook_gen, (void *)payload,       \
				  (void *)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_HOOK(name, response_cb, response_cb_arg_type,          \
			     serialize_payload, payload_type)                  \
	struct plugin_hook name##_hook_gen = {                                 \
	    stringify(name),                                                   \
	    typesafe_cb_cast(void (*)(void *, const char *, const jsmntok_t *),\
			     void (*)(response_cb_arg_type,		       \
				      const char *, const jsmntok_t *),	       \
			     response_cb),                                     \
	    typesafe_cb_cast(void (*)(void *, struct json_stream *),           \
			     void (*)(payload_type, struct json_stream *),     \
			     serialize_payload),                               \
	    NULL, /* .plugin */                                                \
	};                                                                     \
	AUTODATA(hooks, &name##_hook_gen);                                     \
	PLUGIN_HOOK_CALL_DEF(name, payload_type, response_cb_arg_type);

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

/* Special sync plugin hook for db: changes[] are SQL statements, with optional
 * final command appended. */
void plugin_hook_db_sync(struct db *db, const char **changes, const char *final);

#endif /* LIGHTNING_LIGHTNINGD_PLUGIN_HOOK_H */