/*
 * This file is part of ucol_nif released under the MIT license.
 * See the NOTICE for more information.
 */

#ifdef DARWIN
#define U_HIDE_DRAFT_API 1
#define U_DISABLE_RENAMING 1
#endif

#include "erl_nif.h"
#include "unicode/ucol.h"
#include "unicode/ucasemap.h"
#include <stdio.h>
#include <assert.h>

static ERL_NIF_TERM ATOM_TRUE;
static ERL_NIF_TERM ATOM_FALSE;
static ERL_NIF_TERM ATOM_NULL;

typedef struct {
    ErlNifEnv* env;
    int error;
    UCollator* coll;
} ctx_t;

typedef struct {
    UCollator** collators;
    int collStackTop;
    int numCollators;
    ErlNifMutex* collMutex;
} priv_data_t;

static ERL_NIF_TERM ucol(ErlNifEnv*, int, const ERL_NIF_TERM []);
static int collate_binary(priv_data_t*, ctx_t*, ERL_NIF_TERM, ERL_NIF_TERM, ERL_NIF_TERM);
static int on_load(ErlNifEnv*, void**, ERL_NIF_TERM);
static void on_unload(ErlNifEnv*, void*);
static __inline void reserve_coll(priv_data_t*, ctx_t*);
static __inline void release_coll(priv_data_t*, ctx_t*);
int on_reload(ErlNifEnv*, void**, ERL_NIF_TERM);
int on_upgrade(ErlNifEnv*, void**, void**, ERL_NIF_TERM);

void
reserve_coll(priv_data_t* pData, ctx_t *ctx)
{
    if (ctx->coll == NULL) {
        enif_mutex_lock(pData->collMutex);
        assert(pData->collStackTop < pData->numCollators);
        ctx->coll = pData->collators[pData->collStackTop];
        pData->collStackTop += 1;
        enif_mutex_unlock(pData->collMutex);
    }
}


void
release_coll(priv_data_t* pData, ctx_t *ctx)
{
    if (ctx->coll != NULL) {
        enif_mutex_lock(pData->collMutex);
        pData->collStackTop -= 1;
        assert(pData->collStackTop >= 0);
        enif_mutex_unlock(pData->collMutex);
    }
}

/* ------------------------------------------------------------------------- */

static ERL_NIF_TERM
ucol(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
    ERL_NIF_TERM term_a          = argv[0];
    ERL_NIF_TERM term_b          = argv[1];
    ERL_NIF_TERM term_has_nocase = argv[2];

    ctx_t ctx;
    int result;
    priv_data_t* pData;

    ctx.env = env;
    ctx.error = 0;
    ctx.coll = NULL;

    pData = (priv_data_t*) enif_priv_data(env);

    result = collate_binary(pData, &ctx, term_a, term_b, term_has_nocase);
    release_coll(pData, &ctx);

    return enif_make_int(env, result);
}

int
collate_binary(priv_data_t* pData, ctx_t* ctx, ERL_NIF_TERM term_a, ERL_NIF_TERM term_b, ERL_NIF_TERM term_has_nocase)
{
    ErlNifBinary binA, binB;
    int has_nocase, response;

    if(!enif_get_int(ctx->env, term_has_nocase, &has_nocase)) {
        ctx->error = 1;
        return 0;
    }
    if(!enif_inspect_binary(ctx->env, term_a, &binA)) {
        ctx->error = 1;
        return 0;
    }
    if(!enif_inspect_binary(ctx->env, term_b, &binB)) {
        ctx->error = 1;
        return 0;
    }

    switch(has_nocase) {
    case 0: /* COLLATE */
    case 1: /* COLLATE_NO_CASE: */
        {
        UErrorCode status = U_ZERO_ERROR;
        UCharIterator iterA;
        UCharIterator iterB;

        uiter_setUTF8(&iterA, (const char *) binA.data, (uint32_t) binA.size);
        uiter_setUTF8(&iterB, (const char *) binB.data, (uint32_t) binB.size);

        /* grab a collator */
        reserve_coll(pData, ctx);

        if (has_nocase == 1) /* switching this collator to case insensitive */
          ucol_setAttribute(ctx->coll, UCOL_STRENGTH, UCOL_PRIMARY, &status);

        /* by default, it will collate case sensitive */
        response = ucol_strcollIter(ctx->coll, &iterA, &iterB, &status);

        if (has_nocase == 1) /* puting back this collator to case sensitive */
          ucol_setAttribute(ctx->coll, UCOL_STRENGTH, UCOL_DEFAULT, &status);

        break;
        }

    default:
        response = -1;
    }

    return response;
}

/* ------------------------------------------------------------------------- */

int
on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM info)
{
    UErrorCode status = U_ZERO_ERROR;
    priv_data_t* pData = (priv_data_t*)enif_alloc(sizeof(priv_data_t));
    int i, j;

    /* Initialize the structure */
    pData->collators = NULL;
    pData->collStackTop = 0;
    pData->numCollators = 0;
    pData->collMutex = NULL;

    if (!enif_get_int(env, info, &(pData->numCollators) )) {
        enif_free((char*)pData);
        return 1;
    }

    if (pData->numCollators < 1) {
        enif_free((char*)pData);
        return 2;
    }

    pData->collMutex = enif_mutex_create((char *)"coll_mutex");

    if (pData->collMutex == NULL) {
        enif_free((char*)pData);
        return 3;
    }

    pData->collators = enif_alloc(sizeof(UCollator*) * pData->numCollators);

    if (pData->collators == NULL) {
        enif_mutex_destroy(pData->collMutex);
        enif_free((char*)pData);
        return 4;
    }

    for (i = 0; i < pData->numCollators; i++) {
        pData->collators[i] = ucol_open("", &status);

        if (U_FAILURE(status)) {
            for (j = 0; j < i; j++) {
                ucol_close(pData->collators[j]);
            }

            enif_free(pData->collators);
            enif_mutex_destroy(pData->collMutex);

            enif_free((char*)pData);

            return 5;
        }
    }

    ATOM_TRUE = enif_make_atom(env, "true");
    ATOM_FALSE = enif_make_atom(env, "false");
    ATOM_NULL = enif_make_atom(env, "null");

    *priv_data = pData;

    return 0;
}


void
on_unload(ErlNifEnv* env, void* priv_data)
{
    priv_data_t* pData = (priv_data_t*)priv_data;
    if (pData->collators != NULL) {
        int i;

        for (i = 0; i < pData->numCollators; i++) {
            ucol_close(pData->collators[i]);
        }

        enif_free(pData->collators);
    }

    if (pData->collMutex != NULL) {
        enif_mutex_destroy(pData->collMutex);
    }

    enif_free((char*)pData);
}

int
on_reload(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM info)
{
    return 0;
}

int
on_upgrade(ErlNifEnv* env, void** priv_data, void** old_data, ERL_NIF_TERM info)
{
    if (*old_data != NULL) {
        priv_data_t* pData = (priv_data_t*)old_data;

        if (pData->collators != NULL) {
            int i;

            for (i = 0; i < pData->numCollators; i++) {
                ucol_close(pData->collators[i]);
            }

            enif_free(pData->collators);
        }

        if (pData->collMutex != NULL) {
            enif_mutex_destroy(pData->collMutex);
        }

        enif_free((char*)pData);
    }

    return on_load(env, priv_data, info);
}

/* ------------------------------------------------------------------------- */

static ErlNifFunc
nif_funcs[] =
{
    {"cmp", 3, ucol}
};

ERL_NIF_INIT(Elixir.Cldr.Collation, nif_funcs, &on_load, &on_reload, &on_upgrade, &on_unload)
