/*******************************************************************************
 * JMMC project ( http://www.jmmc.fr ) - Copyright (C) CNRS.
 ******************************************************************************/
/**
 * @file
 * Functions to handle the AMBER_WAVEDATA binary table.
 *
 * The AMBER_WAVEDATA binary table contains the spectral dispersion table for
 * the instrument configuration used during exposure.
 */
#define _POSIX_SOURCE 1

/* 
 * System Headers
 */
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include "fitsio.h"

/* 
 * Local Headers
 */
#include "amdlib.h"
#include "amdlibProtected.h"

/*
 * Global variable
 */
/* this is the 3 bands, withous overlap */
static amdlibBAND_DESC bands[amdlibNB_BANDS] = {{"J", 1000.0l, 1439.9999l},
                                                {"H", 1440.0l, 1894.4999l},
                                                {"K", 1894.5l, 3500.0l}};
/* the following is the ESO-AMBER defined bands */
/* static amdlibBAND_DESC bands[amdlibNB_BANDS] = {{"J", 1000.0000, 1411.0000}, */
/*                                                 {"H", 1469.0000, 1869.0000}, */
/*                                                 {"K", 1920.0000, 3500.0000}}; */
/* The following was an old estimate of the overlap bands */
/* static amdlibBAND_DESC bands[amdlibNB_BANDS] = {{"J", 1000.0000, 1469.0000}, */
/*                                                 {"H", 1539.0000, 1892.0000}, */
/*                                                 {"K", 1942.0000, 3500.0000}}; */
/*
 * Protected functions definition
 */
/* Protected functions */
/**
 * Compute bandwidth
 *
 * This function compute the bandwith of the wavelength table. The bandwith is
 * just computed as :
 * \li band(i) = (wlen(i+1) - wlen(i-1)) / 2.0
 * additionnaly, Bw[0]=Bw[1] and 
 * Bw[amdlibNB_SPECTRAL_CHANNELS-1]=Bw[amdlibNB_SPECTRAL_CHANNELS-2]
 * @return
 * Always amdlibSUCCESS
 */
amdlibCOMPL_STAT amdlibComputeBandwidth(amdlibWAVEDATA *waveData)
{
    int l;
    amdlibDOUBLE *wlenTbl = waveData->wlen;
    amdlibDOUBLE *widthTbl = waveData->bandwidth;
    for (l = 1; l < (amdlibNB_SPECTRAL_CHANNELS - 1); l++)
    {
        if (wlenTbl[l] != 0)
        {
            if ((wlenTbl[l-1] != 0) && (wlenTbl[l+1] != 0))
            {
                widthTbl[l] = fabs((wlenTbl[l+1] - wlenTbl[l-1]) / 2.0);
            }
            else if ((wlenTbl[l-1] == 0) && (wlenTbl[l+1] != 0))
            {
                widthTbl[l] = fabs((wlenTbl[l+1] - wlenTbl[l]));
            }
            else if ((wlenTbl[l-1] != 0) && (wlenTbl[l+1] == 0))
            {
                widthTbl[l] = fabs((wlenTbl[l-1] - wlenTbl[l]));
            }
            else
            {
                widthTbl[l] = 0.0;
            }
        }
        else
        {
            widthTbl[l] = 0.0;
        }
    }  
    widthTbl[0] = widthTbl[1];
    widthTbl[amdlibNB_SPECTRAL_CHANNELS-1]=widthTbl[amdlibNB_SPECTRAL_CHANNELS-2];
    return amdlibSUCCESS;
}


