/******************************************************************
 * DPUSER - The Next Generation
 *
 * File:     libfits/fits_funcs.cpp
 * Purpose:  Fits class standard functions
 * Author:   Thomas Ott
 *
 * Revision history
 * ================
 * 07.01.2000: file created
 * 06.02.2009: The trivial cases parallelised (all but Complex Numbers)
 ******************************************************************/

#include <math.h>
#include <limits>
#include "fits.h"

/*!
Sine of all array elements.
If the array is of integer type, it will be changed to float (R4).
\returns TRUE if successful, FALSE otherwise.
*/

bool Fits::Sin(bool radians) {
    dpint64 n;
	
	if (membits > R4) if (!setType(R4)) return FALSE;

	if (radians) switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = sin(r4data[n]);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = sin(r8data[n]);
			break;
		case C16: {
			double re;
		
#pragma omp parallel for private(re)
			for (n = 0; n < n_elements; n++) {
				re = sin(cdata[n].r) * cosh(cdata[n].i);
				cdata[n].i = cos(cdata[n].r) * sinh(cdata[n].i);
				cdata[n].r = re;
			}
		}
			break;
		default: return FALSE; break;
	} else switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = sin(r4data[n] * M_PI / 180.);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = sin(r8data[n] * M_PI / 180.);
			break;
		case C16: {
			double re;
		
			for (n = 0; n < n_elements; n++) {
				cdata[n].r *= M_PI / 180.;
				cdata[n].i *= M_PI / 180.;
				re = sin(cdata[n].r) * cosh(cdata[n].i);
				cdata[n].i = cos(cdata[n].r) * sinh(cdata[n].i);
				cdata[n].r = re;
			}
		}
			break;
		default: return FALSE; break;
	}

	return TRUE;
}

/*!
Cosine of all array elements.
If the array is of integer type, it will be changed to float (R4).
\returns TRUE if successful, FALSE otherwise.
*/

bool Fits::Cos(bool radians) {
    dpint64 n;
	
	if (membits > R4) if (!setType(R4)) return FALSE;

	if (radians) switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = cos(r4data[n]);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = cos(r8data[n]);
			break;
		case C16: {
			double re;
		
			for (n = 0; n < n_elements; n++) {
				re = cos(cdata[n].r) * cosh(cdata[n].i);
				cdata[n].i = -sin(cdata[n].r) * sinh(cdata[n].i);
				cdata[n].r = re;
			}
		}
			break;
		default: return FALSE; break;
	} else switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = cos(r4data[n] * M_PI / 180.);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = cos(r8data[n] * M_PI / 180.);
			break;
		case C16: {
			double re;
		
			for (n = 0; n < n_elements; n++) {
				cdata[n].r *= M_PI / 180.;
				cdata[n].i *= M_PI / 180.;
				re = cos(cdata[n].r) * cosh(cdata[n].i);
				cdata[n].i = -sin(cdata[n].r) * sinh(cdata[n].i);
				cdata[n].r = re;
			}
		}
			break;
		default: return FALSE; break;
	}

	return TRUE;
}

/*!
Tangent of all array elements.
If the array is of integer type, it will be changed to float (R4).
\returns TRUE if successful, FALSE otherwise.
*/

bool Fits::Tan(bool radians) {
    dpint64 n;
	
	if (membits > R4) if (!setType(R4)) return FALSE;

	if (radians) switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = tan(r4data[n]);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = tan(r8data[n]);
			break;
		case C16: {
			double r, i;
		
			for (n = 0; n < n_elements; n++) {
				r = 2.0 * cdata[n].r;
				i = 2.0 * cdata[n].i;
				cdata[n].r = sin(r) / (cos(r) + cosh(i));
				cdata[n].i = sinh(i) / (cos(r) + cosh(i));
			}
		}
			break;
		default: return FALSE; break;
	} else switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = tan(r4data[n] * M_PI / 180.);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = tan(r8data[n] * M_PI / 180.);
			break;
		case C16: {
			double r, i;
		
			for (n = 0; n < n_elements; n++) {
				r = 2.0 * cdata[n].r * M_PI / 180.;
				i = 2.0 * cdata[n].i * M_PI / 180.;
				cdata[n].r = sin(r) / (cos(r) + cosh(i));
				cdata[n].i = sinh(i) / (cos(r) + cosh(i));
			}
		}
			break;
		default: return FALSE; break;
	}

	return TRUE;
}

/*!
Sine hyperbolicus of all array elements.
If the array is of integer type, it will be changed to float (R4).
\returns TRUE if successful, FALSE otherwise.
*/

