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

#include "ascii.h"
#include "ascii-internal.h"
#include "test-utils.h"

static void
test_decimal (void)
{
  static const struct {
    const char *in;
    uint64_t out;
  } valid[] = {
    { "0", 0, },
    { "1", 1, },
    { "123456789", 123456789, },
    { "4611686018427387904", (uint64_t) 1 << 62, },
    { "9223372036854775807", ((uint64_t) 1 << 63)-1, },
  };

  static const char *invalid[] = {
    "", "a", "01", "123x5",
    "9223372036854775808",  /* 2^63, last digit makes it too large. */
    "9223372036854775810",  /* 2^63 + 2, last x10 makes it too large. */
    "18446744073709551620"  /* 2^64 + 4, last x10 would overflow. */
  };

  for (size_t i = 0; i < sizeof (valid) / sizeof(valid[0]); i++)
    {
      uint64_t x = 0xdeadbeef;
      if (!sigsum_decode_decimal (&x, strlen (valid[i].in), valid[i].in))
	die ("sigsum_decode_decimal: decoding valid decimal number %s failed\n", valid[i].in);
      if (x != valid[i].out)
	die ("sigsum_decode_decimal: unexpected decoding of decimal %s, got %llu\n",
	     valid[i].in, (unsigned long long) x);
    }

  for (size_t i = 0; i < sizeof (invalid) / sizeof(invalid[0]); i++)
    {
      uint64_t x = 0xdeadbeef;
      if (sigsum_decode_decimal (&x, strlen (invalid[i]), invalid[i]))
	die ("sigsum_decode_decimal: unexpected decoding of invalid decimal %s, got %llu\n",
	     invalid[i], (unsigned long long) x);
    }
}

static void
test_hex (void)
{
#define TEST(in, size, ...) \
  { in, size, (LITERAL_STATIC const uint8_t[size]){ __VA_ARGS__ } }
  static const uint8_t dummy = 0;
  LITERAL_STATIC const struct {
    const char *in;
    size_t size;
    const uint8_t *out;
  } valid[] = {
    { "", 0, &dummy },
    {"xx", 0, &dummy },
    TEST ("0123456789abcdef", 8, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef),
    TEST ("deadbeaf", 4, 0xde, 0xad, 0xbe, 0xaf),
    TEST ("0123456789ABCDEF", 8, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef),
    TEST ("DEADBEAF", 4, 0xde, 0xad, 0xbe, 0xaf),
    TEST ("DeAdBEaf", 4, 0xde, 0xad, 0xbe, 0xaf),
  };
#undef TEST

  static const struct {
    const char *in;
    size_t size;
  } invalid[] = {
    {"", 1 },
    {"0", 1 },
    {"abcx", 2 },
  };

  for (size_t i = 0; i < sizeof (valid) / sizeof(valid[0]); i++)
    {
      uint8_t res[8];
      assert (valid[i].size <= sizeof(res));
      if (!_sigsum_decode_hex (valid[i].size, res, valid[i].in))
	die ("_sigsum_decode_hex: decoding valid hex %s, size %zd, failed\n",
	     valid[i].in, valid[i].size);
      if (memcmp (res, valid[i].out, valid[i].size) != 0)
	die ("_sigsum_decode_hex: unexpected output decoding valid hex %s, size %zd\n",
	     valid[i].in, valid[i].size);
    }

  for (size_t i = 0; i < sizeof (invalid) / sizeof(invalid[0]); i++)
    {
      uint8_t res[8];
      assert (invalid[i].size <= sizeof(res));
      if (_sigsum_decode_hex (invalid[i].size, res, invalid[i].in))
	die ("_sigsum_decode_hex: unexpected decoding of invalid hex %s, size %zd\n",
	     invalid[i].in, invalid[i].size);
    }
}