/**
 * Load spectral dispersion table from file. 
 *
 * This function reads informations from the AMBER_WAVEDATA binary table and
 * store them into the waveData structure. This concerns:
 *    \li the origin of the data
 *    \li the instrument name
 *    \li the observation date
 *    \li the INS dictionary name
 *    \li the number of spectral channels
 *    \li the offsets between the photometric channels and the interferometric
 *        channel.
 *    \li the effective wavelengths and bandwidths
 *
 * @param filePtr pointer to the FITS file containing wave data.
 * @param waveData structure where wave data information will be stored
 * @param errMsg error description message returned if function fails.
 *
 * @note Missing informations about origin of data, instrument name, date and
 * dictionary are ignored.
 * 
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibReadWaveData (fitsfile        *filePtr, 
                                     amdlibWAVEDATA  *waveData, 
                                     amdlibERROR_MSG errMsg)

{
    int         status = 0;
    int         anynull = 0;
    char        fitsioMsg[256];
#ifdef TEST
    int         i;
#endif

    amdlibLogTrace("amdlibReadWaveData()");
    
    /* Reset error message */
    memset(errMsg, '\0', sizeof(amdlibERROR_MSG));

    /* Clear 'AMBER_WAVEDATA' data structure */
    memset(waveData, '\0', sizeof(amdlibWAVEDATA));

    /* Go to the main header, retrieve ins mode */
    if (fits_movabs_hdu(filePtr, 1, NULL, &status) != 0)
    {
        amdlibReturnFitsError("AMBER_WAVEDATA");
    }

    /* Go to the IMAGING_DATA binary table */
    if (fits_movnam_hdu(filePtr, BINARY_TBL, "AMBER_WAVEDATA", 0, &status) != 0)
    {
        amdlibReturnFitsError("AMBER_WAVEDATA");
    }

    /*** Read header informations */
    /* Read origine of the data and instrument name. If not found, the error
     * is ignored */
    if (fits_read_key(filePtr, TSTRING, "ORIGIN", &waveData->origin,
                      NULL, &status) != 0)
    {
        status = 0;
    }
    if (fits_read_key(filePtr, TSTRING, "INSTRUME",
                      &waveData->instrument, NULL, &status) != 0)
    {
        status = 0;
    }
    /* Read dates */
    if (fits_read_key(filePtr, TDOUBLE, "MJD-OBS", &waveData->dateObsMJD,
                      NULL, &status) != 0)
    {
        status = 0;
    }
    if (fits_read_key(filePtr, TSTRING, "DATE-OBS", &waveData->dateObs,
                      NULL, &status) != 0)
    {
        status = 0;
    }
    if (fits_read_key(filePtr, TSTRING, "DATE", &waveData->date,
                      NULL, &status) != 0)
    {
        status = 0;
    }
    /* Read dictionary names */
    if (fits_read_key(filePtr, TSTRING, "ESO INS DID", 
                      &waveData->insDictionaryId, NULL, &status) != 0)
    {
        status = 0;
    }
    if (fits_read_key(filePtr, TSTRING, "ESO INS ID", &waveData->insId,
                      NULL, &status) != 0)
    {
        status = 0;
    }
    /* Read the number of spectral channels */
    if (fits_read_key(filePtr, TINT, "NWAVE", &waveData->nbWlen,
                      NULL, &status) != 0)
    {
        amdlibReturnFitsError("NWAVE");
    }  
    if (waveData->nbWlen != amdlibNB_SPECTRAL_CHANNELS)
    {
            amdlibSetErrMsg("Wrong number of wavelengths : %d, should be %d", 
                            waveData->nbWlen, amdlibNB_SPECTRAL_CHANNELS);
            return amdlibFAILURE;
    }

    /* Read the wavelength offset, in pixel, for each photometric column  */
    if (fits_read_key(filePtr, TDOUBLE, "ESO DET1 P1 OFFSETY",
                      &waveData->photoOffset[0], NULL, &status) != 0)
    {
        status = 0;
        if (fits_read_key(filePtr, TDOUBLE, "ESO INS P1 OFFSETY",
                          &waveData->photoOffset[0], NULL, &status) != 0)
        {
            amdlibReturnFitsError("ESO DET1 P1 OFFSETY");
        }   
    }   

    if (fits_read_key(filePtr, TDOUBLE, "ESO DET1 P2 OFFSETY",
                      &waveData->photoOffset[1], NULL, &status) != 0)
    {
        status = 0;
        if (fits_read_key(filePtr, TDOUBLE, "ESO INS P2 OFFSETY",
                          &waveData->photoOffset[1], NULL, &status) != 0)
        {
            amdlibReturnFitsError("ESO DET1 P2 OFFSETY");
        }   
    }   

    if (fits_read_key(filePtr, TDOUBLE, "ESO DET1 P3 OFFSETY",
                      &waveData->photoOffset[2], NULL, &status) != 0)
    {
        status = 0;
        if (fits_read_key(filePtr, TDOUBLE, "ESO INS P3 OFFSETY",
                          &waveData->photoOffset[2], NULL, &status) != 0)
        {
            amdlibReturnFitsError("ESO DET1 P3 OFFSETY");
        }   
    }   

    /* Read the effective wavelengths */
    if (fits_read_col(filePtr, TDOUBLE, 1, 1, 1, waveData->nbWlen, NULL,
                      (void *)waveData->wlen, &anynull, &status) != 0)
    {
        amdlibReturnFitsError("EFF_WAVE");
    } 

    /* Read the effective bandwidths */
    if (fits_read_col(filePtr, TDOUBLE, 2, 1, 1, waveData->nbWlen, NULL,
                      (void *)waveData->bandwidth, &anynull, &status) != 0)
    {
        amdlibReturnFitsError("EFF_BAND");
    } 

