/* MIT (BSD) license - see LICENSE file for details */
#include "config.h"
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <ccan/endian/endian.h>
#include <ccan/mem/mem.h>
#include <ccan/tal/str/str.h>
#include <ccan/rune/rune.h>
#include <ccan/rune/internal.h>

/* Helper to produce an id field */
static struct rune_restr *unique_id_restr(const tal_t *ctx,
					  const char *unique_id,
					  const char *version)
{
	const char *id;
	struct rune_restr *restr;

	assert(!strchr(unique_id, '-'));
	if (version)
		id = tal_fmt(NULL, "%s-%s", unique_id, version);
	else
		id = tal_strdup(NULL, unique_id);

	restr = rune_restr_new(ctx);
        /* We use the empty field for this, since it's always present. */
	rune_restr_add_altern(restr,
			      take(rune_altern_new(NULL, "", '=', take(id))));
	return restr;
}

/* We pad between fields with something identical to the SHA end marker */
void rune_sha256_endmarker(struct sha256_ctx *shactx)
{
	static const unsigned char pad[64] = {0x80};
	be64 sizedesc;

	sizedesc = cpu_to_be64((uint64_t)shactx->bytes << 3);
	/* Add '1' bit to terminate, then all 0 bits, up to next block - 8. */
	sha256_update(shactx, pad, 1 + ((128 - 8 - (shactx->bytes % 64) - 1) % 64));
	/* Add number of bits of data (big endian) */
	sha256_update(shactx, &sizedesc, 8);
}

struct rune *rune_new(const tal_t *ctx, const u8 *secret, size_t secret_len,
		      const char *version)
{
	struct rune *rune = tal(ctx, struct rune);
        assert(secret_len + 1 + 8 <= 64);

	if (version)
		rune->version = tal_strdup(rune, version);
	else
		rune->version = NULL;
	rune->unique_id = NULL;
	sha256_init(&rune->shactx);
	sha256_update(&rune->shactx, secret, secret_len);
	rune_sha256_endmarker(&rune->shactx);
	rune->restrs = tal_arr(rune, struct rune_restr *, 0);
	return rune;
}

struct rune *rune_dup(const tal_t *ctx, const struct rune *rune TAKES)
{
	struct rune *dup;

	if (taken(rune))
		return tal_steal(ctx, (struct rune *)rune);

	dup = tal_dup(ctx, struct rune, rune);
	dup->restrs = tal_arr(dup, struct rune_restr *, tal_count(rune->restrs));
	for (size_t i = 0; i < tal_count(rune->restrs); i++) {
		dup->restrs[i] = rune_restr_dup(dup->restrs,
						rune->restrs[i]);
	}
	return dup;
}

struct rune *rune_derive_start(const tal_t *ctx,
			       const struct rune *master,
			       const char *unique_id TAKES)
{
	struct rune *rune = rune_dup(ctx, master);

        /* If they provide a unique_id, it goes first. */
	if (unique_id) {
		if (taken(unique_id))
			rune->unique_id = tal_steal(rune, unique_id);
		else
			rune->unique_id = tal_strdup(rune, unique_id);
		
		rune_add_restr(rune, take(unique_id_restr(NULL,
							  rune->unique_id,
							  rune->version)));
	} else {
		assert(!rune->version);
	}
	return rune;
}

struct rune_altern *rune_altern_new(const tal_t *ctx,
				    const char *fieldname TAKES,
				    enum rune_condition condition,
				    const char *value TAKES)
{
	struct rune_altern *altern = tal(ctx, struct rune_altern);
	altern->condition = condition;
	altern->fieldname = tal_strdup(altern, fieldname);
	altern->value = tal_strdup(altern, value);
	return altern;
}

struct rune_altern *rune_altern_dup(const tal_t *ctx,
				    const struct rune_altern *altern TAKES)
{
	struct rune_altern *dup;

	if (taken(altern))
		return tal_steal(ctx, (struct rune_altern *)altern);
	dup = tal(ctx, struct rune_altern);
	dup->condition = altern->condition;
	dup->fieldname = tal_strdup(dup, altern->fieldname);
	dup->value = tal_strdup(dup, altern->value);
	return dup;
}

