/*******************************************************************************
 * JMMC project ( http://www.jmmc.fr ) - Copyright (C) CNRS.
 ******************************************************************************/
/**
 * @file
 * Functions to perform spectral calibration
 */

#define _POSIX_SOURCE 1

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

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

/* 
 * Public function 
 */

/**
 * Perform spectral calibration.
 *
 * This function perform the spectral calibration. This consists in :
 *      \li computing the photometric channel shift wrt. interferometric channel
 *      \li calibration spectral dispersion table (in low JHK mode only)
 *
 * @param scData data used for spectral calibration.
 * @param scType spectral calibration type (amdlibSC_2T or amdlibSC_3T).
 * @param waveData structure where calibration wave data will be stored
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibPerformSpectralCalibration(amdlibSC_INPUT_DATA *scData,
                                                  amdlibSC_TYPE       scType,
                                                  amdlibWAVEDATA      *waveData,
                                                  amdlibERROR_MSG     errMsg)
{
    int nbTel;
    int beam;
    int nbChannels;
    amdlibDOUBLE *spectrum,*noise;
    int hasAssociatedP2VMFlux[3]={0,0,0};
    amdlibDOUBLE intfSpec [amdlibNB_SPECTRAL_CHANNELS];
    amdlibDOUBLE photoSpec[amdlibNB_SPECTRAL_CHANNELS];

    char keywValue[amdlibKEYW_VAL_LEN+1];    
 
    amdlibLogTrace("amdlibPerformSpectralCalibration()"); 
    
    /* Init the number of telescopes and baselines according to SC type */
    if (scType == amdlibSC_2T)
    {
        nbTel = 2;
    }
    else if (scType == amdlibSC_3T)
    {
        nbTel = 3;
    }
    else
    {
        amdlibSetErrMsg("Invalid type %d (see amdlibSC_TYPE)", (int)scType);
        return amdlibFAILURE;
    }
    
    /* Get instrument configuration */
    memset(keywValue, '\0', (amdlibKEYW_VAL_LEN+1)*sizeof(char));
    if (amdlibGetInsCfgKeyword(&(scData->rawData[0].insCfg), 
                               "HIERARCH ESO OCS OBS SPECCONF",
                               keywValue, errMsg) != amdlibSUCCESS)
    {
        return amdlibFAILURE;
    }

    /* If instrument configuration is High Resolution, Do not even attempt
     * to compute offsets, hardware currently implement prevents it. Just
     * pretend that the offsets have been calibrated and equal 0*/
    if (strstr(keywValue, "High_") != NULL)
    {
        for (beam = 0; beam < nbTel; beam++)
        {
        waveData->photoOffset[beam] = 0.0;
        amdlibLogInfo("High-resolution data");
        amdlibLogInfoDetail("Photometric channel %d set to offset 0", beam);
        }
        return amdlibSUCCESS;
    }
    
    /* Reset error message */
    memset(errMsg, '\0', sizeof(amdlibERROR_MSG));
 
    /* Check the science data have been stored and calibrated */
    for (beam = 0; beam < nbTel; beam++)
    {
        int frameType;

        /* Set frame type (see amdlibFRAME_TYPE) */
        frameType = beam + amdlibTEL1_FRAME; 

        /* Check the corresponding science data has been loaded */
        if (scData->dataLoaded[beam] == amdlibFALSE)
        {
            amdlibSetErrMsg("Missing data file corresponding to the beam %d "
                            "(FRAMETYPE %d, see amdlibFRAME_TYPE)", 
                            beam, frameType);
            return amdlibFAILURE;
        }
        /* Check if data set also has flux correction table (e.g., from P2VM)*/
        if (scData->dataLoaded[beam+3] == amdlibTRUE)
        {
            hasAssociatedP2VMFlux[beam]=1;
        }
    }

    /* Retrieve the number of spectral channels */
    nbChannels = scData->rawData[0].region[0].dimAxis[1];

    /* For each telescope (i.e. beam), retrieve correct photometry and compute
     * shift wrt. interferometric channel */
    for (beam = 0; beam < nbTel; beam++)
    {
        double shift, sigma2Shift;
        int l;

        /* Allocate memory for spectrum */
        spectrum = (amdlibDOUBLE *)calloc(nbChannels, sizeof(*spectrum));
        if (spectrum == NULL)
        {
            amdlibSetErrMsg("Could not allocate memory for spectrum");
            return amdlibFAILURE;
        }
        noise = (amdlibDOUBLE *)calloc(nbChannels, sizeof(*noise));
        if (spectrum == NULL)
        {
            amdlibSetErrMsg("Could not allocate memory for noise");
            return amdlibFAILURE;
        }
        
        /* Compute spectrum for interferometric beam */
        if (amdlibSumAndPackData(&(scData->rawData[beam]), 
                                 amdlibTRUE, amdlibFALSE, amdlibTRUE,
                                 3, &spectrum, &noise, errMsg) != amdlibSUCCESS)
        {
            free(spectrum);
            free(noise);
            return amdlibFAILURE;
        }

        /* Store interferometric spectrum into 512-element array for 'fft' */
        memset(intfSpec, '\0', amdlibNB_SPECTRAL_CHANNELS*sizeof(*intfSpec));
        for (l = 0; l < nbChannels; l++)   
        {
            intfSpec[l] = spectrum[l];
        }
          
        /* if associated flux calibration frame is present, and we are MR (HR?) correct it*/
        if (hasAssociatedP2VMFlux[beam]==1 && (strstr(keywValue, "Low_JHK ") == NULL))
        {
            if (amdlibSumAndPackData(&(scData->rawData[beam+3]), 
                                     amdlibTRUE, amdlibFALSE, amdlibTRUE,
                                     3, &spectrum, &noise, errMsg) != amdlibSUCCESS)
            {
                free(spectrum);
                free(noise);
                return amdlibFAILURE;
            }
            /*normalise intfSpec with this spectrum*/
            for (l = 0; l < nbChannels; l++)   
            {
                intfSpec[l] /= spectrum[l];
            }
        }
        
        /* Compute spectrum for photometric beam */
        if (amdlibSumAndPackData(&(scData->rawData[beam]), 
                                 amdlibTRUE, amdlibFALSE, amdlibTRUE,
                                 beam, &spectrum, &noise, errMsg) != amdlibSUCCESS)
        {
            free(spectrum);
            free(noise);
            return amdlibFAILURE;
        }

        /* Store photometric spectrum into 512-element array for 'fft' */
        memset(photoSpec, '\0', amdlibNB_SPECTRAL_CHANNELS*sizeof(*photoSpec));
        for (l = 0; l < nbChannels; l++)   
        {
            photoSpec[l] = spectrum[l];
        }

        /* if associated flux calibration frame is present, correct it*/
        if (hasAssociatedP2VMFlux[beam]==1 && (strstr(keywValue, "Low_JHK ") == NULL) )
        {
            if (amdlibSumAndPackData(&(scData->rawData[beam+3]), 
                                     amdlibTRUE, amdlibFALSE, amdlibTRUE,
                                     beam, &spectrum, &noise, errMsg) != amdlibSUCCESS)
            {
                free(spectrum);
                free(noise);
                return amdlibFAILURE;
            }
            /*normalise photoSpec with this spectrum*/
            for (l = 0; l < nbChannels; l++)   
            {
                photoSpec[l] /= spectrum[l];
            }
        }

        /* Compute shift between the interferometric and photometric channels */
        if (amdlibComputeShift(nbChannels, intfSpec, photoSpec, 
                              &shift, &sigma2Shift, errMsg) == amdlibFAILURE)
        {	    
            free(spectrum);
            free(noise);
            return amdlibFAILURE;
        }

        /* Check shift value; must be less than 5 pixels */
        if (fabs(shift) > 5.0)
        {
            free(spectrum);
            free(noise);
            amdlibSetErrMsg("Shift (%.2f) between photometric channel #%d "
                            "and interferometric channel too high",
                            shift, beam + 1);
            return amdlibFAILURE;
        }
        
        /* Store shift */
        waveData->photoOffset[beam] = shift;
        amdlibLogInfoDetail("Photometric channel %d has offset %f (%f)", 
                            beam, shift, sigma2Shift);
        
        free(noise);
        free(spectrum);
    }


    /* If it is low JHK mode, calibrate spectral dispersion table using image of
     * the beam 1 */
    if (strstr(keywValue, "Low_JHK ") != NULL)
    {
        int shutterNum;
        int i, l;
        int startPixel;

        /* Assuming all bands are open */
        int shutterPos[amdlibNB_BANDS] = {1, 1, 1};
        amdlibINS_CFG *insCfg = &scData->rawData[1].insCfg;

        /* Reference spectrum */
        amdlibDOUBLE refSpec[amdlibNB_SPECTRAL_CHANNELS];
        double max;
        double shift, sigma2Shift;

        /* Scan all configuration keywords  */
        for (i=0; i < insCfg->nbKeywords; i++)
        {
            /* Look for SFK shutters;  do not check the specific BSL shutters */
            if ((strstr(insCfg->keywords[i].name,
                        "HIERARCH ESO INS SHUT") != NULL) &&
                (strstr(insCfg->keywords[i].name, " ST") != NULL))
            {

                /* Get the shutter number; used to know the optical path to
                 * which it belongs */
                sscanf(insCfg->keywords[i].name, 
                        "HIERARCH ESO INS SHUT%d", &shutterNum);   
                /* If shutter is closed */
                if (strstr(insCfg->keywords[i].value, "F") != 0)
                {
                    /* Set correspondind band to close */
                    switch(shutterNum)
                    {
                        /* Shutters of J band */
                        case 10:
                        case 11:
                            shutterPos[amdlibJ_BAND] = 0;
                            break;
                        case 12:
                            if (nbTel == 3)
                            {
                                shutterPos[amdlibJ_BAND] = 0;
                            }
                            break;
                            /* Shutters of H band */
                        case 7:
                        case 8:
                            shutterPos[amdlibH_BAND] = 0;
                            break;
                        case 9:
                            if (nbTel == 3)
                            {
                                shutterPos[amdlibH_BAND] = 0;
                            }
                            break;
                            /* Shutters of K band */
                        case 4:
                        case 5:
                            shutterPos[amdlibK_BAND] = 0;
                            break;
                        case 6:
                            if (nbTel == 3)
                            {
                                shutterPos[amdlibK_BAND] = 0;
                            }
                            break;
                    }
                }
            }
        }
        /* If all bands are off, return an error */
        if ((shutterPos[amdlibJ_BAND] == 0) &&
            (shutterPos[amdlibH_BAND] == 0) &&
            (shutterPos[amdlibK_BAND] == 0))
        {
            amdlibSetErrMsg("Unable to build JHK reference Spectrum: check "
                            "shutter status !");
            return amdlibFAILURE;
        }

        /* Get reference spectrum corresponding to the instrument
         * configuration */
        if (amdlibGetRefLowJHKSpectrumForCal(shutterPos, refSpec) 
                                            == amdlibFAILURE)
        {
            return amdlibFAILURE;
        }
        
        /* Allocate memory for spectrum */
        spectrum = (amdlibDOUBLE *)calloc(nbChannels, sizeof(*spectrum));
        noise = (amdlibDOUBLE *)calloc(nbChannels, sizeof(*noise));
        
        /* Compute spectrum for interferometric beam */
        if (amdlibSumAndPackData(&(scData->rawData[0]), 
                                 amdlibTRUE, amdlibFALSE, amdlibTRUE,
                                 3, &spectrum, &noise, errMsg) != amdlibSUCCESS)
        {
            free(noise);
            free(spectrum);
            return amdlibFAILURE;
        }

        /* Get the start pixel; -1 because this value is on the detector where
         * pixels numbers start at 1 */
        startPixel = scData->rawData[0].region[3].corner[1] - 1;
        /* Store interferometric spectrum into 512-element array for 'fft'
         * taking into account the start pixel. */
        memset(intfSpec, '\0', amdlibNB_SPECTRAL_CHANNELS*sizeof(*intfSpec));
        for (l = 0; l < nbChannels; l++)   
        {
            intfSpec[startPixel + l] = spectrum[l];
         }
        free(noise);
        free(spectrum); 

        /* Since spectra have now different powers, normalize the spectra
         * observed to unity */
        max = intfSpec[0];
        for (l = 0; l < amdlibNB_SPECTRAL_CHANNELS; l++)
        {
            if (intfSpec[l] > max)
            {
                max = intfSpec[l];
            }
        }
        for (l = 0; l < amdlibNB_SPECTRAL_CHANNELS; l++)   
        {
            intfSpec[l] /= max;
        }

        /* Compute shift */
        if (amdlibComputeShift(amdlibNB_SPECTRAL_CHANNELS, refSpec,
                               intfSpec, &shift, &sigma2Shift,
                               errMsg) == amdlibFAILURE)
        {
            return amdlibFAILURE;
        }
        amdlibLogInfo("Wavelength table has offset %f (%f)", 
                      shift, sigma2Shift);

        /* Check offset value; must be less than 10 pixels */
        if (fabs(shift) > 10.0)
        {
            //free(noise);
            //free(spectrum);
            amdlibSetErrMsg("Offset (%.2f) of wavelength table too high",
                            shift);
            return amdlibFAILURE;
        }

        /* Update wavelength and bandwidth by removing data, inserting reference
         * spectrum wavelength at the good location and shifting this by the
         * nint of measured shift value (this to maintain zeroes in all
         * wavelength undefined -- with the non-integer shift method, invalid
         * wavelength would contain noise, not zeroes)
         */
        /* Get reference spectral dispersion table for low JHK mode */
        if (amdlibGetRefLowJHKSpectralDispersion(refSpec) == amdlibFAILURE)
        {
            return amdlibFAILURE;
        }
        /* Shift reference spectral dispersion table of the measured shift */
        if (amdlibShift(amdlibNB_SPECTRAL_CHANNELS, refSpec, shift,
                        waveData->wlen, errMsg) == amdlibFAILURE)
        {
            return amdlibFAILURE;
        }

        /* Set all wavelengths that do not belong to J, H or K band to 0 */
        l = 0;
        while (l < amdlibNB_SPECTRAL_CHANNELS)
        {
            if (amdlibGetBand(waveData->wlen[l]) == amdlibUNKNOWN_BAND)
            {
                waveData->wlen[l] = 0.;
                l++;
            }
            else 
            {
                break;
            }
        }
        i = amdlibNB_SPECTRAL_CHANNELS - 1;
        while (i > l)
        {
            if (amdlibGetBand(waveData->wlen[i]) == amdlibUNKNOWN_BAND)
            {
                waveData->wlen[i] = 0.;
                i--;
            }
            else 
            {
                break;
            }
        }

        /* Compute bandwidth */
        if (amdlibComputeBandwidth(waveData) == amdlibFAILURE)
        {
            amdlibSetErrMsg("Could not compute bandwidth");
            return amdlibFAILURE;
        }
    }

    return amdlibSUCCESS;
}

