#include "cwtmath.h"

static void nsfft_fd(const fft_object obj, fft_data* inp, fft_data* oup, const double lb, const double ub, double* w)
{
	int i;

	const int N = obj->N;
	const int L = N / 2;
	//w = (double*)malloc(sizeof(double)*N);

	const int M = divideby(N, 2);

	if (M == 0)
	{
		printf("The Non-Standard FFT Length must be a power of 2");
		exit(1);
	}

	double* temp1 = (double*)malloc(sizeof(double) * L);
	double* temp2 = (double*)malloc(sizeof(double) * L);

	const double delta = (ub - lb) / N;
	int j              = -N;
	const double den   = 2 * (ub - lb);

	for (i = 0; i < N; ++i)
	{
		w[i] = (double)j / den;
		j += 2;
	}

	fft_exec(obj, inp, oup);


	for (i = 0; i < L; ++i)
	{
		temp1[i] = oup[i].re;
		temp2[i] = oup[i].im;
	}

	for (i = 0; i < N - L; ++i)
	{
		oup[i].re = oup[i + L].re;
		oup[i].im = oup[i + L].im;
	}

	for (i = 0; i < L; ++i)
	{
		oup[N - L + i].re = temp1[i];
		oup[N - L + i].im = temp2[i];
	}

	const double plb = PI2 * lb;

	for (i = 0; i < N; ++i)
	{
		const double tempr = oup[i].re;
		const double tempi = oup[i].im;
		const double theta = w[i] * plb;

		oup[i].re = delta * (tempr * cos(theta) + tempi * sin(theta));
		oup[i].im = delta * (tempi * cos(theta) - tempr * sin(theta));
	}

	
	//free(w);
	free(temp1);
	free(temp2);
}

static void nsfft_bk(fft_object obj, fft_data* inp, fft_data* oup, double lb, double ub, double* t)
{
	int i;

	const int N = obj->N;
	const int L = N / 2;

	const int M = divideby(N, 2);

	if (M == 0)
	{
		printf("The Non-Standard FFT Length must be a power of 2");
		exit(1);
	}

	double* temp1  = (double*)malloc(sizeof(double) * L);
	double* temp2  = (double*)malloc(sizeof(double) * L);
	double* w      = (double*)malloc(sizeof(double) * N);
	fft_data* inpt = (fft_data*)malloc(sizeof(fft_data) * N);

	const double delta = (ub - lb) / N;
	int j              = -N;
	const double den   = 2 * (ub - lb);

	for (i = 0; i < N; ++i)
	{
		w[i] = (double)j / den;
		j += 2;
	}

	const double plb = PI2 * lb;

	for (i = 0; i < N; ++i)
	{
		const double theta = w[i] * plb;

		inpt[i].re = (inp[i].re * cos(theta) - inp[i].im * sin(theta)) / delta;
		inpt[i].im = (inp[i].im * cos(theta) + inp[i].re * sin(theta)) / delta;
	}

	for (i = 0; i < L; ++i)
	{
		temp1[i] = inpt[i].re;
		temp2[i] = inpt[i].im;
	}

	for (i = 0; i < N - L; ++i)
	{
		inpt[i].re = inpt[i + L].re;
		inpt[i].im = inpt[i + L].im;
	}

	for (i = 0; i < L; ++i)
	{
		inpt[N - L + i].re = temp1[i];
		inpt[N - L + i].im = temp2[i];
	}

	fft_exec(obj, inpt, oup);

	for (i = 0; i < N; ++i) { t[i] = lb + i * delta; }

	free(w);
	free(temp1);
	free(temp2);
	free(inpt);
}

void nsfft_exec(fft_object obj, fft_data* inp, fft_data* oup, double lb, double ub, double* w)
{
	if (obj->sgn == 1) { nsfft_fd(obj, inp, oup, lb, ub, w); }
	else if (obj->sgn == -1) { nsfft_bk(obj, inp, oup, lb, ub, w); }
}

