/* Copyright, 2024 Dominic Letz */

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "erl_nif.h"

static ErlNifResourceType *weak_ref_type;
static ErlNifUInt64 weak_ref_counter;
static ErlNifMutex *weak_ref_mutex;

struct weak_ref {
    ErlNifPid owner;
    ErlNifUInt64 id;
};

static ERL_NIF_TERM
weak_ref_new(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
    ErlNifPid pid;
    if (argc != 1) return enif_make_badarg(env);
    if (!enif_get_local_pid(env, argv[0], &pid)) return enif_make_badarg(env);

    struct weak_ref *wr = (struct weak_ref*)enif_alloc_resource(weak_ref_type, sizeof(struct weak_ref));
    wr->owner = pid;
    enif_mutex_lock(weak_ref_mutex);
    wr->id = weak_ref_counter++;
    enif_mutex_unlock(weak_ref_mutex);
    ERL_NIF_TERM res = enif_make_resource(env, wr);
    ERL_NIF_TERM id = enif_make_uint64(env, wr->id);
    enif_release_resource(wr);
    return enif_make_tuple2(env, res, id);
}

static void
destruct_weak_ref_type(ErlNifEnv* env, void *arg)
{
    (void)env;

    struct weak_ref *wr = (struct weak_ref *) arg;
    ErlNifEnv* msg_env = enif_alloc_env();  // Create new environment
    ERL_NIF_TERM id = enif_make_uint64(msg_env, wr->id);
    ERL_NIF_TERM down = enif_make_atom(msg_env, "DOWN");
    ERL_NIF_TERM ref = enif_make_atom(msg_env, "weak_ref");
    enif_send(NULL, &wr->owner, msg_env, enif_make_tuple3(msg_env, down, id, ref));
    enif_free_env(msg_env);  // Free the environment
}

static int
on_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info)
{
    (void)priv; // Suppress unused parameter warning
    (void)info; // Suppress unused parameter warning

    ErlNifResourceType *rt;

    rt = enif_open_resource_type(env, "weak_ref_nif", "weak_ref_type",
            destruct_weak_ref_type, ERL_NIF_RT_CREATE, NULL);
    if(!rt) return -1;


    weak_ref_mutex = enif_mutex_create("weak_ref_mutex");
    weak_ref_counter = 0;
    weak_ref_type = rt;
    return 0;
}

static int on_reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info)
{
    (void)env; // Suppress unused parameter warning
    (void)priv; // Suppress unused parameter warning
    (void)load_info; // Suppress unused parameter warning
    return 0;
}

static int on_upgrade(ErlNifEnv* env, void** priv, void** old_priv_data, ERL_NIF_TERM load_info)
{
    (void)env; // Suppress unused parameter warning
    (void)priv; // Suppress unused parameter warning
    (void)old_priv_data; // Suppress unused parameter warning
    (void)load_info; // Suppress unused parameter warning
    return 0;
}

static ErlNifFunc nif_funcs[] = {
    {"new", 1, weak_ref_new, 0},
};

ERL_NIF_INIT(Elixir.WeakRef, nif_funcs, on_load, on_reload, on_upgrade, NULL)

