#ifndef LIGHTNING_LIGHTNINGD_PLUGIN_H
#define LIGHTNING_LIGHTNINGD_PLUGIN_H
#include "config.h"
#include <ccan/intmap/intmap.h>
#include <lightningd/jsonrpc.h>
#include <lightningd/lightningd.h>


enum plugin_state {
	/* We have to ask getmanifest */
	UNCONFIGURED,
	/* We sent getmanifest, need response. */
	AWAITING_GETMANIFEST_RESPONSE,
	/* Got `getmanifest` reply, now we need to send `init`. */
	NEEDS_INIT,
	/* We have to get `init` response */
	AWAITING_INIT_RESPONSE,
	/* We have `init` response. */
	INIT_COMPLETE
};

struct plugin_subscription {
	struct plugin *owner;
	const char *topic;
};

static inline size_t hash_str(const char *str)
{
	return siphash24(siphash_seed(), str, strlen(str));
}

static inline const char *plugin_subscription_key(const struct plugin_subscription *ps)
{
	return ps->topic;
}

static inline bool plugin_subscription_eq_topic(const struct plugin_subscription *ps,
						const char *topic)
{
	return streq(ps->topic, topic);
}

HTABLE_DEFINE_DUPS_TYPE(struct plugin_subscription,
			plugin_subscription_key,
			hash_str,
			plugin_subscription_eq_topic,
			plugin_subscription_htable);

/**
 * A plugin, exposed as a stub so we can pass it as an argument.
 */
struct plugin {
	/* Must be first element in the struct otherwise we get false
	 * positives for leaks. */
	struct list_node list;

	/* The filename that can be used to refer to the plugin. */
	const char *shortname;

	pid_t pid;
	char *cmd;
	u32 checksum;
	struct io_conn *stdin_conn, *stdout_conn;
	struct plugins *plugins;
	const char **plugin_path;

	/* If there's a json command which ordered this to start */
	struct plugin_command *start_cmd;

	enum plugin_state plugin_state;

	/* Our unique index, which is default hook ordering. */
	u64 index;

	/* If this plugin can be restarted without restarting lightningd */
	bool dynamic;

	/* Stuff we read */
	struct jsonrpc_io *json_in;

	/* Our plugin_jstream_out list. Since multiple streams could start
	 * returning data at once, we always service these in order. */
	struct list_head jsouts;

	struct logger *log;

	/* List of options that this plugin registered */
	struct list_head plugin_opts;

	const char **methods;

	/* Timer to add a timeout to some plugin RPC calls. Used to
	 * guarantee that `getmanifest` doesn't block indefinitely. */
	const struct oneshot *timeout_timer;

	/* An array of subscribed topics */
	struct plugin_subscription *subscriptions;

	/* Our pending requests by their request ID */
	STRMAP(struct jsonrpc_request *) pending_requests;

	/* If set, the plugin is so important that if it terminates early,
	 * C-lightning should terminate as well.  */
	bool important;

	/* Parameters for dynamically-started plugins. */
	const char *parambuf;
	const jsmntok_t *params;

	/* Notification topics that this plugin has registered with us
	 * and that other plugins may subscribe to. */
	const char **notification_topics;

	/* Custom message types we want to allow incoming */
	u16 *custom_msgs;

	/* Can this handle check commands? */
	bool can_check;

	/* custom feature-bits */
	struct feature_set *fset;
};

/**
 * A collection of plugins, and some associated information.
 *
 * Mainly used as root context for calls in the plugin subsystem.
 */
struct plugins {
	struct list_head plugins;
	bool startup;

	/* Normally we want to wrap callbacks in a db transaction, but
	 * not for the db hook servicing */
	bool want_db_transaction;

	struct logger *log;

	struct lightningd *ld;
	const char *default_dir;

	/* If there are json commands waiting for plugin resolutions. */
	struct plugin_command **plugin_cmds;

	/* Blacklist of plugins from --disable-plugin */
	const char **blacklist;

	/* Index to show what order they were added in */
	u64 plugin_idx;

	/* Table of who subscribed to what hooks/notifications */
	struct plugin_subscription_htable *subscriptions;

	/* Whether builtin plugins should be overridden as unimportant.  */
	bool dev_builtin_plugins_unimportant;

