/*
  Copyright (c) 2015, Rafat Hussain
*/
/*
This code is a C translation ( with some modifications) of Wavelet Software provided by
C. Torrence and G. Compo, and is available at URL: http://atoc.colorado.edu/research/wavelets/''.
*/

#include "cwt.h"

double factorial(int N) {
	static const double fact[41] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000,
		20922789888000, 355687428096000, 6402373705728000, 121645100408832000, 2432902008176640000, 51090942171709440000.0, 1124000727777607680000.0,
		25852016738884976640000.0, 620448401733239439360000.0, 15511210043330985984000000.0, 403291461126605635584000000.0, 10888869450418352160768000000.0,
		304888344611713860501504000000.0, 8841761993739701954543616000000.0, 265252859812191058636308480000000.0, 8222838654177922817725562880000000.0,
		263130836933693530167218012160000000.0, 8683317618811886495518194401280000000.0, 295232799039604140847618609643520000000.0, 10333147966386144929666651337523200000000.0,
		371993326789901217467999448150835200000000.0, 13763753091226345046315979581580902400000000.0, 523022617466601111760007224100074291200000000.0,
		20397882081197443358640281739902897356800000000.0, 815915283247897734345611269596115894272000000000.0 };

	if (N > 40 || N < 0) {
		printf("This program is only valid for 0 <= N <= 40 \n");
		return -1.0;
	}

	return fact[N];

}
static void wave_function(int nk, double dt,int mother, double param,double scale1, double *kwave, double pi,double *period1,
	double *coi1, fft_data *daughter) {

	double norm, expnt, fourier_factor;
	int k, m;
	double temp;
	int sign,re;


	if (mother == 0) {
		//MORLET
		if (param < 0.0) {
			param = 6.0;
		}
		norm = sqrt(2.0*pi*scale1 / dt)*pow(pi,-0.25);

		for (k = 1; k <= nk / 2 + 1; ++k) {
			temp = (scale1*kwave[k-1] - param);
			expnt = -0.5 * temp * temp;
			daughter[k - 1].re = norm * exp(expnt);
			daughter[k - 1].im = 0.0;
		}
		for (k = nk / 2 + 2; k <= nk; ++k) {
			daughter[k - 1].re = daughter[k - 1].im = 0.0;
		}
		fourier_factor = (4.0*pi) / (param + sqrt(2.0 + param*param));
		*period1 = scale1*fourier_factor;
		*coi1 = fourier_factor / sqrt(2.0);
	}
	else if (mother == 1) {
		// PAUL
		if (param < 0.0) {
			param = 4.0;
		}
		m = (int)param;
		norm = sqrt(2.0*pi*scale1 / dt)*(pow(2.0,(double)m) / sqrt((double)(m*factorial(2 * m - 1))));
		for (k = 1; k <= nk / 2 + 1; ++k) {
			temp = scale1 * kwave[k - 1];
			expnt = - temp;
			daughter[k - 1].re = norm * pow(temp,(double)m) * exp(expnt);
			daughter[k - 1].im = 0.0;
		}
		for (k = nk / 2 + 2; k <= nk; ++k) {
			daughter[k - 1].re = daughter[k - 1].im = 0.0;
		}
		fourier_factor = (4.0*pi) / (2.0 * m + 1.0);
		*period1 = scale1*fourier_factor;
		*coi1 = fourier_factor * sqrt(2.0);
	}
	else if (mother == 2) {
		if (param < 0.0) {
			param = 2.0;
		}
		m = (int)param;

		if (m % 2 == 0) {
			re = 1;
		}
		else {
			re = 0;
		}

		if (m % 4 == 0 || m % 4 == 1) {
			sign = -1;
		}
		else {
			sign = 1;
		}


		norm = sqrt(2.0*pi*scale1 / dt)*sqrt(1.0 / cwt_gamma(m + 0.50));
		norm *= sign;

		if (re == 1) {
			for (k = 1; k <= nk; ++k) {
				temp = scale1 * kwave[k - 1];
				daughter[k - 1].re = norm*pow(temp,(double)m)*exp(-0.50*pow(temp,2.0));
				daughter[k - 1].im = 0.0;
			}
		}
		else if (re == 0) {
			for (k = 1; k <= nk; ++k) {
				temp = scale1 * kwave[k - 1];
				daughter[k - 1].re = 0.0;
				daughter[k - 1].im = norm*pow(temp, (double)m)*exp(-0.50*pow(temp, 2.0));
			}
		}
		fourier_factor = (2.0*pi) * sqrt(2.0 / (2.0 * m + 1.0));
		*period1 = scale1*fourier_factor;
		*coi1 = fourier_factor / sqrt(2.0);
	}
}