bool Fits::Sinh(bool radians) {
    dpint64 n;
	
	if (membits > R4) if (!setType(R4)) return FALSE;

	if (radians) switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = sinh(r4data[n]);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = sinh(r8data[n]);
			break;
		case C16: {
			double re;
		
			for (n = 0; n < n_elements; n++) {
				re = sinh(cdata[n].r) * cos(cdata[n].i);
				cdata[n].i = cosh(cdata[n].r) * sin(cdata[n].i);
				cdata[n].r = re;
			}
		}
			break;
		default: return FALSE; break;
	} else switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = sinh(r4data[n] * M_PI / 180.);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = sinh(r8data[n] * M_PI / 180.);
			break;
		case C16: {
			double re;
		
			for (n = 0; n < n_elements; n++) {
				cdata[n].r *= M_PI / 180.;
				cdata[n].i *= M_PI / 180.;
				re = sinh(cdata[n].r) * cos(cdata[n].i);
				cdata[n].i = cosh(cdata[n].r) * sin(cdata[n].i);
				cdata[n].r = re;
			}
		}
			break;
		default: return FALSE; break;
	}

	return TRUE;
}

/*!
Cosine hyperbolicus of all array elements.
If the array is of integer type, it will be changed to float (R4).
\returns TRUE if successful, FALSE otherwise.
*/

bool Fits::Cosh(bool radians) {
    dpint64 n;
	
	if (membits > R4) if (!setType(R4)) return FALSE;

	if (radians) switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = cosh(r4data[n]);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = cosh(r8data[n]);
			break;
		case C16: {
			double re;
		
			for (n = 0; n < n_elements; n++) {
				re = cosh(cdata[n].r) * cos(cdata[n].i);
				cdata[n].i = sinh(cdata[n].r) * sin(cdata[n].i);
				cdata[n].r = re;
			}
		}
			break;
		default: return FALSE; break;
	} else switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = cosh(r4data[n] * M_PI / 180.);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = cosh(r8data[n] * M_PI / 180.);
			break;
		case C16: {
			double re;
		
			for (n = 0; n < n_elements; n++) {
				cdata[n].r *= M_PI / 180.;
				cdata[n].i *= M_PI / 180.;
				re = cosh(cdata[n].r) * cos(cdata[n].i);
				cdata[n].i = sinh(cdata[n].r) * sin(cdata[n].i);
				cdata[n].r = re;
			}
		}
			break;
		default: return FALSE; break;
	}

	return TRUE;
}

/*!
Tangent hyperbolicus of all array elements.
If the array is of integer type, it will be changed to float (R4).
\returns TRUE if successful, FALSE otherwise.
*/

bool Fits::Tanh(bool radians) {
    dpint64 n;
	
	if (membits > R4) if (!setType(R4)) return FALSE;

	if (radians) switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = tanh(r4data[n]);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = tanh(r8data[n]);
			break;
		case C16: {
			double r, i;
		
			for (n = 0; n < n_elements; n++) {
				r = 2.0 * cdata[n].r;
				i = 2.0 * cdata[n].i;
				cdata[n].r = sinh(r) / (cosh(r) + cos(i));
				cdata[n].i = sin(i) / (cos(r) + cos(i));
			}
		}
			break;
		default: return FALSE; break;
	} else switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = tanh(r4data[n] * M_PI / 180.);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = tanh(r8data[n] * M_PI / 180.);
			break;
		case C16: {
			double r, i;
		
			for (n = 0; n < n_elements; n++) {
				r = 2.0 * cdata[n].r * M_PI / 180.;
				i = 2.0 * cdata[n].i * M_PI / 180.;
				cdata[n].r = sinh(r) / (cosh(r) + cos(i));
				cdata[n].i = sin(i) / (cos(r) + cos(i));
			}
		}
			break;
		default: return FALSE; break;
	}

	return TRUE;
}

/*!Arcus Sine of all array elements.
If the array is of integer type, it will be changed to float (R4).
If the minimum value of array elements is < -1.0 or the maximum is > 1.0, the array
type will be changed to complex (C16).
\returns TRUE if successful, FALSE otherwise.
*/

bool Fits::Asin(bool radians) {
    dpint64 n;
	double min, max;
	
	get_minmax(&min, &max);
	if ((min < -1.0) || (max > 1.0)) if (!setType(C16)) return FALSE;
	if (membits > R4) if (!setType(R4)) return FALSE;

	switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = asin(r4data[n]);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = asin(r8data[n]);
			break;
		case C16: {
			dpComplex c;

			for (n = 0; n < n_elements; n++) {
				c = complex_asin(dpComplex(cdata[n].r, cdata[n].i));
				cdata[n].r = real(c);
				cdata[n].i = imag(c);
			}
		}
			break;
		default: return FALSE; break;
	}
	if (!radians) mul(180. / M_PI);

	return TRUE;
}

/*!
Arcus Cosine of all array elements.
If the array is of integer type, it will be changed to float (R4).
If the minimum value of array elements is < -1.0 or the maximum is > 1.0, the array
type will be changed to complex (C16).
\returns TRUE if successful, FALSE otherwise.
*/

