#include <ruby.h>
#include <unistd.h>
#include <sys/mman.h>
#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include "raindrops_atomic.h"

#ifndef SIZET2NUM
#  define SIZET2NUM(x) ULONG2NUM(x)
#endif
#ifndef NUM2SIZET
#  define NUM2SIZET(x) NUM2ULONG(x)
#endif

/*
 * most modern CPUs have a cache-line size of 64 or 128.
 * We choose a bigger one by default since our structure is not
 * heavily used
 */
static size_t raindrop_size = 128;
static size_t rd_page_size;

#define PAGE_MASK               (~(rd_page_size - 1))
#define PAGE_ALIGN(addr)        (((addr) + rd_page_size - 1) & PAGE_MASK)

/* each raindrop is a counter */
struct raindrop {
	unsigned long counter;
} __attribute__((packed));

/* allow mmap-ed regions to store more than one raindrop */
struct raindrops {
	size_t size;
	size_t capa;
	pid_t pid;
	struct raindrop *drops;
};

/* called by GC */
static void gcfree(void *ptr)
{
	struct raindrops *r = ptr;

	if (r->drops != MAP_FAILED) {
		int rv = munmap(r->drops, raindrop_size * r->capa);
		if (rv != 0)
			rb_bug("munmap failed in gc: %s", strerror(errno));
	}

	xfree(ptr);
}

/* automatically called at creation (before initialize) */
static VALUE alloc(VALUE klass)
{
	struct raindrops *r;
	VALUE rv = Data_Make_Struct(klass, struct raindrops, NULL, gcfree, r);

	r->drops = MAP_FAILED;
	return rv;
}

static struct raindrops *get(VALUE self)
{
	struct raindrops *r;

	Data_Get_Struct(self, struct raindrops, r);

	if (r->drops == MAP_FAILED)
		rb_raise(rb_eStandardError, "invalid or freed Raindrops");

	return r;
}

/*
 * call-seq:
 *	Raindrops.new(size)	-> raindrops object
 *
 * Initializes a Raindrops object to hold +size+ counters.  +size+ is
 * only a hint and the actual number of counters the object has is
 * dependent on the CPU model, number of cores, and page size of
 * the machine.  The actual size of the object will always be equal
 * or greater than the specified +size+.
 */
static VALUE init(VALUE self, VALUE size)
{
	struct raindrops *r = DATA_PTR(self);
	int tries = 1;
	size_t tmp;

	if (r->drops != MAP_FAILED)
		rb_raise(rb_eRuntimeError, "already initialized");

	r->size = NUM2SIZET(size);
	if (r->size < 1)
		rb_raise(rb_eArgError, "size must be >= 1");

	tmp = PAGE_ALIGN(raindrop_size * r->size);
	r->capa = tmp / raindrop_size;
	assert(PAGE_ALIGN(raindrop_size * r->capa) == tmp && "not aligned");

retry:
	r->drops = mmap(NULL, tmp,
	                PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0);
	if (r->drops == MAP_FAILED) {
		if ((errno == EAGAIN || errno == ENOMEM) && tries-- > 0) {
			rb_gc();
			goto retry;
		}
		rb_sys_fail("mmap");
	}
	r->pid = getpid();

	return self;
}

/*
 * mremap() is currently broken with MAP_SHARED
 * https://bugzilla.kernel.org/show_bug.cgi?id=8691
 */
#if defined(HAVE_MREMAP) && !defined(MREMAP_WORKS_WITH_MAP_SHARED)
#  undef HAVE_MREMAP
#endif

#ifdef HAVE_MREMAP
#ifndef MREMAP_MAYMOVE
#  warn MREMAP_MAYMOVE undefined
#  define MREMAP_MAYMOVE 0
#endif
static void resize(struct raindrops *r, size_t new_rd_size)
{
	size_t old_size = raindrop_size * r->capa;
	size_t new_size = PAGE_ALIGN(raindrop_size * new_rd_size);
	void *old_address = r->drops;
	void *rv;

	if (r->pid != getpid())
		rb_raise(rb_eRuntimeError, "cannot mremap() from child");

	rv = mremap(old_address, old_size, new_size, MREMAP_MAYMOVE);
	if (rv == MAP_FAILED) {
		if (errno == EAGAIN || errno == ENOMEM) {
			rb_gc();
			rv = mremap(old_address, old_size, new_size, 0);
		}
		if (rv == MAP_FAILED)
			rb_sys_fail("mremap");
	}
	r->drops = rv;
	r->size = new_rd_size;
	r->capa = new_size / raindrop_size;
	assert(r->capa >= r->size && "bad sizing");
}
#else /* ! HAVE_MREMAP */
/*
 * we cannot use munmap + mmap to reallocate the buffer since it may
 * already be shared by other processes, so we just fail
 */