static double fix(double x)
{
	// Rounds to the integer nearest to zero 
	if (x >= 0.) { return floor(x); }
	return ceil(x);
}

int nint(double N)
{
	//const int i = (int)(N + 0.49999);
	//return i;
	return (int)(N + 0.49999);
}

double cwt_gamma(double x)
{
	/*
	 * This C program code is based on  W J Cody's fortran code.
	 * http://www.netlib.org/specfun/gamma
	 * 
	 * References:
   "An Overview of Software Development for Special Functions",
	W. J. Cody, Lecture Notes in Mathematics, 506,
	Numerical Analysis Dundee, 1975, G. A. Watson (ed.),
	Springer Verlag, Berlin, 1976.

   Computer Approximations, Hart, Et. Al., Wiley and sons, New York, 1968.
   */ 
   
	// numerator and denominator coefficients for 1 <= x <= 2

	double oup, yi, z;
	int i;

	const double spi   = 0.9189385332046727417803297;
	const double pi    = 3.1415926535897932384626434;
	const double xmax  = 171.624e+0;
	const double xinf  = 1.79e308;
	const double eps   = 2.22e-16;
	const double xninf = 1.79e-308;

	double num[8] = {
		-1.71618513886549492533811e+0,
		2.47656508055759199108314e+1,
		-3.79804256470945635097577e+2,
		6.29331155312818442661052e+2,
		8.66966202790413211295064e+2,
		-3.14512729688483675254357e+4,
		-3.61444134186911729807069e+4,
		6.64561438202405440627855e+4
	};

	double den[8] = {
		-3.08402300119738975254353e+1,
		3.15350626979604161529144e+2,
		-1.01515636749021914166146e+3,
		-3.10777167157231109440444e+3,
		2.25381184209801510330112e+4,
		4.75584627752788110767815e+3,
		-1.34659959864969306392456e+5,
		-1.15132259675553483497211e+5
	}; 
                       
	// Coefficients for Hart's Minimax approximation x >= 12       


	double c[7] = {
		-1.910444077728e-03,
		8.4171387781295e-04,
		-5.952379913043012e-04,
		7.93650793500350248e-04,
		-2.777777777777681622553e-03,
		8.333333333333333331554247e-02,
		5.7083835261e-03
	};

	double y    = x;
	int swi     = 0;
	double fact = 1.0;
	int n       = 0;


	if (y < 0.)
	{
		// Negative x
		y   = -x;
		yi  = fix(y);
		oup = y - yi;

		if (oup != 0.0)
		{
			if (yi != fix(yi * .5) * 2.) { swi = 1; }
			fact = -pi / sin(pi * oup);
			y += 1.;
		}
		else { return xinf; }
	}

	if (y < eps)
	{
		if (y >= xninf) { oup = 1.0 / y; }
		else { return xinf; }
	}
	else if (y < 12.)
	{
		yi = y;
		if (y < 1.)
		{
			z = y;
			y += 1.;
		}
		else
		{
			n = (int)y - 1;
			y -= (double)n;
			z = y - 1.0;
		}
		double nsum = 0.;
		double dsum = 1.;
		for (i = 0; i < 8; ++i)
		{
			nsum = (nsum + num[i]) * z;
			dsum = dsum * z + den[i];
		}
		oup = nsum / dsum + 1.;

		if (yi < y) { oup /= yi; }
		else if (yi > y)
		{
			for (i = 0; i < n; ++i)
			{
				oup *= y;
				y += 1.;
			}
		}
	}
	else
	{
		if (y <= xmax)
		{
			const double y2 = y * y;
			double sum      = c[6];
			for (i = 0; i < 6; ++i) { sum = sum / y2 + c[i]; }
			sum = sum / y - y + spi;
			sum += (y - .5) * log(y);
			oup = exp(sum);
		}
		else { return (xinf); }
	}

	if (swi) { oup = -oup; }
	if (fact != 1.) { oup = fact / oup; }

	return oup;
}