bool Fits::Acos(bool radians) {
    dpint64 n;
	double min, max;
	
	get_minmax(&min, &max);
	if ((min < -1.0) || (max > 1.0)) if (!setType(C16)) return FALSE;

	if (membits > R4) if (!setType(R4)) return FALSE;

	switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = acos(r4data[n]);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = acos(r8data[n]);
			break;
		case C16: {
			dpComplex c;

			for (n = 0; n < n_elements; n++) {
				c = complex_acos(dpComplex(cdata[n].r, cdata[n].i));
				cdata[n].r = real(c);
				cdata[n].i = imag(c);
			}
		}
			break;
		default: return FALSE; break;
	}
	if (!radians) mul(180. / M_PI);

	return TRUE;
}

/*!Arcus Tangent of all array elements.
If the array is of integer type, it will be changed to float (R4).
\returns TRUE if successful, FALSE otherwise.
*/

bool Fits::Atan(bool radians) {
    dpint64 n;

	if (membits > R4) if (!setType(R4)) return FALSE;

	switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = atan(r4data[n]);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = atan(r8data[n]);
			break;
		case C16: {
			dpComplex c;

			for (n = 0; n < n_elements; n++) {
				c = complex_atan(dpComplex(cdata[n].r, cdata[n].i));
				cdata[n].r = real(c);
				cdata[n].i = imag(c);
			}
		}
			break;
		default: return FALSE; break;
	}
	if (!radians) mul(180. / M_PI);

	return TRUE;
}

bool Fits::Atan2(Fits &arg2, bool radians) {
    dpint64 n;

	if (membits > R4) if (!setType(R4)) return FALSE;

	switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = atan2((double)r4data[n], arg2[n]);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = atan2(r8data[n], arg2[n]);
			break;
		case C16: {
			dpComplex c;

			for (n = 0; n < n_elements; n++) {
				c = complex_atan(dpComplex(cdata[n].r, cdata[n].i));
				cdata[n].r = real(c);
				cdata[n].i = imag(c);
			}
		}
			break;
		default: return FALSE; break;
	}
	if (!radians) mul(180. / M_PI);

	return TRUE;
}

/*!
Arcus Sine hyperbolicus of all array elements.
If the array is of integer type, it will be changed to float (R4).
\returns TRUE if successful, FALSE otherwise.
*/

bool Fits::Asinh(bool radians) {
    dpint64 n;
	
	if (membits > R4) if (!setType(R4)) return FALSE;

	switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = asinh(r4data[n]);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = asinh(r8data[n]);
			break;
		case C16: {
			dpComplex c;

			for (n = 0; n < n_elements; n++) {
				c = complex_asinh(dpComplex(cdata[n].r, cdata[n].i));
				cdata[n].r = real(c);
				cdata[n].i = imag(c);
			}
		}
			break;
		default: return FALSE; break;
	}
	if (!radians) mul(180. / M_PI);

	return TRUE;
}

/*!
Arcus Cosine hyperbolicus of all array elements.
If the array is of integer type, it will be changed to float (R4).
If the minimum value of array elements is < 1.0 the array
type will be changed to complex (C16).
\returns TRUE if successful, FALSE otherwise.
*/

bool Fits::Acosh(bool radians) {
    dpint64 n;
	double min, max;
	
	get_minmax(&min, &max);
	if (min < 1.0) if (!setType(C16)) return FALSE;
	
	if (membits > R4) if (!setType(R4)) return FALSE;

	switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = acosh(r4data[n]);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = acosh(r8data[n]);
			break;
		case C16: {
			dpComplex c;

			for (n = 0; n < n_elements; n++) {
				c = complex_acosh(dpComplex(cdata[n].r, cdata[n].i));
				cdata[n].r = real(c);
				cdata[n].i = imag(c);
			}
		}
			break;
		default: return FALSE; break;
	}
	if (!radians) mul(180. / M_PI);

	return TRUE;
}

/*!
Arcus Tangent hyperbolicus of all array elements.
If the array is of integer type, it will be changed to float (R4).
If the minimum value of array elements is <= -1.0 or the maximum is >= 1.0, the array
type will be changed to complex (C16).
\returns TRUE if successful, FALSE otherwise.
*/

bool Fits::Atanh(bool radians) {
    dpint64 n;
	double min, max;
	
	get_minmax(&min, &max);
	if ((min <= -1.0) || (max >= 1.0)) if (!setType(C16)) return FALSE;
	
	if (membits > R4) if (!setType(R4)) return FALSE;

	switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = atanh(r4data[n]);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = atanh(r8data[n]);
			break;
		case C16: {
			dpComplex c;

			for (n = 0; n < n_elements; n++) {
				c = complex_atanh(dpComplex(cdata[n].r, cdata[n].i));
				cdata[n].r = real(c);
				cdata[n].i = imag(c);
			}
		}
			break;
		default: return FALSE; break;
	}
	if (!radians) mul(180. / M_PI);

	return TRUE;
}

