// Copyright 2011-2023 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC

#undef NDEBUG

#include "serd/serd.h"

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

#define USTR(s) ((const uint8_t*)(s))

static void
test_uri_string_has_scheme(void)
{
  assert(!serd_uri_string_has_scheme(USTR("relative")));
  assert(!serd_uri_string_has_scheme(USTR("http")));
  assert(!serd_uri_string_has_scheme(USTR("5nostartdigit")));
  assert(!serd_uri_string_has_scheme(USTR("+nostartplus")));
  assert(!serd_uri_string_has_scheme(USTR("-nostartminus")));
  assert(!serd_uri_string_has_scheme(USTR(".nostartdot")));
  assert(!serd_uri_string_has_scheme(USTR(":missing")));
  assert(!serd_uri_string_has_scheme(USTR("a/slash/is/not/a/scheme/char")));

  assert(serd_uri_string_has_scheme(USTR("http://example.org/")));
  assert(serd_uri_string_has_scheme(USTR("https://example.org/")));
  assert(serd_uri_string_has_scheme(USTR("allapha:path")));
  assert(serd_uri_string_has_scheme(USTR("w1thd1g1t5:path")));
  assert(serd_uri_string_has_scheme(USTR("with.dot:path")));
  assert(serd_uri_string_has_scheme(USTR("with+plus:path")));
  assert(serd_uri_string_has_scheme(USTR("with-minus:path")));
}

static void
test_file_uri(const char* const hostname,
              const char* const path,
              const bool        escape,
              const char* const expected_uri,
              const char*       expected_path)
{
  if (!expected_path) {
    expected_path = path;
  }

  SerdNode node = serd_node_new_file_uri(USTR(path), USTR(hostname), 0, escape);

  uint8_t* out_hostname = NULL;
  uint8_t* out_path     = serd_file_uri_parse(node.buf, &out_hostname);
  assert(!strcmp((const char*)node.buf, expected_uri));
  assert((hostname && out_hostname) || (!hostname && !out_hostname));
  assert(!hostname || !strcmp(hostname, (const char*)out_hostname));
  assert(!strcmp((const char*)out_path, (const char*)expected_path));

  serd_free(out_path);
  serd_free(out_hostname);
  serd_node_free(&node);
}

#if defined(__GNUC__)
#  pragma GCC diagnostic push
#  pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif

static void
test_uri_to_path(void)
{
  assert(!strcmp(
    (const char*)serd_uri_to_path((const uint8_t*)"file:///home/user/foo.ttl"),
    "/home/user/foo.ttl"));

  assert(!strcmp((const char*)serd_uri_to_path(
                   (const uint8_t*)"file://localhost/home/user/foo.ttl"),
                 "/home/user/foo.ttl"));

  assert(!serd_uri_to_path((const uint8_t*)"file:illegal/file/uri"));

  assert(!strcmp(
    (const char*)serd_uri_to_path((const uint8_t*)"file:///c:/awful/system"),
    "c:/awful/system"));

  assert(!strcmp(
    (const char*)serd_uri_to_path((const uint8_t*)"file:///c:awful/system"),
    "/c:awful/system"));

  assert(!strcmp((const char*)serd_uri_to_path((const uint8_t*)"file:///0/1"),
                 "/0/1"));

  assert(
    !strcmp((const char*)serd_uri_to_path((const uint8_t*)"C:\\Windows\\Sucks"),
            "C:\\Windows\\Sucks"));

  assert(
    !strcmp((const char*)serd_uri_to_path((const uint8_t*)"C|/Windows/Sucks"),
            "C|/Windows/Sucks"));

  assert(!serd_uri_to_path((const uint8_t*)"http://example.org/path"));
}

#if defined(__GNUC__)
#  pragma GCC diagnostic pop
#endif

