/******************************************************************
 * DPUSER - The Next Generation
 *
 * File:     libfits/fits_filters.cpp
 * Purpose:  Fits class methods to create filters
 * Author:   Thomas Ott
 *
 * Revision history
 * ================
 * 13.08.1999: file created
 * 20.07.2002: Added qgauss method
 ******************************************************************/

#include <stdlib.h>
#include <math.h>
#include <string.h>

#include "fits.h"
#include "math_utils.h"
#include <algorithm>

#include <gsl/gsl_sf_bessel.h>

/*!
create a 2-dimensional circular gaussian with bitpix = -32
Speed gives the number of fwhm that the gaussian spans.
 */

bool Fits::qgauss(int n1, int n2, float xcen, float ycen, float fwhm, int speed)
{
	float sigma, dy2, dx2, d2;
	int i, j, x1, x2, y1, y2;

	if (!create(n1, n2)) return FALSE;

	sigma = -FOUR_LN2 / (fwhm * fwhm);
	x1 = (int)(xcen - fwhm * (float)speed);
	x2 = (int)(xcen + fwhm * (float)speed);
	y1 = (int)(ycen - fwhm * (float)speed);
	y2 = (int)(ycen + fwhm * (float)speed);
	if (x1 < 1) x1 = 1;
	if (x2 > n1) x2 = n1;
	if (y1 < 1) y1 = 1;
	if (y2 > n2) y2 = n2;

// Create the gaussian
	for (i = x1; i <= x2; i++) {
		for (j = y1; j <= y2; j++) {
			dx2 = (float)i - xcen;
			dy2 = (float)j - ycen;
			d2 = sqr(dx2) * sigma + sqr(dy2) * sigma;
			setValue(exp(d2), i, j);
		}
	}

	return TRUE;
}

/*!
create a 2-dimensional elliptical gaussian with bitpix = -32
*/

bool Fits::qgauss(int n1, int n2, float xcen, float ycen, float fwhm1, float fwhm2, float angle, int speed)
{
    float sigma1, sigma2, dy2, dx2, d2, xc, xs;
    int i, j, x1, x2, y1, y2;

    if (n1 != naxis[1] || n2 != naxis[2] || membits != -32) {
        if (!create(n1, n2)) return FALSE;
    } else {
        memset(r4data, 0, bytesAllocated);
    }

    xc = cosdeg(angle);
    xs = sindeg(angle);
    sigma1 = -FOUR_LN2 / (fwhm1 * fwhm1);
    sigma2 = -FOUR_LN2 / (fwhm2 * fwhm2);

    float fwhm = std::max(fwhm1, fwhm2);
    x1 = (int)(xcen - fwhm * (float)speed);
    x2 = (int)(xcen + fwhm * (float)speed);
    y1 = (int)(ycen - fwhm * (float)speed);
    y2 = (int)(ycen + fwhm * (float)speed);
    if (x1 < 1) x1 = 1;
    if (x2 > n1) x2 = n1;
    if (y1 < 1) y1 = 1;
    if (y2 > n2) y2 = n2;

// Create the gaussian
    for (i = x1; i <= x2; i++) {
        for (j = y1; j <= y2; j++) {
            dx2 = ((float)i - xcen) * xc + ((float)j - ycen) * xs;
            dy2 = -((float)i - xcen) * xs + ((float)j - ycen) * xc;
            d2 = sqr(dx2) * sigma1 + sqr(dy2) * sigma2;
            r4data[F_I(i, j)] = exp(d2);
        }
    }

    return TRUE;
}



/*!
create a 2-dimensional elliptical gaussian with bitpix = -32
if fwhm2 < 0, it will be a circular gaussian
 */

bool Fits::gauss(int n1, int n2, float xcen, float ycen, float fwhm1, float fwhm2, float angle)
{
	float sigma1, sigma2, dy2, dx2, d2, xc, xs;
	int i, j;

	if (!create(n1, n2)) return FALSE;

	xc = cosdeg(angle);
	xs = sindeg(angle);
	if (fwhm2 < 0.0) fwhm2 = fwhm1;
	sigma1 = -FOUR_LN2 / (fwhm1 * fwhm1);
	sigma2 = -FOUR_LN2 / (fwhm2 * fwhm2);

// Create the gaussian
	for (i = 1; i <= n1; i++) {
		for (j = 1; j <= n2; j++) {
			dx2 = ((float)i - xcen) * xc + ((float)j - ycen) * xs;
			dy2 = -((float)i - xcen) * xs + ((float)j - ycen) * xc;
			d2 = sqr(dx2) * sigma1 + sqr(dy2) * sigma2;
			setValue(exp(d2), i, j);
		}
	}

	return TRUE;
}

