#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include <stdlib.h>
#include <string.h>


typedef double snum_t;
#define SN_FLOOR floor
#define SN_CEIL ceil
#define SN_NV(sv) (SvNV(sv))
static snum_t PRECISION = 0.01;
static snum_t OFFSET = 0.5555555;
static int precision_places(snum_t prec) {
	int places = 0;
	snum_t p = prec;
	while (p < 1.0) {
		p *= 10.0;
		places++;
		if (places > 18) break;
	}
   	return places;
}

static SV *sn_to_sv(snum_t val) {
	dTHX;
	int places = precision_places(PRECISION);
	char fmt[16];
	snprintf(fmt, sizeof(fmt), "%%.%df", places);
	char *num = Perl_form(fmt, val);
	size_t len = strlen(num);
	char *dot = strchr(num, '.');
	if (dot) {
		while (len > 1 && num[len - 1] == '0') {
			num[--len] = '\0';
		}
		if (len > 0 && num[len - 1] == '.') {
			num[--len] = '\0';
		}
	}
	snum_t val_out = strtod(num, NULL);
	if (val_out < val) {
		return newSVnv(val);
	}
	return newSVpv(num, 0);
}

static SV * new (snum_t num) {
	dTHX;
	return sv_bless(newRV_noinc(sn_to_sv(num)), gv_stashsv(newSVpv("smallnum::XS", 12), 0));
}

static snum_t _sref (SV * n) {
	dTHX;
	snum_t num;
	if (SvROK(n)) {
		num = SN_NV(SvRV(n));
	} else {
		num = SN_NV(n);
	}
	return num;
}

MODULE = smallnum::XS  PACKAGE = smallnum::XS
PROTOTYPES: ENABLE
FALLBACK: TRUE

SV * _smallnum (...)
	CODE:
		snum_t num = SN_NV(ST(0));
		RETVAL = new(num);
	OUTPUT:
		RETVAL

SV * _set_precision (num)
	SV * num
	CODE:
		PRECISION = SN_NV(num);
		RETVAL = sn_to_sv(PRECISION);
	OUTPUT:
		RETVAL

SV * _set_offset (num)
	SV * num
	CODE:
		OFFSET = SN_NV(num);
		RETVAL = sn_to_sv(OFFSET);
	OUTPUT:
		RETVAL

SV * _num (...)
	OVERLOAD: \"\"
	CODE:
		snum_t num = _sref(ST(0));
		snum_t out = num >= 0
			? PRECISION * SN_FLOOR((num + (OFFSET * PRECISION)) / PRECISION)
			: PRECISION * SN_CEIL((num - OFFSET * PRECISION) / PRECISION);
		RETVAL = sn_to_sv(out);
	OUTPUT:
		RETVAL

SV * _divide (...)
	OVERLOAD: /
	CODE:
		snum_t num1 = _sref(ST(0));
		snum_t num2 = _sref(ST(1));
		snum_t out = (num1 && num2)
			? (SvOK(ST(2)) && SvIV(ST(2)) > 0
				? num2 / num1
				: num1 / num2)
			: 0;
		RETVAL = new(out);
	OUTPUT:
		RETVAL

SV * _multiply (...)
	OVERLOAD: *
	CODE:
		snum_t num1 = _sref(ST(0));
		snum_t num2 = _sref(ST(1));
		snum_t out = num2 * num1;
		RETVAL = new(out);
	OUTPUT:
		RETVAL