struct rune_restr *rune_restr_dup(const tal_t *ctx,
				  const struct rune_restr *restr TAKES)
{
	struct rune_restr *dup;
	size_t num_altern;

	if (taken(restr))
		return tal_steal(ctx, (struct rune_restr *)restr);

	num_altern = tal_count(restr->alterns);
	dup = tal(ctx, struct rune_restr);
	dup->alterns = tal_arr(dup, struct rune_altern *, num_altern);
	for (size_t i = 0; i < num_altern; i++) {
		dup->alterns[i] = rune_altern_dup(dup->alterns,
						  restr->alterns[i]);
	}
	return dup;
}

struct rune_restr *rune_restr_new(const tal_t *ctx)
{
	struct rune_restr *restr = tal(ctx, struct rune_restr);
	restr->alterns = tal_arr(restr, struct rune_altern *, 0);
	return restr;
}

void rune_restr_add_altern(struct rune_restr *restr,
			   const struct rune_altern *alt TAKES)
{
	size_t num = tal_count(restr->alterns);

	tal_resize(&restr->alterns, num+1);
	restr->alterns[num] = rune_altern_dup(restr->alterns, alt);
}

static bool is_unique_id(const struct rune_altern *alt)
{
	return streq(alt->fieldname, "");
}
	
/* Return unique_id if valid, and sets *version */
static const char *extract_unique_id(const tal_t *ctx,
				     const struct rune_altern *alt,
				     const char **version)
{
	size_t len;
	/* Condition must be '='! */
	if (alt->condition != '=')
		return NULL;

	len = strcspn(alt->value, "-");
	if (alt->value[len])
		*version = tal_strdup(ctx, alt->value + len + 1);
	else
		*version = NULL;
	return tal_strndup(ctx, alt->value, len);
}

bool rune_add_restr(struct rune *rune,
		    const struct rune_restr *restr TAKES)
{
	size_t num = tal_count(rune->restrs);

	/* An empty fieldname is additional correctness checks */
	for (size_t i = 0; i < tal_count(restr->alterns); i++) {
		if (!is_unique_id(restr->alterns[i]))
			continue;

		/* Must be the only alternative */
		if (tal_count(restr->alterns) != 1)
			goto fail;
		/* Must be the first restriction */
		if (num != 0)
			goto fail;

		rune->unique_id = extract_unique_id(rune,
						    restr->alterns[i],
						    &rune->version);
		if (!rune->unique_id)
			goto fail;
	}

	tal_resize(&rune->restrs, num+1);
	rune->restrs[num] = rune_restr_dup(rune->restrs, restr);

	rune_sha256_add_restr(&rune->shactx, rune->restrs[num]);
	return true;

fail:
	if (taken(restr))
		tal_free(restr);
	return false;
}

static const char *rune_restr_test(const tal_t *ctx,
				   const struct rune *rune,
				   const struct rune_restr *restr,
				   const char *(*check)(const tal_t *ctx,
							const struct rune *rune,
							const struct rune_altern *alt,
							void *arg),
				   void *arg)
{
	size_t num = tal_count(restr->alterns);
	const char **errs = tal_arr(NULL, const char *, num);
	char *err;

	/* Only one alternative has to pass! */
	for (size_t i = 0; i < num; i++) {
		errs[i] = check(errs, rune, restr->alterns[i], arg);
		if (!errs[i]) {
			tal_free(errs);
			return NULL;
		}
	}

	err = tal_fmt(ctx, "%s", errs[0]);
	for (size_t i = 1; i < num; i++)
		tal_append_fmt(&err, " AND %s", errs[i]);
	tal_free(errs);
	return err;
}

static const char *cond_test(const tal_t *ctx,
			     const struct rune_altern *alt,
			     const char *complaint,
			     bool cond)
{
	if (cond)
		return NULL;

	return tal_fmt(ctx, "%s %s %s", alt->fieldname, complaint, alt->value);
}