#ifdef TEST
    for (i=0; i < waveData->nbWlen; i++)
    {
        if ((i % 10) == 0)
        {
            amdlibLogTest("%3d : ", i);
        }

        amdlibLogTest("%.4f(%.4f)", waveData->wlen[i], waveData->bandwidth[i]);

        if (((i+1) % 10) == 0)
        {
            amdlibLogTest("");
        }
    }
    amdlibLogTest("");
#endif

    return amdlibSUCCESS;
}

/**
 * Get the spectral band to which wavelength belongs 
 *
 * @param wavelength wavelength in nm 
 *
 * @return
 * The spectral band, amdlibUNKNOWN_BAND if wavelength does not belongs to J, H
 * or K band.
 */
amdlibBAND amdlibGetBand (amdlibDOUBLE wavelength)
{
    int band;

    amdlibLogTrace("amdlibGetBand()");

    for (band = amdlibJ_BAND; band <= amdlibK_BAND; band++)
    {
        if ((wavelength-bands[band].lowerBound)>=0.0l && 
            (wavelength-bands[band].upperBound)<0.0l)
        {
            return band;
        }
    }
    return amdlibUNKNOWN_BAND;
}

/**
 * Return band as character (J, H or K) 
 *
 * @param band spectral band 
 *
 * @return
 * 'J', 'H' or 'K', or ' ' if invalid band.
 */
char amdlibBandNumToStr(amdlibBAND band)
{
    if (band == amdlibUNKNOWN_BAND)
    {
        return ' ';
    }
    return (bands[band].name[0]);
}

/**
 * Return band description.
 *
 * @param band spectral band 
 *
 * @return
 * a pointer on the corresponding amdlibBAND_DESC structure, or NULL if invalid
 * band.
 */
amdlibBAND_DESC *amdlibGetBandDescription(amdlibBAND band)
{
    if (band == amdlibUNKNOWN_BAND)
    {
        return NULL;
    }
    return &bands[band];
}

/**
 * Retrieve wave data structure from the specified raw data 
 *
 * @param rawData input raw data 
 * @param waveData output duplicated wave data
 * @param errMsg error description message returned if function fails.
 *
 * @return 
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibGetWaveDataFromRawData(amdlibRAW_DATA  *rawData,
                                              amdlibWAVEDATA  *waveData,
                                              amdlibERROR_MSG errMsg)
{
    /* Copy 'amdlibWAVEDATA' data structure */
    memcpy(waveData, &rawData->waveData, sizeof(amdlibWAVEDATA));

    return amdlibSUCCESS;
}

