#include <assert.h>
#include <bare.h>
#include <js.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <url.h>
#include <utf.h>
#include <utf/string.h>

static js_value_t *
bare_url_parse (js_env_t *env, js_callback_info_t *info) {
  int err;

  size_t argc = 4;
  js_value_t *argv[4];

  err = js_get_callback_info(env, info, &argc, argv, NULL, NULL);
  assert(err == 0);

  assert(argc == 4);

  bool should_throw;
  err = js_get_value_bool(env, argv[3], &should_throw);
  assert(err == 0);

  bool has_base;
  err = js_is_string(env, argv[1], &has_base);
  assert(err == 0);

  url_t base;
  url_init(&base);

  if (has_base) {
    size_t len;
    err = js_get_value_string_utf8(env, argv[1], NULL, 0, &len);
    assert(err == 0);

    utf8_t *input = malloc(len);
    err = js_get_value_string_utf8(env, argv[1], input, len, NULL);
    assert(err == 0);

    err = url_parse(&base, input, len, NULL);

    free(input);

    if (err < 0) {
      url_destroy(&base);

      if (should_throw) js_throw_error(env, NULL, "Invalid base URL");

      return NULL;
    }
  }

  size_t len;
  err = js_get_value_string_utf8(env, argv[0], NULL, 0, &len);
  assert(err == 0);

  utf8_t *input = malloc(len);
  err = js_get_value_string_utf8(env, argv[0], input, len, NULL);
  assert(err == 0);

  js_value_t *handle;

  url_t url;
  url_init(&url);

  err = url_parse(&url, input, len, has_base ? &base : NULL);

  free(input);

  if (err < 0) {
    url_destroy(&base);
    url_destroy(&url);

    if (should_throw) js_throw_error(env, NULL, "Invalid URL");

    return NULL;
  }

  js_value_t *href;
  err = js_create_string_utf8(env, url.href.data, url.href.len, &href);
  assert(err == 0);

  uint32_t *components;
  err = js_get_typedarray_info(env, argv[2], NULL, (void **) &components, NULL, NULL, NULL);
  assert(err == 0);

  memcpy(components, &url.components, sizeof(url.components));

  url_destroy(&base);
  url_destroy(&url);

  return href;
}

static js_value_t *
bare_url_can_parse (js_env_t *env, js_callback_info_t *info) {
  int err;

  size_t argc = 2;
  js_value_t *argv[2];

  err = js_get_callback_info(env, info, &argc, argv, NULL, NULL);
  assert(err == 0);

  assert(argc == 2);

  bool has_base;
  err = js_is_string(env, argv[1], &has_base);
  assert(err == 0);

  url_t base;
  url_init(&base);

  if (has_base) {
    size_t len;
    err = js_get_value_string_utf8(env, argv[1], NULL, 0, &len);
    assert(err == 0);

    utf8_t *input = malloc(len);
    err = js_get_value_string_utf8(env, argv[1], input, len, NULL);
    assert(err == 0);

    err = url_parse(&base, input, len, NULL);

    free(input);

    if (err < 0) {
      url_destroy(&base);

      js_value_t *result;
      err = js_get_boolean(env, false, &result);
      assert(err == 0);

      return result;
    }
  }

  size_t len;
  err = js_get_value_string_utf8(env, argv[0], NULL, 0, &len);
  assert(err == 0);

  utf8_t *input = malloc(len);
  err = js_get_value_string_utf8(env, argv[0], input, len, NULL);
  assert(err == 0);

  url_t url;
  url_init(&url);

  err = url_parse(&url, input, len, has_base ? &base : NULL);

  free(input);

  url_destroy(&base);
  url_destroy(&url);

  js_value_t *result;
  err = js_get_boolean(env, err == 0, &result);
  assert(err == 0);

  return result;
}

static js_value_t *
bare_url_exports (js_env_t *env, js_value_t *exports) {
  int err;

#define V(name, fn) \
  { \
    js_value_t *val; \
    err = js_create_function(env, name, -1, fn, NULL, &val); \
    assert(err == 0); \
    err = js_set_named_property(env, exports, name, val); \
    assert(err == 0); \
  }

  V("parse", bare_url_parse)
  V("canParse", bare_url_can_parse)
#undef V

  return exports;
}

BARE_MODULE(bare_url, bare_url_exports)