/*!
Exponential of all array elements.
If the array is of integer type, it will be changed to float (R4).
\returns TRUE if successful, FALSE otherwise.
*/

bool Fits::Exp() {
    dpint64 n;
	
	if (membits > R4) if (!setType(R4)) return FALSE;

	switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = exp(r4data[n]);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = exp(r8data[n]);
			break;
		case C16: {
			dpComplex c;

			for (n = 0; n < n_elements; n++) {
				c = complex_exp(dpComplex(cdata[n].r, cdata[n].i));
				cdata[n].r = real(c);
				cdata[n].i = imag(c);
			}
		}
			break;
		default: return FALSE; break;
	}

	return TRUE;
}

/*!
Logarithm of all array elements to the base (default 10).
If the array is of integer type, it will be changed to float (R4).
If the minimum value of array elements is <= 0.0, the array
type will be changed to complex (C16).
\returns TRUE if successful, FALSE otherwise.
*/

bool Fits::Log(const double & base) {
    dpint64 n;
	double denom = log(base);
	
        if (get_min() <= 0.0)
            if (!setType(C16))
                return FALSE;
        if (membits > R4)
            if (!setType(R4))
                return FALSE;

	switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = log(r4data[n]) / denom;
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = log(r8data[n]) / denom;
			break;
		case C16: {
			dpComplex c;

			for (n = 0; n < n_elements; n++) {
                            if ((cdata[n].r == 0) && (cdata[n].i == 0)) {
                                // The number is complex and equals (0,0), since complex_log (using GSL)
                                // returns nan we set it directly to (-inf,0)
                                cdata[n].r = -std::numeric_limits<double>::infinity(); //-1./0.;
                                cdata[n].i = 0.;
                            } else {
				c = complex_log(dpComplex(cdata[n].r, cdata[n].i)) / denom;
				cdata[n].r = real(c);
				cdata[n].i = imag(c);
                            }
			}
		}
			break;
		default: return FALSE; break;
	}

	return TRUE;
}
	
/*!
Natural logarithm of all array elements.
If the array is of integer type, it will be changed to float (R4).
If the minimum value of array elements is <= 0.0, the array
type will be changed to complex (C16).
\returns TRUE if successful, FALSE otherwise.
*/

bool Fits::Ln() {
    dpint64 n;
	
	if (get_min() <= 0.0) if (!setType(C16)) return FALSE;
	if (membits > R4) if (!setType(R4)) return FALSE;

	switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = log(r4data[n]);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = log(r8data[n]);
			break;
		case C16: {
			dpComplex c;

			for (n = 0; n < n_elements; n++) {
				c = complex_log(dpComplex(cdata[n].r, cdata[n].i));
				cdata[n].r = real(c);
				cdata[n].i = imag(c);
			}
		}
			break;
		default: return FALSE; break;
	}

	return TRUE;
}

/*!
Square root of all array elements.
If the array is of integer type, it will be changed to float (R4).
If the minimum value of array elements is < 0.0, the array
type will be changed to complex (C16).
\returns TRUE if successful, FALSE otherwise.
*/

bool Fits::Sqrt() {
    dpint64 n;
	
	if (get_min() < 0.0) if (!setType(C16)) return FALSE;
	if (membits > R4) if (!setType(R4)) return FALSE;

	switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = sqrt(r4data[n]);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = sqrt(r8data[n]);
			break;
		case C16: {
			dpComplex c;

			for (n = 0; n < n_elements; n++) {
				c = complex_sqrt(dpComplex(cdata[n].r, cdata[n].i));
				cdata[n].r = real(c);
				cdata[n].i = imag(c);
			}
		}
			break;
		default: return FALSE; break;
	}

	return TRUE;
}

/*!
Absolute value of all array elements.
If the array is of complex type (C16), it will be changed to double (R8).
If the array is of integer type, it will be changed to float (R4).
\returns TRUE if successful, FALSE otherwise.
*/

bool Fits::Abs() {
    dpint64 n;

	if (membits > R4) if (!setType(R4)) return FALSE;

	switch (membits) {
		case R4:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r4data[n] = fabs(r4data[n]);
			break;
		case R8:
#pragma omp parallel for
			for (n = 0; n < n_elements; n++) r8data[n] = fabs(r8data[n]);
			break;
		case C16:
			for (n = 0; n < n_elements; n++) cdata[n].r = sqrt(cdata[n].r * cdata[n].r + cdata[n].i * cdata[n].i);
			if (!setType(R8)) return FALSE;
			break;
		default: return FALSE; break;
	}

	return TRUE;
}
