/******************************************************************
 * DPUSER - The Next Generation
 *
 * File:     libfits/3d_stuff.cpp
 * Purpose:  Implementation of several 3D data reduction routines
 * Author:   Thomas Ott
 *
 * Revision history
 * ================
 * 1998 - 2002: Implementation
 ******************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_sort.h>
#include <gsl/gsl_statistics.h>
#include "fits.h"
#include "math_utils.h"
#include "platform.h"

#ifdef WIN
#pragma warning (disable: 4305) // disable warning 'truncation from const double to float'
#endif

float argonlist[][2] = {
{17914.677000, 15.000000},
{17888.677000, 0.350000},
{17823.991000, 1.500000},
{17445.075000, 3.000000},
{17401.908000, 0.220000},
{16940.584000, 50.000000},
{16860.088000, 0.140000},
{16740.078000, 3.000000},
{16549.352000, 2.500000},
{16519.867000, 5.000000},
{16436.575000, 4.000000},
{16264.070000, 0.160000},
{16180.023000, 0.900000},
{16122.656000, 0.120000},
{15989.491000, 4.000000},
{15899.687000, 2.400000},
{15883.164000, 0.400000},
{15816.777000, 0.180000},
{15446.772000, 0.100000},
{15402.640000, 1.200000},
{15353.128000, 0.600000},
{15348.884000, 1.200000},
{15329.344000, 1.500000},
{15301.925000, 5.000000},
{15172.691000, 3.000000},
{15052.567000, 0.120000},
{15046.503000, 7.000000},
{15030.843000, 0.300000},
{14876.973000, 0.110000},
{14785.722000, 0.400000},
{14739.139000, 0.750000},
{0.0, 0.0}
};

float neonlist[][2] = {
{19577.136000, 1.200000},
{20350.238000, 0.800000},
{21041.295000, 3.000000},
{21708.145000, 3.200000},
{22247.345000, 1.200000},
{22428.130000, 1.200000},
{22466.804000, 0.300000},
{22530.404000, 12.000000},
{22661.813000, 1.500000},
{22687.768000, 0.200000},
{23100.514000, 3.000000},
{23260.302000, 6.000000},
{23373.000000, 7.000000},
{23565.362000, 5.000000},
{23636.515000, 24.000000},
{23701.636000, 1.500000},
{23707.611000, 8.500000},
{23709.160000, 8.500000},
{23951.417000, 16.000000},
{23978.115000, 7.000000},
{24098.557000, 1.000000},
{24161.429000, 2.000000},
{24249.638000, 3.000000},
{24365.048000, 11.500000},
{24371.601000, 5.500000},
{24383.359000, 0.800000},
{24447.853000, 3.000000},
{24459.367000, 4.000000},
{24459.678000, 4.000000},
{24776.473000, 1.500000},
{24903.733000, 1.000000},
{24928.870000, 3.000000},
{25161.682000, 1.100000},
{25524.366000, 2.500000},
{0.0, 0.0}
};

int expandCal3d(Fits & a)
{
	int i, j, k;
	float v;
	
	if (!a.setType(R4)) return FALSE;
	for (i = 1; i <= a.Naxis(3); i++) {
		v = 0.0;
		for (j = 1; j <= a.Naxis(1); j++) {
			for (k = 1; k <= a.Naxis(2); k++) {
				v += a.r4data[a.at(j, k, i)];
			}
		}
		v /= (float)(a.Naxis(1) * a.Naxis(2));
		for (j = 1; j <= a.Naxis(1); j++) {
			for (k = 1; k <= a.Naxis(2); k++) {
				a.r4data[a.at(j, k, i)] = v;
			}
		}
	}
	return(1);
}

bool norm3d(Fits & a, const char *fname)
{
	long i, x, y;
	float w[600], v[600];
	FILE *fd;
	
	if ((a.Naxis(0) != 3) || (a.Naxis(1) != 16) || (a.Naxis(2) != 16) || (a.Naxis(3) != 600)) {
		printf("norm3d: must be array of size 16x16x600\n");
		return FALSE;
	}
	if (!a.setType(R4)) return FALSE;
	a.norm();
	if ((fd = fopen(fname, "rb")) == NULL) return FALSE;
	for (i = 0; i < 600; i++) fscanf(fd, "%f %f\n", &w[i], &v[i]);
	fclose(fd);

// datacube for K-long starts at 2.1305, KLEINMANN at 2.1500
	for (i = 100; i < 600; i++) {
		for (x = 1; x <= 16; x++) {
			for (y = 1; y <= 16; y++) {
				a.r4data[a.F_I(x, y, i)] /= v[i-100];
			}
		}
	}
	return TRUE;
}

bool Fits::spec3d(Fits &cube, int x, int y, int r1, int r2, int method) {
    double  value;
    int     i, j, z;
    Fits    _tmp;
    bool    subtract_sky;
    char    key[255];

    subtract_sky = (r2 > r1);
    if (!create(cube.Naxis(3), 1, R4)) {
        return FALSE;
    }
    CopyHeader(cube);
    SetFloatKey("CRVAL1", cube.getCRVAL(3));
    SetFloatKey("CRPIX1", cube.getCRPIX(3));
    SetFloatKey("CDELT1", cube.getCDELT(3));
    if (cube.GetStringKey("CTYPE3", key)) {
        SetStringKey("CTYPE1", key);
    } else {
        DeleteKey("CTYPE1");
    }
    if (cube.GetStringKey("CUNIT3", key)) {
        SetStringKey("CUNIT1", key);
    } else {
        DeleteKey("CUNIT1");
    }
    for (i = 1; i <= MAXNAXIS; i++) {
        for (j = 1; j <= MAXNAXIS; j++) {
            sprintf(key, "CD%i_%i", i, j);
            DeleteKey(key);
            sprintf(key, "PC%i_%i", i, j);
            DeleteKey(key);
        }
    }
    for (i = 2; i <= MAXNAXIS; i++) {
        naxis[i] = 1;
        sprintf(key, "CUNIT%i", i);
        DeleteKey(key);
        sprintf(key, "CTYPE%i", i);
        DeleteKey(key);
        sprintf(key, "CRPIX%i", i);
        DeleteKey(key);
        sprintf(key, "CRVAL%i", i);
        DeleteKey(key);
        sprintf(key, "CDELT%i", i);
        DeleteKey(key);
    }
    DeleteKey("CROTA1");
    DeleteKey("CROTA2");
    sprintf(crtype, "");

    if (subtract_sky && (!_tmp.copy(*this))){
        return FALSE;
    }

    // Calculate spectrum of single pixel at position (x,y)
    if ((r1 == 0) && (r2 <= r1)) {
        if ((x > 0) && (x <= (int)cube.Naxis(1)) && (y > 0) && (y <= (int)cube.Naxis(2))) {
            for (z = 1; z <= cube.Naxis(3); z++) {
                value = cube.ValueAt(cube.F_I(x, y, z));
                if (gsl_finite(value)) {
                    r4data[z-1] = value;
                }
            }
        }
        return TRUE;
    }

    int radius = r2;
    if (r1 > r2) {
        radius = r1;
    }

    // case of median filtering
    float *medmem, *medmem2;
    medmem = (float *)malloc(4*(radius+1)*(radius+1)*sizeof(float));
    medmem2 = (float *)malloc(4*(radius+1)*(radius+1)*sizeof(float));
    int counter, counter2;
    for (z = 1; z <= cube.Naxis(3); z++) {
        counter = counter2 = 0;
        for (i = x - radius; i <= x + radius; i++) {
            for (j = y - radius; j <= y + radius; j++) {
                if ((i > 0) && (i <= (int)cube.Naxis(1)) && (j > 0) && (j <= (int)cube.Naxis(2))) {
                    if ((double)((i-x)*(i-x)) + (double)((j-y)*(j-y)) <= (double)(r1 * r1)) {
                        value = cube.ValueAt(cube.F_I(i, j, z));
                        if (gsl_finite(value)) {
                            medmem[counter] = value;
                            counter++;
                        }
                    } else if (subtract_sky) {
                        if ((double)((i-x)*(i-x)) + (double)((j-y)*(j-y)) <= (double)(r2 * r2)) {
                            value = cube.ValueAt(cube.F_I(i, j, z));
                            if (gsl_finite(value)) {
                                medmem2[counter2] = value;
                                counter2++;
                            }
                        }
                    }
                }
            }
        }
        r4data[z-1] = 0.;
        if (counter > 0) {
            if (method == 0 || method == 1) { // sum, average
                r4data[z-1] = gsl_stats_float_mean(medmem, 1, counter);
            } else { // median
                gsl_sort_float(medmem, 1, counter);
                r4data[z-1] = gsl_stats_float_median_from_sorted_data(medmem, 1, counter);
            }
        }
        if (counter2 > 0) {
            if (method == 0 || method == 1) { // sum, average
                r4data[z-1] -= gsl_stats_float_mean(medmem2, 1, counter2);
            } else { // median
                gsl_sort_float(medmem2, 1, counter2);
                r4data[z-1] -= gsl_stats_float_median_from_sorted_data(medmem2, 1, counter2);
            }
        }
        if (method == 0) { // sum
            r4data[z-1] *= counter;
        }
    }
    free(medmem);
    free(medmem2);

    return TRUE;

    double v1 = 0.0;
    double v2 = 0.0;
    for (i = x - radius; i <= x + radius; i++) {
        for (j = y - radius; j <= y + radius; j++) {
            if ((i > 0) && (i <= (int)cube.Naxis(1)) && (j > 0) && (j <= (int)cube.Naxis(2))) {
                if ((double)((i-x)*(i-x)) + (double)((j-y)*(j-y)) <= (double)(r1 * r1)) {
                    v1++;
                    for (z = 1; z <= cube.Naxis(3); z++) {
                        value = cube.ValueAt(cube.F_I(i, j, z));
                        if (gsl_finite(value)) {
                            r4data[z-1] += value;
                        }
                    }
                } else if (subtract_sky) {
                    if ((double)((i-x)*(i-x)) + (double)((j-y)*(j-y)) <= (double)(r2 * r2)) {
                        v2++;
                        for (z = 1; z <= cube.Naxis(3); z++) {
                            value = cube.ValueAt(cube.F_I(i, j, z));
                            if (gsl_finite(value)) {
                                _tmp.r4data[z-1] += value;
                            }
                        }
                    }
                }
            }
        }
    }
    if (v1 > 0.0) {
        div(v1);
    }
    if (subtract_sky) {
        if (v2 > 0.0) {
            _tmp.div(v2);
        }
        sub(_tmp);
    }
    if (v1 > 0.0) {
        mul(v1);
    }

    return TRUE;
}

/*
bool Fits::spec3d(Fits &cube, int x, int y, int r1, int r2, double reject, int method) {
    double  value;
    int     i, j, z;
    Fits    _tmp;
    bool    subtract_sky;
    char    key[255];

    subtract_sky = (r2 > r1);
    if (!create(cube.Naxis(3), 1, R4)) {
        return FALSE;
    }
    CopyHeader(cube);
    SetFloatKey("CRVAL1", cube.getCRVAL(3));
    SetFloatKey("CRPIX1", cube.getCRPIX(3));
    SetFloatKey("CDELT1", cube.getCDELT(3));
    if (cube.GetStringKey("CTYPE3", key)) {
        SetStringKey("CTYPE1", key);
    } else {
        DeleteKey("CTYPE1");
    }
    if (cube.GetStringKey("CUNIT3", key)) {
        SetStringKey("CUNIT1", key);
    } else {
        DeleteKey("CUNIT1");
    }
    for (i = 1; i <= MAXNAXIS; i++) {
        for (j = 1; j <= MAXNAXIS; j++) {
            sprintf(key, "CD%i_%i", i, j);
            DeleteKey(key);
        }
    }
    for (i = 2; i <= MAXNAXIS; i++) {
        naxis[i] = 1;
        sprintf(key, "CUNIT%i", i);
        DeleteKey(key);
        sprintf(key, "CTYPE%i", i);
        DeleteKey(key);
        sprintf(key, "CRPIX%i", i);
        DeleteKey(key);
        sprintf(key, "CRVAL%i", i);
        DeleteKey(key);
        sprintf(key, "CDELT%i", i);
        DeleteKey(key);
    }
    DeleteKey("CROTA1");
    DeleteKey("CROTA2");
    sprintf(crtype, "");

    if (subtract_sky && (!_tmp.copy(*this))) {
        return FALSE;
    }

    // Calculate spectrum of single pixel at position (x,y)
    if ((r1 == 0) && (r2 <= r1)) {
        if ((x > 0) && (x <= (int)cube.Naxis(1)) && (y > 0) && (y <= (int)cube.Naxis(2))) {
            for (z = 1; z <= cube.Naxis(3); z++) {
                value = cube.ValueAt(cube.F_I(x, y, z));
                if ((value != reject) && gsl_finite(value)) {
                    r4data[z-1] = value;
                }
            }
        }
        return TRUE;
    }

    int radius = r2;
    if (r1 > r2) {
        radius = r1;
    }

// case of median filtering
    float *medmem, *medmem2;
    medmem = (float *)malloc(4*(radius+1)*(radius+1)*sizeof(float));
    medmem2 = (float *)malloc(4*(radius+1)*(radius+1)*sizeof(float));
    int counter, counter2;
    for (z = 1; z <= cube.Naxis(3); z++) {
        counter = counter2 = 0;
        for (i = x - radius; i <= x + radius; i++) {
            for (j = y - radius; j <= y + radius; j++) {
                if ((i > 0) && (i <= (int)cube.Naxis(1)) && (j > 0) && (j <= (int)cube.Naxis(2))) {
                    if ((double)((i-x)*(i-x)) + (double)((j-y)*(j-y)) <= (double)(r1 * r1)) {
                        value = cube.ValueAt(cube.F_I(i, j, z));
                        if ((value != reject) && gsl_finite(value)) {
                            medmem[counter] = value;
                            counter++;
                        }
                    } else if (subtract_sky) {
                        if ((double)((i-x)*(i-x)) + (double)((j-y)*(j-y)) <= (double)(r2 * r2)) {
                            counter2++;
                            value = cube.ValueAt(cube.F_I(i, j, z));
                            if ((value != reject) && gsl_finite(value)) {
                                medmem2[counter2] = value;
                                counter2++;
                            }
                        }
                    }
                }
            }
        }
        r4data[z-1] = 0.;
        if (counter > 0) {
            if (method == 0 || method == 1) { // sum, average
                r4data[z-1] = gsl_stats_float_mean(medmem, 1, counter);
            } else { // median
                gsl_sort_float(medmem, 1, counter);
                r4data[z-1] = gsl_stats_float_median_from_sorted_data(medmem, 1, counter);
            }
        }
        if (counter2 > 0) {
            if (method == 0 || method == 1) { // sum, average
                r4data[z-1] = gsl_stats_float_mean(medmem2, 1, counter2);
            } else { // median
                gsl_sort_float(medmem2, 1, counter2);
                r4data[z-1] -= gsl_stats_float_median_from_sorted_data(medmem2, 1, counter2);
            }
        }
        if (method == 0) { // sum
            r4data[z-1] *= counter;
        }
    }
    free(medmem);
    free(medmem2);

    return TRUE;

    double v1 = 0.0;
    double v2 = 0.0;
    for (i = x - radius; i <= x + radius; i++) {
        for (j = y - radius; j <= y + radius; j++) {
            if ((i > 0) && (i <= (int)cube.Naxis(1)) && (j > 0) && (j <= (int)cube.Naxis(2))) {
                if ((double)((i-x)*(i-x)) + (double)((j-y)*(j-y)) <= (double)(r1 * r1)) {
                    v1++;
                    for (z = 1; z <= cube.Naxis(3); z++) {
                        value = cube.ValueAt(cube.F_I(i, j, z));
                        if ((value != reject) && gsl_finite(value)) {
                            r4data[z-1] += value;
                        }
                    }
                } else if (subtract_sky) {
                    if ((double)((i-x)*(i-x)) + (double)((j-y)*(j-y)) <= (double)(r2 * r2)) {
                        v2++;
                        for (z = 1; z <= cube.Naxis(3); z++) {
                            value = cube.ValueAt(cube.F_I(i, j, z));
                            if ((value != reject) && gsl_finite(value)) {
                                _tmp.r4data[z-1] += value;
                            }
                        }
                    }
                }
            }
        }
    }
    if (v1 > 0.0) {
        div(v1);
    }
    if (subtract_sky) {
        if (v2 > 0.0) {
            _tmp.div(v2);
        }
        _tmp.cblank();
        sub(_tmp);
    }
    if (v1 > 0.0) {
        mul(v1);
    }
    cblank();

    return TRUE;
}
*/

#ifdef WIN
#pragma warning (default: 4305)
#endif