/**
 * Extract an amdlibWAVEDATA structure from p2vm data. 
 *
 * @param p2vm input p2vm.
 * @param waveData amdlibWAVEDATA structure produced.
 * @param errMsg error description message returned if function fails.
 * 
 * @return 
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibGetWaveDataFromP2vm(amdlibP2VM_MATRIX *p2vm,
                                           amdlibWAVEDATA    *waveData,
                                           amdlibERROR_MSG   errMsg)
{
    int  l;
    char offsetValue[amdlibKEYW_VAL_LEN+1];
        
    waveData->nbWlen = amdlibNB_SPECTRAL_CHANNELS;
    
    /* Fill array wlen of waveData */
    memset(waveData->wlen, '\0', 
           amdlibNB_SPECTRAL_CHANNELS * sizeof(amdlibDOUBLE));
    for (l = 0; l < p2vm->nbChannels; l++)
    {
        waveData->wlen[p2vm->firstChannel + l] = p2vm->wlen[l];
    }
    
    /* Compute bandwidth */
    if (amdlibComputeBandwidth(waveData) == amdlibFAILURE)
    {
        amdlibSetErrMsg("Could not compute bandwidth");
        return amdlibFAILURE;
    }

    /* Find photoOffsets */
    memset(offsetValue, '\0', (amdlibKEYW_VAL_LEN+1)*sizeof(char));    
    if (amdlibGetInsCfgKeyword(&p2vm->insCfg, 
                               "HIERARCH ESO QC P1 OFFSETY", 
                               offsetValue, errMsg) != amdlibSUCCESS)
    {
        if (amdlibGetInsCfgKeyword(&p2vm->insCfg, 
                                   "HIERARCH ESO DET1 P1 OFFSETY", 
                                   offsetValue, errMsg) != amdlibSUCCESS)
        {
            return amdlibFAILURE;
        }
    }
    sscanf(offsetValue, "%lf", &waveData->photoOffset[0]);

    memset(offsetValue, '\0', (amdlibKEYW_VAL_LEN+1)*sizeof(char));
    if (amdlibGetInsCfgKeyword(&p2vm->insCfg, "HIERARCH ESO QC P2 OFFSETY", 
                               offsetValue, errMsg) != amdlibSUCCESS)
    {
        if (amdlibGetInsCfgKeyword(&p2vm->insCfg,
                                   "HIERARCH ESO DET1 P2 OFFSETY", 
                                   offsetValue, errMsg) != amdlibSUCCESS)
        {
            return amdlibFAILURE;
        }
    }
    sscanf(offsetValue, "%lf", &waveData->photoOffset[1]);

    memset(offsetValue, '\0', (amdlibKEYW_VAL_LEN+1)*sizeof(char));
    if (amdlibGetInsCfgKeyword(&p2vm->insCfg, "HIERARCH ESO QC P3 OFFSETY", 
                               offsetValue, errMsg) != amdlibSUCCESS)
    {
        if (amdlibGetInsCfgKeyword(&p2vm->insCfg, 
                                   "HIERARCH ESO DET1 P3 OFFSETY", 
                                   offsetValue, errMsg) != amdlibSUCCESS)
        {
            return amdlibFAILURE;
        }
    }
    sscanf(offsetValue, "%lf", &waveData->photoOffset[2]);

    return amdlibSUCCESS;
}

/**
 * Get valid channel for the given spectral band.
 * 
 * This function cross-checks spectral channels of the science data with P2VM,
 * and returns the number of valid spectral channels belonging to the specified
 * spectral band. 
 *
 * The returned channels are valid in the p2vm. This because the p2vm may
 * contain flagged 'lines' because of bad pixels or insufficient data during the
 * p2vm calibration phase. 
 * 
 * @param p2vm p2vm data.
 * @param data science data.
 * @param waveData data on wavelengths.
 * @param band spectral band considered : amdlibJ_BAND, amdlibH_BAND, 
 * amdlibK_BAND.
 * @param nbChannelsInBand number of channels belonging to input band.
 * @param channelNoInBand array where indexes are stored.
 *
 * @warning channelNoInBand is assumed to be already allocated.
 *
 * @return 
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibGetChannelsInBand(amdlibP2VM_MATRIX  *p2vm,
                                         amdlibSCIENCE_DATA *data,
                                         amdlibWAVEDATA     *waveData,
                                         amdlibBAND         band, 
                                         int                *nbChannelsInBand,
                                         int                *channelNoInBand)
{
    int channel;
    int channelNo;
    int nbValidChannel = 0;
    
    /* Search for first channel in band and compute nbChannelsInBand */
    for (channel = 0; channel < data->nbChannels; channel++)
    {
        channelNo = data->channelNo[channel];
        if ((amdlibGetBand(waveData->wlen[channelNo]) == band))
        {
            int lP2vm;
            if (amdlibIsValidChannel(p2vm, channelNo, &lP2vm) == amdlibTRUE)
            {
                channelNoInBand[nbValidChannel] = channelNo;
                nbValidChannel++;
            }
        }
    }
    
    *nbChannelsInBand = nbValidChannel;

    return amdlibSUCCESS;
}

amdlibBOOLEAN amdlibIsBandPresentInData(amdlibSCIENCE_DATA     *data,
                                        amdlibP2VM_MATRIX      *p2vm,
                                        amdlibWAVEDATA         *waveData,
                                        amdlibBAND             band)
{
    int nbChannelsInBand = 0;
    int channelNoForBand[amdlibNB_SPECTRAL_CHANNELS];
 
    amdlibLogTrace("amdlibIsBandPresentInData()");

    /* Search for the number of valid (considering p2vm flags) spectral channels
     * for specified band */
    if (amdlibGetChannelsInBand(p2vm, data, waveData, band, 
                                &nbChannelsInBand, 
                                channelNoForBand) != amdlibSUCCESS)
    {
        return amdlibFALSE;
    }
    
    if (nbChannelsInBand == 0)
    {
        return amdlibFALSE;
    }
    else
    {
        return amdlibTRUE;
    }
}

/*___oOo___*/
