#include "config.h"
#include <assert.h>
#include <ccan/json_out/json_out.h>
#include <ccan/read_write_all/read_write_all.h>
#include <ccan/tal/str/str.h>
#include <common/json_stream.h>
#include <common/route.h>
#include <common/utils.h>
#include <plugins/askrene/child/child.h>
#include <plugins/askrene/child/child_log.h>
#include <plugins/askrene/child/flow.h>
#include <plugins/askrene/child/mcf.h>
#include <plugins/askrene/child/route_query.h>

/* A single route. */
struct route {
	/* Actual path to take */
	struct route_hop *hops;
	/* Probability estimate (0-1) */
	double success_prob;
};

static const char *fmt_route(const tal_t *ctx,
			     const struct route *route,
			     struct amount_msat delivers,
			     u32 final_cltv)
{
	char *str = tal_strdup(ctx, "");

	for (size_t i = 0; i < tal_count(route->hops); i++) {
		struct short_channel_id_dir scidd;
		scidd.scid = route->hops[i].scid;
		scidd.dir = route->hops[i].direction;
		tal_append_fmt(&str, "%s/%u %s -> ",
			       fmt_amount_msat(tmpctx, route->hops[i].amount),
			       route->hops[i].delay,
			       fmt_short_channel_id_dir(tmpctx, &scidd));
	}
	tal_append_fmt(&str, "%s/%u",
		       fmt_amount_msat(tmpctx, delivers), final_cltv);
	return str;
}

/* Convert back into routes, with delay and other information fixed */
static struct route **convert_flows_to_routes(const tal_t *ctx,
					      struct route_query *rq,
					      u32 finalcltv,
					      struct flow **flows,
					      struct amount_msat **amounts,
					      bool include_fees)
{
	struct route **routes;
	routes = tal_arr(ctx, struct route *, tal_count(flows));
	*amounts = tal_arr(ctx, struct amount_msat, tal_count(flows));

	for (size_t i = 0; i < tal_count(flows); i++) {
		struct route *r;
		struct amount_msat msat;
		u32 delay;

		routes[i] = r = tal(routes, struct route);
		r->success_prob = flow_probability(flows[i], rq);
		r->hops = tal_arr(r, struct route_hop, tal_count(flows[i]->path));

		msat = flows[i]->delivers;
		delay = finalcltv;

		if (!include_fees) {
			/* Fill in backwards to calc amount and delay */
			for (int j = tal_count(flows[i]->path) - 1; j >= 0;
			     j--) {
				struct route_hop *rh = &r->hops[j];
				struct gossmap_node *far_end;
				const struct half_chan *h =
				    flow_edge(flows[i], j);

				if (!amount_msat_add_fee(&msat, h->base_fee,
							 h->proportional_fee))
					abort();
				delay += h->delay;

				rh->scid = gossmap_chan_scid(rq->gossmap,
							     flows[i]->path[j]);
				rh->direction = flows[i]->dirs[j];
				far_end = gossmap_nth_node(rq->gossmap,
							   flows[i]->path[j],
							   !flows[i]->dirs[j]);
				gossmap_node_get_id(rq->gossmap, far_end,
						    &rh->node_id);
				rh->amount = msat;
				rh->delay = delay;
			}
		        (*amounts)[i] = flows[i]->delivers;
		} else {
			/* Fill in backwards to calc delay */
			for (int j = tal_count(flows[i]->path) - 1; j >= 0;
			     j--) {
				struct route_hop *rh = &r->hops[j];
				struct gossmap_node *far_end;
				const struct half_chan *h =
				    flow_edge(flows[i], j);

				delay += h->delay;

				rh->scid = gossmap_chan_scid(rq->gossmap,
							     flows[i]->path[j]);
				rh->direction = flows[i]->dirs[j];
				far_end = gossmap_nth_node(rq->gossmap,
							   flows[i]->path[j],
							   !flows[i]->dirs[j]);
				gossmap_node_get_id(rq->gossmap, far_end,
						    &rh->node_id);
				rh->delay = delay;
			}
			/* Compute fees forward */
			for (int j = 0; j < tal_count(flows[i]->path); j++) {
				struct route_hop *rh = &r->hops[j];
				const struct half_chan *h =
				    flow_edge(flows[i], j);

				rh->amount = msat;
                                msat = amount_msat_sub_fee(msat, h->base_fee,
							   h->proportional_fee);
			}
		        (*amounts)[i] = msat;
		}

		child_log(tmpctx, LOG_INFORM, "Flow %zu/%zu: %s",
			  i, tal_count(flows),
			  fmt_route(tmpctx, r, (*amounts)[i], finalcltv));
	}

	return routes;
}

