#define PERL_NO_GET_CONTEXT // we'll define thread context if necessary (faster)
#include "EXTERN.h"         // globals/constant import locations
#include "perl.h"           // Perl symbols, structures and constants definition
#include "XSUB.h"           // xsubpp functions and macros
#include <stdlib.h>         // rand()
#include <string.h>

double PRECISION = 0;
double OFFSET = 0.5555555;

static SV * new (double num) {
        dTHX;
	AV * n = newAV();
	av_push(n, newSVnv(num));
	AV * e = newAV();
	av_push(e, newSVnv(num));
	av_push(n, newRV_noinc((SV*)e));
        return sv_bless(newRV_noinc((SV*)n), gv_stashsv(newSVpv("Number::Equation::XS", 20), 0));
}

static double precise (double num) {
 	if (PRECISION) {
		num = num >= 0
			? PRECISION * floor(( num + ( OFFSET * PRECISION )) / PRECISION)
			: PRECISION * ceil(( num - OFFSET * PRECISION) / PRECISION);
	}
	return num;
}

MODULE = Number::Equation::XS  PACKAGE = Number::Equation::XS
PROTOTYPES: ENABLE
FALLBACK: TRUE

SV * new (...)
	CODE:
		double num = SvNV(ST(1));
		if (ST(2) && SvOK(ST(2)) && (SvTYPE(ST(2)) == SVt_NV || SvTYPE(ST(2)) == SVt_IV)) {
			PRECISION = SvNV(ST(2));
		}
		if (ST(3) && SvOK(ST(3)) && SvTYPE(ST(3)) == SVt_NV) {
			OFFSET = SvNV(ST(3));
		}
		RETVAL = new(num);
	OUTPUT:
		RETVAL


SV * stringify (self, ...)
	SV * self
	OVERLOAD: \"\"
	CODE: 
		AV * s = (AV*)SvRV(self);
		double val = SvNV(*av_fetch(s, 0, 0));
		RETVAL = newSVnv(precise(val));
	OUTPUT:
		RETVAL


SV * add (self, num, ...)
	SV * self
	SV * num
	OVERLOAD: +
	CODE:
		AV * s = (AV*)SvRV(self);
		AV * last = (AV*)SvRV(*av_fetch(s, -1, 0));
		av_push(last, newSVpv("+", 1));
		av_push(last, newSVsv(num));
		double val = SvNV(*av_fetch(s, 0, 0));
		val = val + SvNV(num);
		av_store(s, 0, newSVnv(val));
		RETVAL = newSVsv(self);
	OUTPUT:
		RETVAL

SV * mult (self, num, ...)
	SV * self
	SV * num
	OVERLOAD: *
	CODE:
		AV * s = (AV*)SvRV(self);
		AV * last = (AV*)SvRV(*av_fetch(s, -1, 0));
		av_push(last, newSVpv("*", 1));
		av_push(last, newSVsv(num));
		double val = SvNV(*av_fetch(s, 0, 0));
		val = val * SvNV(num);
		av_store(s, 0, newSVnv(val));
		RETVAL = newSVsv(self);
	OUTPUT:
		RETVAL

SV * subt (self, num, ...)
	SV * self
	SV * num
	OVERLOAD: -
	CODE:
		AV * s = (AV*)SvRV(self);
		double val = SvNV(*av_fetch(s, 0, 0));
		if (ST(2) && SvOK(ST(2)) && SvIV(ST(2)) > 0) {
			av_unshift(s, 1);
			AV * n = newAV();
			av_push(n, newSVsv(num));
			av_push(n, newSVpv("-", 1));
			av_store(s, 1, newRV_noinc((SV*)n));
			val = SvNV(num) - val;	
		} else {
			AV * last = (AV*)SvRV(*av_fetch(s, -1, 0));
			av_push(last, newSVpv("-", 1));
			av_push(last, newSVsv(num));
			val = val - SvNV(num);
		}
		av_store(s, 0, newSVnv(val));
		RETVAL = newSVsv(self);
	OUTPUT:
		RETVAL

