#include <assert.h>
#include <stdlib.h>
#include <string.h>

#include "policy.h"

#include "ascii.h"
#include "crypto-internal.h"
#include "policy-internal.h"
#include "sort-internal.h"
#include "xalloc.h"

static inline bool
sigsum_buffer_equal (const struct sigsum_buffer *a, const struct sigsum_buffer *b)
{
  return a->size == b->size && memcmp (a->data, b->data, a->size) == 0;
}

DEFINE_LIST (token_list, token_alloc, struct sigsum_buffer);

/* Split into white-space separated fields, returns zero fields for
   lines that are comments or whitespace-only. */
static void
split_line (struct sigsum_buffer *line, struct token_list *fields)
{
  fields->size = 0;

  for (;;)
    {
      struct sigsum_buffer token;

      _sigsum_buffer_token (line, &token);
      if (!token.size)
	return;

      /* Skip comment lines */
      if (!fields->size && token.data[0] == '#')
	return;

      *(token_alloc (fields)) = token;
    }
}

static int
parse_key (const struct sigsum_buffer *token, struct sigsum_key_with_hash *key)
{
  if (! (token->size == 64
	 && sigsum_decode_hex_key (&key->key, token->data)))
    return 0;

  sigsum_hash_key (&key->hash, &key->key);
  return 1;
}

struct witness_key {
  /* First member, to be able to use same key_sort / key_cmp. */
  struct sigsum_key_with_hash key;
  /* Original index in list. */
  size_t id;
};

static struct sigsum_key_with_hash *
witnesses_copy (size_t n, const struct witness_key *witnesses)
{
  struct sigsum_key_with_hash *keys = _sigsum_xalloc (n * sizeof (keys[0]));
  for (size_t i = 0; i < n; i++)
    keys[i] = witnesses[i].key;

  return keys;
}

DEFINE_LIST (witness_list, witness_alloc, struct witness_key);
DEFINE_LIST (key_list, key_alloc, struct sigsum_key_with_hash);
DEFINE_LIST (def_list, def_alloc, struct sigsum_policy_def);

/* Returns index of defined name, or (size_t) -1 if not found. */
static size_t
def_lookup (const struct def_list *defs, const struct sigsum_buffer *name)
{
  size_t i;
  for (i = 0; i < defs->size; i++)
    if (sigsum_buffer_equal(&defs->list[i].name, name))
      return i;
  return -1;
}

/* Frees the group list, and all member lists. */
static void
defs_free (struct def_list *defs)
{
  for (size_t i = 0; i < defs->size; i++)
    {
      if (defs->list[i].type == SIGSUM_POLICY_DEF_GROUP)
	free (defs->list[i].group.members);
    }
  free (defs->list);
}

static struct sigsum_policy_def *
def_new (struct def_list *defs, const struct sigsum_buffer *name,
	 enum sigsum_policy_def_type type)
{
  if (def_lookup (defs, name) != (size_t) -1)
    return NULL;

  struct sigsum_policy_def *def = def_alloc (defs);
  def->name = *name;
  def->is_referenced = false;
  /* Assigned later. */
  def->bytecode_size = 0;

  def->type = type;
  return def;
}

static bool
def_new_witness (struct def_list *defs, const struct sigsum_buffer *name, size_t id)
{
  struct sigsum_policy_def *def = def_new (defs, name, SIGSUM_POLICY_DEF_WITNESS);
  if (!def)
    return false;

  def->witness_index = id;
  return true;
}

static bool
def_new_group (struct def_list *defs, const struct sigsum_buffer *name,
	       size_t num_members, size_t threshold, size_t *members)
{
  assert (num_members > 0);
  assert (threshold > 0);
  assert (threshold <= num_members);

  struct sigsum_policy_def *def = def_new (defs, name, SIGSUM_POLICY_DEF_GROUP);
  if (!def)
    return false;

  struct sigsum_policy_group *group = &def->group;
  group->num_members = num_members;
  group->threshold = threshold;
  group->members = members;
  return true;
}

static void
def_new_none (struct def_list *defs)
{
  assert (defs->size == 0);

  static const struct sigsum_buffer name = { 4, "none" };
  struct sigsum_policy_def *def = def_new (defs, &name, SIGSUM_POLICY_DEF_NONE);
  assert (def != NULL);
}

static size_t *
members_resolve (struct def_list *defs,
		 size_t num_members, const struct sigsum_buffer *names,
		 const char **err)
{
  size_t *members = _sigsum_xalloc (num_members * sizeof (*members));

#define FAIL(error_msg) do { \
    free (members);	     \
    *err = error_msg;	     \
    return NULL;	     \
  } while (0)

  for (size_t i = 0; i < num_members; i++)
    {
      size_t m = def_lookup (defs, &names[i]);
      if (m == (size_t) -1)
	FAIL ("unknown group member");
      if (defs->list[m].type == SIGSUM_POLICY_DEF_NONE)
	FAIL ("invalid none member");
      if (defs->list[m].is_referenced)
	FAIL ("already a member");

      defs->list[m].is_referenced = true;
      members[i] = m;
    }
  return members;
#undef FAIL
}
/* Key must be a string literal. */
#define MATCH(token, key)					\
  ((token).size == sizeof (key) - 1				\
   && memcmp ((token).data, (key), sizeof (key) - 1) == 0)