static void
test_buffer_line (void)
{
#define TEST(in, size, ...) \
  { in, size, (LITERAL_STATIC const char *[size]){ __VA_ARGS__ } }
  LITERAL_STATIC const struct {
    const char *in;
    size_t size;
    const char * const *out;
  } valid[] = {
    TEST ("", 1, ""),
    TEST ("", 2, "", ""),
    TEST ("foo", 2, "foo", ""),
    TEST ("\nfoo", 3, "", "foo", ""),
    TEST ("foo\n", 2, "foo", ""),
    TEST ("foo\r\n", 2, "foo\r", ""),
    TEST ("foo\nbar", 3, "foo", "bar", ""),
    TEST ("foo\nbar\n", 3, "foo", "bar", ""),
  };
#undef TEST

  for (size_t i = 0; i < sizeof (valid) / sizeof(valid[0]); i++)
    {
      struct sigsum_buffer input = {
	strlen(valid[i].in), valid[i].in
      };
      for (size_t j = 0; j < valid[i].size; j++)
	{
	  struct sigsum_buffer line;
	  _sigsum_buffer_line (&input, &line);
	  if (line.size != strlen (valid[i].out[j]))
	    die ("_sigsum_buffer_line: unexpected line size %zd for test %zd, line %zd",
		 line.size, i, j);
	  if (memcmp (line.data, valid[i].out[j], line.size) != 0)
	    die ("_sigsum_buffer_line: unexpected line data for test %zd, line %zd", i, j);
	}
      if (input.size > 0)
	die ("_sigsum_buffer_line: input not consumed for for test %zd", i);
    }
}

static void
test_buffer_line_strict (void)
{
#define TEST(in, size, ...) \
  { in, size, (LITERAL_STATIC const char *[size]){ __VA_ARGS__ } }
  struct test {
    const char *in;
    size_t size;
    const char * const *out;
  };
  LITERAL_STATIC const struct test valid[] = {
    TEST ("\nfoo\n", 2, "", "foo"),
    TEST ("foo\n", 1, "foo"),
    TEST ("foo\r\n", 1, "foo\r"),
    TEST ("foo\nbar\n\n", 3, "foo", "bar", ""),
  };
  LITERAL_STATIC const struct test invalid[] = {
    { "", 0, NULL },
    { "foo", 0, NULL },
    TEST ("\nfoo", 1, "" ),
    TEST ("foo\nbar", 1, "foo"),
  };
#undef TEST

  for (size_t i = 0; i < sizeof (valid) / sizeof(valid[0]); i++)
    {
      struct sigsum_buffer input = {
	strlen(valid[i].in), valid[i].in
      };
      for (size_t j = 0; j < valid[i].size; j++)
	{
	  struct sigsum_buffer line;
	  if (!_sigsum_buffer_line_strict (&input, &line))
	    die ("_sigsum_buffer_line_strict: failed for test %zd, line %zd", i, j);
	  if (line.size != strlen (valid[i].out[j]))
	    die ("_sigsum_buffer_line_strict: unexpected line size %zd for test %zd, line %zd",
		 line.size, i, j);
	  if (memcmp (line.data, valid[i].out[j], line.size) != 0)
	    die ("_sigsum_buffer_line_strict: unexpected line data for test %zd, line %zd", i, j);
	}
      if (input.size > 0)
	die ("_sigsum_buffer_line_strict: input not consumed for for test %zd", i);
    }
  for (size_t i = 0; i < sizeof (invalid) / sizeof(invalid[0]); i++)
    {
      struct sigsum_buffer input = {
	strlen(invalid[i].in), invalid[i].in
      };
      struct sigsum_buffer line;
      for (size_t j = 0; j < invalid[i].size; j++)
	{
	  if (!_sigsum_buffer_line_strict (&input, &line))
	    die ("_sigsum_buffer_line_strict: failed for test %zd, line %zd", i, j);
	  if (line.size != strlen (invalid[i].out[j]))
	    die ("_sigsum_buffer_line_strict: unexpected line size %zd for test %zd, line %zd",
		 line.size, i, j);
	  if (memcmp (line.data, invalid[i].out[j], line.size) != 0)
	    die ("_sigsum_buffer_line_strict: unexpected line data for test %zd, line %zd", i, j);
	}
      if (_sigsum_buffer_line_strict (&input, &line))
	die ("_sigsum_buffer_line_strict: failed for test %zd, line w/o newline accepted", i);
    }
}

int
main (void)
{
  test_decimal ();
  test_hex ();
  test_buffer_line ();
  test_buffer_line_strict ();

  return 0;
}