static void
test_uri_parsing(void)
{
  test_file_uri(NULL, "C:/My 100%", true, "file:///C:/My%20100%%", NULL);
  test_file_uri(NULL, "/foo/bar", true, "file:///foo/bar", NULL);
  test_file_uri("bhost", "/foo/bar", true, "file://bhost/foo/bar", NULL);
  test_file_uri(NULL, "a/relative path", false, "a/relative path", NULL);
  test_file_uri(
    NULL, "a/relative <path>", true, "a/relative%20%3Cpath%3E", NULL);

#ifdef _WIN32
  test_file_uri(
    NULL, "C:\\My 100%", true, "file:///C:/My%20100%%", "C:/My 100%");

  test_file_uri(NULL,
                "\\drive\\relative",
                true,
                "file:///drive/relative",
                "/drive/relative");

  test_file_uri(NULL,
                "C:\\Program Files\\Serd",
                true,
                "file:///C:/Program%20Files/Serd",
                "C:/Program Files/Serd");

  test_file_uri("ahost",
                "C:\\Pointless Space",
                true,
                "file://ahost/C:/Pointless%20Space",
                "C:/Pointless Space");
#else
  /* What happens with Windows paths on other platforms is a bit weird, but
     more or less unavoidable.  It doesn't work to interpret backslashes as
     path separators on any other platform. */

  test_file_uri("ahost",
                "C:\\Pointless Space",
                true,
                "file://ahost/C:%5CPointless%20Space",
                "/C:\\Pointless Space");

  test_file_uri(NULL,
                "\\drive\\relative",
                true,
                "%5Cdrive%5Crelative",
                "\\drive\\relative");

  test_file_uri(NULL,
                "C:\\Program Files\\Serd",
                true,
                "file:///C:%5CProgram%20Files%5CSerd",
                "/C:\\Program Files\\Serd");

  test_file_uri("ahost",
                "C:\\Pointless Space",
                true,
                "file://ahost/C:%5CPointless%20Space",
                "/C:\\Pointless Space");
#endif

  // Test tolerance of parsing junk URI escapes

  uint8_t* out_path = serd_file_uri_parse(USTR("file:///foo/%0Xbar"), NULL);
  assert(!strcmp((const char*)out_path, "/foo/bar"));
  serd_free(out_path);
}

static void
test_uri_from_string(void)
{
  SerdNode nonsense = serd_node_new_uri_from_string(NULL, NULL, NULL);
  assert(nonsense.type == SERD_NOTHING);

  SerdURI  base_uri;
  SerdNode base =
    serd_node_new_uri_from_string(USTR("http://example.org/"), NULL, &base_uri);
  SerdNode nil  = serd_node_new_uri_from_string(NULL, &base_uri, NULL);
  SerdNode nil2 = serd_node_new_uri_from_string(USTR(""), &base_uri, NULL);
  assert(nil.type == SERD_URI);
  assert(!strcmp((const char*)nil.buf, (const char*)base.buf));
  assert(nil2.type == SERD_URI);
  assert(!strcmp((const char*)nil2.buf, (const char*)base.buf));
  serd_node_free(&nil);
  serd_node_free(&nil2);

  serd_node_free(&base);
}

static inline bool
chunk_equals(const SerdChunk* a, const SerdChunk* b)
{
  return (!a->len && !b->len && !a->buf && !b->buf) ||
         (a->len && b->len && a->buf && b->buf &&
          !strncmp((const char*)a->buf, (const char*)b->buf, a->len));
}