bool Fits::dgauss(int n1, int n2, double xcen, double ycen, double fwhm1, double fwhm2, double angle)
{
	double sigma1, sigma2, dy2, dx2, d2, xc, xs;
	int i, j;

	if (!create(n1, n2, R8)) return FALSE;

	xc = cosdeg(angle);
	xs = sindeg(angle);
	if (fwhm2 < 0.0) fwhm2 = fwhm1;
	sigma1 = -FOUR_LN2 / (fwhm1 * fwhm1);
	sigma2 = -FOUR_LN2 / (fwhm2 * fwhm2);

// Create the gaussian
	for (i = 1; i <= n1; i++) {
		for (j = 1; j <= n2; j++) {
			dx2 = ((double)i - xcen) * xc + ((double)j - ycen) * xs;
			dy2 = -((double)i - xcen) * xs + ((double)j - ycen) * xc;
			d2 = sqr(dx2) * sigma1 + sqr(dy2) * sigma2;
			setValue(exp(d2), i, j);
		}
	}

	return TRUE;
}

/*!
create a 2-dimensional elliptical with bitpix = 8
if fwhm2 < 0, it will be a circle
 */

bool Fits::ellipse(int n1, int n2, float xcen, float ycen, float fwhm1, float fwhm2, float angle)
{
	float sigma1, sigma2, dy2, dx2, d2, xc, xs;
	int i, j;
        double lnhalf;
	
	if (!create(n1, n2, I1)) return FALSE;

	xc = cosdeg(angle);
	xs = sindeg(angle);
	if (fwhm2 < 0.0) fwhm2 = fwhm1;
	sigma1 = -FOUR_LN2 / (fwhm1 * fwhm1 * 4.0);
	sigma2 = -FOUR_LN2 / (fwhm2 * fwhm2 * 4.0);
        lnhalf = log(0.5);

// Create the elliptical
	for (i = 1; i <= n1; i++) {
		for (j = 1; j <= n2; j++) {
			dx2 = ((float)i - xcen) * xc + ((float)j - ycen) * xs;
			dy2 = -((float)i - xcen) * xs + ((float)j - ycen) * xc;
			d2 = sqr(dx2) * sigma1 + sqr(dy2) * sigma2;
//                        if (exp(d2) >= 0.5) i1data[F_I(i, j)] = 1;
                        if (d2 >= lnhalf) i1data[F_I(i, j)] = 1;
                }
	}
	return TRUE;
}

/*!
create a 2-dimensional circle with bitpix = 8
 */

bool Fits::circle(int n1, int n2, float xcen, float ycen, float radius)
{
	int i, j;
	float d, dy2, dx2;

	if (!create(n1, n2, I1)) return FALSE;

	for (i = 1; i <= n1; i++) {
		dx2 = sqr((float)(i - xcen));
		for (j = 1; j <= n2; j++) {
			dy2 = sqr((float)(j - ycen));
			d = sqrt(dx2 + dy2);
			if (d <= radius) i1data[F_I(i, j)] = 1;
		}
	}

	return TRUE;
}

/*!
Create an airy function at the center of the array.
 */

bool Fits::airy(int n1, int n2, float dia, float scl, float wav, float obscuration)
{
	float con, d, dx2, dy2;
    float ratio = 0.0;
    float correction = 1.0;
	int i, j;
    if (obscuration > 0.0) {
        ratio = obscuration / dia;
        correction = 1.0 / sqr(1.0 - sqr(ratio));
    }

	if (!create(n1, n2)) return FALSE;

	con = sqr(M_PI)*dia/(wav*1.0e-6*180.0*3600.0);
	for (i = 1; i <= n1; i++) {
		dx2 = sqr((float)(i - (n1/2 + 1)));
		for (j = 1; j <= n2; j++) {
			dy2 = sqr((float)(j - (n2/2 + 1)));
			d = scl * sqrt(dx2 + dy2) * con;
			if (d != 0.0) {
//				setValue(sqr(2.0 * dp_bessj1(d) / d), i, j);
                if (obscuration > 0.0) {
                    setValue(sqr(2.0 * gsl_sf_bessel_J1(d) / d - 2.0 * ratio * gsl_sf_bessel_J1(ratio * d) / d) * correction, i, j);
                } else {
                    setValue(sqr(2.0 * gsl_sf_bessel_J1(d) / d), i, j);
                }
			} else {
				setValue(1.0, i, j);
			}
		}
	}
	return TRUE;
}

/*!
Create a 2-dimensional low transfer function
*/

bool Fits::lftf(int n1, int n2, float x1)
{
	float con, d, dx2, dy2;
	long i, j;
	
	if (!create(n1, n2)) return FALSE;
	con = 2.0 * x1 / (float)naxis[1];
	con = pow(-3.44 * con, 5.0/3.0);
	
	for (j = 1; j <= n1; j++) {
		dy2 = sqr((float)(j - (n2/2 + 1)));
		for (i = 1; i <= n2; i++) {
			dx2 = sqr((float)(i - (n1/2 + 1)));
			d = sqrt(dx2 + dy2);
			setValue(exp(con * pow(d, 5.0f/3.0f)), i, j);
		}
	}
	return TRUE;
}

/*!
Create a 2-dimensional high transfer function
*/

