// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <ruby.h>

#ifdef HAVE_SYS_ATOMIC_H
#include <sys/atomic.h>
#else
# ifdef HAVE_ATOMIC_H
#  include <atomic.h>
# else
#  ifdef HAVE_LIBKERN_OSATOMIC_H
#    include <libkern/OSAtomic.h>
#  endif
# endif
#endif

static void ir_mark(void *value) {
    rb_gc_mark_maybe((VALUE) value);
}

static VALUE ir_alloc(VALUE klass) {
    return rb_data_object_alloc(klass, (void *) Qnil, ir_mark, NULL);
}

static VALUE ir_initialize(int argc, VALUE* argv, VALUE self) {
    VALUE value = Qnil;
    if (rb_scan_args(argc, argv, "01", &value) == 1) {
	value = argv[0];
    }
    DATA_PTR(self) = (void *) value;
    return Qnil;
}

static VALUE ir_get(VALUE self) {
#if HAVE_GCC_SYNC
    __sync_synchronize();
#elif defined _MSC_VER
    MemoryBarrier();
#elif __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050
    OSMemoryBarrier();
#endif
    return (VALUE) DATA_PTR(self);
}

static VALUE ir_set(VALUE self, VALUE new_value) {
    DATA_PTR(self) = (void *) new_value;
#if HAVE_GCC_SYNC
    __sync_synchronize();
#elif defined _MSC_VER
    MemoryBarrier();
#elif __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050
    OSMemoryBarrier();
#endif
    return new_value;
}

static VALUE ir_get_and_set(VALUE self, VALUE new_value) {
    VALUE old_value = ir_get(self);
    ir_set(self, new_value);
    return old_value;
}

static VALUE ir_compare_and_set(volatile VALUE self, VALUE expect_value, VALUE new_value) {
#if defined(__NetBSD__)
    if (atomic_cas_ptr((volatile void *) &DATA_PTR(self), expect_value, new_value)) {
	return Qtrue;
    }
#elif __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050
    if (OSAtomicCompareAndSwap64(expect_value, new_value, &DATA_PTR(self))) {
	return Qtrue;
    }
#elif defined(__sun)
/*  Assuming VALUE is uintptr_t */
/*  Based on the definition of uintptr_t from /usr/include/sys/int_types.h */
#if defined(_LP64) || defined(_I32LPx)
    /*  64-bit: uintptr_t === unsigned long */
    if (atomic_cas_ulong((uintptr_t *) &DATA_PTR(self), expect_value, new_value)) {
        return Qtrue;
    }
#else
    /*  32-bit: uintptr_t === unsigned int */
    if (atomic_cas_uint((uintptr_t *) &DATA_PTR(self), expect_value, new_value)) {
        return Qtrue;
    }
#endif
#elif defined _MSC_VER && defined _M_AMD64
    if (InterlockedCompareExchange64((LONGLONG*)&DATA_PTR(self), new_value, expect_value)) {
	return Qtrue;
    }
#elif defined _MSC_VER && defined _M_IX86
    if (InterlockedCompareExchange((LONG*)&DATA_PTR(self), new_value, expect_value)) {
	return Qtrue;
    }
#else
    if (__sync_bool_compare_and_swap(&DATA_PTR(self), expect_value, new_value)) {
	return Qtrue;
    }
#endif
    return Qfalse;
}

void Init_atomic_reference() {
    VALUE cAtomic;

    cAtomic = rb_define_class_under(rb_cObject, "Atomic", rb_cObject);

    rb_define_alloc_func(cAtomic, ir_alloc);

    rb_define_method(cAtomic, "initialize", ir_initialize, -1);
    rb_define_method(cAtomic, "get", ir_get, 0);
    rb_define_method(cAtomic, "value", ir_get, 0);
    rb_define_method(cAtomic, "set", ir_set, 1);
    rb_define_method(cAtomic, "value=", ir_set, 1);
    rb_define_method(cAtomic, "get_and_set", ir_get_and_set, 1);
    rb_define_method(cAtomic, "swap", ir_get_and_set, 1);
    rb_define_method(cAtomic, "compare_and_set", ir_compare_and_set, 2);
    rb_define_method(cAtomic, "compare_and_swap", ir_compare_and_set, 2);
}