static void json_add_getroutes(struct json_stream *js,
			       struct route **routes,
			       const struct amount_msat *amounts,
			       double probability,
			       u32 final_cltv)
{
	json_add_u64(js, "probability_ppm", (u64)(probability * 1000000));
	json_array_start(js, "routes");
	for (size_t i = 0; i < tal_count(routes); i++) {
		json_object_start(js, NULL);
		json_add_u64(js, "probability_ppm",
			     (u64)(routes[i]->success_prob * 1000000));
		json_add_amount_msat(js, "amount_msat", amounts[i]);
		json_add_u32(js, "final_cltv", final_cltv);
		json_array_start(js, "path");
		for (size_t j = 0; j < tal_count(routes[i]->hops); j++) {
			struct short_channel_id_dir scidd;
			const struct route_hop *r = &routes[i]->hops[j];
			json_object_start(js, NULL);
			scidd.scid = r->scid;
			scidd.dir = r->direction;
			json_add_short_channel_id_dir(
			    js, "short_channel_id_dir", scidd);
			json_add_node_id(js, "next_node_id", &r->node_id);
			json_add_amount_msat(js, "amount_msat", r->amount);
			json_add_u32(js, "delay", r->delay);
			json_object_end(js);
		}
		json_array_end(js);
		json_object_end(js);
	}
	json_array_end(js);
}


static struct route_query *new_route_query(const tal_t *ctx,
					   const struct gossmap *gossmap,
					   const char *cmd_id,
					   const struct layer **layers,
					   const s8 *biases,
					   const struct additional_cost_htable *additional_costs,
					   struct reserve_htable *reserved,
					   fp16_t *capacities TAKES)
{
	struct route_query *rq = tal(ctx, struct route_query);

	rq->gossmap = gossmap;
	rq->cmd_id = tal_strdup(rq, cmd_id);
	rq->layers = layers;
	rq->biases = biases;
	rq->additional_costs = additional_costs;
	rq->reserved = reserved;
	rq->capacities = tal_dup_talarr(rq, fp16_t, capacities);
	rq->disabled_chans =
	    tal_arrz(rq, bitmap,
		     2 * BITMAP_NWORDS(gossmap_max_chan_idx(gossmap)));

	return rq;
}

void run_child(const struct gossmap *gossmap,
	       const struct layer **layers,
	       const s8 *biases,
	       const struct additional_cost_htable *additional_costs,
	       struct reserve_htable *reserved,
	       fp16_t *capacities TAKES,
	       bool single_path,
	       struct timemono deadline,
	       const struct gossmap_node *srcnode,
	       const struct gossmap_node *dstnode,
	       struct amount_msat amount, struct amount_msat maxfee,
	       u32 finalcltv, u32 maxdelay, size_t maxparts,
              bool include_fees,
	       const char *cmd_id,
	       struct json_filter *cmd_filter,
	       int replyfd)
{
	double probability;
	struct flow **flows;
	struct route **routes;
	struct amount_msat *amounts;
	const char *err, *p;
	size_t len;
	struct route_query *rq;

	/* We exit below, so we don't bother freeing this */
	rq = new_route_query(NULL, gossmap, cmd_id, layers,
			     biases, additional_costs,
			     reserved, capacities);
	if (single_path) {
		err = single_path_routes(rq, rq, deadline, srcnode, dstnode,
					 amount, maxfee, finalcltv,
					 maxdelay, &flows, &probability);
	} else {
		err = default_routes(rq, rq, deadline, srcnode, dstnode,
				     amount, maxfee, finalcltv, maxdelay,
				     maxparts, &flows, &probability);
	}
	if (err) {
		write_all(replyfd, err, strlen(err));
		/* Non-zero exit tells parent this is an error string. */
		exit(1);
	}

	/* otherwise we continue */
	assert(tal_count(flows) > 0);
	child_log(tmpctx, LOG_DBG, "Final answer has %zu flows",
		  tal_count(flows));

	/* convert flows to routes */
	routes = convert_flows_to_routes(rq, rq, finalcltv, flows,
					 &amounts, include_fees);
	assert(tal_count(routes) == tal_count(flows));
	assert(tal_count(amounts) == tal_count(flows));

	/* output the results */
	struct json_stream *js = new_json_stream(tmpctx, NULL, NULL);
	json_object_start(js, NULL);
	json_add_string(js, "jsonrpc", "2.0");
	json_add_id(js, cmd_id);
	json_object_start(js, "result");
	if (cmd_filter)
		json_stream_attach_filter(js, cmd_filter);
	json_add_getroutes(js, routes, amounts, probability, finalcltv);

	/* Detach filter before it complains about closing object it never saw */
	if (cmd_filter) {
		err = json_stream_detach_filter(tmpctx, js);
		if (err)
			json_add_string(js, "warning_parameter_filter", err);
	}
	/* "result" object */
	json_object_end(js);
	/* Global object */
	json_object_end(js);
	json_stream_close(js, NULL);

	p = json_out_contents(js->jout, &len);
	if (!write_all(replyfd, p, len))
		abort();
	exit(0);
}