	/* Whether to save all IO to a file */
	char *dev_save_io;
};

/**
 * Simple storage for plugin options inbetween registering them on the
 * command line and passing them off to the plugin
 */
struct plugin_opt {
	struct plugin *plugin;
	/* off plugin->plugin_opts */
	struct list_node list;
	/* includes -- prefix! */
	const char *name;
	const char *description;
	/* NULL if no default */
	const char *def;
	/* NULL if not deprecated */
	const char *depr_start, *depr_end;
};

/**
 * Create a new plugins context.
 */
struct plugins *plugins_new(const tal_t *ctx, struct log_book *log_book,
			    struct lightningd *ld);

/**
 * Recursively add all plugins from the default plugins directory.
 */
void plugins_add_default_dir(struct plugins *plugins);

/**
 * Initialize the registered plugins.
 *
 * Initialization includes spinning up the plugins, reading their
 * manifest, and registering the JSON-RPC passthrough and command line
 * arguments. In order to read the getmanifest reply from the plugins
 * we spin up our own io_loop that exits once all plugins have
 * responded.
 */
void plugins_init(struct plugins *plugins);

/**
 * Register a plugin for initialization and execution.
 *
 * @param plugins: Plugin context
 * @param path: The path of the executable for this plugin
 * @param start_cmd: The optional JSON command which caused this.
 * @param important: The plugin is important.
 * @param parambuf: NULL, or the JSON buffer for extra parameters.
 * @param params: NULL, or the tokens for extra parameters.
 *
 * If @start_cmd, then plugin_cmd_killed or plugin_cmd_succeeded will be called
 * on it eventually.
 */
struct plugin *plugin_register(struct plugins *plugins,
			       const char* path TAKES,
			       struct plugin_command *start_cmd,
			       bool important,
			       const char *parambuf STEALS,
			       const jsmntok_t *params STEALS);


/**
 * Returns true if the provided name matches a plugin command
 */
bool plugin_paths_match(const char *cmd, const char *name);

/**
 * Remove a plugin registered for initialization.
 *
 * @param plugins: Plugin context
 * @param arg: The basename or fullname of the executable for this plugin
 */
void plugin_blacklist(struct plugins *plugins, const char *name);

/**
 * Is a plugin disabled?.
 *
 * @param plugins: Plugin context
 * @param arg: The basename or fullname of the executable for this plugin
 */
bool plugin_blacklisted(struct plugins *plugins, const char *name);

/**
 * Kick off initialization of a plugin.
 * @p: plugin
 * @cmd_id: optional JSON cmd_id which caused this.
 *
 * Returns error string, or NULL.
 */
const char *plugin_send_getmanifest(struct plugin *p, const char *cmd_id);

/**
 * Kick of initialization of all plugins which need it/
 *
 * Return true if any were started.
 */
bool plugins_send_getmanifest(struct plugins *plugins, const char *cmd_id);

/**
 * Kill a plugin process and free @plugin, with an error message.
 */
void plugin_kill(struct plugin *plugin, enum log_level loglevel,
		 const char *fmt, ...);

/**
 * Tell all the plugins we're shutting down, and free them.
 */
void shutdown_plugins(struct lightningd *ld);

/**
 * Returns the plugin which registers the command with name {cmd_name}
 */
struct plugin *find_plugin_for_command(struct lightningd *ld,
				       const char *cmd_name);


/**
 * Call plugin_cmd_all_complete once all plugins are init or killed.
 *
 * Returns NULL if it's still pending. otherwise, returns
 * plugin_cmd_all_complete().
 */
struct command_result *plugin_register_all_complete(struct lightningd *ld,
						    struct plugin_command *pcmd);

/**
 * Send the configure message to all plugins.
 *
 * Once we've collected all the command line arguments we can go ahead
 * and send them over to the plugin. This finalizes the initialization
 * of the plugins and signals that lightningd is now ready to process
 * incoming JSON-RPC calls and messages.
 *
 * It waits for plugins to be initialized, but returns false if we
 * should exit (an important plugin failed, or we got a shutdown command).
 */
bool plugins_config(struct plugins *plugins);

