//
// Copyright 2021 Staysail Systems, Inc. <info@staysail.tech>
// Copyright 2018 Capitar IT Group BV <info@capitar.com>
//
// This software is supplied under the terms of the MIT License, a
// copy of which should be located in the distribution where this
// file was obtained (LICENSE.txt).  A copy of the license may also be
// found online at https://opensource.org/licenses/MIT.
//

// POSIX atomics.

#include "core/nng_impl.h"

#ifdef NNG_PLATFORM_POSIX

#ifdef NNG_HAVE_STDATOMIC

#include <stdatomic.h>

bool
nni_atomic_flag_test_and_set(nni_atomic_flag *f)
{
	return (atomic_flag_test_and_set(&f->f));
}

void
nni_atomic_flag_reset(nni_atomic_flag *f)
{
	atomic_flag_clear(&f->f);
}

void
nni_atomic_set_bool(nni_atomic_bool *v, bool b)
{
	atomic_store(&v->v, b);
}

bool
nni_atomic_get_bool(nni_atomic_bool *v)
{
	return (atomic_load(&v->v));
}

bool
nni_atomic_swap_bool(nni_atomic_bool *v, bool b)
{
	return (atomic_exchange(&v->v, b));
}

void
nni_atomic_init_bool(nni_atomic_bool *v)
{
	atomic_init(&v->v, false);
}

void
nni_atomic_init(nni_atomic_int *v)
{
	atomic_init(&v->v, 0);
}

void
nni_atomic_add(nni_atomic_int *v, int bump)
{
	(void) atomic_fetch_add_explicit(&v->v, bump, memory_order_relaxed);
}

void
nni_atomic_sub(nni_atomic_int *v, int bump)
{
	(void) atomic_fetch_sub_explicit(&v->v, bump, memory_order_relaxed);
}

int
nni_atomic_get(nni_atomic_int *v)
{
	return (atomic_load(&v->v));
}

void
nni_atomic_set(nni_atomic_int *v, int i)
{
	atomic_store(&v->v, i);
}

void *
nni_atomic_get_ptr(nni_atomic_ptr *v)
{
	return ((void *) atomic_load(&v->v));
}

void
nni_atomic_set_ptr(nni_atomic_ptr *v, void *p)
{
	atomic_store(&v->v, (uintptr_t) p);
}

int
nni_atomic_swap(nni_atomic_int *v, int i)
{
	return (atomic_exchange(&v->v, i));
}

void
nni_atomic_inc(nni_atomic_int *v)
{
	atomic_fetch_add(&v->v, 1);
}

void
nni_atomic_dec(nni_atomic_int *v)
{
	atomic_fetch_sub(&v->v, 1);
}

int
nni_atomic_dec_nv(nni_atomic_int *v)
{
	return (atomic_fetch_sub(&v->v, 1) - 1);
}

bool
nni_atomic_cas(nni_atomic_int *v, int comp, int new)
{
	return (atomic_compare_exchange_strong(&v->v, &comp, new));
}

void
nni_atomic_add64(nni_atomic_u64 *v, uint64_t bump)
{
	(void) atomic_fetch_add_explicit(
	    &v->v, (uint_fast64_t) bump, memory_order_relaxed);
}

void
nni_atomic_sub64(nni_atomic_u64 *v, uint64_t bump)
{
	(void) atomic_fetch_sub_explicit(
	    &v->v, (uint_fast64_t) bump, memory_order_relaxed);
}

uint64_t
nni_atomic_get64(nni_atomic_u64 *v)
{
	return ((uint64_t) atomic_load(&v->v));
}

void
nni_atomic_set64(nni_atomic_u64 *v, uint64_t u)
{
	atomic_store(&v->v, (uint_fast64_t) u);
}

uint64_t
nni_atomic_swap64(nni_atomic_u64 *v, uint64_t u)
{
	return ((uint64_t) atomic_exchange(&v->v, (uint_fast64_t) u));
}

void
nni_atomic_init64(nni_atomic_u64 *v)
{
	atomic_init(&v->v, 0);
}

void
nni_atomic_inc64(nni_atomic_u64 *v)
{
	atomic_fetch_add(&v->v, 1);
}