static const char *integer_compare_valid(const tal_t *ctx,
					 const s64 *fieldval_int,
					 const struct rune_altern *alt,
					 s64 *runeval_int)
{
	long l;
	char *p;

	if (!fieldval_int)
		return tal_fmt(ctx, "%s is not an integer field",
			       alt->fieldname);

	errno = 0;
	l = strtol(alt->value, &p, 10);
	if (p == alt->value
	    || *p
	    || ((l == LONG_MIN || l == LONG_MAX) && errno == ERANGE))
		return tal_fmt(ctx, "%s is not a valid integer", alt->value);

	*runeval_int = l;
	return NULL;
}

static int lexo_order(const char *fieldval_str,
		      size_t fieldval_strlen,
		      const char *alt)
{
	int ret = strncmp(fieldval_str, alt, fieldval_strlen);

	/* If alt is same but longer, fieldval is < */
	if (ret == 0 && strlen(alt) > fieldval_strlen)
		ret = -1;
	return ret;
}

static const char *rune_alt_single(const tal_t *ctx,
				   const struct rune_altern *alt,
				   const char *fieldval_str,
				   size_t fieldval_strlen,
				   const s64 *fieldval_int)
{
	char strfield[STR_MAX_CHARS(s64) + 1];
	s64 runeval_int = 0 /* gcc v9.4.0 gets upset with uninitiaized var at -O3 */;
	const char *err;

	/* Caller can't set both! */
	if (fieldval_int) {
		assert(!fieldval_str);
		snprintf(strfield, sizeof(strfield) - 1, "%"PRIi64, *fieldval_int);
		strfield[sizeof(strfield) - 1] = '\0';
		fieldval_str = strfield;
		fieldval_strlen = strlen(strfield);
	}

	switch (alt->condition) {
	case RUNE_COND_IF_MISSING:
		if (!fieldval_str)
			return NULL;
		return tal_fmt(ctx, "%s is present", alt->fieldname);
	case RUNE_COND_EQUAL:
		if (!fieldval_str)
			return tal_fmt(ctx, "%s not present", alt->fieldname);
		return cond_test(ctx, alt, "is not equal to",
				 memeqstr(fieldval_str, fieldval_strlen, alt->value));
	case RUNE_COND_NOT_EQUAL:
		if (!fieldval_str)
			return tal_fmt(ctx, "%s not present", alt->fieldname);
		return cond_test(ctx, alt, "is equal to",
				 !memeqstr(fieldval_str, fieldval_strlen, alt->value));
	case RUNE_COND_BEGINS:
		if (!fieldval_str)
			return tal_fmt(ctx, "%s not present", alt->fieldname);
		return cond_test(ctx, alt, "does not start with",
				 memstarts_str(fieldval_str, fieldval_strlen, alt->value));
	case RUNE_COND_ENDS:
		if (!fieldval_str)
			return tal_fmt(ctx, "%s not present", alt->fieldname);
		return cond_test(ctx, alt, "does not end with",
				 memends_str(fieldval_str, fieldval_strlen, alt->value));
	case RUNE_COND_CONTAINS:
		if (!fieldval_str)
			return tal_fmt(ctx, "%s not present", alt->fieldname);
		return cond_test(ctx, alt, "does not contain",
				 memmem(fieldval_str, fieldval_strlen,
					alt->value, strlen(alt->value)));
	case RUNE_COND_INT_LESS:
		if (!fieldval_str && !fieldval_int)
			return tal_fmt(ctx, "%s not present", alt->fieldname);
		err = integer_compare_valid(ctx, fieldval_int,
					    alt, &runeval_int);
		if (err)
			return err;
		return cond_test(ctx, alt, "is greater or equal to",
				 *fieldval_int < runeval_int);
	case RUNE_COND_INT_GREATER:
		if (!fieldval_str && !fieldval_int)
			return tal_fmt(ctx, "%s not present", alt->fieldname);
		err = integer_compare_valid(ctx, fieldval_int,
					    alt, &runeval_int);
		if (err)
			return err;
		return cond_test(ctx, alt, "is less or equal to",
				 *fieldval_int > runeval_int);
	case RUNE_COND_LEXO_BEFORE:
		if (!fieldval_str)
			return tal_fmt(ctx, "%s not present", alt->fieldname);
		return cond_test(ctx, alt, "is equal to or ordered after",
				 lexo_order(fieldval_str, fieldval_strlen, alt->value) < 0);
	case RUNE_COND_LEXO_AFTER:
		if (!fieldval_str)
			return tal_fmt(ctx, "%s not present", alt->fieldname);
		return cond_test(ctx, alt, "is equal to or ordered before",
				 lexo_order(fieldval_str, fieldval_strlen, alt->value) > 0);
	case RUNE_COND_COMMENT:
		return NULL;
	}
	/* We should never create any other values! */
	abort();
}

