#include "config.h"
#include <assert.h>
#include <ccan/htable/htable_type.h>
#include <ccan/tal/str/str.h>
#include <common/gossmap.h>
#include <common/json_stream.h>
#include <common/memleak.h>
#include <inttypes.h>
#include <plugins/askrene/askrene.h>
#include <plugins/askrene/reserve.h>

/* Note!  We can have multiple of these! */
struct reserve {
	/* What */
	struct reserve_hop rhop;
	/* When */
	struct timemono timestamp;
	/* ID of command which reserved it */
	const char *cmd_id;
};

/* Hash table for reservations */
static const struct short_channel_id_dir *
reserve_scidd(const struct reserve *r)
{
	return &r->rhop.scidd;
}

static bool reserve_eq_scidd(const struct reserve *r,
			     const struct short_channel_id_dir *scidd)
{
	return short_channel_id_dir_eq(scidd, &r->rhop.scidd);
}

HTABLE_DEFINE_DUPS_TYPE(struct reserve, reserve_scidd, hash_scidd,
			reserve_eq_scidd, reserve_htable);

struct reserve_htable *new_reserve_htable(const tal_t *ctx)
{
	return new_htable(ctx, reserve_htable);
}

static void destroy_reserve(struct reserve *r, struct reserve_htable *reserved)
{
	if (!reserve_htable_del(reserved, r))
		abort();
}

void reserve_add(struct reserve_htable *reserved,
		 const struct reserve_hop *rhop,
		 const char *cmd_id TAKES)
{
	struct reserve *r = tal(reserved, struct reserve);
	r->rhop = *rhop;
	r->timestamp = time_mono();
	r->cmd_id = tal_strdup(r, cmd_id);

	/* If owned by a layer, clean up when layer destroyed */
	if (rhop->layer) {
		tal_steal(rhop->layer, r);
		tal_add_destructor2(r, destroy_reserve, reserved);
	}
	reserve_htable_add(reserved, r);
}

bool reserve_remove(struct reserve_htable *reserved,
		    const struct reserve_hop *rhop)
{
	struct reserve *r;
	struct reserve_htable_iter rit;

	/* Note!  This may remove the "wrong" one, but since they're only
	 * differentiated for debugging, that's OK */
	for (r = reserve_htable_getfirst(reserved, &rhop->scidd, &rit);
	     r;
	     r = reserve_htable_getnext(reserved, &rhop->scidd, &rit)) {
		if (!amount_msat_eq(r->rhop.amount, rhop->amount))
			continue;
		if (r->rhop.layer != rhop->layer)
			continue;

		/* hops on layers have a destructor which does this. */
		if (r->rhop.layer == NULL)
			reserve_htable_del(reserved, r);
		tal_free(r);
		return true;
	}
	return false;
}

void reserves_clear_capacities(struct reserve_htable *reserved,
			       const struct gossmap *gossmap,
			       fp16_t *capacities)
{
	struct reserve *r;
	struct reserve_htable_iter rit;

	for (r = reserve_htable_first(reserved, &rit);
	     r;
	     r = reserve_htable_next(reserved, &rit)) {
		struct gossmap_chan *c = gossmap_find_chan(gossmap, &r->rhop.scidd.scid);
		size_t idx;
		if (!c)
			continue;
		idx = gossmap_chan_idx(gossmap, c);
		if (idx < tal_count(capacities))
			capacities[idx] = 0;
	}
}

static bool layer_in(const struct layer *layer,
		     const struct layer **layers)
{
	for (size_t i = 0; i < tal_count(layers); i++)
		if (layer == layers[i])
			return true;
	return false;
}

void reserve_sub(const struct reserve_htable *reserved,
		 const struct short_channel_id_dir *scidd,
		 const struct layer **layers,
		 struct amount_msat *amount)
{
	struct reserve *r;
	struct reserve_htable_iter rit;

	for (r = reserve_htable_getfirst(reserved, scidd, &rit);
	     r;
	     r = reserve_htable_getnext(reserved, scidd, &rit)) {
		if (r->rhop.layer && !layer_in(r->rhop.layer, layers))
			continue;
		if (!amount_msat_deduct(amount, r->rhop.amount))
			*amount = AMOUNT_MSAT(0);
	}
}

bool reserve_accumulate(const struct reserve_htable *reserved,
			const struct short_channel_id_dir *scidd,
			const struct layer *layer,
			struct amount_msat *amount)
{
	struct reserve *r;
	struct reserve_htable_iter rit;

	for (r = reserve_htable_getfirst(reserved, scidd, &rit);
	     r;
	     r = reserve_htable_getnext(reserved, scidd, &rit)) {
		/* Non-layer ones always get counted.  Layered ones have
		 * to match this layer. */
		if (r->rhop.layer && r->rhop.layer != layer)
			continue;
		if (!amount_msat_add(amount, *amount, r->rhop.amount))
			return false;
	}
	return true;
}

void json_add_reservations(struct json_stream *js,
			   const struct reserve_htable *reserved,
			   const char *fieldname,
			   const struct layer **layers)
{
	struct reserve *r;
	struct reserve_htable_iter rit;

	json_array_start(js, fieldname);
	for (r = reserve_htable_first(reserved, &rit);
	     r;
	     r = reserve_htable_next(reserved, &rit)) {
		if (r->rhop.layer && !layer_in(r->rhop.layer, layers))
			continue;
		json_object_start(js, NULL);
		json_add_short_channel_id_dir(js,
					      "short_channel_id_dir",
					      r->rhop.scidd);
		json_add_amount_msat(js,
				     "amount_msat",
				     r->rhop.amount);
		json_add_u64(js, "age_in_seconds",
			     timemono_between(time_mono(), r->timestamp).ts.tv_sec);
		json_add_string(js, "command_id", r->cmd_id);
		json_object_end(js);
	}
	json_array_end(js);
}

const char *fmt_reservations(const tal_t *ctx,
			     const struct reserve_htable *reserved,
			     const struct short_channel_id_dir *scidd,
			     const struct layer **layers)
{
	struct reserve *r;
	struct reserve_htable_iter rit;
	char *ret = NULL;

	for (r = reserve_htable_getfirst(reserved, scidd, &rit);
	     r;
	     r = reserve_htable_getnext(reserved, scidd, &rit)) {
		u64 seconds;
		if (r->rhop.layer && !layer_in(r->rhop.layer, layers))
			continue;
		if (!ret)
			ret = tal_strdup(ctx, "");
		else
			tal_append_fmt(&ret, ", ");
		tal_append_fmt(&ret, "%s by command %s",
			       fmt_amount_msat(tmpctx, r->rhop.amount), r->cmd_id);
		seconds = timemono_between(time_mono(), r->timestamp).ts.tv_sec;
		/* Add a note if it's old */
		if (seconds > 0)
			tal_append_fmt(&ret, " (%"PRIu64" seconds ago)", seconds);
	}
	return ret;
}