static void resize(struct raindrops *r, size_t new_rd_size)
{
	rb_raise(rb_eRangeError, "mremap(2) is not available");
}
#endif /* ! HAVE_MREMAP */

/*
 * call-seq:
 *	rd.size = new_size
 *
 * Increases or decreases the current capacity of our Raindrop.
 * Raises RangeError if +new_size+ is too big or small for the
 * current backing store
 */
static VALUE setsize(VALUE self, VALUE new_size)
{
	size_t new_rd_size = NUM2SIZET(new_size);
	struct raindrops *r = get(self);

	if (new_rd_size <= r->capa)
		r->size = new_rd_size;
	else
		resize(r, new_rd_size);

	return new_size;
}

/*
 * call-seq:
 *	rd.capa		-> Integer
 *
 * Returns the number of slots allocated (but not necessarily used) by
 * the Raindrops object.
 */
static VALUE capa(VALUE self)
{
	return SIZET2NUM(get(self)->capa);
}

/*
 * call-seq:
 *	rd.dup		-> rd_copy
 *
 * Duplicates and snapshots the current state of a Raindrops object.
 */
static VALUE init_copy(VALUE dest, VALUE source)
{
	struct raindrops *dst = DATA_PTR(dest);
	struct raindrops *src = get(source);

	init(dest, SIZET2NUM(src->size));
	memcpy(dst->drops, src->drops, raindrop_size * src->size);

	return dest;
}

static unsigned long *addr_of(VALUE self, VALUE index)
{
	struct raindrops *r = get(self);
	unsigned long off = FIX2ULONG(index) * raindrop_size;

	if (off >= raindrop_size * r->size)
		rb_raise(rb_eArgError, "offset overrun");

	return (unsigned long *)((unsigned long)r->drops + off);
}

static unsigned long incr_decr_arg(int argc, const VALUE *argv)
{
	if (argc > 2 || argc < 1)
		rb_raise(rb_eArgError,
		         "wrong number of arguments (%d for 1+)", argc);

	return argc == 2 ? NUM2ULONG(argv[1]) : 1;
}

/*
 * call-seq:
 *	rd.incr(index[, number])	-> result
 *
 * Increments the value referred to by the +index+ by +number+.
 * +number+ defaults to +1+ if unspecified.
 */
static VALUE incr(int argc, VALUE *argv, VALUE self)
{
	unsigned long nr = incr_decr_arg(argc, argv);

	return ULONG2NUM(__sync_add_and_fetch(addr_of(self, argv[0]), nr));
}

/*
 * call-seq:
 *	rd.decr(index[, number])	-> result
 *
 * Decrements the value referred to by the +index+ by +number+.
 * +number+ defaults to +1+ if unspecified.
 */
static VALUE decr(int argc, VALUE *argv, VALUE self)
{
	unsigned long nr = incr_decr_arg(argc, argv);

	return ULONG2NUM(__sync_sub_and_fetch(addr_of(self, argv[0]), nr));
}

/*
 * call-seq:
 *	rd.to_ary	-> Array
 *
 * converts the Raindrops structure to an Array
 */
static VALUE to_ary(VALUE self)
{
	struct raindrops *r = get(self);
	VALUE rv = rb_ary_new2(r->size);
	size_t i;
	unsigned long base = (unsigned long)r->drops;

	for (i = 0; i < r->size; i++) {
		rb_ary_push(rv, ULONG2NUM(*((unsigned long *)base)));
		base += raindrop_size;
	}

	return rv;
}

/*
 * call-seq:
 *	rd.size		-> Integer
 *
 * Returns the number of counters a Raindrops object can hold.  Due to
 * page alignment, this is always equal or greater than the number of
 * requested slots passed to Raindrops.new
 */
static VALUE size(VALUE self)
{
	return SIZET2NUM(get(self)->size);
}

/*
 * call-seq:
 *	rd[index] = value
 *
 * Assigns +value+ to the slot designated by +index+
 */
static VALUE aset(VALUE self, VALUE index, VALUE value)
{
	unsigned long *addr = addr_of(self, index);

	*addr = NUM2ULONG(value);

	return value;
}

/*
 * call-seq:
 *	rd[index]	-> value
 *
 * Returns the value of the slot designated by +index+
 */