SV * div (self, num, ...)
	SV * self
	SV * num
	OVERLOAD: /
	CODE:
		AV * s = (AV*)SvRV(self);
		double val = SvNV(*av_fetch(s, 0, 0));
		if (ST(2) && SvOK(ST(2)) && SvIV(ST(2)) > 0) {
			av_unshift(s, 1);
			AV * n = newAV();
			av_push(n, newSVsv(num));
			av_push(n, newSVpv("/", 1));
			av_store(s, 1, newRV_noinc((SV*)n));
			val = SvNV(num) / val;	
		} else {
			AV * last = (AV*)SvRV(*av_fetch(s, -1, 0));
			av_push(last, newSVpv("/", 1));
			av_push(last, newSVsv(num));
			val = val / SvNV(num);
		}
		av_store(s, 0, newSVnv(val));
		RETVAL = newSVsv(self);
	OUTPUT:
		RETVAL


SV * mod (self, num, ...)
	SV * self
	SV * num
	OVERLOAD: %
	CODE:
		AV * s = (AV*)SvRV(self);
		long val = SvIV(*av_fetch(s, 0, 0));
		if (ST(2) && SvOK(ST(2)) && SvIV(ST(2)) > 0) {
			av_unshift(s, 1);
			AV * n = newAV();
			av_push(n, newSVsv(num));
			av_push(n, newSVpv("%", 1));
			av_store(s, 1, newRV_noinc((SV*)n));
			val = SvIV(num) % val;	
		} else {
			AV * last = (AV*)SvRV(*av_fetch(s, -1, 0));
			av_push(last, newSVpv("%", 1));
			av_push(last, newSVsv(num));
			val = val % SvIV(num);
		}
		av_store(s, 0, newSVnv(val));
		RETVAL = newSVsv(self);
	OUTPUT:
		RETVAL

SV * exp (self, num, ...)
	SV * self
	SV * num
	OVERLOAD: **
	CODE:
		AV * s = (AV*)SvRV(self);
		double val = SvNV(*av_fetch(s, 0, 0));
		if (ST(2) && SvOK(ST(2)) && SvIV(ST(2)) > 0) {
			av_unshift(s, 1);
			AV * n = newAV();
			av_push(n, newSVsv(num));
			av_push(n, newSVpv("**", 2));
			av_store(s, 1, newRV_noinc((SV*)n));
			val = pow(SvNV(num), val);	
		} else {
			AV * last = (AV*)SvRV(*av_fetch(s, -1, 0));
			av_push(last, newSVpv("**", 2));
			av_push(last, newSVsv(num));
			val = pow(val, SvNV(num));
		}
		av_store(s, 0, newSVnv(val));
		RETVAL = newSVsv(self);
	OUTPUT:
		RETVAL


SV * equation (self, ...)
	SV * self
	CODE:
		STRLEN retlen;
		AV * s = (AV*)SvRV(self);
		char query[4321] = "";
		int closing = 0;
		int l = av_len(s);
		for (int i = 1; i <= l; i++) {
			AV * equation = (AV*)SvRV(*av_fetch(s, i, 0));
			int el = av_len(equation) + 1;
			for (int x = 0; x < el / 2; x++) {
				strcat(query, "(");
			}
			strcat(query, SvPV(*av_fetch(equation, 0, 0), retlen));
			for (int x = 1; x <= el - 1; x++) {
				char * operator = SvPV(*av_fetch(equation, x, 0), retlen);
				x++;
				char * val = x <= el - 1 ? SvPV(*av_fetch(equation, x, 0), retlen) : "";
				strcat(query, " ");
				strcat(query, operator);
				strcat(query, " ");
				if (val != "") {
					strcat(query, val);
					strcat(query, ")");
				} else {
					closing++;
				}
			}
		}

		for (int x = 0; x < closing; x++) {
			strcat(query, ")");
		}

		if (PRECISION) {
			strcat(query, " ≈ ");
		} else {
			strcat(query, " = ");
		}

		double p = precise(SvNV(*av_fetch(s, 0, 0)));
		SV * fun = newSVnv(p);	
		strcat(query, SvPV(fun, retlen));

		RETVAL = newSVpv(query, strlen(query));
	OUTPUT:
		RETVAL