void cwavelet(const double *y, int N, double dt, int mother, double param, double s0, double dj, int jtot, int npad,
	double *wave, double *scale, double *period, double *coi) {

	int i, j, k, iter;
	double ymean, freq1, pi, period1, coi1;
	double tmp1, tmp2;
	double scale1;
	double *kwave;
	fft_object obj, iobj;
	fft_data *ypad, *yfft,*daughter;

	(void)s0; (void)dj; /* yes, we need these parameters unused */

    // init with some value to avoid compilation warnings
    period1 = 0;
    coi1 = 0;

	pi = 4.0 * atan(1.0);

	if (npad < N) {
		printf("npad must be >= N \n");
		exit(-1);
	}

	obj = fft_init(npad, 1);
	iobj = fft_init(npad, -1);

	ypad = (fft_data*)malloc(sizeof(fft_data)* npad);
	yfft = (fft_data*)malloc(sizeof(fft_data)* npad);
	daughter = (fft_data*)malloc(sizeof(fft_data)* npad);
	kwave = (double*)malloc(sizeof(double)* npad);

	ymean = 0.0;

	for (i = 0; i < N; ++i) {
		ymean += y[i];
	}

	ymean /= N;

	for (i = 0; i < N; ++i) {
		ypad[i].re = y[i] - ymean;
		ypad[i].im = 0.0;
	}

	for (i = N; i < npad; ++i) {
		ypad[i].re = ypad[i].im = 0.0;
	}


	// Find FFT of the input y (ypad)

	fft_exec(obj, ypad, yfft);

	for (i = 0; i < npad; ++i) {
		yfft[i].re /= (double) npad;
		yfft[i].im /= (double) npad;
	}


	//Construct the wavenumber array

	freq1 = 2.0*pi / ((double)npad*dt);
	kwave[0] = 0.0;

	for (i = 1; i < npad / 2 + 1; ++i) {
		kwave[i] = i * freq1;
	}

	for (i = npad / 2 + 1; i < npad; ++i) {
		kwave[i] = -kwave[npad - i ];
	}

	
	// Main loop

	for (j = 1; j <= jtot; ++j) {
		scale1 = scale[j - 1];// = s0*pow(2.0, (double)(j - 1)*dj);
		wave_function(npad, dt, mother, param, scale1, kwave, pi,&period1,&coi1, daughter);
		period[j - 1] = period1;
		for (k = 0; k < npad; ++k) {
			tmp1 = daughter[k].re * yfft[k].re - daughter[k].im * yfft[k].im;
			tmp2 = daughter[k].re * yfft[k].im + daughter[k].im * yfft[k].re;
			daughter[k].re = tmp1;
			daughter[k].im = tmp2;
		}
		fft_exec(iobj, daughter, ypad);
		iter = 2 * (j - 1) * N;
		for (i = 0; i < N; ++i) {
			wave[iter + 2 * i] = ypad[i].re;
			wave[iter + 2 * i + 1] = ypad[i].im;
		}
	}
	

	for (i = 1; i <= (N + 1) / 2; ++i) {
		coi[i - 1] = coi1 * dt * ((double)i - 1.0);
		coi[N - i] = coi[i - 1];
	}
	

	
	free(kwave);
	free(ypad);
	free(yfft);
	free(daughter);

	free_fft(obj);
	free_fft(iobj);

}