static VALUE aref(VALUE self, VALUE index)
{
	return  ULONG2NUM(*addr_of(self, index));
}

#ifdef __linux__
void Init_raindrops_linux_inet_diag(void);
void Init_raindrops_linux_tcp_info(void);
#endif

#ifndef _SC_NPROCESSORS_CONF
#  if defined _SC_NPROCESSORS_ONLN
#    define _SC_NPROCESSORS_CONF _SC_NPROCESSORS_ONLN
#  elif defined _SC_NPROC_ONLN
#    define _SC_NPROCESSORS_CONF _SC_NPROC_ONLN
#  elif defined _SC_CRAY_NCPU
#    define _SC_NPROCESSORS_CONF _SC_CRAY_NCPU
#  endif
#endif

/*
 * call-seq:
 *	rd.evaporate!	-> nil
 *
 * Releases mmap()-ed memory allocated for the Raindrops object back
 * to the OS.  The Ruby garbage collector will also release memory
 * automatically when it is not needed, but this forces release
 * under high memory pressure.
 */
static VALUE evaporate_bang(VALUE self)
{
	struct raindrops *r = get(self);
	void *addr = r->drops;

	r->drops = MAP_FAILED;
	if (munmap(addr, raindrop_size * r->capa) != 0)
		rb_sys_fail("munmap");
	return Qnil;
}

void Init_raindrops_ext(void)
{
	VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject);
	long tmp = 2;

#ifdef _SC_NPROCESSORS_CONF
	tmp = sysconf(_SC_NPROCESSORS_CONF);
#endif
	/* no point in padding on single CPU machines */
	if (tmp == 1)
		raindrop_size = sizeof(unsigned long);
#ifdef _SC_LEVEL1_DCACHE_LINESIZE
	if (tmp != 1) {
		tmp = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
		if (tmp > 0)
			raindrop_size = (size_t)tmp;
	}
#endif
#if defined(_SC_PAGE_SIZE)
	rd_page_size = (size_t)sysconf(_SC_PAGE_SIZE);
#elif defined(_SC_PAGESIZE)
	rd_page_size = (size_t)sysconf(_SC_PAGESIZE);
#elif defined(HAVE_GETPAGESIZE)
	rd_page_size = (size_t)getpagesize();
#elif defined(PAGE_SIZE)
	rd_page_size = (size_t)PAGE_SIZE;
#elif defined(PAGESIZE)
	rd_page_size = (size_t)PAGESIZE;
#else
#  error unable to detect page size for mmap()
#endif
	if ((rd_page_size == (size_t)-1) || (rd_page_size < raindrop_size))
		rb_raise(rb_eRuntimeError,
			 "system page size invalid: %llu",
			 (unsigned long long)rd_page_size);

	/*
	 * The size of one page of memory for a mmap()-ed Raindrops region.
	 * Typically 4096 bytes under Linux.
	 */
	rb_define_const(cRaindrops, "PAGE_SIZE", SIZET2NUM(rd_page_size));

	/*
	 * The size (in bytes) of a slot in a Raindrops object.
	 * This is the size of a word on single CPU systems and
	 * the size of the L1 cache line size if detectable.
	 *
	 * Defaults to 128 bytes if undetectable.
	 */
	rb_define_const(cRaindrops, "SIZE", SIZET2NUM(raindrop_size));

	/*
	 * The maximum value a raindrop counter can hold
	 */
	rb_define_const(cRaindrops, "MAX", ULONG2NUM((unsigned long)-1));

	rb_define_alloc_func(cRaindrops, alloc);

	rb_define_method(cRaindrops, "initialize", init, 1);
	rb_define_method(cRaindrops, "incr", incr, -1);
	rb_define_method(cRaindrops, "decr", decr, -1);
	rb_define_method(cRaindrops, "to_ary", to_ary, 0);
	rb_define_method(cRaindrops, "[]", aref, 1);
	rb_define_method(cRaindrops, "[]=", aset, 2);
	rb_define_method(cRaindrops, "size", size, 0);
	rb_define_method(cRaindrops, "size=", setsize, 1);
	rb_define_method(cRaindrops, "capa", capa, 0);
	rb_define_method(cRaindrops, "initialize_copy", init_copy, 1);
	rb_define_method(cRaindrops, "evaporate!", evaporate_bang, 0);

#ifdef __linux__
	Init_raindrops_linux_inet_diag();
	Init_raindrops_linux_tcp_info();
#endif
}