/**
 * This populates the jsonrpc request with the plugin/lightningd specifications
 */
void plugin_populate_init_request(struct plugin *p, struct jsonrpc_request *req);

/**
 * Add the plugin option and their respective options to listconfigs.
 *
 * This adds a dict that maps the plugin name to a dict of configuration options
 * for the corresponding plugins.
 */
void json_add_opt_plugins(struct json_stream *response,
			  const struct plugins *plugins);


/**
 * Add the disable-plugins options to listconfigs.
 */
void json_add_opt_disable_plugins(struct json_stream *response,
				  const struct plugins *plugins);

/**
 * Used by db hooks which can't have any other I/O while talking to
 * hooked plugins.
 *
 * @param plugins - a `tal`-allocated array of plugins that are the
 * only ones we talk to.
 *
 * @return output of io_loop() (ie. whatever gets passed to io_break()
 * to end exclusive loop).
 */
void *plugins_exclusive_loop(struct plugin **plugins);

/**
 * Add a directory to the plugin path to automatically load plugins.
 */
char *add_plugin_dir(struct plugins *plugins, const char *dir,
		     bool error_ok);

/**
 * Clear all plugins registered so far.
 */
void clear_plugins(struct plugins *plugins);

/**
 * Send notification to this single plugin, if interested.
 *
 * Returns true if it was subscribed to the notification.
 */
bool plugin_single_notify(struct plugin *p,
			  const struct jsonrpc_notification *n TAKES);

/**
 * Send notification to all interested plugins.
 */
void plugins_notify(struct plugins *plugins,
		    const struct jsonrpc_notification *n TAKES);

/**
 * Send a jsonrpc_request to the specified plugin
 * @plugin: the plugin to send the request to
 * @req: the request.
 *
 * If @req is freed, any response from the plugin is ignored.
 */
void plugin_request_send(struct plugin *plugin,
			 struct jsonrpc_request *req);

/**
 * Callback called when parsing options. It just stores the value in
 * the plugin_opt
 */
char *plugin_opt_set(const char *arg, struct plugin_opt *popt);

/**
 * Callback called when plugin flag-type options.It just stores
 * the value in the plugin_opt
 */
char *plugin_opt_flag_set(struct plugin_opt *popt);

/**
 * Helpers to initialize a connection to a plugin; we read from their
 * stdout, and write to their stdin.
 */
struct io_plan *plugin_stdin_conn_init(struct io_conn *conn,
                                       struct plugin *plugin);
struct io_plan *plugin_stdout_conn_init(struct io_conn *conn,
                                        struct plugin *plugin);

/**
 * Needed for I/O logging for plugin messages.
*/
struct logger *plugin_get_logger(struct plugin *plugin);

/**
 * Tells the plugin system the directory for builtin plugins.
 */
void plugins_set_builtin_plugins_dir(struct plugins *plugins,
				     const char *dir);

/* Is this option for a plugin? */
bool is_plugin_opt(const struct opt_table *ot);

/* Does any plugin care about this notification? */
bool plugins_anyone_cares(struct plugins *plugins, const char *method);

/* Add this field if this ot is owned by a plugin */
void json_add_config_plugin(struct json_stream *stream,
			    const struct plugins *plugins,
			    const char *fieldname,
			    const struct opt_table *ot);

/* Attempt to setconfig an option in a plugin.  Calls success or fail, may be async!
 * For scalar options: vals is 1-element array (or NULL/0 for NOARG).
 * For multi options: vals is the complete new set of values. */
struct command_result *plugin_set_dynamic_opt(struct command *cmd,
					      const struct opt_table *ot,
					      const char **vals,
					      size_t nvals,
					      bool transient,
					      struct command_result *(*success)
					      (struct command *,
					       const struct opt_table *,
					       const char **,
					       size_t,
					       bool));

/* --dev-plugin-save-io */
void dev_save_plugin_io_in(struct plugins *plugins,
			   const char *type,
			   const char *name,
			   const char *buffer,
			   const jsmntok_t *tok);

void dev_save_plugin_io_out(struct plugins *plugins,
			    const char *type,
			    const char *name,
			    const struct json_stream *stream);

#endif /* LIGHTNING_LIGHTNINGD_PLUGIN_H */