void psi0(int mother, double param,double *val,int *real) {
	double pi,coeff;
	int m,sign;

	m = (int)param;
	pi = 4.0 * atan(1.0);

	if (mother == 0) {
		// Morlet
		*val = 1.0 / sqrt(sqrt(pi));
		*real = 1;
	}
	else if (mother == 1) {
		//Paul
		if (m % 2 == 0) {
			*real = 1;
		}
		else {
			*real = 0;
		}

		if (m % 4 == 0 || m % 4 == 1) {
			sign = 1;
		}
		else {
			sign = -1;
		}
		*val = sign * pow(2.0, (double)m) * factorial(m) / (sqrt(pi * factorial(2 * m)));

	}
	else if (mother == 2) {
		// D.O.G
		*real = 1;

		if (m % 2 == 0) {
			if (m % 4 == 0) {
				sign = -1;
			}
			else {
				sign = 1;
			}
			coeff = sign * pow(2.0, (double)m / 2) / cwt_gamma(0.5);
			*val = coeff * cwt_gamma(((double)m + 1.0) / 2.0) / sqrt(cwt_gamma(m + 0.50));
		}
		else {
			*val = 0;
		}
	}
}

static int maxabs(double *array,int N) {
	double maxval,temp;
	int i,index;
	maxval = 0.0;
	index = -1;

	for (i = 0; i < N; ++i) {
		temp = fabs(array[i]);
		if (temp >= maxval) {
			maxval = temp;
			index = i;
		}
	}

	return index;
}


double cdelta(int mother, double param, double psi0 ) {
	int N,i,j,iter;
	double *delta, *scale,*period,*wave,*coi,*mval;
	double den,cdel;
	double subscale,dt,dj,s0;
	int jtot;
	int maxarr;

	subscale = 8.0;
	dt = 0.25;
	if (mother == 0) {
		N = 16;
		s0 = dt/4;
	}
	else if (mother == 1) {
		N = 16;
		s0 = dt / 4.0;
	}
	else if (mother == 2)
	{
		s0 = dt/8.0;
		N = 256;
		if (param == 2.0) {
			subscale = 16.0;
			s0 = dt / 16.0;
			N = 2048;
		}
	} else {
		printf("Mother only takes 0,1 or 2 as values");
		exit(-1);
	}

	dj = 1.0 / subscale;
	jtot = 16 * (int) subscale;

	delta = (double*)malloc(sizeof(double)* N);
	wave = (double*)malloc(sizeof(double)* 2 * N * jtot);
	coi = (double*)malloc(sizeof(double)* N);
	scale = (double*)malloc(sizeof(double)* jtot);
	period = (double*)malloc(sizeof(double)* jtot);
	mval = (double*)malloc(sizeof(double)* N);


	delta[0] = 1;

	for (i = 1; i < N; ++i) {
		delta[i] = 0;
	}

	for (i = 0; i < jtot; ++i) {
		scale[i] = s0*pow(2.0, (double)(i)*dj);
	}

	cwavelet(delta, N, dt, mother, param, s0, dj, jtot, N, wave, scale, period, coi);

	for (i = 0; i < N; ++i) {
		mval[i] = 0;
	}

	for (j = 0; j < jtot; ++j) {
		iter = 2 * j * N;
		den = sqrt(scale[j]);
		for (i = 0; i < N; ++i) {
			mval[i] += wave[iter + 2 * i]/den;
		}
	}


	maxarr = maxabs(mval, N);

	cdel = sqrt(dt) * dj * mval[maxarr] / psi0;

	free(delta);
	free(wave);

	free(scale);
	free(period);
	free(coi);
	free(mval);

	return cdel;
}

void icwavelet(double *wave, int N, double *scale,int jtot,double dt,double dj,double cdelta,double psi0,double *oup) {
	int i, j,iter;
	double den, coeff;

	coeff = sqrt(dt) * dj / (cdelta *psi0);

	for (i = 0; i < N; ++i) {
		oup[i] = 0.0;
	}

	for (j = 0; j < jtot; ++j) {
		iter = 2 * j * N;
		den = sqrt(scale[j]);
		for (i = 0; i < N; ++i) {
			oup[i] += wave[iter + 2 * i] / den;
		}
	}

	for (i = 0; i < N; ++i) {
		oup[i] *= coeff;
	}
}