bool
sigsum_policy_parse (struct sigsum_policy *policy,
		     size_t size, const char *data,
		     struct sigsum_error *err)
{
  struct sigsum_buffer buffer = { size, data };
  struct key_list logs = { 0, 0, NULL };
  struct witness_list witnesses = { 0, 0, NULL };
  struct token_list tokens = { 0, 0, NULL };
  struct def_list defs = { 0, 0, NULL };
  size_t quorum = (size_t) -1;
  size_t lineno = 0;

  /* Add "none" group. */
  def_new_none (&defs);

  while (buffer.size > 0)
    {
      struct sigsum_buffer line_buffer;
      _sigsum_buffer_line (&buffer, &line_buffer);
      lineno++;

      /* Reject invalid control characters. */
      for (size_t i = 0; i < line_buffer.size; i++)
	{
	  uint8_t c = line_buffer.data[i];
	  if (c == '\t' || (c >= 0x20 && c != 0x7F))
	    continue;

#define FAIL(error_msg) do {			\
	    if (err)				\
	      {					\
		err->msg = error_msg;		\
		err->line = lineno;		\
	      }					\
	    goto fail;				\
	  } while (0)

	  FAIL ("invalid control character");
	fail:
	  free (logs.list);
	  free (witnesses.list);
	  free (tokens.list);
	  defs_free (&defs);
	  return false;
	}
      split_line (&line_buffer, &tokens);

      if (!tokens.size)
	/* Empty or all-comment line */
	continue;

      if (MATCH (tokens.list[0], "log"))
	{
	  if (tokens.size < 2 || tokens.size > 3)
	    FAIL ("bad log line");
	  struct sigsum_key_with_hash *entry = key_alloc (&logs);
	  if (!parse_key (&tokens.list[1], entry))
	    FAIL ("bad log key");
	}
      else if (MATCH (tokens.list[0], "witness"))
	{
	  if (tokens.size < 3 || tokens.size > 4)
	    FAIL ("bad witness line");
	  size_t id = witnesses.size;

	  struct witness_key *entry = witness_alloc (&witnesses);
	  entry->id = id;

	  if (!def_new_witness (&defs, &tokens.list[1], id))
	    FAIL ("duplicate witness name");
	  if (!parse_key (&tokens.list[2], &entry->key))
	    FAIL ("bad witness key");
	}

      else if (MATCH (tokens.list[0], "group"))
	{
	  if (tokens.size < 4)
	    FAIL ("bad group line");

	  size_t num_members = tokens.size - 3;
	  sigsum_size_t threshold;
	  if (MATCH (tokens.list[2], "any"))
	    threshold = 1;
	  else if (MATCH (tokens.list[2], "all"))
	    threshold = num_members;
	  else
	    {
	      if (! sigsum_decode_decimal (&threshold, tokens.list[2].size, tokens.list[2].data)
		  || threshold < 1 || threshold > num_members)
		FAIL ("bad group threshold");
	    }

	  const char *member_err;
	  size_t *members = members_resolve (&defs, num_members, tokens.list + 3,
					     &member_err);
	  if (!members)
	    FAIL(member_err);

	  if (!def_new_group (&defs, &tokens.list[1], num_members, threshold, members))
	    {
	      free (members);
	      FAIL ("duplicate group name");
	    }
	}
      else if (MATCH (tokens.list[0], "quorum"))
	{
	  if (tokens.size != 2)
	    FAIL ("bad quorum line");
	  if (quorum != (size_t) -1)
	    FAIL ("duplicate quorum line");
	  quorum = def_lookup (&defs, &tokens.list[1]);
	  if (quorum == (size_t) -1)
	    FAIL ("unknown quorum");
	}
      else
	FAIL ("unknown policy keyword");
    }
  if (quorum == (size_t) -1)
    FAIL ("missing quorum definition");

  free (tokens.list); tokens.list = NULL;

  if (!SIGSUM_SORT_BY_HASH (logs.size, logs.list, hash))
    FAIL ("duplicate log key");

  if (!SIGSUM_SORT_BY_HASH (witnesses.size, witnesses.list, key.hash))
    FAIL ("duplicate witness key");

#undef FAIL

  /* Compute reverse permutation; for each original index, record the
     index it was moved to by the sorting. */
  size_t *permutation = _sigsum_xalloc (witnesses.size * sizeof (size_t));
  for (size_t i = 0; i < witnesses.size; i++)
    permutation[witnesses.list[i].id] = i;

  /* Apply witness renumbering to the definitions. */
  for (size_t i = 0; i < defs.size; i++)
    {
      struct sigsum_policy_def *def = &defs.list[i];
      if (def->type == SIGSUM_POLICY_DEF_WITNESS)
	def->witness_index = permutation[def->witness_index];
    }
  free (permutation);

  _sigsum_bytecode_compile_quorum (&policy->quorum, defs.list, quorum);

  defs_free (&defs);

  policy->logs.num_keys = logs.size;
  policy->logs.keys = logs.list;
  policy->witnesses.num_keys = witnesses.size;
  /* We need to allocate a copy without the extra indices. */
  policy->witnesses.keys = witnesses_copy (witnesses.size, witnesses.list);

  free (witnesses.list);

  return true;
}

void
sigsum_policy_clear (struct sigsum_policy *policy)
{
  XFREE (policy->logs.keys);
  XFREE (policy->witnesses.keys);
  XFREE (policy->quorum.bytecode);
}