uint64_t
nni_atomic_dec64_nv(nni_atomic_u64 *v)
{
	uint64_t ov;

	// C11 atomics give the old rather than new value.
	ov = (uint64_t) atomic_fetch_sub(&v->v, 1);
	return (ov - 1);
}

bool
nni_atomic_cas64(nni_atomic_u64 *v, uint64_t comp, uint64_t new)
{
	// It's possible that uint_fast64_t is not the same type underneath
	// as uint64_t.  (Would be unusual!)
	uint_fast64_t cv = (uint_fast64_t) comp;
	uint_fast64_t nv = (uint_fast64_t) new;
	return (atomic_compare_exchange_strong(&v->v, &cv, nv));
}

#elif NNI_GCC_VERSION >= 40700 || \
    defined(__clang__) // we have "new" GCC __atomic builtins
bool
nni_atomic_flag_test_and_set(nni_atomic_flag *f)
{
	return (__atomic_test_and_set(&f->f, __ATOMIC_SEQ_CST));
}

void
nni_atomic_flag_reset(nni_atomic_flag *f)
{
	__atomic_clear(&f->f, __ATOMIC_SEQ_CST);
}

void
nni_atomic_set_bool(nni_atomic_bool *b, bool n)
{
	__atomic_store_n(&b->b, n, __ATOMIC_SEQ_CST);
}

bool
nni_atomic_get_bool(nni_atomic_bool *b)
{
	return (__atomic_load_n(&b->b, __ATOMIC_SEQ_CST));
}

bool
nni_atomic_swap_bool(nni_atomic_bool *b, bool n)
{
	return (__atomic_exchange_n(&b->b, n, __ATOMIC_SEQ_CST));
}

void
nni_atomic_init_bool(nni_atomic_bool *b)
{
	__atomic_store_n(&b->b, false, __ATOMIC_SEQ_CST);
}

void
nni_atomic_add64(nni_atomic_u64 *v, uint64_t bump)
{
	__atomic_add_fetch(&v->v, bump, __ATOMIC_RELAXED);
}

void
nni_atomic_sub64(nni_atomic_u64 *v, uint64_t bump)
{
	__atomic_sub_fetch(&v->v, bump, __ATOMIC_RELAXED);
}

uint64_t
nni_atomic_get64(nni_atomic_u64 *v)
{
	return (__atomic_load_n(&v->v, __ATOMIC_SEQ_CST));
}

void
nni_atomic_set64(nni_atomic_u64 *v, uint64_t u)
{
	__atomic_store_n(&v->v, u, __ATOMIC_SEQ_CST);
}

uint64_t
nni_atomic_swap64(nni_atomic_u64 *v, uint64_t u)
{
	return (__atomic_exchange_n(&v->v, u, __ATOMIC_SEQ_CST));
}

void
nni_atomic_init64(nni_atomic_u64 *v)
{
	__atomic_store_n(&v->v, 0, __ATOMIC_SEQ_CST);
}

void
nni_atomic_inc64(nni_atomic_u64 *v)
{
	__atomic_add_fetch(&v->v, 1, __ATOMIC_SEQ_CST);
}

uint64_t
nni_atomic_dec64_nv(nni_atomic_u64 *v)
{
	return (__atomic_sub_fetch(&v->v, 1, __ATOMIC_SEQ_CST));
}

bool
nni_atomic_cas64(nni_atomic_u64 *v, uint64_t comp, uint64_t new)
{

	return (__atomic_compare_exchange_n(
	    &v->v, &comp, new, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST));
}

int
nni_atomic_swap(nni_atomic_int *v, int u)
{
	return (__atomic_exchange_n(&v->v, u, __ATOMIC_SEQ_CST));
}

void
nni_atomic_init(nni_atomic_int *v)
{
	__atomic_store_n(&v->v, 0, __ATOMIC_SEQ_CST);
}

void
nni_atomic_add(nni_atomic_int *v, int bump)
{
	__atomic_add_fetch(&v->v, bump, __ATOMIC_RELAXED);
}