static void
check_relative_uri(const char* const uri_string,
                   const char* const base_string,
                   const char* const root_string,
                   const char* const expected_string)
{
  assert(uri_string);
  assert(base_string);
  assert(expected_string);

  SerdURI uri    = SERD_URI_NULL;
  SerdURI base   = SERD_URI_NULL;
  SerdURI result = SERD_URI_NULL;

  SerdNode uri_node =
    serd_node_new_uri_from_string(USTR(uri_string), NULL, &uri);

  SerdNode base_node =
    serd_node_new_uri_from_string(USTR(base_string), NULL, &base);

  SerdNode result_node = SERD_NODE_NULL;
  if (root_string) {
    SerdURI  root = SERD_URI_NULL;
    SerdNode root_node =
      serd_node_new_uri_from_string(USTR(root_string), NULL, &root);

    result_node = serd_node_new_relative_uri(&uri, &base, &root, &result);
    serd_node_free(&root_node);
  } else {
    result_node = serd_node_new_relative_uri(&uri, &base, NULL, &result);
  }

  assert(!strcmp((const char*)result_node.buf, expected_string));

  SerdURI expected = SERD_URI_NULL;
  assert(!serd_uri_parse(USTR(expected_string), &expected));
  assert(chunk_equals(&result.scheme, &expected.scheme));
  assert(chunk_equals(&result.authority, &expected.authority));
  assert(chunk_equals(&result.path_base, &expected.path_base));
  assert(chunk_equals(&result.path, &expected.path));
  assert(chunk_equals(&result.query, &expected.query));
  assert(chunk_equals(&result.fragment, &expected.fragment));

  serd_node_free(&result_node);
  serd_node_free(&base_node);
  serd_node_free(&uri_node);
}

static void
test_relative_uri(void)
{
  // Unrelated base

  check_relative_uri("http://example.org/a/b",
                     "ftp://example.org/",
                     NULL,
                     "http://example.org/a/b");

  check_relative_uri("http://example.org/a/b",
                     "http://example.com/",
                     NULL,
                     "http://example.org/a/b");

  // Related base

  check_relative_uri(
    "http://example.org/a/b", "http://example.org/", NULL, "a/b");

  check_relative_uri(
    "http://example.org/a/b", "http://example.org/a/", NULL, "b");

  check_relative_uri(
    "http://example.org/a/b", "http://example.org/a/b", NULL, "");

  check_relative_uri(
    "http://example.org/a/b", "http://example.org/a/b/", NULL, "../b");

  check_relative_uri(
    "http://example.org/a/b/", "http://example.org/a/b/", NULL, "");

  check_relative_uri("http://example.org/", "http://example.org/", NULL, "");

  check_relative_uri("http://example.org/", "http://example.org/a", NULL, "");

  check_relative_uri(
    "http://example.org/", "http://example.org/a/", NULL, "../");

  check_relative_uri(
    "http://example.org/", "http://example.org/a/b", NULL, "../");

  check_relative_uri(
    "http://example.org/", "http://example.org/a/b/", NULL, "../../");

  // Unrelated root

  check_relative_uri("http://example.org/",
                     "http://example.org/a/b",
                     "relative",
                     "http://example.org/");

  check_relative_uri("http://example.org/",
                     "http://example.org/a/b",
                     "ftp://example.org/",
                     "http://example.org/");

  check_relative_uri("http://example.org/",
                     "http://example.org/a/b",
                     "http://example.com/",
                     "http://example.org/");

  // Related root

  check_relative_uri("http://example.org/a/b",
                     "http://example.org/",
                     "http://example.org/c/d",
                     "http://example.org/a/b");

  check_relative_uri("http://example.org/",
                     "http://example.org/a/b",
                     "http://example.org/a/b",
                     "http://example.org/");

  check_relative_uri("http://example.org/a/b",
                     "http://example.org/a/b",
                     "http://example.org/a/b",
                     "");

  check_relative_uri("http://example.org/a/",
                     "http://example.org/a/",
                     "http://example.org/a/",
                     "");

  check_relative_uri("http://example.org/a/b",
                     "http://example.org/a/b/c",
                     "http://example.org/a/b",
                     "../b");

  check_relative_uri("http://example.org/a",
                     "http://example.org/a/b/c",
                     "http://example.org/a/b",
                     "http://example.org/a");
}

int
main(void)
{
  test_uri_to_path();
  test_uri_string_has_scheme();
  test_uri_parsing();
  test_uri_from_string();
  test_relative_uri();

  printf("Success\n");
  return 0;
}