#include <stdint.h>
#include <stdlib.h>
#include <strings.h>

#if defined(__linux__)
#include <malloc.h>
#elif defined(BSD)
#include <malloc_np.h>
#endif

#include "erl_nif.h"
#include "ucl.h"

static ERL_NIF_TERM to_json(ErlNifEnv *env, int argc,
                            const ERL_NIF_TERM argv[]) {
  // ucl bits and pieces
  struct ucl_parser *parser = NULL;

  ucl_object_t *ucl = NULL;
  ucl_emitter_t emitter = UCL_EMIT_JSON;

  if (!enif_is_binary(env, argv[0])) {
    ERL_NIF_TERM error = enif_make_atom(env, "error");
    ERL_NIF_TERM reason = enif_make_atom(env, "badarg");
    return enif_make_tuple2(env, error, reason);
  }

  ErlNifBinary binary;
  enif_inspect_binary(env, argv[0], &binary);

  // parse UCL
  // https://github.com/vstakhov/libucl/blob/master/doc/api.md#parser-functions-1
  parser = ucl_parser_new(UCL_PARSER_KEY_LOWERCASE);
  ucl_parser_add_chunk(parser, binary.data, binary.size);

  // does it even parse correctly
  if (ucl_parser_get_error(parser)) {
    ERL_NIF_TERM error = enif_make_atom(env, "error");
    ERL_NIF_TERM reason = enif_make_atom(env, "ucl_invalid");
    return enif_make_tuple2(env, error, reason);
  }

  ucl = ucl_parser_get_object(parser);

  // there should always be a root object to generate a tree from
  if (ucl == NULL) {
    ERL_NIF_TERM error = enif_make_atom(env, "error");
    ERL_NIF_TERM reason = enif_make_atom(env, "ucl_no_root");
    return enif_make_tuple2(env, error, reason);
  }

  // finished with parser
  if (parser != NULL) {
    ucl_parser_free(parser);
  }

  // turn it into JSON
  size_t len;
  unsigned char *blob;
  blob = ucl_object_emit_len(ucl, emitter, &len);

  // finished with UCL
  if (ucl != NULL) {
    ucl_object_unref(ucl);
  }

  // buffer for returning parsed JSON
  unsigned char *buf;
  ERL_NIF_TERM json;

  // return JSON as binary
  ERL_NIF_TERM ok = enif_make_atom(env, "ok");
  buf = enif_make_new_binary(env, len, &json);
  memcpy(buf, blob, len);

  // free JSON blob
  free(blob);

  return enif_make_tuple2(env, ok, json);
}

static ErlNifFunc nif_funcs[] = {{"to_json", 1, to_json, ERL_NIF_DIRTY_JOB_CPU_BOUND}};

// Public Domain code from https://github.com/davisp/nif-examples
// There are three functions that may be called during the lifetime
// of a NIF. load, upgrade, and unload. Any of these functions
// can be left unspecified by passing NULL to the ERL_NIF_INIT macro.
//
// reload is no longer supported since OTP20
//
// NIFs are awesome.

// Return value of 0 indicates success.
// Docs: http://erlang.org/doc/man/erl_nif.html#load

static int load(ErlNifEnv *env, void **priv, ERL_NIF_TERM load_info) {
  return 0;
}

// Called when changing versions of the C code for a module's NIF
// implementation if I read the docs correctly.
//
// Return value of 0 indicates success.
// Docs: http://erlang.org/doc/man/erl_nif.html#upgrade

static int upgrade(ErlNifEnv *env, void **priv, void **old_priv,
                   ERL_NIF_TERM load_info) {
  return 0;
}

// Called when the library is unloaded. Not called after a reload
// executes.
//
// No return value
// Docs: http://erlang.org/doc/man/erl_nif.html#load

static void unload(ErlNifEnv *env, void *priv) { return; }

// initialise NIF library
// support load, reload, upgrade, unload

ERL_NIF_INIT(ucl, nif_funcs, &load, NULL, &upgrade, &unload);