void
nni_atomic_sub(nni_atomic_int *v, int bump)
{
	__atomic_sub_fetch(&v->v, bump, __ATOMIC_RELAXED);
}

void
nni_atomic_set(nni_atomic_int *v, int val)
{
	__atomic_store_n(&v->v, val, __ATOMIC_SEQ_CST);
}

int
nni_atomic_get(nni_atomic_int *v)
{
	return (__atomic_load_n(&v->v, __ATOMIC_SEQ_CST));
}
void
nni_atomic_inc(nni_atomic_int *v)
{
	__atomic_add_fetch(&v->v, 1, __ATOMIC_SEQ_CST);
}

void
nni_atomic_dec(nni_atomic_int *v)
{
	__atomic_sub_fetch(&v->v, 1, __ATOMIC_SEQ_CST);
}

int
nni_atomic_dec_nv(nni_atomic_int *v)
{
	return (__atomic_sub_fetch(&v->v, 1, __ATOMIC_SEQ_CST));
}

bool
nni_atomic_cas(nni_atomic_int *v, int comp, int new)
{
	return (__atomic_compare_exchange_n(
	    &v->v, &comp, new, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST));
}

void *
nni_atomic_get_ptr(nni_atomic_ptr *v)
{
	return (__atomic_load_n(&v->v, __ATOMIC_SEQ_CST));
}

void
nni_atomic_set_ptr(nni_atomic_ptr *v, void *p)
{
	__atomic_store_n(&v->v, p, __ATOMIC_SEQ_CST);
}

#else

#include <pthread.h>

static pthread_mutex_t plat_atomic_lock = PTHREAD_MUTEX_INITIALIZER;

bool
nni_atomic_flag_test_and_set(nni_atomic_flag *f)
{
	bool v;
	pthread_mutex_lock(&plat_atomic_lock);
	v    = f->f;
	f->f = true;
	pthread_mutex_unlock(&plat_atomic_lock);
	return (v);
}

void
nni_atomic_flag_reset(nni_atomic_flag *f)
{
	pthread_mutex_lock(&plat_atomic_lock);
	f->f = false;
	pthread_mutex_unlock(&plat_atomic_lock);
}

void
nni_atomic_set_bool(nni_atomic_bool *b, bool n)
{
	pthread_mutex_lock(&plat_atomic_lock);
	b->b = n;
	pthread_mutex_unlock(&plat_atomic_lock);
}

bool
nni_atomic_get_bool(nni_atomic_bool *b)
{
	bool v;
	pthread_mutex_lock(&plat_atomic_lock);
	v = b->b;
	pthread_mutex_unlock(&plat_atomic_lock);
	return (v);
}

bool
nni_atomic_swap_bool(nni_atomic_bool *b, bool n)
{
	bool v;
	pthread_mutex_lock(&plat_atomic_lock);
	v    = b->b;
	b->b = n;
	pthread_mutex_unlock(&plat_atomic_lock);
	return (v);
}

void
nni_atomic_init_bool(nni_atomic_bool *b)
{
	b->b = false;
}

void
nni_atomic_add64(nni_atomic_u64 *v, uint64_t bump)
{
	pthread_mutex_lock(&plat_atomic_lock);
	v->v += bump;
	pthread_mutex_unlock(&plat_atomic_lock);
}

void
nni_atomic_sub64(nni_atomic_u64 *v, uint64_t bump)
{
	pthread_mutex_lock(&plat_atomic_lock);
	v->v -= bump;
	pthread_mutex_unlock(&plat_atomic_lock);
}

uint64_t
nni_atomic_get64(nni_atomic_u64 *v)
{
	uint64_t rv;
	pthread_mutex_lock(&plat_atomic_lock);
	rv = v->v;
	pthread_mutex_unlock(&plat_atomic_lock);
	return (rv);
}

void
nni_atomic_set64(nni_atomic_u64 *v, uint64_t u)
{
	pthread_mutex_lock(&plat_atomic_lock);
	v->v = u;
	pthread_mutex_unlock(&plat_atomic_lock);
}