const char *rune_alt_single_str(const tal_t *ctx,
				const struct rune_altern *alt,
				const char *fieldval_str,
				size_t fieldval_strlen)
{
	return rune_alt_single(ctx, alt, fieldval_str, fieldval_strlen, NULL);
}

const char *rune_alt_single_int(const tal_t *ctx,
				const struct rune_altern *alt,
				s64 fieldval_int)
{
	return rune_alt_single(ctx, alt, NULL, 0, &fieldval_int);
}

const char *rune_alt_single_missing(const tal_t *ctx,
				    const struct rune_altern *alt)
{
	return rune_alt_single(ctx, alt, NULL, 0, NULL);
}

const char *rune_meets_criteria_(const tal_t *ctx,
				 const struct rune *rune,
				 const char *(*check)(const tal_t *ctx,
						      const struct rune *rune,
						      const struct rune_altern *alt,
						      void *arg),
				 void *arg)
{
	for (size_t i = 0; i < tal_count(rune->restrs); i++) {
		const char *err;

		/* Don't "check" unique id */
		if (i == 0 && is_unique_id(rune->restrs[i]->alterns[0]))
			continue;
		
		err = rune_restr_test(ctx, rune, rune->restrs[i], check, arg);
		if (err)
			return err;
	}
	return NULL;
}

const char *rune_test_(const tal_t *ctx,
		       const struct rune *master,
		       const struct rune *rune,
		       const char *(*check)(const tal_t *ctx,
					    const struct rune *rune,
					    const struct rune_altern *alt,
					    void *arg),
		       void *arg)
{
	const char *err;

	err = rune_is_derived(master, rune);
	if (err)
		return err;
	return rune_meets_criteria_(ctx, rune, check, arg);
}

bool rune_altern_eq(const struct rune_altern *alt1,
		    const struct rune_altern *alt2)
{
	return alt1->condition == alt2->condition
		&& streq(alt1->fieldname, alt2->fieldname)
		&& streq(alt1->value, alt2->value);
}

bool rune_restr_eq(const struct rune_restr *rest1,
		   const struct rune_restr *rest2)
{
	if (tal_count(rest1->alterns) != tal_count(rest2->alterns))
		return false;

	for (size_t i = 0; i < tal_count(rest1->alterns); i++)
		if (!rune_altern_eq(rest1->alterns[i], rest2->alterns[i]))
			return false;
	return true;
}

/* Equal, as in both NULL, or both non-NULL and matching */
bool runestr_eq(const char *a, const char *b)
{
	if (a) {
		if (!b)
			return false;
		return streq(a, b);
	} else
		return b == NULL;
}

bool rune_eq(const struct rune *rune1, const struct rune *rune2)
{
	if (!runestr_eq(rune1->unique_id, rune2->unique_id))
		return false;
	if (!runestr_eq(rune1->version, rune2->version))
		return false;

	if (memcmp(rune1->shactx.s, rune2->shactx.s, sizeof(rune1->shactx.s)))
		return false;
	if (rune1->shactx.bytes != rune2->shactx.bytes)
		return false;
	if (memcmp(rune1->shactx.buf.u8, rune2->shactx.buf.u8,
		   rune1->shactx.bytes % 64))
		return false;

	if (tal_count(rune1->restrs) != tal_count(rune2->restrs))
		return false;

	for (size_t i = 0; i < tal_count(rune1->restrs); i++)
		if (!rune_restr_eq(rune1->restrs[i], rune2->restrs[i]))
			return false;
	return true;
}