bool Fits::hftf(int n1, int n2, float x1)
{
	float con, d, dx2, dy2;
	int i, j;
	
	if (!create(n1, n2)) return FALSE;

	con = 2.0 / (2.3 * M_PI * sqr(x1));
	
	for (j = 1; j <= n2; j++) {
		dy2 = sqr((float)(j - (n2/2 + 1)));
		for (i = 1; i <= n1; i++) {
			dx2 = sqr((float)(i - (n1/2 + 1)));
			d = 2.0 * sqrt(dx2 + dy2) / (float)n1;
			if (d >= 1.0) {
				setValue(con * (acos(d) - d * sqrt(1-sqr(d))), i, j);
			}
		}
	}
	return TRUE;
}

/*!
Create a cosine bell function
*/

bool Fits::cosbell(int n1, int n2, float xcen, float ycen, float radius1, float radius2)
{
	float dnaxis1, dx2, dy2, d;
	int i, j;
	
	if (!create(n1, n2)) return FALSE;

	dnaxis1 = radius2 - radius1;
	for (i = 1; i <= n1; i++) {
		dx2 = sqr((float)(i - xcen));
		for (j = 1; j <= n2; j++) {
			dy2 = sqr((float)(j - ycen));
			d = sqrt(dx2 + dy2);
			if (d <= radius1) setValue(1.0, i, j);
			else if (d <= radius2) setValue(cos(M_PI / 2.0 * (d - radius1) / dnaxis1), i, j);
		}
	}
	return TRUE;
}

/*!
Create a chinese hat function
*/

bool Fits::chinese_hat(int n1, int n2, float xcen, float ycen, float r)
{
	int i, j;
	float d, dx2, dy2;
	
	if (!create(n1, n2)) return FALSE;

	for (i = 1; i <= n1; i++) {
		dx2 = sqr((float)i - xcen);
		for (j = 1; j <= n2; j++) {
			dy2 = sqr((float)j - ycen);
			d = sqrt(dx2 + dy2);
			if (d <= 1.0) {
				setValue((r - d) / r, i, j);
			}
		}
	}
	return TRUE;
}

bool Fits::apodizer(int n1, int n2, float xcen, float radius1, float radius2)
{
	return(cosbell(n1, n2, xcen, (float)n1/2, radius1, radius2));
}

/*!
create a rectangle
*/

bool Fits::rect(int n1, int n2, int xcen, int ycen, int width, int height)
{
	int i, j, w1, w2, h1, h2;
	
	if (!create(n1, n2, I1)) return FALSE;

	w1 = xcen - width;
	if (w1 < 1) w1 = 1;
	w2 = xcen + width;
	if (w2 > n1) w2 = n1;
	h1 = ycen - height;
	if (h1 < 1) h1 = 1;
	h2 = ycen + height;
	if (h2 > n2) h2 = n2;
	
	for (i = w1; i <= w2; i++) {
		for (j = h1; j <= h2; j++) {
			i1data[F_I(i, j)] = 1;
		}
	}
	
	return TRUE;
}

/*!
create a delta function
*/

bool Fits::delta(int n1, int n2, int x, int y)
{
	if (!create(n1, n2, I1)) return FALSE;
	i1data[F_I(x, y)] = 1;

	return TRUE;
}

/*!
create a 2-dimensional elliptical moffat function with bitpix = -32
if fwhm2 < 0, it will be a circular moffat
 */

bool Fits::moffat(int n1, int n2, float xcen, float ycen, float power, float fwhm1, float fwhm2, float angle)
{
	float sigma1, sigma2, dy2, dx2, d2, xc, xs;
	int i, j;

	if (!create(n1, n2)) return FALSE;

	xc = cosdeg(angle);
	xs = sindeg(angle);
	if (fwhm2 < 0.0) fwhm2 = fwhm1;
	sigma1 = 4.0 / (fwhm1 * fwhm1);
	sigma2 = 4.0 / (fwhm2 * fwhm2);

// Create the moffat function
	for (i = 1; i <= n1; i++) {
		for (j = 1; j <= n2; j++) {
			dx2 = ((float)i - xcen) * xc + ((float)j - ycen) * xs;
			dy2 = -((float)i - xcen) * xs + ((float)j - ycen) * xc;
			d2 = sqr(dx2) * sigma1 + sqr(dy2) * sigma2;
			setValue(1.0 / pow(d2 + 1.0, (double)power), i, j);
		}
	}

	return TRUE;
}

/*!
create a 2-dimansional sersic function
*/

bool Fits::sersic(int n1, int n2, double Re, double x0, double y0, double angle, double q, double n) {
	int x, y;
	long nn;
	double xp, yp, R;

	double cosa = cos(angle * M_PI / 180.);
	double sina = sin(angle * M_PI / 180.);
	double bn = 1.9992 * n - 0.3271;

	if (!create(n1, n2, R8)) return FALSE;

	nn = 0;
	for (x = 1; x <= n1; x++) {
		for (y = 1; y <= n2; y++) {
			xp = (x-x0) * cosa + (y-y0) * sina;
			yp = -(x-x0) * sina + (y-y0) * cosa;
			R = sqrt(pow(xp, 2) + pow(yp / q, 2));

			r8data[F_I(x,y)] = exp(-bn * pow(R / Re, 1. / n) - 1);
			nn++;
		}
	}

	return TRUE;
}