uint64_t
nni_atomic_swap64(nni_atomic_u64 *v, uint64_t u)
{
	uint64_t rv;
	pthread_mutex_lock(&plat_atomic_lock);
	rv   = v->v;
	v->v = u;
	pthread_mutex_unlock(&plat_atomic_lock);
	return (rv);
}

void
nni_atomic_init64(nni_atomic_u64 *v)
{
	v->v = 0;
}

void
nni_atomic_inc64(nni_atomic_u64 *v)
{
	pthread_mutex_lock(&plat_atomic_lock);
	v->v++;
	pthread_mutex_unlock(&plat_atomic_lock);
}

uint64_t
nni_atomic_dec64_nv(nni_atomic_u64 *v)
{
	uint64_t nv;
	pthread_mutex_lock(&plat_atomic_lock);
	v->v--;
	nv = v->v;
	pthread_mutex_unlock(&plat_atomic_lock);
	return (nv);
}

bool
nni_atomic_cas64(nni_atomic_u64 *v, uint64_t comp, uint64_t new)
{
	bool result = false;
	pthread_mutex_lock(&plat_atomic_lock);
	if (v->v == comp) {
		v->v   = new;
		result = true;
	}
	pthread_mutex_unlock(&plat_atomic_lock);
	return (result);
}

void
nni_atomic_init(nni_atomic_int *v)
{
	pthread_mutex_lock(&plat_atomic_lock);
	v->v = 0;
	pthread_mutex_unlock(&plat_atomic_lock);
}

void
nni_atomic_add(nni_atomic_int *v, int bump)
{
	pthread_mutex_lock(&plat_atomic_lock);
	v->v += bump;
	pthread_mutex_unlock(&plat_atomic_lock);
}

void
nni_atomic_sub(nni_atomic_int *v, int bump)
{
	pthread_mutex_lock(&plat_atomic_lock);
	v->v -= bump;
	pthread_mutex_unlock(&plat_atomic_lock);
}

int
nni_atomic_get(nni_atomic_int *v)
{
	int rv;
	pthread_mutex_lock(&plat_atomic_lock);
	rv = v->v;
	pthread_mutex_unlock(&plat_atomic_lock);
	return (rv);
}

void
nni_atomic_set(nni_atomic_int *v, int i)
{
	pthread_mutex_lock(&plat_atomic_lock);
	v->v = i;
	pthread_mutex_unlock(&plat_atomic_lock);
}

void *
nni_atomic_get_ptr(nni_atomic_ptr *v)
{
	void *p;
	pthread_mutex_lock(&plat_atomic_lock);
	p = v->v;
	pthread_mutex_unlock(&plat_atomic_lock);
	return (p);
}

void
nni_atomic_set_ptr(nni_atomic_ptr *v, void *p)
{
	pthread_mutex_lock(&plat_atomic_lock);
	v->v = p;
	pthread_mutex_unlock(&plat_atomic_lock);
}

int
nni_atomic_swap(nni_atomic_int *v, int i)
{
	int rv;
	pthread_mutex_lock(&plat_atomic_lock);
	rv   = v->v;
	v->v = i;
	pthread_mutex_unlock(&plat_atomic_lock);
	return (rv);
}

void
nni_atomic_inc(nni_atomic_int *v)
{
	pthread_mutex_lock(&plat_atomic_lock);
	v->v++;
	pthread_mutex_unlock(&plat_atomic_lock);
}

void
nni_atomic_dec(nni_atomic_int *v)
{
	pthread_mutex_lock(&plat_atomic_lock);
	v->v--;
	pthread_mutex_unlock(&plat_atomic_lock);
}

int
nni_atomic_dec_nv(nni_atomic_int *v)
{
	int nv;
	pthread_mutex_lock(&plat_atomic_lock);
	v->v--;
	nv = v->v;
	pthread_mutex_unlock(&plat_atomic_lock);
	return (nv);
}

bool
nni_atomic_cas(nni_atomic_int *v, int comp, int new)
{
	bool result = false;
	pthread_mutex_lock(&plat_atomic_lock);
	if (v->v == comp) {
		v->v   = new;
		result = true;
	}
	pthread_mutex_unlock(&plat_atomic_lock);
	return (result);
}

#endif

#endif // NNG_PLATFORM_POSIX