static amdlibDARK_DATA dark = {NULL};
static amdlibRAW_DATA        rawData = {NULL};
static amdlibSC_INPUT_DATA   spectralCalibrationData = {NULL};
static amdlibWAVEDATA        calWaveData;

/** 
 * Compute spectral calibration for the 2 telescopes configuration.
 *
 * This function computes the spectral calibration  for the 2 telescope 
 * configuration. The resulting calculated offsets are saved into the file 
 * given as parameter. If this file exists, it is overwritten.
 *
 * @param badPixelFile name of file containing bad pixel map
 * @param flatFieldFile name of file containing flat-field map
 * @param darkFile name of file containing data for dark compensation
 * @param inputFile1 name of file containing data on specific shutter
 * configuration useful to perform spectral calibration
 * @param inputFile2 name of file containing data on specific shutter
 * configuration useful to perform spectral calibration
 * @param spectralOffsets amdlibDOUBLE array containing computed calibration offsets.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
  */
amdlibCOMPL_STAT amdlibComputeSpectralCalibration2T(
                                        const char *badPixelFile,
                                        const char *flatFieldFile,
                                        const char *darkFile,
                                        const char *inputFile1,
                                        const char *inputFile2,
                                        amdlibDOUBLE      spectralOffsets[])
{
    const char            *inputFiles[3];
    int                   i;
    amdlibERROR_MSG       errMsg;

    amdlibLogTrace("amdlibComputeSpectralCalibration2T()");

    /* Init list of input files */
    inputFiles[0] = darkFile;
    inputFiles[1] = inputFile1;
    inputFiles[2] = inputFile2;

    /* If a bad pixel file has been specified */
    if ((badPixelFile != NULL) && (strlen(badPixelFile) != 0))
    {
        /* Load it */
        if (amdlibLoadBadPixelMap(badPixelFile, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not load bad pixel map '%s'", badPixelFile);
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE;
        }
    }

    /* Load flat field map */
    if ((flatFieldFile != NULL) && (strlen(flatFieldFile) != 0))
    {
        if(amdlibLoadFlatFieldMap(flatFieldFile, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not load flat Field map '%s'", flatFieldFile);
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE;
        }
    }

    /* For each input files */
    amdlibLogInfo("Loading input files ...");
    for (i = 0; i < 3; i++)
    {
        if ((inputFiles[i] == NULL) || (strlen(inputFiles[i]) == 0))
        {
            amdlibLogError("Invalid name for %dth input file", i+1);
            return amdlibFAILURE;
        }

        /* Load raw data */
        amdlibLogInfoDetail("%s", inputFiles[i]);

        if (amdlibLoadRawData(inputFiles[i], &rawData, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not load raw data from file '%s'", 
                           inputFiles[i]);
            amdlibLogErrorDetail(errMsg);
            amdlibReleaseRawData(&rawData);
            return amdlibFAILURE;
        }   

        
        if (rawData.frameType == amdlibUNKNOWN_FRAME)
        {
            amdlibLogError("Invalid frame type '%d'", amdlibUNKNOWN_FRAME);
            amdlibReleaseRawData(&rawData);
            return amdlibFAILURE;
        }
        else if (rawData.frameType == amdlibDARK_FRAME)
        {
            /* Compute pixel dark map */
            if (amdlibGenerateDarkData(&rawData, &dark,
                                            errMsg) != amdlibSUCCESS)
            {
                amdlibLogError("Could not generate dark map");
                amdlibLogErrorDetail(errMsg);
                amdlibReleaseDarkData(&dark);
                amdlibReleaseRawData(&rawData);
                return amdlibFAILURE;
            }
        }
        else
        {
            /* Equalize raw data */
            if (amdlibCalibrateRawData(&dark, &rawData, errMsg) 
                !=amdlibSUCCESS)
            {
                amdlibLogError("Could not calibrate raw data");
                amdlibLogErrorDetail(errMsg);
                amdlibReleaseDarkData(&dark);
                amdlibReleaseRawData(&rawData);
                return amdlibFAILURE;
            }

            /* Store calibrated data into Spectral Calibration data structure */ 
            if (amdlibAddToSpectralCalibrationData(&rawData, 
                                                   &spectralCalibrationData,
                                                   errMsg) != amdlibSUCCESS)
            {
                amdlibLogError("Could not store calibrated wave data into "
                               "spectral calibration data structure");
                amdlibLogErrorDetail(errMsg);
                amdlibReleaseDarkData(&dark);
                amdlibReleaseRawData(&rawData);
                amdlibReleaseSpectralCalibrationData(&spectralCalibrationData);
                return amdlibFAILURE;
            }
        }
        /* copy first rawData->waveData structure in calWaveData to fill all 
         * fields*/
        if (i != 0) 
        {
            memcpy(&calWaveData, &rawData.waveData, sizeof(amdlibWAVEDATA));
        }
        amdlibReleaseRawData(&rawData);
    }
    /* End for */

    amdlibReleaseDarkData(&dark);

    /* Compute Spectral Calibration */
    amdlibLogInfo("Computing spectral calibration ...");
    if (amdlibPerformSpectralCalibration(&spectralCalibrationData, amdlibSC_2T,
                                         &calWaveData, errMsg) == amdlibFAILURE)
    {
        amdlibLogError("Could not perform spectral calibration");
        amdlibLogErrorDetail(errMsg);
        amdlibReleaseSpectralCalibrationData(&spectralCalibrationData);
        return amdlibFAILURE;
    }

    amdlibReleaseSpectralCalibrationData(&spectralCalibrationData);

    for (i=0; i < 2; i++)
    {
        spectralOffsets[i] = calWaveData.photoOffset[i];
    }

    return amdlibSUCCESS;
}

/** 
 * Compute spectral calibration for the 3 telescopes configuration.
 *
 * Ditto amdlibComputeSpectralCalibration2T for the 3 telescopes configuration
 * 
 * @param badPixelFile name of file containing bad pixel map
 * @param flatFieldFile name of file containing flat-field map
 * @param darkFile name of file containing data for dark estimation
 * @param inputFile1 name of file containing data on specific shutter
 * configuration useful to perform spectral calibration
 * @param inputFile2 name of file containing data on specific shutter
 * configuration useful to perform spectral calibration
 * @param inputFile3 name of file containing data on specific shutter
 * configuration useful to perform spectral calibration
 * @param spectralOffsets amdlibDOUBLE array containing computed calibration offsets.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibComputeSpectralCalibration3T(const char *badPixelFile,
                                                    const char *flatFieldFile,
                                                    const char *darkFile,
                                                    const char *inputFile1,
                                                    const char *inputFile2,
                                                    const char *inputFile3,
                                                    amdlibDOUBLE spectralOffsets[])
{
    const char            *inputFiles[4];
    int                   i;
    amdlibERROR_MSG       errMsg;

    amdlibLogTrace("amdlibComputeSpectralCalibration3T()");

    /* Init list of input files */
    inputFiles[0] = darkFile;
    inputFiles[1] = inputFile1;
    inputFiles[2] = inputFile2;
    inputFiles[3] = inputFile3;

    /* If a bad pixel file has been specified */
    if ((badPixelFile != NULL) && (strlen(badPixelFile) != 0))
    {
        /* Load it */
        if (amdlibLoadBadPixelMap(badPixelFile, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not load bad pixel map '%s'", badPixelFile);
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE;
        }
    }

    /* Load flat field map */
    if ((flatFieldFile != NULL) && (strlen(flatFieldFile) != 0))
    {
        if(amdlibLoadFlatFieldMap(flatFieldFile, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not load flat field map '%s'", flatFieldFile);
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE;
        }
    }

    /* For each input files */
    amdlibLogInfo("Loading input files ...");
    for (i = 0; i < 4; i++)
    {
        if ((inputFiles[i] == NULL) || (strlen(inputFiles[i]) == 0))
        {
            amdlibLogError("Invalid name for %dth input file", i+1);
            return amdlibFAILURE;
        }

        /* Load raw data */
        amdlibLogInfoDetail("%s", inputFiles[i]);

        if (amdlibLoadRawData(inputFiles[i], &rawData, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not load raw data from file '%s'", inputFiles[i]);
            amdlibLogErrorDetail(errMsg);
            amdlibReleaseRawData(&rawData);
            return amdlibFAILURE;
        }   

        if (rawData.frameType == amdlibUNKNOWN_FRAME)
        {
            amdlibLogError("Invalid frame type '%d'", amdlibUNKNOWN_FRAME);
            amdlibReleaseRawData(&rawData);
            return amdlibFAILURE;
        }
        else if (rawData.frameType == amdlibDARK_FRAME)
        {
            /* Compute dark map */
            if (amdlibGenerateDarkData(&rawData, &dark,
                                            errMsg) != amdlibSUCCESS)
            {
                amdlibLogError("Could not generate dark map");
                amdlibLogErrorDetail(errMsg);
                amdlibReleaseDarkData(&dark);
                amdlibReleaseRawData(&rawData);
                return amdlibFAILURE;
            }
        }
        else
        {
            /* Equalize raw data */
            if (amdlibCalibrateRawData(&dark, &rawData, errMsg) 
                !=amdlibSUCCESS)
            {
                amdlibLogError("Could not calibrate raw data");
                amdlibLogErrorDetail(errMsg);
                amdlibReleaseDarkData(&dark);
                amdlibReleaseRawData(&rawData);
                return amdlibFAILURE;
            }

            /* Store calibrated data into Spectral Calibration data structure */
            if (amdlibAddToSpectralCalibrationData(&rawData, 
                                                   &spectralCalibrationData,
                                                   errMsg) != amdlibSUCCESS)
            {
                amdlibLogError("Could not store calibrated wave data into "
                               "spectral calibration data structure");
                amdlibLogErrorDetail(errMsg);
                amdlibReleaseDarkData(&dark);
                amdlibReleaseRawData(&rawData);
                amdlibReleaseSpectralCalibrationData(&spectralCalibrationData);
                return amdlibFAILURE;
            }
        }
        /* copy first rawData->waveData structure in calWaveData to fill 
         * all fields*/
        if (i != 0)
        {
            memcpy(&calWaveData, &rawData.waveData, sizeof(amdlibWAVEDATA));
        }
        amdlibReleaseRawData(&rawData);
    }
    /* End for */

    amdlibReleaseDarkData(&dark);

    /* Compute Spectral Calibration */
    amdlibLogInfo("Computing spectral calibration ...");
    if (amdlibPerformSpectralCalibration(&spectralCalibrationData, amdlibSC_3T,
                                         &calWaveData, errMsg) == amdlibFAILURE)
    {
        amdlibLogError("Could not perform spectral calibration");
        amdlibLogErrorDetail(errMsg);
        amdlibReleaseSpectralCalibrationData(&spectralCalibrationData);
        return amdlibFAILURE;
    }

    amdlibReleaseSpectralCalibrationData(&spectralCalibrationData);

    for (i=0; i < 3; i++)
    {
        spectralOffsets[i] = calWaveData.photoOffset[i];
    }

    return amdlibSUCCESS;
}

/*___oOo__*/
