/*
  Copyright 2011-2018 David Robillard <http://drobilla.net>

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted, provided that the above
  copyright notice and this permission notice appear in all copies.

  THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#undef NDEBUG

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

#include "serd/serd.h"

#define WILDCARD_NODE NULL

#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
#define RDF_FIRST NS_RDF "first"
#define RDF_REST NS_RDF "rest"
#define RDF_NIL NS_RDF "nil"

#define N_OBJECTS_PER 2U

typedef const SerdNode* Quad[4];

typedef struct {
  Quad query;
  int  expected_num_results;
} QueryTest;

static const SerdNode*
manage(SerdWorld* world, SerdNode* node)
{
  return serd_nodes_manage(serd_world_nodes(world), node);
}

static const SerdNode*
uri(SerdWorld* world, const unsigned num)
{
  char str[] = "eg:0000000000";
  snprintf(str + 3, 11, "%03u", num);
  return manage(world, serd_new_uri(SERD_STATIC_STRING(str)));
}

static int
generate(SerdWorld*      world,
         SerdModel*      model,
         size_t          n_quads,
         const SerdNode* graph)
{
  SerdNodes* nodes = serd_world_nodes(world);

  for (unsigned i = 0; i < n_quads; ++i) {
    unsigned num = (i * N_OBJECTS_PER) + 1U;

    const SerdNode* ids[2 + N_OBJECTS_PER];
    for (unsigned j = 0; j < 2 + N_OBJECTS_PER; ++j) {
      ids[j] = uri(world, num++);
    }

    for (unsigned j = 0; j < N_OBJECTS_PER; ++j) {
      assert(!serd_model_add(model, ids[0], ids[1], ids[2 + j], graph));
    }
  }

  // Add some literals

  // (98 4 "hello") and (98 4 "hello"^^<5>)
  const SerdNode* hello =
    manage(world, serd_new_string(SERD_STATIC_STRING("hello")));

  const SerdNode* hello_gb =
    manage(world,
           serd_new_plain_literal(SERD_STATIC_STRING("hello"),
                                  SERD_STATIC_STRING("en-gb")));

  const SerdNode* hello_us =
    manage(world,
           serd_new_plain_literal(SERD_STATIC_STRING("hello"),
                                  SERD_STATIC_STRING("en-us")));

  const SerdNode* hello_t4 = serd_nodes_manage(
    nodes,
    serd_new_typed_literal(SERD_STATIC_STRING("hello"),
                           serd_node_string_view(uri(world, 4))));

  const SerdNode* hello_t5 = serd_nodes_manage(
    nodes,
    serd_new_typed_literal(SERD_STATIC_STRING("hello"),
                           serd_node_string_view(uri(world, 5))));

  assert(!serd_model_add(model, uri(world, 98), uri(world, 4), hello, graph));
  assert(
    !serd_model_add(model, uri(world, 98), uri(world, 4), hello_t5, graph));

  // (96 4 "hello"^^<4>) and (96 4 "hello"^^<5>)
  assert(
    !serd_model_add(model, uri(world, 96), uri(world, 4), hello_t4, graph));
  assert(
    !serd_model_add(model, uri(world, 96), uri(world, 4), hello_t5, graph));

  // (94 5 "hello") and (94 5 "hello"@en-gb)
  assert(!serd_model_add(model, uri(world, 94), uri(world, 5), hello, graph));
  assert(
    !serd_model_add(model, uri(world, 94), uri(world, 5), hello_gb, graph));

  // (92 6 "hello"@en-us) and (92 6 "hello"@en-gb)
  assert(
    !serd_model_add(model, uri(world, 92), uri(world, 6), hello_us, graph));
  assert(
    !serd_model_add(model, uri(world, 92), uri(world, 6), hello_gb, graph));

  // (14 6 "bonjour"@fr) and (14 6 "salut"@fr)
  const SerdNode* const bonjour =
    manage(world,
           serd_new_plain_literal(SERD_STATIC_STRING("bonjour"),
                                  SERD_STATIC_STRING("fr")));

  const SerdNode* const salut =
    manage(world,
           serd_new_plain_literal(SERD_STATIC_STRING("salut"),
                                  SERD_STATIC_STRING("fr")));

  assert(!serd_model_add(model, uri(world, 14), uri(world, 6), bonjour, graph));
  assert(!serd_model_add(model, uri(world, 14), uri(world, 6), salut, graph));

  // Attempt to add duplicates
  assert(serd_model_add(model, uri(world, 14), uri(world, 6), salut, graph));

  // Add a blank node subject
  const SerdNode* ablank =
    manage(world, serd_new_blank(SERD_STATIC_STRING("ablank")));

  assert(!serd_model_add(model, ablank, uri(world, 6), salut, graph));

  // Add statement with URI object
  assert(!serd_model_add(model, ablank, uri(world, 6), uri(world, 7), graph));

  return EXIT_SUCCESS;
}

static int
test_read(SerdWorld*      world,
          SerdModel*      model,
          const SerdNode* g,
          const unsigned  n_quads)
{
  SerdIter*            iter = serd_model_begin(model);
  const SerdStatement* prev = NULL;
  for (; !serd_iter_equals(iter, serd_model_end(model)); serd_iter_next(iter)) {
    const SerdStatement* statement = serd_iter_get(iter);
    assert(statement);
    assert(serd_statement_subject(statement));
    assert(serd_statement_predicate(statement));
    assert(serd_statement_object(statement));
    assert(!serd_statement_equals(statement, prev));
    assert(!serd_statement_equals(prev, statement));
    prev = statement;
  }

  // Attempt to increment past end
  assert(serd_iter_next(iter));
  serd_iter_free(iter);

  const SerdStringView s = SERD_STATIC_STRING("hello");

  const SerdNode* plain_hello = manage(world, serd_new_string(s));

  const SerdNode* type4_hello = manage(
    world, serd_new_typed_literal(s, serd_node_string_view(uri(world, 4))));

  const SerdNode* type5_hello = manage(
    world, serd_new_typed_literal(s, serd_node_string_view(uri(world, 5))));

  const SerdNode* gb_hello =
    manage(world, serd_new_plain_literal(s, SERD_STATIC_STRING("en-gb")));

  const SerdNode* us_hello =
    manage(world, serd_new_plain_literal(s, SERD_STATIC_STRING("en-us")));

#define NUM_PATTERNS 18

  QueryTest patterns[NUM_PATTERNS] = {
    {{NULL, NULL, NULL}, (int)(n_quads * N_OBJECTS_PER) + 12},
    {{uri(world, 1), WILDCARD_NODE, WILDCARD_NODE}, 2},
    {{uri(world, 9), uri(world, 9), uri(world, 9)}, 0},
    {{uri(world, 1), uri(world, 2), uri(world, 4)}, 1},
    {{uri(world, 3), uri(world, 4), WILDCARD_NODE}, 2},
    {{WILDCARD_NODE, uri(world, 2), uri(world, 4)}, 1},
    {{WILDCARD_NODE, WILDCARD_NODE, uri(world, 4)}, 1},
    {{uri(world, 1), WILDCARD_NODE, WILDCARD_NODE}, 2},
    {{uri(world, 1), WILDCARD_NODE, uri(world, 4)}, 1},
    {{WILDCARD_NODE, uri(world, 2), WILDCARD_NODE}, 2},
    {{uri(world, 98), uri(world, 4), plain_hello}, 1},
    {{uri(world, 98), uri(world, 4), type5_hello}, 1},
    {{uri(world, 96), uri(world, 4), type4_hello}, 1},
    {{uri(world, 96), uri(world, 4), type5_hello}, 1},
    {{uri(world, 94), uri(world, 5), plain_hello}, 1},
    {{uri(world, 94), uri(world, 5), gb_hello}, 1},
    {{uri(world, 92), uri(world, 6), gb_hello}, 1},
    {{uri(world, 92), uri(world, 6), us_hello}, 1}};

  Quad match = {uri(world, 1), uri(world, 2), uri(world, 4), g};
  assert(serd_model_ask(model, match[0], match[1], match[2], match[3]));

  Quad nomatch = {uri(world, 1), uri(world, 2), uri(world, 9), g};
  assert(
    !serd_model_ask(model, nomatch[0], nomatch[1], nomatch[2], nomatch[3]));

  assert(!serd_model_get(model, NULL, NULL, uri(world, 3), g));
  assert(!serd_model_get(model, uri(world, 1), uri(world, 99), NULL, g));

  assert(serd_node_equals(
    serd_model_get(model, uri(world, 1), uri(world, 2), NULL, g),
    uri(world, 3)));
  assert(serd_node_equals(
    serd_model_get(model, uri(world, 1), NULL, uri(world, 3), g),
    uri(world, 2)));
  assert(serd_node_equals(
    serd_model_get(model, NULL, uri(world, 2), uri(world, 3), g),
    uri(world, 1)));
  if (g) {
    assert(serd_node_equals(
      serd_model_get(model, uri(world, 1), uri(world, 2), uri(world, 3), NULL),
      g));
  }

  for (unsigned i = 0; i < NUM_PATTERNS; ++i) {
    QueryTest test = patterns[i];
    Quad      pat  = {test.query[0], test.query[1], test.query[2], g};

    SerdRange* range = serd_model_range(model, pat[0], pat[1], pat[2], pat[3]);
    int        num_results = 0;
    for (; !serd_range_empty(range); serd_range_next(range)) {
      ++num_results;

      const SerdStatement* first = serd_range_front(range);
      assert(first);
      assert(serd_statement_matches(first, pat[0], pat[1], pat[2], pat[3]));
    }

    serd_range_free(range);

    assert(num_results == test.expected_num_results);
  }

  // Query blank node subject

  const SerdNode* ablank =
    manage(world, serd_new_blank(SERD_STATIC_STRING("ablank")));

  Quad       pat         = {ablank, 0, 0};
  int        num_results = 0;
  SerdRange* range = serd_model_range(model, pat[0], pat[1], pat[2], pat[3]);
  for (; !serd_range_empty(range); serd_range_next(range)) {
    ++num_results;
    const SerdStatement* statement = serd_range_front(range);
    assert(serd_statement_matches(statement, pat[0], pat[1], pat[2], pat[3]));
  }
  serd_range_free(range);

  assert(num_results == 2);

  // Test nested queries
  const SerdNode* last_subject = 0;
  range = serd_model_range(model, NULL, NULL, NULL, NULL);
  for (; !serd_range_empty(range); serd_range_next(range)) {
    const SerdStatement* statement = serd_range_front(range);
    const SerdNode*      subject   = serd_statement_subject(statement);
    if (subject == last_subject) {
      continue;
    }

    Quad       subpat = {subject, 0, 0};
    SerdRange* subrange =
      serd_model_range(model, subpat[0], subpat[1], subpat[2], subpat[3]);
    const SerdStatement* substatement    = serd_range_front(subrange);
    uint64_t             num_sub_results = 0;
    assert(serd_statement_subject(substatement) == subject);
    for (; !serd_range_empty(subrange); serd_range_next(subrange)) {
      assert(serd_statement_matches(serd_range_front(subrange),
                                    subpat[0],
                                    subpat[1],
                                    subpat[2],
                                    subpat[3]));
      ++num_sub_results;
    }
    serd_range_free(subrange);
    assert(num_sub_results == N_OBJECTS_PER);

    uint64_t count = serd_model_count(model, subject, 0, 0, 0);
    assert(count == num_sub_results);

    last_subject = subject;
  }
  serd_range_free(range);

  return 0;
}

static SerdStatus
expected_error(void* handle, const SerdLogEntry* entry)
{
  (void)handle;

  fprintf(stderr, "expected: ");
  vfprintf(stderr, entry->fmt, *entry->args);
  return SERD_SUCCESS;
}

static int
test_free_null(SerdWorld* world, const unsigned n_quads)
{
  (void)world;
  (void)n_quads;

  serd_model_free(NULL); // Shouldn't crash
  return 0;
}

static int
test_get_world(SerdWorld* world, const unsigned n_quads)
{
  (void)n_quads;

  SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);
  assert(serd_model_world(model) == world);
  serd_model_free(model);
  return 0;
}

static int
test_get_flags(SerdWorld* world, const unsigned n_quads)
{
  (void)n_quads;

  const SerdModelFlags flags = SERD_INDEX_OPS | SERD_INDEX_GRAPHS;
  SerdModel*           model = serd_model_new(world, flags);
  assert(serd_model_flags(model) == (SERD_INDEX_SPO | flags));
  serd_model_free(model);
  return 0;
}

static int
test_all_begin(SerdWorld* world, const unsigned n_quads)
{
  (void)n_quads;

  SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);
  SerdRange* all   = serd_model_all(model);
  SerdIter*  begin = serd_model_find(model, NULL, NULL, NULL, NULL);
  assert(serd_iter_equals(serd_range_begin(all), begin));
  assert(serd_iter_equals(serd_range_cbegin(all), begin));

  serd_range_free(all);
  serd_iter_free(begin);
  serd_model_free(model);
  return 0;
}

static int
test_add_null(SerdWorld* world, const unsigned n_quads)
{
  (void)n_quads;

  SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);

  serd_world_set_log_func(world, expected_error, NULL);

  assert(serd_model_add(model, 0, 0, 0, 0));
  assert(serd_model_add(model, uri(world, 1), 0, 0, 0));
  assert(serd_model_add(model, uri(world, 1), uri(world, 2), 0, 0));
  assert(serd_model_empty(model));

  serd_model_free(model);
  return 0;
}

static int
test_add_with_iterator(SerdWorld* world, const unsigned n_quads)
{
  (void)n_quads;

  SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);

  serd_world_set_log_func(world, expected_error, NULL);
  assert(
    !serd_model_add(model, uri(world, 1), uri(world, 2), uri(world, 3), 0));

  // Add a statement with an active iterator
  SerdIter* iter = serd_model_begin(model);
  assert(
    !serd_model_add(model, uri(world, 1), uri(world, 2), uri(world, 4), 0));

  // Check that iterator has been invalidated
  assert(!serd_iter_get(iter));
  assert(serd_iter_next(iter));

  serd_iter_free(iter);
  serd_model_free(model);
  return 0;
}

static int
test_erase_with_iterator(SerdWorld* world, const unsigned n_quads)
{
  (void)n_quads;

  SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);

  serd_world_set_log_func(world, expected_error, NULL);
  assert(
    !serd_model_add(model, uri(world, 1), uri(world, 2), uri(world, 3), 0));
  assert(
    !serd_model_add(model, uri(world, 4), uri(world, 5), uri(world, 6), 0));

  // Erase a statement with an active iterator
  SerdIter* iter1 = serd_model_begin(model);
  SerdIter* iter2 = serd_model_begin(model);
  assert(!serd_model_erase(model, iter1));

  // Check that erased iterator points to the next statement
  const SerdStatement* const s1 = serd_iter_get(iter1);
  assert(s1);
  assert(
    serd_statement_matches(s1, uri(world, 4), uri(world, 5), uri(world, 6), 0));

  // Check that other iterator has been invalidated
  assert(!serd_iter_get(iter2));
  assert(serd_iter_next(iter2));

  serd_iter_free(iter2);
  serd_iter_free(iter1);
  serd_model_free(model);
  return 0;
}

static int
test_add_erase(SerdWorld* world, const unsigned n_quads)
{
  (void)n_quads;

  SerdModel* model = serd_model_new(world, SERD_INDEX_SPO | SERD_INDEX_GRAPHS);

  // Add (s p "hello")
  const SerdNode* s = uri(world, 1);
  const SerdNode* p = uri(world, 2);
  const SerdNode* hello =
    manage(world, serd_new_string(SERD_STATIC_STRING("hello")));

  assert(!serd_model_add(model, s, p, hello, 0));
  assert(serd_model_ask(model, s, p, hello, 0));

  // Add (s p "hi")
  const SerdNode* hi = manage(world, serd_new_string(SERD_STATIC_STRING("hi")));
  assert(!serd_model_add(model, s, p, hi, NULL));
  assert(serd_model_ask(model, s, p, hi, 0));

  // Erase (s p "hi")
  SerdIter* iter = serd_model_find(model, s, p, hi, NULL);
  assert(!serd_model_erase(model, iter));
  assert(serd_model_size(model) == 1);
  serd_iter_free(iter);

  // Check that erased statement can not be found
  SerdRange* empty = serd_model_range(model, s, p, hi, NULL);
  assert(serd_range_empty(empty));
  serd_range_free(empty);

  serd_model_free(model);
  return 0;
}

static int
test_erase_all(SerdWorld* world, const unsigned n_quads)
{
  SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);
  generate(world, model, n_quads, NULL);

  SerdIter* iter = serd_model_begin(model);
  while (!serd_iter_equals(iter, serd_model_end(model))) {
    assert(!serd_model_erase(model, iter));
  }

  serd_iter_free(iter);
  serd_model_free(model);
  return 0;
}

static int
test_copy(SerdWorld* world, const unsigned n_quads)
{
  SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);
  generate(world, model, n_quads, NULL);

  SerdModel* copy = serd_model_copy(model);
  assert(serd_model_equals(model, copy));

  serd_model_free(model);
  serd_model_free(copy);
  return 0;
}

static int
test_equals(SerdWorld* world, const unsigned n_quads)
{
  SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);
  generate(world, model, n_quads, NULL);
  serd_model_add(
    model, uri(world, 0), uri(world, 1), uri(world, 2), uri(world, 3));

  assert(serd_model_equals(NULL, NULL));
  assert(!serd_model_equals(NULL, model));
  assert(!serd_model_equals(model, NULL));

  SerdModel* empty = serd_model_new(world, SERD_INDEX_SPO);
  assert(!serd_model_equals(model, empty));

  SerdModel* different = serd_model_new(world, SERD_INDEX_SPO);
  generate(world, different, n_quads, NULL);
  serd_model_add(
    different, uri(world, 1), uri(world, 1), uri(world, 2), uri(world, 3));

  assert(serd_model_size(model) == serd_model_size(different));
  assert(!serd_model_equals(model, different));

  serd_model_free(model);
  serd_model_free(empty);
  serd_model_free(different);
  return 0;
}

static int
test_find_past_end(SerdWorld* world, const unsigned n_quads)
{
  (void)n_quads;

  SerdModel*      model = serd_model_new(world, SERD_INDEX_SPO);
  const SerdNode* s     = uri(world, 1);
  const SerdNode* p     = uri(world, 2);
  const SerdNode* o     = uri(world, 3);
  assert(!serd_model_add(model, s, p, o, 0));
  assert(serd_model_ask(model, s, p, o, 0));

  const SerdNode* huge  = uri(world, 999);
  SerdRange*      range = serd_model_range(model, huge, huge, huge, 0);
  assert(serd_range_empty(range));

  serd_range_free(range);
  serd_model_free(model);
  return 0;
}

static int
test_range(SerdWorld* world, const unsigned n_quads)
{
  SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);
  generate(world, model, n_quads, NULL);

  SerdRange* range1 = serd_model_all(model);
  SerdRange* range2 = serd_model_all(model);

  assert(!serd_range_empty(range1));
  assert(serd_range_empty(NULL));

  assert(!serd_range_equals(range1, NULL));
  assert(!serd_range_equals(NULL, range1));
  assert(serd_range_equals(range1, range2));

  assert(serd_iter_equals(serd_range_begin(range1), serd_range_begin(range2)));
  assert(
    serd_iter_equals(serd_range_cbegin(range1), serd_range_cbegin(range2)));
  assert(serd_iter_equals(serd_range_end(range1), serd_range_end(range2)));
  assert(serd_iter_equals(serd_range_cend(range1), serd_range_cend(range2)));

  assert(!serd_range_next(range2));
  assert(!serd_range_equals(range1, range2));

  serd_range_free(range2);
  serd_range_free(range1);
  serd_model_free(model);

  return 0;
}

static int
test_iter_comparison(SerdWorld* world, const unsigned n_quads)
{
  (void)n_quads;

  SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);

  assert(serd_iter_equals(serd_iter_copy(NULL), NULL));

  serd_world_set_log_func(world, expected_error, NULL);
  assert(
    !serd_model_add(model, uri(world, 1), uri(world, 2), uri(world, 3), 0));

  // Add a statement with an active iterator
  SerdIter* iter1 = serd_model_begin(model);
  SerdIter* iter2 = serd_model_begin(model);
  assert(serd_iter_equals(iter1, iter2));

  serd_iter_next(iter1);
  assert(!serd_iter_equals(iter1, iter2));

  const SerdIter* end = serd_model_end(model);
  assert(serd_iter_equals(iter1, end));

  serd_iter_free(iter2);
  serd_iter_free(iter1);
  serd_model_free(model);
  return 0;
}

static int
test_triple_index_read(SerdWorld* world, const unsigned n_quads)
{
  for (unsigned i = 0; i < 6; ++i) {
    SerdModel* model = serd_model_new(world, (1u << i));
    generate(world, model, n_quads, 0);
    assert(!test_read(world, model, 0, n_quads));
    serd_model_free(model);
  }

  return 0;
}

static int
test_quad_index_read(SerdWorld* world, const unsigned n_quads)
{
  for (unsigned i = 0; i < 6; ++i) {
    SerdModel* model = serd_model_new(world, (1u << i) | SERD_INDEX_GRAPHS);
    const SerdNode* graph = uri(world, 42);
    generate(world, model, n_quads, graph);
    assert(!test_read(world, model, graph, n_quads));
    serd_model_free(model);
  }

  return 0;
}

static int
test_remove_graph(SerdWorld* world, const unsigned n_quads)
{
  (void)n_quads;

  SerdModel* model = serd_model_new(world, SERD_INDEX_SPO | SERD_INDEX_GRAPHS);

  // Generate a couple of graphs
  const SerdNode* graph42 = uri(world, 42);
  const SerdNode* graph43 = uri(world, 43);
  generate(world, model, 1, graph42);
  generate(world, model, 1, graph43);

  // Remove one graph via range
  SerdRange* range = serd_model_range(model, NULL, NULL, NULL, graph43);
  SerdStatus st    = serd_model_erase_range(model, range);
  assert(!st);
  serd_range_free(range);

  // Erase the first tuple (an element in the default graph)
  SerdIter* iter = serd_model_begin(model);
  assert(!serd_model_erase(model, iter));
  serd_iter_free(iter);

  // Ensure only the other graph is left
  Quad pat = {0, 0, 0, graph42};
  for (iter = serd_model_begin(model);
       !serd_iter_equals(iter, serd_model_end(model));
       serd_iter_next(iter)) {
    const SerdStatement* const s = serd_iter_get(iter);
    assert(s);
    assert(serd_statement_matches(s, pat[0], pat[1], pat[2], pat[3]));
  }
  serd_iter_free(iter);

  serd_model_free(model);
  return 0;
}

static int
test_default_graph(SerdWorld* world, const unsigned n_quads)
{
  (void)n_quads;

  SerdModel* model  = serd_model_new(world, SERD_INDEX_SPO | SERD_INDEX_GRAPHS);
  const SerdNode* s = uri(world, 1);
  const SerdNode* p = uri(world, 2);
  const SerdNode* o = uri(world, 3);
  const SerdNode* g1 = uri(world, 101);
  const SerdNode* g2 = uri(world, 102);

  // Insert the same statement into two graphs
  assert(!serd_model_add(model, s, p, o, g1));
  assert(!serd_model_add(model, s, p, o, g2));

  // Ensure we only see statement once in the default graph
  assert(serd_model_count(model, s, p, o, NULL) == 1);

  serd_model_free(model);
  return 0;
}

static int
test_write_bad_list(SerdWorld* world, const unsigned n_quads)
{
  (void)n_quads;

  SerdModel* model = serd_model_new(world, SERD_INDEX_SPO | SERD_INDEX_GRAPHS);
  SerdNodes* nodes = serd_nodes_new();

  const SerdNode* s = manage(world, serd_new_uri(SERD_STATIC_STRING("urn:s")));
  const SerdNode* p = manage(world, serd_new_uri(SERD_STATIC_STRING("urn:p")));
  const SerdNode* list1 =
    manage(world, serd_new_blank(SERD_STATIC_STRING("l1")));
  const SerdNode* list2 =
    manage(world, serd_new_blank(SERD_STATIC_STRING("l2")));
  const SerdNode* nofirst =
    manage(world, serd_new_blank(SERD_STATIC_STRING("nof")));
  const SerdNode* norest =
    manage(world, serd_new_blank(SERD_STATIC_STRING("nor")));
  const SerdNode* pfirst =
    manage(world, serd_new_uri(SERD_STATIC_STRING(RDF_FIRST)));
  const SerdNode* prest =
    manage(world, serd_new_uri(SERD_STATIC_STRING(RDF_REST)));
  const SerdNode* val1 =
    manage(world, serd_new_string(SERD_STATIC_STRING("a")));
  const SerdNode* val2 =
    manage(world, serd_new_string(SERD_STATIC_STRING("b")));

  // List where second node has no rdf:first
  serd_model_add(model, s, p, list1, NULL);
  serd_model_add(model, list1, pfirst, val1, NULL);
  serd_model_add(model, list1, prest, nofirst, NULL);

  // List where second node has no rdf:rest
  serd_model_add(model, s, p, list2, NULL);
  serd_model_add(model, list2, pfirst, val1, NULL);
  serd_model_add(model, list2, prest, norest, NULL);
  serd_model_add(model, norest, pfirst, val2, NULL);

  SerdBuffer    buffer    = {NULL, 0};
  SerdEnv*      env       = serd_env_new(SERD_EMPTY_STRING());
  SerdByteSink* byte_sink = serd_byte_sink_new_buffer(&buffer);

  SerdWriter* writer = serd_writer_new(world, SERD_TURTLE, 0, env, byte_sink);

  SerdRange* all = serd_model_all(model);
  serd_range_serialise(all, serd_writer_sink(writer), 0);
  serd_range_free(all);

  serd_writer_finish(writer);
  const char* str      = serd_buffer_sink_finish(&buffer);
  const char* expected = "<urn:s>\n"
                         "	<urn:p> (\n"
                         "		\"a\"\n"
                         "	) , (\n"
                         "		\"a\"\n"
                         "		\"b\"\n"
                         "	) .\n";

  assert(!strcmp(str, expected));

  free(buffer.buf);
  serd_writer_free(writer);
  serd_byte_sink_free(byte_sink);
  serd_model_free(model);
  serd_env_free(env);
  serd_nodes_free(nodes);
  return 0;
}

typedef struct {
  size_t n_written;
  size_t max_successes;
} FailingWriteFuncState;

/// Write function that fails after a certain number of writes
static size_t
failing_write_func(const void* buf, size_t size, size_t nmemb, void* stream)
{
  (void)buf;
  (void)size;
  (void)nmemb;

  FailingWriteFuncState* state = (FailingWriteFuncState*)stream;

  return (++state->n_written > state->max_successes) ? 0 : nmemb;
}

static int
test_write_error_in_list(SerdWorld* world, const unsigned n_quads)
{
  (void)n_quads;

  serd_world_set_log_func(world, expected_error, NULL);

  SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);
  SerdNodes* nodes = serd_nodes_new();

  const SerdNode* s  = manage(world, serd_new_uri(SERD_STATIC_STRING("urn:s")));
  const SerdNode* p  = manage(world, serd_new_uri(SERD_STATIC_STRING("urn:p")));
  const SerdNode* l1 = manage(world, serd_new_blank(SERD_STATIC_STRING("l1")));

  const SerdNode* one = manage(world, serd_new_integer(1, NULL));
  const SerdNode* l2  = manage(world, serd_new_blank(SERD_STATIC_STRING("l2")));
  const SerdNode* two = manage(world, serd_new_integer(2, NULL));

  const SerdNode* rdf_first =
    manage(world, serd_new_uri(SERD_STATIC_STRING(RDF_FIRST)));
  const SerdNode* rdf_rest =
    manage(world, serd_new_uri(SERD_STATIC_STRING(RDF_REST)));
  const SerdNode* rdf_nil =
    manage(world, serd_new_uri(SERD_STATIC_STRING(RDF_NIL)));

  serd_model_add(model, s, p, l1, NULL);
  serd_model_add(model, l1, rdf_first, one, NULL);
  serd_model_add(model, l1, rdf_rest, l2, NULL);
  serd_model_add(model, l2, rdf_first, two, NULL);
  serd_model_add(model, l2, rdf_rest, rdf_nil, NULL);

  SerdEnv* env = serd_env_new(SERD_EMPTY_STRING());

  for (size_t max_successes = 0; max_successes < 21; ++max_successes) {
    FailingWriteFuncState state = {0, max_successes};

    SerdByteSink* byte_sink =
      serd_byte_sink_new_function(failing_write_func, &state, 1);

    SerdWriter* writer = serd_writer_new(world, SERD_TURTLE, 0, env, byte_sink);

    const SerdSink* const sink = serd_writer_sink(writer);
    SerdRange* const      all  = serd_model_all(model);
    const SerdStatus      st   = serd_range_serialise(all, sink, 0);
    serd_range_free(all);

    assert(st == SERD_ERR_BAD_WRITE);

    serd_writer_free(writer);
    serd_byte_sink_free(byte_sink);
  }

  serd_env_free(env);
  serd_model_free(model);
  serd_nodes_free(nodes);
  return 0;
}

int
main(void)
{
  static const unsigned n_quads = 300;

  serd_model_free(NULL); // Shouldn't crash

  typedef int (*TestFunc)(SerdWorld*, unsigned);

  const TestFunc tests[] = {test_free_null,
                            test_get_world,
                            test_get_flags,
                            test_all_begin,
                            test_add_null,
                            test_add_with_iterator,
                            test_erase_with_iterator,
                            test_add_erase,
                            test_erase_all,
                            test_copy,
                            test_equals,
                            test_find_past_end,
                            test_range,
                            test_iter_comparison,
                            test_triple_index_read,
                            test_quad_index_read,
                            test_remove_graph,
                            test_default_graph,
                            test_write_bad_list,
                            test_write_error_in_list,
                            NULL};

  SerdWorld* world = serd_world_new();
  int        ret   = 0;

  for (const TestFunc* t = tests; *t; ++t) {
    serd_world_set_log_func(world, NULL, NULL);
    ret += (*t)(world, n_quads);
  }

  serd_world_free(world);
  return ret;
}
