/*******************************************************************************
 * JMMC project ( http://www.jmmc.fr ) - Copyright (C) CNRS.
 ******************************************************************************/
/**
 * @file
 * High-level function for AMBER data reduction processing. 
 *     
 * This file includes high-level functions which provide interface to AMBER data
 * reduction functions with no internal data structure as input or output
 * paremeters.
 *
 * These functions very often uses : 
 * \li a bad pixel map file which contains for each pixel of the detector
 * (512x512 pixels) a flag indicating if pixel is good or not. This file is
 * generated by the detector Maintenance Software (see amdmsServer(1)) through
 * the AMBER Maintenance Templates. If the file name is an empty string all
 * pixels are marked as good.
 *
 * \li a flat field map file which contains for each pixel of the detector
 * (512x512 pixels) a factor which gives the gain of that pixel. This file is
 * generated by the detector Maintenance Software (see amdmsServer(1)) through
 * the AMBER Maintenance Templates. If the file name is an empty string the gain
 * of all pixels is set to 1.
 *
 * \li a dark file which is used to compensate for detector effects. It
 * corresponds to a series of sky images or to a series of images with all
 * shutters closed. The detector subwindow setup and exposure time must be the
 * same than the ones corresponding to the raw data to be calibrated. If the
 * file name is an empty string the dark for each pixel is set to 0.
 */

#define _POSIX_SOURCE 1

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

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

/*
 * Local function definition
 */
#define amdlibTYPICAL_RON 10.0

static amdlibDARK_DATA dark = {NULL};
static amdlibRAW_DATA        rawData = {NULL};

#ifndef ESO_CPL_PIPELINE_VARIANT
static amdlibCOMPL_STAT amdlibAppendKeywordListToP2VM(
                                                const char *p2vmFile,
                                                const char *badPixelFile,
                                                const char *flatFieldFile,
                                                const char **inputFiles,
                                                int nbInputFiles,
                                                amdlibDOUBLE newSpectralOffsets[],
                                                amdlibBOOLEAN addOffsets,
                                                amdlibERROR_MSG errMsg);
static amdlibCOMPL_STAT amdlibAppendKeywordListToOIFITS(
                                const char *oifitsFile,
                                const char *badPixelFile,
                                const char *flatFieldFile,
                                const char *darkFile,
                                const char *inputFile, 
                                const int   nbBinning,
                                const amdlibERROR_TYPE errorType,
                                const amdlibPISTON_ALGORITHM pistonType,
                                const amdlibBOOLEAN noCheckP2vmId,
                                const amdlibBOOLEAN mergeBandsInOutputFile,
                                const amdlibFRAME_SELECTION selectionType,
                                const double selectionRatio,
                                const int   nbRecords,
                                amdlibERROR_MSG errMsg);
#endif
static amdlibCOMPL_STAT amdlibAppendSelectionKeywords(
                                 const char *oifitsFile,
                                 const char *selFile,
                                 const char *inOiFits,
                                 amdlibINS_CFG *selKewList,
                                 amdlibBOOLEAN saveInOiFits,
                                 const amdlibFRAME_SELECTION selectionType,
                                 const double selectionRatio,
                                 amdlibERROR_MSG errMsg);
                                
amdlibBOOLEAN amdlibIsBandPresentInData(amdlibSCIENCE_DATA     *data,
                                        amdlibP2VM_MATRIX      *p2vm,
                                        amdlibWAVEDATA         *waveData,
                                        amdlibBAND             band);
/*
 * Public function
 */
/** 
 * Return version of AMBER Data reduction library.
 *
 * @param version string where software version is copied in
 */
void amdlibGetVersion(char version[32])
{
    strcpy(version, amdlibVERSION);
}

/** 
 * Print version of AMBER Data reduction library.
 */
void amdlibPrintVersion()
{
    char version[32];

    amdlibGetVersion(version);

    printf(" amdlib %s version\n",version);
}

/*
 * Protected functions
 */
/** 
 * Calibrate raw image.
 *
 * This function converts raw image into calibrated image, by correcting
 * detector effects, and stores calibrated image in the ouput file (if given) or
 * the input file otherwise. 
 *
 * @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 inputFile name of file containing data to calibrate
 * @param outputFile name of file where calibrated data will be stored 
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
  */
amdlibCOMPL_STAT amdlibGenerateCalImage(const char *badPixelFile,
                                        const char *flatFieldFile,
                                        const char *darkFile,
                                        const char *inputFile,
                                        const char *outputFile)
{
    amdlibERROR_MSG       errMsg;

    amdlibLogTrace("amdlibGenerateCalImage()");
    
    /* 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);
            amdlibReleaseRawData(&rawData);
            amdlibReleaseDarkData(&dark);
            return amdlibFAILURE;
        }
    }
    /* Else */
    else
    {
        /* Set it with all pixels marked as good */
        if (amdlibSetBadPixelMap(amdlibTRUE) != amdlibSUCCESS)
        {
            amdlibLogError("Could not set bad pixel map");
            amdlibReleaseRawData(&rawData);
            amdlibReleaseDarkData(&dark);
            return amdlibFAILURE;
        }
    }
    /* End if */

    /* If a flat field file has been specified */
    if ((flatFieldFile != NULL) && (strlen(flatFieldFile) != 0))
    {
        /* Load it */
        if (amdlibLoadFlatFieldMap(flatFieldFile, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not load flat field map '%s'", flatFieldFile);
            amdlibLogErrorDetail(errMsg);
            amdlibReleaseRawData(&rawData);
            amdlibReleaseDarkData(&dark);
            return amdlibFAILURE;
        }
    }
    /* Else */
    else
    {
        /* Set it with gain set to 1.0 */
        if (amdlibSetFlatFieldMap(1.0) != amdlibSUCCESS)
        {
            amdlibLogError("Could not set flat field map");
            amdlibReleaseRawData(&rawData);
            amdlibReleaseDarkData(&dark);
            return amdlibFAILURE;
        }
    }
    /* End if */

    /* If a dark file has been specified */
    if ((darkFile != NULL) && (strlen(darkFile) != 0))
    {
        /* Load it */
        if (amdlibLoadRawData(darkFile, &rawData, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not load raw data from file '%s'", darkFile);
            amdlibLogErrorDetail(errMsg);
            amdlibReleaseRawData(&rawData);
            amdlibReleaseDarkData(&dark);
            return amdlibFAILURE;
        }   

        /* Compute dark map */
        if (amdlibGenerateDarkData(&rawData, &dark,
                                        errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not generate dark map");
            amdlibLogErrorDetail(errMsg);
            amdlibReleaseRawData(&rawData);
            amdlibReleaseDarkData(&dark);
            return amdlibFAILURE;
        }
    }
    /* Else */
    else
    {
        /* Load input data file */
        if (amdlibLoadRawData(inputFile, &rawData, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not load raw data from file '%s'", inputFile);
            amdlibLogErrorDetail(errMsg);
            amdlibReleaseRawData(&rawData);
            amdlibReleaseDarkData(&dark);
            return amdlibFAILURE;
        }  

        /* Set dark map to 0.0 */
        if (amdlibSetDarkData(&rawData, &dark, 
                                   0.0, amdlibTYPICAL_RON, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not set dark map");
            amdlibLogErrorDetail(errMsg);
            amdlibReleaseRawData(&rawData);
            amdlibReleaseDarkData(&dark);
            return amdlibFAILURE;
        }
    }
    /* End if */

    /* Load input file */
    if ((inputFile == NULL) || (strlen(inputFile) == 0))
    {
        amdlibLogError("Invalid name for input file");
        amdlibReleaseRawData(&rawData);
        amdlibReleaseDarkData(&dark);
        return amdlibFAILURE;
    }
    if (amdlibLoadRawData(inputFile, &rawData, errMsg) != amdlibSUCCESS)
    {
        amdlibLogError("Could not load raw data from file '%s'", inputFile);
        amdlibLogErrorDetail(errMsg);
        amdlibReleaseRawData(&rawData);
        amdlibReleaseDarkData(&dark);
        return amdlibFAILURE;
    }

    /* Equalize raw data */
    if (amdlibCalibrateRawData(&dark, &rawData, errMsg) !=amdlibSUCCESS)
    {
        amdlibLogError("Could not calibrate raw data");
        amdlibLogErrorDetail(errMsg);
        amdlibReleaseRawData(&rawData);
        amdlibReleaseDarkData(&dark);
        return amdlibFAILURE;
    }

    /* If output file has been specified */
    if ((outputFile != NULL) && (strlen(outputFile) != 0))
    {
        /* Copy input file to output file */
        if (amdlibCopyRawDataFile(inputFile, 
                                  outputFile, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not copy '%s' raw data file to '%s' file", 
                           inputFile, outputFile);
            amdlibLogErrorDetail(errMsg);
            amdlibReleaseRawData(&rawData);
            amdlibReleaseDarkData(&dark);
            return amdlibFAILURE;
        }        

        /* Store calibrated data in specified output file or in original file */
        if (amdlibStoreRawData(outputFile, &rawData, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not store raw data in file '%s'", outputFile);
            amdlibLogError("(hint: doe not use a compressed output file).");

            amdlibLogErrorDetail(errMsg);
            amdlibReleaseRawData(&rawData);
            amdlibReleaseDarkData(&dark);
            return amdlibFAILURE;
        }
    }
    else
    {
        if (amdlibStoreRawData(inputFile, &rawData, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not store raw data in file '%s'", inputFile);
            amdlibLogErrorDetail(errMsg);
            amdlibReleaseRawData(&rawData);
            amdlibReleaseDarkData(&dark);
            return amdlibFAILURE;
        }
    }

    amdlibReleaseRawData(&rawData);
    amdlibReleaseDarkData(&dark);
    return amdlibSUCCESS;
}

/**
 * Convert AMBER data file into FITS file.
 *
 * This function creates FITS files from data files produced by the AMBER
 * instrument which are stored as binary tables. This consists to load the
 * original data file, to create glued images from data of detector regions and
 * save them into FITS file.
 *
 * @param inputFile name of file containing AMBER data
 * @param outputFile name of file where 2D images will be stored 
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibBtbl2Fits(const char *inputFile,
                                 const char *outputFile)
{
    amdlibERROR_MSG  errMsg;

    amdlibLogTrace("amdlibBtbl2Fits()");

    /* Load input file */
    if ((inputFile == NULL) || (strlen(inputFile) == 0))
    {
        amdlibLogError("Invalid name for input file");
        amdlibReleaseRawData(&rawData);
        return amdlibFAILURE;
    }
    if (amdlibLoadRawData(inputFile, &rawData, errMsg) != amdlibSUCCESS)
    {
        amdlibLogError("Could not load raw data from file '%s'", inputFile);
        amdlibLogErrorDetail(errMsg);
        amdlibReleaseRawData(&rawData);
        return amdlibFAILURE;
    }       

    /* Store image into FITS file */
    if ((outputFile == NULL) || (strlen(outputFile) == 0))
    {
        amdlibLogError("Invalid name for output file");
        amdlibReleaseRawData(&rawData);
        return amdlibFAILURE;
    }
    if (amdlibSaveRawDataToFits(outputFile, &rawData, errMsg) != amdlibSUCCESS)
    {
        amdlibLogError("Could not save raw data into FITS file '%s'", 
                       outputFile);
        amdlibLogErrorDetail(errMsg);
        amdlibReleaseRawData(&rawData);
        return amdlibFAILURE;
    }
    amdlibReleaseRawData(&rawData);
    return amdlibSUCCESS;
}

/**
 * Compute the P2VM (Photometry to Visibility Matrix) for the 2 telescope
 * configuration.
 *
 * This function computes the P2VM (Photometry to Visibility Matrix) for the 2
 * telescope configuration by processing the input data files. The resulting
 * P2VM is 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 estimation
 * @param inputFile1 name of the 1st file containing AMBER data
 * @param inputFile2 name of the 2nd file containing AMBER data
 * @param inputFile3 name of the 3rd file containing AMBER data
 * @param inputFile4 name of the 4th file containing AMBER data
 * @param p2vmFile name of file where P2VM will be stored 
 * @param newSpectralOffsets offset to be applied on photometric channels 
 * @param verbose indicates if information have to be displayed. 
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibComputeP2vm2T(const char *badPixelFile,
                                     const char *flatFieldFile,
                                     const char *darkFile,
                                     const char *inputFile1,
                                     const char *inputFile2,
                                     const char *inputFile3,
                                     const char *inputFile4,
                                     const char *p2vmFile,
                                     amdlibDOUBLE      newSpectralOffsets[],
                                     const amdlibBOOLEAN verbose)
{
    amdlibBOOLEAN         newSpecOffset;
    const char            *inputFiles[5];
    int                   i;
    amdlibERROR_MSG       errMsg;
    amdlibP2VM_INPUT_DATA p2vmData = {NULL};
    amdlibP2VM_MATRIX     p2vm = {NULL};
    amdlibWAVEDATA        waveData;
    amdlibBOOLEAN         waveDataLoaded;

    amdlibLogTrace("amdlibComputeP2vm2T()");
    
    /* Init list of input files */
    inputFiles[0] = darkFile;
    inputFiles[1] = inputFile1;
    inputFiles[2] = inputFile2;
    inputFiles[3] = inputFile3;
    inputFiles[4] = inputFile4;

    /* 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;
        }
    }

    /* Check P2VM file name */
    if ((p2vmFile == NULL) || (strlen(p2vmFile) == 0))
    {
        amdlibLogError("Invalid name for P2VM file");
        return amdlibFAILURE;
    }

    /* For each input files */
    waveDataLoaded = amdlibFALSE;
    newSpecOffset = amdlibFALSE;
    amdlibLogInfo("Loading input files ...");
    for (i = 0; i < 5; 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;
            }

            /* Test if some pixels are saturated */
            if (rawData.dataIsSaturated)
            {
                amdlibLogWarning("Saturation present in Data of file %s.", 
                               inputFiles[i]);
            }

            /* Get wave data from the first given file */
            if (waveDataLoaded == amdlibFALSE)
            {
                int p;

                if (amdlibGetWaveDataFromRawData(&rawData, &waveData, 
                                                 errMsg) == amdlibFAILURE)
                {
                    amdlibLogError("Could not get wave data from raw data");
                    amdlibLogErrorDetail(errMsg);
                    amdlibReleaseDarkData(&dark);
                    amdlibReleaseRawData(&rawData);
                    amdlibReleaseP2vmData(&p2vmData);
                    return amdlibFAILURE;
                }

                /* And set new offsets (if given) */
                for (p = 0; p < 3; p++)
                {
                    if (newSpectralOffsets[p] != amdlibOFFSETY_NOT_CALIBRATED)
                    {
                        newSpecOffset = amdlibTRUE;
                        waveData.photoOffset[p] = newSpectralOffsets[p];
                    }
                }

                waveDataLoaded = amdlibTRUE;
            }

            /* Store calibrated data into P2VM data structure */ 
            if (amdlibAddToP2vmData(&rawData, &waveData, &p2vmData, 
                                    errMsg) != amdlibSUCCESS)
            {
                amdlibLogError("Could not add data into P2VM data structure");
                amdlibLogErrorDetail(errMsg);
                amdlibReleaseDarkData(&dark);
                amdlibReleaseRawData(&rawData);
                amdlibReleaseP2vmData(&p2vmData);
                return amdlibFAILURE;
            }
        }

        amdlibReleaseRawData(&rawData);
    }
    /* End for */

    amdlibReleaseDarkData(&dark);

    /* Compute P2VM */
    amdlibLogInfo("Computing P2VM ...");
    if (amdlibComputeP2VM(&p2vmData, amdlibP2VM_2T, 
                          &waveData, &p2vm, errMsg) == amdlibFAILURE)
    {
        amdlibLogError("Could not compute P2VM");
        amdlibLogErrorDetail(errMsg);
        amdlibReleaseP2vmData(&p2vmData);
        amdlibReleaseP2VM(&p2vm);
        return amdlibFAILURE;
    }

    if (verbose == amdlibTRUE)
    {
        amdlibDisplayP2vm(&p2vm);
    }

    amdlibReleaseP2vmData(&p2vmData);

    amdlibLogInfo("Saving P2VM file ...");
    amdlibLogInfoDetail(p2vmFile);
    if (amdlibSaveP2VM(p2vmFile, &p2vm, 
                       amdlibP2VM_STD_ACC,
                       errMsg) == amdlibFAILURE)
    {
        amdlibLogError("Could not save P2VM");
        amdlibLogErrorDetail(errMsg);
        amdlibReleaseP2VM(&p2vm);
        return amdlibFAILURE;
    }

    amdlibReleaseP2VM(&p2vm);

#ifndef ESO_CPL_PIPELINE_VARIANT
    if (amdlibAppendKeywordListToP2VM(p2vmFile, badPixelFile, flatFieldFile,
                                     inputFiles, 5, newSpectralOffsets,
                                     newSpecOffset, errMsg) != amdlibSUCCESS)
    {
        amdlibLogError("Could not add PRO keywords - %s", errMsg);
        return amdlibFAILURE;
    }
#endif

    return amdlibSUCCESS;
}

/**
 * Compute the P2VM (Photometry to Visibility Matrix) for the 3 telescope
 * configuration.
 *
 * Ditto amdlibComputeP2vm2T for a 3 telescope 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 the 1st file containing AMBER data
 * @param inputFile2 name of the 2nd file containing AMBER data
 * @param inputFile3 name of the 3rd file containing AMBER data
 * @param inputFile4 name of the 4th file containing AMBER data
 * @param inputFile5 name of the 5th file containing AMBER data
 * @param inputFile6 name of the 6th file containing AMBER data
 * @param inputFile7 name of the 7th file containing AMBER data
 * @param inputFile8 name of the 8th file containing AMBER data
 * @param inputFile9 name of the 9th file containing AMBER data
 * @param p2vmFile name of file where P2VM will be stored 
 * @param newSpectralOffsets offset to be applied on photometric channels 
 * @param verbose indicates if information have to be displayed. 
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibComputeP2vm3T(const char *badPixelFile,
                                     const char *flatFieldFile,
                                     const char *darkFile,
                                     const char *inputFile1,
                                     const char *inputFile2,
                                     const char *inputFile3,
                                     const char *inputFile4,
                                     const char *inputFile5,
                                     const char *inputFile6,
                                     const char *inputFile7,
                                     const char *inputFile8,
                                     const char *inputFile9,
                                     const char *p2vmFile,
                                     amdlibDOUBLE *newSpectralOffsets,
                                     const amdlibBOOLEAN verbose)
{
    amdlibBOOLEAN         newSpecOffset;
    const char            *inputFiles[10];
    int                   i;
    amdlibERROR_MSG       errMsg;
    amdlibP2VM_INPUT_DATA p2vmData = {NULL};
    amdlibP2VM_MATRIX     p2vm = {NULL};
    amdlibWAVEDATA        waveData;
    amdlibBOOLEAN         waveDataLoaded;

    amdlibLogTrace("amdlibComputeP2vm3T()");
    
    /* Init list of input files */
    inputFiles[0] = darkFile;
    inputFiles[1] = inputFile1;
    inputFiles[2] = inputFile2;
    inputFiles[3] = inputFile3;
    inputFiles[4] = inputFile4;
    inputFiles[5] = inputFile5;
    inputFiles[6] = inputFile6;
    inputFiles[7] = inputFile7;
    inputFiles[8] = inputFile8;
    inputFiles[9] = inputFile9;

    /* 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;
        }
    }

    /* Check P2VM file name */
    if ((p2vmFile == NULL) || (strlen(p2vmFile) == 0))
    {
        amdlibLogError("Invalid name for P2VM file");
        return amdlibFAILURE;
    }

    /* For each input files */
    waveDataLoaded = amdlibFALSE;
    newSpecOffset = amdlibFALSE;
    amdlibLogInfo("Loading input files ...");
    for (i = 0; i < 10; 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
        {
            int p;

            /* Equalize raw data */
            if (amdlibCalibrateRawData(&dark,
                                       &rawData, errMsg) != amdlibSUCCESS)
            {
                amdlibLogError("Could not calibrate raw data");
                amdlibLogErrorDetail(errMsg);
                amdlibReleaseDarkData(&dark);
                amdlibReleaseRawData(&rawData);
                return amdlibFAILURE;
            }
           
            /* Test if some pixels are saturated */
            if (rawData.dataIsSaturated)
            {
                amdlibLogWarning("Saturation present in Data of file %s.", 
                               inputFiles[i]);
            }

            /* Get wave data from the first given file */
            if (waveDataLoaded == amdlibFALSE)
            {
                if (amdlibGetWaveDataFromRawData(&rawData, &waveData, 
                                                 errMsg) == amdlibFAILURE)
                {
                    amdlibLogError("Could not get wave data");
                    amdlibLogErrorDetail(errMsg);
                    amdlibReleaseDarkData(&dark);
                    amdlibReleaseRawData(&rawData);
                    amdlibReleaseP2vmData(&p2vmData);
                    return amdlibFAILURE;
                }

                /* And set new offsets (if given) */
                for (p = 0; p < 3; p++)
                {
                    if (newSpectralOffsets[p] != amdlibOFFSETY_NOT_CALIBRATED)
                    {
                        newSpecOffset = amdlibTRUE;
                        waveData.photoOffset[p] = newSpectralOffsets[p];
                    }
                }
                waveDataLoaded = amdlibTRUE;
            }
              
            /* Store calibrated data into P2VM data structure */ 
            if (amdlibAddToP2vmData(&rawData, &waveData, &p2vmData,
                                    errMsg) != amdlibSUCCESS)
            {
                amdlibLogError("Could not add data into P2VM data structure");
                amdlibLogErrorDetail(errMsg);
                amdlibReleaseDarkData(&dark);
                amdlibReleaseRawData(&rawData);
                amdlibReleaseP2vmData(&p2vmData);
                return amdlibFAILURE;
            }

        }
        amdlibReleaseRawData(&rawData);
    }
    /* End for */

    amdlibReleaseDarkData(&dark);

    /* Compute P2VM */
    amdlibLogInfo("Computing P2VM ...");
    if (amdlibComputeP2VM(&p2vmData, amdlibP2VM_3T, &waveData, &p2vm, errMsg) 
        == amdlibFAILURE)
    {
        amdlibLogError("Could not compute P2VM");
        amdlibLogErrorDetail(errMsg);
        amdlibReleaseP2vmData(&p2vmData);
        amdlibReleaseP2VM(&p2vm);
        return amdlibFAILURE;
    }
    amdlibReleaseP2vmData(&p2vmData);

    if (verbose == amdlibTRUE)
    {
        amdlibDisplayP2vm(&p2vm);
    }
    
    amdlibLogInfo("Saving P2VM file ...");
    amdlibLogInfoDetail(p2vmFile);
    if (amdlibSaveP2VM(p2vmFile, &p2vm, 
                       amdlibP2VM_STD_ACC,
                       errMsg) == amdlibFAILURE)
    {
        amdlibLogError("Could not save P2VM file");
        amdlibLogErrorDetail(errMsg);
        amdlibReleaseP2VM(&p2vm);
        return amdlibFAILURE;
    }
    amdlibReleaseP2VM(&p2vm);

#ifndef ESO_CPL_PIPELINE_VARIANT
    if (amdlibAppendKeywordListToP2VM(p2vmFile, badPixelFile, flatFieldFile,
                                     inputFiles, 10, newSpectralOffsets,
                                     newSpecOffset, errMsg) != amdlibSUCCESS)
    {
        amdlibLogError("Could not add PRO keywords");
        amdlibLogErrorDetail(errMsg);
        return amdlibFAILURE;
    }
#endif


    return amdlibSUCCESS;
}
/**
 * Compute visibilities.
 * 
 * This function extracts the visibilities and piston from the input file, using
 * the given p2vm, and save result into the output file. If the dark/sky file 
 * file is not specified (i.e set to ""), it is simply ignored. In the same
 * way, if file name for output is "", it is not created.
 *
 * @param badPixelFile name of file containing bad pixel map
 * @param flatFieldFile name of file containing flat-field map
 * @param p2vmFile name of file containing P2VM
 * @param darkFile name of file containing data for dark estimation (dark or sky)
 * @param inputFile name of the file containing AMBER data
 * @param outputFile name of the resulting file
 * @param nbBinning number of binnings 
 * @param errorType indicates wether the noise figures are estimated 
 * statistically from the sequence of photometries during the bin 
 * (amdlibSTATISTICAL_ERROR) or theoretically by the poisson error on N photons
 * (amdlibTHEORETICAL_ERROR). The latter is forced obviously when the binsize
 * is 1. 
 * @param pistonType indicates wether the piston is to be measured by fitting
 * a slope in the phasors amdlibITERATIVE_PHASOR or in the differential phases
 * after dewrapping (amdlibUNWRAPPED_PHASE). UNWRAPPED_PHASE is deprecated, 
 * and not used anymore. 
 * @param noCheckP2vmId forces amdlib to use without wuestion the passed p2vm,
 * even if its magic number is not OK. Can happen if the P2VM has been 
 * observed AFTER the science observations. 
 * @param mergeBandsInOutputFile indicates if many files will be produced (a single 
 * one if TRUE or as much as there are bands otherwise).
 * @param selectionType name of the chosen selection criteria 
 * (amdlibNO_FRAME_SEL if no selection).
 * @param selectionRatio ratio or threshold associated to the selection
 * criteria.
 * @param bands spectral bands to be treated; could be amdlibALL_BANDS to
 * compute visibilities for all bands 
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibComputeOiData(const char *badPixelFile,
                                     const char *flatFieldFile,
                                     const char *p2vmFile,
                                     const char *darkFile,
                                     const char *inputFile, 
                                     const char *outputFile,
                                     const int   nbBinning,
                                     const amdlibERROR_TYPE errorType,
                                     const amdlibPISTON_ALGORITHM pistonType,
                                     const amdlibBOOLEAN noCheckP2vmId,
                                     amdlibBOOLEAN mergeBandsInOutputFile,
                                     const amdlibFRAME_SELECTION selectionType,
                                     const double selectionRatio,
                                     const amdlibBAND bands)
{
    amdlibERROR_MSG       errMsg;
    amdlibSCIENCE_DATA    scienceData = {NULL};
    amdlibP2VM_MATRIX     p2vm = {NULL};
    amdlibPHOTOMETRY      photometry = {NULL}, imdPhot = {NULL};
    amdlibWAVELENGTH      wave = {NULL}, imdWave = {NULL};
    amdlibPISTON          opd = {NULL}, imdOpd = {NULL};
    amdlibOI_TARGET       target = {NULL};
    amdlibOI_ARRAY        array = {NULL};
    amdlibVIS             vis = {NULL}, imdVis = {NULL};
    amdlibVIS2            vis2 = {NULL}, imdVis2 = {NULL};
    amdlibVIS3            vis3 = {NULL}, imdVis3 = {NULL};
    amdlibSPECTRUM        spectrum = {NULL};
    amdlibSCIENCE_DATA    *sciencePtr;
    amdlibWAVEDATA        waveData;
    amdlibCPT_VIS_OPTIONS visOptions = {nbBinning, errorType, pistonType, 
        noCheckP2vmId, selectionType, selectionRatio};
    char localfile[512];
    int firstBand=amdlibJ_BAND, lastBand=amdlibK_BAND;
    int band;

    amdlibLogTrace("amdlibComputeOiData()");

    /* Load bad pixel map */
    amdlibLogInfo("Loading bad pixel map ...");
    amdlibLogInfoDetail(badPixelFile);
    if (amdlibLoadBadPixelMap(badPixelFile, errMsg) != amdlibSUCCESS)
    {
        amdlibLogError("Could not load bad pixel map '%s'", badPixelFile);
        amdlibLogErrorDetail(errMsg);
        return amdlibFAILURE;
    }

    /* Load flat field map */
    amdlibLogInfo("Loading flat-field file ...");
    amdlibLogInfoDetail(flatFieldFile);
    if (amdlibLoadFlatFieldMap(flatFieldFile, errMsg)!=amdlibSUCCESS)
    {
        amdlibLogError("Could not load flat-field map '%s'", flatFieldFile);
        amdlibLogErrorDetail(errMsg);
        return amdlibFAILURE;
    }

    /* Load P2VM */
    amdlibLogInfo("Loading P2VM file ...");
    amdlibLogInfoDetail(p2vmFile);
    if (amdlibLoadP2VM(p2vmFile,  &p2vm, errMsg) == amdlibFAILURE)
    {
        amdlibLogError("Could not load P2VM file '%s'", p2vmFile);
        amdlibLogErrorDetail(errMsg);
        return amdlibFAILURE; 
    }

    /* Retrieve wavedata */
    if (amdlibGetWaveDataFromP2vm(&p2vm, &waveData, errMsg) != amdlibSUCCESS)
    { 
        amdlibLogError("Could not get wave data");
        amdlibLogErrorDetail(errMsg);
        return amdlibFAILURE; 
    }

    /* Load dark file */
    if (strlen(darkFile) != 0)
    {
        amdlibLogInfo("Loading sky (or dark) file ...");
        amdlibLogInfoDetail(darkFile);
        if (amdlibLoadRawData(darkFile, &rawData, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not load raw data from dark file '%s'", 
                           darkFile);
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE; 
        }
        /* Compute dark map */
        if (amdlibGenerateDarkData(&rawData, &dark,
                                        errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not generate dark map");
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE; 
        }
        amdlibReleaseRawData(&rawData);
    }
    else
    {
        amdlibLogWarning("No dark used");
        
        /* Load data file so that the false dark mimics its structure */
        if (amdlibLoadRawData(inputFile, &rawData, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not load raw data from file '%s'", inputFile);
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE; 
        }
        amdlibSetDarkData( &rawData,  &dark, 0.0, amdlibTYPICAL_RON , errMsg);
        amdlibReleaseRawData(&rawData);
    }
    
    /* Load data file */
    amdlibLogInfo("Loading data file ...");
    amdlibLogInfoDetail(inputFile);
    if (amdlibLoadRawData(inputFile, &rawData, errMsg) != amdlibSUCCESS)
    {
        amdlibLogError("Could not load raw data from file '%s'", inputFile);
        amdlibLogErrorDetail(errMsg);
        return amdlibFAILURE; 
    }
    
    /* Equalize raw data */
    if (amdlibCalibrateRawData(&dark, &rawData, errMsg) !=amdlibSUCCESS)
    {
        amdlibLogError("Could not calibrate raw data");
        amdlibLogErrorDetail(errMsg);
        return amdlibFAILURE; 
    }

    /* Retrieve array information from raw data*/
    if (amdlibGetOiArrayFromRawData(&rawData, &array, errMsg) != amdlibSUCCESS)
    {
        amdlibLogWarning("Unable to retrieve OI_ARRAY information from data "
                         "file");
        amdlibReleaseOiArray(&array);
    }

    /* retrieve target information */
    if (amdlibAllocateOiTarget(&target, 1) != amdlibSUCCESS)
    {
        amdlibLogError("Could not allocate target structure");
        return amdlibFAILURE; 
    }
    if (amdlibGetOiTargetFromRawData(&rawData,&target) != amdlibSUCCESS)
    {
        amdlibLogError("Could not retrieve target information from data file");
        return amdlibFAILURE;
    }

    /* Produce science data */
    if (amdlibRawData2ScienceData(&rawData, &waveData, &scienceData, 
                                  amdlibFALSE,
                                  errMsg) != amdlibSUCCESS)
    { 
        amdlibLogError("Could not convert raw data to science data");
        amdlibLogErrorDetail(errMsg);
        return amdlibFAILURE; 
    }

    sciencePtr = &scienceData;
    amdlibReleaseRawData(&rawData);
    amdlibReleaseDarkData(&dark);
    
    /* Check band(s) to be processed */
    if (bands != amdlibALL_BANDS)
    {
        firstBand = bands;
        lastBand = bands;
        mergeBandsInOutputFile = amdlibFALSE;
    }
        
    for (band = firstBand; band <= lastBand; band++)
    {
        int nbChannels;
        if (amdlibIsBandPresentInData(sciencePtr, &p2vm, &waveData, band))
        {
            amdlibLogInfoDetail("Processing band %c...",amdlibBandNumToStr(band));
        }
        /* Compute visibilities */
        nbChannels = amdlibComputeVisibilities
            (/* Input */
             sciencePtr, &p2vm, &waveData, band, &visOptions,
             /* Output */
             &imdPhot, &imdVis, &imdVis2, &imdVis3, &imdWave, &imdOpd,
             errMsg);
        if (nbChannels == -1)
        {
            amdlibLogError("Could not compute visibilities for '%c' band", 
                           amdlibBandNumToStr(band));
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE; 
        }
        else if (nbChannels > 0) 
        {
            if (mergeBandsInOutputFile == amdlibTRUE)
            {
                if (amdlibMergeOiStructures(&wave, &imdWave,
                                            &photometry, &imdPhot,
                                            &vis, &imdVis,
                                            &vis2, &imdVis2,
                                            &vis3, &imdVis3,
                                            &opd, &imdOpd,
                                            errMsg) != amdlibSUCCESS)
                {
                    amdlibLogError("Could not merge OI structures");
                    amdlibLogErrorDetail(errMsg);
                    return amdlibFAILURE;
                }
            }
            else
            {
                /* Save OI file */
                if (strlen(outputFile) != 0)
                {
                    if (amdlibGetUserPref(amdlibNORMALIZE_SPECTRUM).set==amdlibTRUE)
                    {
                        /* Get normalized spectrum */
                        if (amdlibGetAndNormalizeSpectrumFromScienceData
                            (sciencePtr, &p2vm, &waveData, &imdWave,
                             &spectrum, errMsg) != amdlibSUCCESS)
                        { 
                            amdlibLogError("Could not get normalized spectrum from science "
                                           "data");
                            amdlibLogErrorDetail(errMsg);
                            return amdlibFAILURE; 
                        }
                    }
                    else
                    {
                        if (amdlibGetSpectrumFromScienceData
                            (sciencePtr, &waveData, &imdWave,
                             &spectrum, errMsg) != amdlibSUCCESS)
                        { 
                            amdlibLogError("Could not get spectrum from science "
                                           "data");
                            amdlibLogErrorDetail(errMsg);
                            return amdlibFAILURE; 
                        }
                    }

                    /* Prepare OI file name */
                    strcpy(localfile, outputFile);
                    char *p;
                    strcpy(localfile, outputFile);
                    p = strstr(localfile, ".fits");
                    if (p != NULL)
                    {
                        sprintf(p, "_%c.%s", amdlibBandNumToStr(band), "fits");
                    }
                    else
                    {
                        sprintf(localfile, "%s_%c", outputFile, 
                                amdlibBandNumToStr(band));
                    }
                    amdlibLogInfo("Saving OI file ...");
                    amdlibLogInfoDetail(localfile);
                    if (amdlibSaveOiFile(localfile, &sciencePtr->insCfg,
                                         &array, &target,
                                         &imdPhot, &imdVis, &imdVis2, 
                                         &imdVis3, &imdWave, &imdOpd, &spectrum,
                                         errMsg) != amdlibSUCCESS)
                    { 
                        amdlibLogError("Could not save OI file '%s'", 
                                       localfile);
                        amdlibLogErrorDetail(errMsg);
                        return amdlibFAILURE; 
                    }
#ifndef ESO_CPL_PIPELINE_VARIANT
                    if (amdlibAppendKeywordListToOIFITS(
                                    localfile, badPixelFile, flatFieldFile,
                                    darkFile, inputFile, nbBinning,
                                    errorType, pistonType, noCheckP2vmId,
                                    mergeBandsInOutputFile, selectionType,
                                    selectionRatio, vis.nbFrames, 
                                    errMsg)!= amdlibSUCCESS)
                    {
                        amdlibLogError("Could not add PRO keywords to '%s'",
                                       localfile);
                        amdlibLogErrorDetail(errMsg);
                        return amdlibFAILURE;
                    }
#endif
                }

                /* Display for test */
                if (amdlibLogGetLevel() >= amdlibLOG_TRACE)
                {
                    printf("\nDisplay spectral dispersion structure ...\n");
                    amdlibDisplayWavelength(&imdWave);

                    printf("\nDisplay photometry structure ...\n");
                    amdlibDisplayPhotometry(&imdPhot);

                    printf("\nDisplay visibility structure ...\n");
                    amdlibDisplayVis(&imdVis);
                    
                    printf("\nDisplay squared visibility structure ...\n");
                    amdlibDisplayVis2(&imdVis2);
                    
                    printf("\nDisplay closure phases structure ...\n");
                    amdlibDisplayVis3(&imdVis3);
                    
                    printf("\nDisplay piston structure ...\n");
                    amdlibDisplayPiston(&imdOpd);
                }
            }
            
            amdlibReleasePhotometry(&imdPhot);
            amdlibReleaseVis(&imdVis);
            amdlibReleaseVis2(&imdVis2);
            amdlibReleaseVis3(&imdVis3);
            amdlibReleaseWavelength(&imdWave);
            amdlibReleasePiston(&imdOpd);
        }
    }
    
    if (mergeBandsInOutputFile == amdlibTRUE)
    {
        /* Save OI file */
        if (strlen(outputFile) != 0)
        {
            if (amdlibGetUserPref(amdlibNORMALIZE_SPECTRUM).set==amdlibTRUE)
            {
                if (amdlibGetAndNormalizeSpectrumFromScienceData
                    (sciencePtr, &p2vm, &waveData, &wave, 
                     &spectrum, errMsg) != amdlibSUCCESS)
                { 
                    amdlibLogError("Could not get spectrum from science data");
                    amdlibLogErrorDetail(errMsg);
                    return amdlibFAILURE; 
                }
            }
            else
            {
                if (amdlibGetSpectrumFromScienceData
                    (sciencePtr, &waveData, &wave, 
                     &spectrum, errMsg) != amdlibSUCCESS)
                { 
                    amdlibLogError("Could not get spectrum from science data");
                    amdlibLogErrorDetail(errMsg);
                    return amdlibFAILURE; 
                }
            }
            amdlibLogInfo ("Saving OI file...");
            amdlibLogInfoDetail (outputFile);

            if (amdlibSaveOiFile(outputFile,  &sciencePtr->insCfg,
                                 &array, &target,
                                 &photometry, &vis, &vis2, &vis3, &wave, 
                                 &opd, &spectrum, errMsg) != amdlibSUCCESS)
            { 
                amdlibLogError("Could not save OI file '%s'", outputFile);
                amdlibLogErrorDetail(errMsg);
                return amdlibFAILURE; 
            }
#ifndef ESO_CPL_PIPELINE_VARIANT
            if (amdlibAppendKeywordListToOIFITS(
                                      outputFile, badPixelFile, flatFieldFile,
                                      darkFile, inputFile, nbBinning,
                                      errorType, pistonType, noCheckP2vmId,
                                      mergeBandsInOutputFile, selectionType,
                                      selectionRatio, vis.nbFrames,
                                      errMsg)!= amdlibSUCCESS)
            {
                amdlibLogError("Could not add PRO keywords to '%s'",
                               outputFile);
                amdlibLogErrorDetail(errMsg);
                return amdlibFAILURE;
            }
#endif
        }

        /* Display for test */
        if (amdlibLogGetLevel() >= amdlibLOG_TRACE)
        {
            printf("\nDisplay spectral dispersion structure ...\n");
            amdlibDisplayWavelength(&wave);

            printf("\nDisplay photometry structure ...\n");
            amdlibDisplayPhotometry(&photometry);

            printf("\nDisplay visibility structure ...\n");
            amdlibDisplayVis(&vis);

            printf("\nDisplay squared visibility structure ...\n");
            amdlibDisplayVis2(&vis2);

            printf("\nDisplay closure phases structure ...\n");
            amdlibDisplayVis3(&vis3);

            printf("\nDisplay piston structure ...\n");
            amdlibDisplayPiston(&opd);
        }
    }

    amdlibReleaseScienceData(sciencePtr);
    amdlibReleaseP2VM(&p2vm);
    amdlibReleaseVis(&vis);
    amdlibReleaseVis2(&vis2);
    amdlibReleaseVis3(&vis3);
    amdlibReleaseOiArray(&array);
    amdlibReleaseWavelength(&wave);
    amdlibReleasePiston(&opd);
    amdlibReleasePhotometry(&photometry);
    amdlibReleaseOiTarget(&target);
    amdlibReleaseSpectrum(&spectrum);
    
    return amdlibSUCCESS;
}

/**
 * Perform a frame selection on input file.
 * 
 * This function loads the input file, performs a selection on its frames to
 * keep only good ones using the given criterion and ratio, and/or the
 * selection input file. Then it can, if specified,  
 *      - average all data according to good frames kept and then save result 
 *      into the 'outputFile' file.
 *      - save selection information into the 'outputSelFileName' file.
 *
 * @param inputFile name of the file containing AMBER data
 * @param inputSelFileName name of the input selection file
 * @param outputFile name of the resulting file
 * @param outputSelFileName name of the output selection file 
 * @param selectionType name of the chosen selection criteria 
 * (amdlibNO_FRAME_SEL if no selection).
 * @param selectionRatio rato or threshold associated to the selection criteria.
 * @param useSelFile indicates whether input selection file has to be used or
 * not 
 * @param saveSelFile indicates whether output selection file has to be saved or
 * not
 * @param averageData indicates whether OI data has to be averaged or not 
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibPerformSelection(const char *inputFile, 
                                    const char *inputSelFileName,
                                    const char *outputFile,
                                    const char *outputSelFileName,
                                    const amdlibFRAME_SELECTION selectionType,
                                    const double selectionRatio,
                                    const amdlibBOOLEAN useSelFile,
                                    const amdlibBOOLEAN saveSelFile,
                                    const amdlibBOOLEAN averageData)
{
    amdlibERROR_MSG  errMsg;
    static  amdlibPHOTOMETRY photometry = {NULL}, dstPhot = {NULL};
    static  amdlibPHOTOMETRY imdPhot[amdlibNB_BANDS];
    static  amdlibWAVELENGTH wave = {NULL}, dstWave = {NULL};
    static  amdlibWAVELENGTH imdWave[amdlibNB_BANDS];
    static  amdlibPISTON     opd = {NULL}, dstOpd = {NULL};
    static  amdlibPISTON     imdOpd[amdlibNB_BANDS];
    static  amdlibOI_TARGET  target = {NULL};
    static  amdlibOI_ARRAY   array = {NULL};
    static  amdlibVIS        vis = {NULL}, dstVis = {NULL};
    static  amdlibVIS        imdVis[amdlibNB_BANDS];
    static  amdlibVIS2       vis2 = {NULL}, dstVis2 = {NULL};
    static  amdlibVIS2       imdVis2[amdlibNB_BANDS];
    static  amdlibVIS3       vis3 = {NULL}, dstVis3 = {NULL};
    static  amdlibVIS3       imdVis3[amdlibNB_BANDS];
    static  amdlibINS_CFG    insCfg;
    static  amdlibINS_CFG    selKewList;
    static  amdlibSELECTION  selectedFrames;
    static  amdlibSPECTRUM   spectrum = {NULL};
    int band, base, i;
    int nbSelectedFrames[3];
    int totalNbSelectedFrames;
    int noVis3=0;

    amdlibLogTrace("amdlibPerformSelection()");

    /* Load input OI-FITS file */
    amdlibLogInfo("Loading OI-FITS file...");
    amdlibLogInfoDetail(inputFile);
    if (amdlibLoadOiFile(inputFile, &insCfg, &array, &target, &photometry, 
                         &vis, &vis2, &vis3, &wave, &opd, &spectrum, 
                         errMsg) != amdlibSUCCESS)
    {
        amdlibLogError("Could not load OI-FITS file '%s'", inputFile);
        amdlibLogErrorDetail(errMsg);
        return amdlibFAILURE;
    }

    if (useSelFile == amdlibTRUE)
    {
        if (amdlibReadSelectionFile(inputSelFileName, &selKewList,
                                    &selectedFrames, errMsg) == amdlibFAILURE)
        {
            amdlibLogError("Could not read selection file or extract selection "
                           "information");
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE;
        }
    }
    else
    {
        if (amdlibAllocateSelection(&selectedFrames, vis.nbFrames, 
                                    vis.nbBases, errMsg) == amdlibFAILURE)
        {
            amdlibLogError("Could not allocate frame selection structure");
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE;
        }
    }

    if (amdlibSplitOiStructures(&wave, imdWave, &photometry, imdPhot,
                                &vis, imdVis, &vis2, imdVis2, &vis3, imdVis3,
                                &opd, imdOpd, errMsg) != amdlibSUCCESS)
    {
        amdlibLogError("Could not split OI data into structures");        
        amdlibLogErrorDetail(errMsg);
        return amdlibFAILURE;
    }
    
    totalNbSelectedFrames = 0;
    for (band = amdlibJ_BAND; band <= amdlibK_BAND; band++)
    {
        nbSelectedFrames[band] = 0;       
        
        /* If required, perform frame selection */
        if (imdOpd[band].thisPtr == NULL)
        {
            amdlibLogInfo("No channel for band %c: no selection for that "
			  "band", amdlibBandNumToStr(band));
            if (saveSelFile == amdlibTRUE)
            {
                /* Indicate all frames are not selected for that band */
                amdlibSetSelection(&selectedFrames, band, amdlibFALSE);
            }
        }
        else
        {
            /* Select 'good' frames depending on selection criterion. If no
             * criterion is specified, this function does nothing. */
            if (amdlibSelectFrames(&vis, &photometry, &opd, selectionType, 
                                   selectionRatio, &selectedFrames, 
                                   band, errMsg) != amdlibSUCCESS)
            {
                amdlibLogError("Could not select good frames");
                amdlibLogErrorDetail(errMsg);
                return amdlibFAILURE;
            }

            /* Determine if there are frames selected in at least one 
             * baseline */
            for (base = 0; base < vis.nbBases; base++)
            {
                nbSelectedFrames[band] += 
                    selectedFrames.band[band].nbSelectedFrames[base];
            }
            if (nbSelectedFrames[band] != 0)
            {
                if (averageData == amdlibTRUE)
                {
                    /* Average visibilities, photometries and pistons on good 
                     * frames */
                    if (amdlibAverageVisibilities(&imdPhot[band], &imdVis[band],
                                                  &imdVis2[band], 
                                                  &imdVis3[band], 
                                                  &imdOpd[band], band, 
                                                  &imdWave[band], 
                                                  &selectedFrames, 
                                                  errMsg) != amdlibSUCCESS)
                    {
                        amdlibLogError("Could not average data after frame "
                                       "selection for band %c", 
                                       amdlibBandNumToStr(band));
                        amdlibLogErrorDetail(errMsg);
                        return amdlibFAILURE;
                    }
                }
                for (base = 0; base < vis.nbBases; base++)
                {
                     
                    if (selectedFrames.band[band].nbSelectedFrames[base] == 0)
                    {
                        /* No frames are selected for that base, so flag them */
                        for (i=0; i < imdVis[band].nbFrames; i++)
                        {
                            memset(imdVis[band].table[i*vis.nbBases+base].flag,
                                   amdlibTRUE, 
                                   imdVis[band].nbWlen * sizeof(amdlibBOOLEAN));
                            memset(imdVis2[band].table[i*vis.nbBases+base].flag,
                                   amdlibTRUE, 
                                   imdVis2[band].nbWlen *sizeof(amdlibBOOLEAN));
                        }
                    }
                    else /* take note that there is a base with some unflagged values */
                    {
                        noVis3 += 1;
                    }

                }
                /* flag vis3 if all bases are null (perhaps already done elsehere!)*/
                if ((&(imdVis3[band]) != NULL) && (noVis3 == 0))
                {
                    for (i=0; 
                         i < imdVis3[band].nbClosures * imdVis3[band].nbFrames;
                         i++)
                    {
                        memset(imdVis3[band].table[i].flag, amdlibTRUE, 
                               imdVis3[band].nbWlen * sizeof(amdlibBOOLEAN));
                    }
                }

                if (amdlibMergeOiStructures(&dstWave, &imdWave[band],
                                            &dstPhot, &imdPhot[band],
                                            &dstVis, &imdVis[band],
                                            &dstVis2, &imdVis2[band],
                                            &dstVis3, &imdVis3[band],
                                            &dstOpd, &imdOpd[band],
                                            errMsg) != amdlibSUCCESS)
                {
                    amdlibLogError("Could not merge OI structures");
                    amdlibLogErrorDetail(errMsg);
                    return amdlibFAILURE;
                }
            }
            else
            {
                amdlibLogWarning("No frames selected for band %c", 
                                 amdlibBandNumToStr(band));
            }
        }
        totalNbSelectedFrames += nbSelectedFrames[band];
    }

    if (totalNbSelectedFrames == 0)
    {
        amdlibLogError("No frames selected at all for given criteria");
        return amdlibFAILURE;
    }

    amdlibLogInfo("Number of selected frames :");
    for (band = amdlibJ_BAND; band <= amdlibK_BAND; band++)
    {
        amdlibVIS_TABLE_ENTRY **visTablePtr = NULL;
        int base;
        /* Wrap vis table */
        visTablePtr = 
            (amdlibVIS_TABLE_ENTRY **)amdlibWrap2DArray(vis.table,
                                            vis.nbBases, vis.nbFrames,
                                            sizeof(amdlibVIS_TABLE_ENTRY),
                                            errMsg);
        for (base = 0; base < vis.nbBases; base++)
        { 
            if (visTablePtr[0][base].bandFlag[band] == amdlibTRUE)
            {
                amdlibLogInfoDetail("Band %c, base %d = %d frames",
                                    amdlibBandNumToStr(band), base, 
                            selectedFrames.band[band].nbSelectedFrames[base]);
            }
        }
        if (visTablePtr[0][0].bandFlag[band] == amdlibTRUE)
        {
            if (selectedFrames.nbBases == 3)
            {
                amdlibLogInfoDetail("%d frames for phase closure",
                              selectedFrames.band[band].nbFramesOkForClosure);
            }
        }
        amdlibFree2DArrayWrapping((void **)visTablePtr);
    }

    if (saveSelFile == amdlibTRUE)
    {
        amdlibLogInfo("Saving selection file ...");
        amdlibLogInfoDetail(outputSelFileName);
        if (amdlibWriteSelectionFile(outputSelFileName,
                                     &selectedFrames, errMsg) == amdlibFAILURE)
        {
            amdlibLogError("Could not write selection file");
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE;
        }
        if (amdlibAppendSelectionKeywords(outputFile, outputSelFileName,
                                          inputFile, &selKewList, amdlibFALSE, 
                                          selectionType, selectionRatio, 
                                          errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not add PRO keywords");
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE;
        }
    }

    if (averageData == amdlibTRUE)
    {
        char    dprCatg[256];
        char    proCatg[256];
        if (amdlibGetInsCfgKeyword(&insCfg, "HIERARCH ESO DPR CATG", 
                                   dprCatg, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not get DPR CATG keywords");
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE;
        }
        amdlibStripQuotes(dprCatg);
        sprintf(proCatg, " '%s_%s' ", dprCatg, "AVERAGED");
        if (amdlibSetInsCfgKeyword(&insCfg, "HIERARCH ESO PRO CATG", 
                                   proCatg, "Category of product frames",
                                   errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not set PRO CATG keywords");
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE;
        }

        amdlibLogInfo("Saving OI-FITS file ...");
        amdlibLogInfoDetail(outputFile);
        
        if (amdlibSaveOiFile(outputFile, &insCfg, &array, &target, &dstPhot, 
                             &dstVis, &dstVis2, &dstVis3, &dstWave, &dstOpd, 
                             &spectrum, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not save OI-FITS file '%s'", outputFile);
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE;
        }
        
        if (amdlibAppendSelectionKeywords(outputFile, outputSelFileName,
                                          inputFile, &selKewList, amdlibTRUE, 
                                          selectionType,
                                          selectionRatio, 
                                          errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not add PRO keywords");
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE;
        }
    }

    /* Release all data structures */
    for (band=amdlibJ_BAND; band <= amdlibK_BAND; band++)
    {
        amdlibReleaseWavelength(&imdWave[band]);
        amdlibReleasePiston(&imdOpd[band]);
        amdlibReleaseVis(&imdVis[band]);
        amdlibReleaseVis2(&imdVis2[band]);
        amdlibReleaseVis3(&imdVis3[band]);
        amdlibReleasePhotometry(&imdPhot[band]);
    }
    amdlibReleaseSelection(&selectedFrames);
    amdlibReleasePhotometry(&photometry);
    amdlibReleasePhotometry(&dstPhot);
    amdlibReleaseWavelength(&wave);
    amdlibReleaseWavelength(&dstWave);
    amdlibReleasePiston(&opd);
    amdlibReleasePiston(&dstOpd);
    amdlibReleaseVis(&vis);
    amdlibReleaseVis(&dstVis);
    amdlibReleaseVis2(&vis2);
    amdlibReleaseVis2(&dstVis2);
    amdlibReleaseVis3(&vis3);
    amdlibReleaseVis3(&dstVis3);
    amdlibReleaseOiArray(&array);
    amdlibReleaseOiTarget(&target);
    amdlibReleaseSpectrum(&spectrum);

    return amdlibSUCCESS;
}

/**
 * Merge p2vms
 * 
 * Not yet implemented
 * 
 * @param nbFiles number of P2VM to merge
 * @param p2vmFile list of names of files containing P2VM 
 * @param outputFile name of file containing resulting P2VM
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibMergeP2vmFromFiles(const int  nbFiles,
                                          const char **p2vmFile,
                                          const char *outputFile)
{
    
    static amdlibP2VM_MATRIX p2vm1;
    static amdlibP2VM_MATRIX p2vm2;
    static amdlibP2VM_MATRIX p2vmRes;
    amdlibERROR_MSG          errMsg;
    int                      i;

    amdlibLogTrace("amdlibMergeP2vmFromFiles()");

    if (nbFiles < 2)
    {
        amdlibLogError("P2VM merge needs at least 2 input files");
        return amdlibFAILURE;
    }

    if (amdlibLoadP2VM(p2vmFile[0], &p2vmRes, errMsg) != amdlibSUCCESS)
    {
        amdlibLogError("Could not load P2VM file '%s'", p2vmFile[0]);
        amdlibLogErrorDetail(errMsg);
        return amdlibFAILURE;
    }
    for (i = 1; i < nbFiles; i++)
    {
        if (amdlibDuplicateP2VM(&p2vmRes, &p2vm1, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not duplicate P2VM");
            amdlibLogErrorDetail(errMsg);
            amdlibReleaseP2VM(&p2vm2);
            amdlibReleaseP2VM(&p2vmRes);
            return amdlibFAILURE;   
        }
        amdlibReleaseP2VM(&p2vmRes);
        if (amdlibLoadP2VM(p2vmFile[i], &p2vm2, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not load P2VM file '%s'", p2vmFile[i]);
            amdlibLogErrorDetail(errMsg);
            amdlibReleaseP2VM(&p2vm1);
            amdlibReleaseP2VM(&p2vmRes);
            return amdlibFAILURE;
        }
        if (amdlibMergeP2VM(&p2vm1, &p2vm2, &p2vmRes, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not merge P2VM");
            amdlibLogErrorDetail(errMsg);
            amdlibReleaseP2VM(&p2vm1);
            amdlibReleaseP2VM(&p2vm2);
            amdlibReleaseP2VM(&p2vmRes);
            return amdlibFAILURE;            
        }
    }
    
    if (amdlibSaveP2VM(outputFile, &p2vmRes, amdlibP2VM_STD_ACC,
                       errMsg) != amdlibSUCCESS)
    {
        amdlibLogError("Could not save P2VM");
        amdlibLogErrorDetail(errMsg);
        amdlibReleaseP2VM(&p2vm1);
        amdlibReleaseP2VM(&p2vm2);
        amdlibReleaseP2VM(&p2vmRes);
        return amdlibFAILURE;
    }

    amdlibReleaseP2VM(&p2vm1);
    amdlibReleaseP2VM(&p2vm2);
    amdlibReleaseP2VM(&p2vmRes);
    return amdlibSUCCESS;
}

/**
 * Append Oi-Fits Files into a single one
 *  
 * @param nbFiles number of OI-FITS files to append
 * @param oiFitsFile list of names of OI-FITS files to be append
 * @param outputFile name of resulting file
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibAppendOiFitsFiles(const int  nbFiles,
                                         const char **oiFitsFile,
                                         const char *outputFile)
{
    amdlibPHOTOMETRY srcPhot   = {NULL};
    amdlibPHOTOMETRY refPhot   = {NULL};
    amdlibPHOTOMETRY dstPhot   = {NULL};
    amdlibWAVELENGTH srcWave   = {NULL};
    amdlibWAVELENGTH refWave   = {NULL};
    amdlibPISTON     srcPst    = {NULL};
    amdlibPISTON     refPst    = {NULL};
    amdlibPISTON     dstPst    = {NULL};
    amdlibOI_TARGET  srcTarget = {NULL};
    amdlibOI_TARGET  refTarget = {NULL};
    amdlibOI_ARRAY   srcArray  = {NULL};
    amdlibOI_ARRAY   refArray  = {NULL};
    amdlibVIS        srcVis    = {NULL};
    amdlibVIS        refVis    = {NULL};
    amdlibVIS        dstVis    = {NULL};
    amdlibVIS2       srcVis2   = {NULL};
    amdlibVIS2       refVis2   = {NULL};
    amdlibVIS2       dstVis2   = {NULL};
    amdlibVIS3       srcVis3   = {NULL};
    amdlibVIS3       refVis3   = {NULL};
    amdlibVIS3       dstVis3   = {NULL};
    amdlibINS_CFG    insCfg;
    amdlibINS_CFG    srcCfg;
    amdlibSPECTRUM   srcSpectrum = {NULL};
    amdlibSPECTRUM   refSpectrum = {NULL};
    amdlibSPECTRUM   dstSpectrum = {NULL};
    amdlibERROR_MSG  errMsg;
    int  iFile=0;
    int  insertIndex=0;
    int  nbFrames = 0;
    int  nbBases = 0;
    int  nbWlen = 0;
    int  nbTels = 0;
    int  finalNbFrames = 0;
    char arrayName[16];
    int  hasAmberData = 0;
    int  hasSpectrum = 0;
    int  hasVis3 = 0;
    char keywLine[amdlibKEYW_NAME_LEN+1];
    char fullFileName[1024];
    char keywValue[amdlibKEYW_VAL_LEN+1];
    char *keyValBis;
   
    amdlibLogTrace("amdlibAppendOiFitsFiles()");

    if (nbFiles < 2)
    {
        amdlibLogError("OI-FITS file concatenation needs at least 2 input "
                       "files");
        return amdlibFAILURE;
    }

    /* Load first file */
    amdlibClearInsCfg(&insCfg);
    if (amdlibLoadOiFile(oiFitsFile[0], &insCfg, &refArray, &refTarget, 
                         &refPhot, &refVis, &refVis2, &refVis3, &refWave, 
                         &refPst, &refSpectrum, errMsg) != amdlibSUCCESS)
    {
        amdlibLogError("Could not load OI-FITS file '%s'", oiFitsFile[0]);
        amdlibLogErrorDetail(errMsg);
        return amdlibFAILURE;
    }
    /* Remove invalid keywords in Header*/
    amdlibRemoveInsCfgKeyword(&insCfg,"HIERARCH ESO PRO REC1");
    amdlibRemoveInsCfgKeyword(&insCfg,"HIERARCH ESO PRO DATANCOM");
    amdlibRemoveInsCfgKeyword(&insCfg,"HIERARCH ESO QC");
    amdlibRemoveInsCfgKeyword(&insCfg,"HIERARCH ESO OCS DRS");
    amdlibRemoveInsCfgKeyword(&insCfg,"DATASUM");
    amdlibRemoveInsCfgKeyword(&insCfg,"CHECKSUM");

    if (refPst.nbFrames != 0)
    {
        hasAmberData = 1;
    }

    nbFrames = finalNbFrames = refVis2.nbFrames;
    nbBases = refVis2.nbBases;
    nbWlen = refVis2.nbWlen;
    nbTels = refSpectrum.nbTels;
    strcpy(arrayName, refArray.arrayName);

    /* Loop on files and count the final size (or error!) */
    for (iFile = 1; iFile < nbFiles; iFile++)
    {
        /* Load file */
        if (amdlibLoadOiFile(oiFitsFile[iFile], NULL, &srcArray, &srcTarget, 
                             &srcPhot, &srcVis, &srcVis2, &srcVis3, &srcWave, 
                             &srcPst, &srcSpectrum, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not load OI-FITS file '%s'", oiFitsFile[iFile]);
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE;
        }
        /* Perform simple checks */
        if (strcmp(arrayName, srcArray.arrayName) != 0)
        {
            amdlibLogError("Different ARRNAME in file '%s' (%s) and in "
                           "previous file(s) (%s)", 
                           oiFitsFile[iFile], srcArray.arrayName, arrayName );
            return amdlibFAILURE;
        }
        if (nbBases != srcVis2.nbBases) 
        {
            amdlibLogError("Different number of bases (%d vs. %d)" 
                           "in file '%s' and in previous file(s)",
                           nbBases, srcVis2.nbBases, oiFitsFile[iFile]);
            return amdlibFAILURE;
        }
        if (nbWlen != srcVis2.nbWlen)
        {
            amdlibLogError("Different number of Wavelength (%d vs. %d)" 
                           "in file '%s' and in previous file(s)",
                           nbWlen, srcVis2.nbWlen, oiFitsFile[iFile]);
            return amdlibFAILURE;
        }
        
        /* Check amdlibWAVELENGTH structures are identical */
        if (amdlibCompareWavelengths(&srcWave, &refWave, errMsg) != amdlibTRUE)
        {
            amdlibLogError("Could not append files with different wavelength "
                           "data structures ('%s' and '%s')",
                           oiFitsFile[0], oiFitsFile[iFile]);
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE; 
        }

        if ((hasAmberData==0)&&(srcPst.nbFrames != 0))
        {
            amdlibLogError("Could not append files with different AmberData "
                           "structures ('%s' and '%s')",
                           oiFitsFile[0], oiFitsFile[iFile]);
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE; 
        }

        /* All OK: count these frames also */
        finalNbFrames+=srcVis2.nbFrames;       

        amdlibReleaseOiArray(&srcArray);
        amdlibReleaseOiTarget(&srcTarget);
        amdlibReleaseVis(&srcVis);
        amdlibReleaseVis2(&srcVis2);
        amdlibReleaseVis3(&srcVis3);
        amdlibReleaseWavelength(&srcWave);
        amdlibReleasePiston(&srcPst);
        amdlibReleasePhotometry(&srcPhot);
        amdlibReleaseSpectrum(&srcSpectrum);
    }
       
    sprintf(keywLine,"HIERARCH ESO DRS DATANCOM = %d / Nb of Combined Files",nbFiles);
    amdlibAddInsCfgKeyword(&insCfg,keywLine,errMsg);

   /* Allocate the dst structures. */
    if (nbTels>0)
    {
        if (amdlibAllocateSpectrum(&dstSpectrum,nbTels,nbWlen) != amdlibSUCCESS)
        {
            amdlibSetErrMsg("Could not allocate memory for spectrum");
            return amdlibFAILURE;
        }
        hasSpectrum=1;
    }
    else
    {
        amdlibLogWarning("Output File will not have Spectrum Table");
    }
    /* Allocate memory for phot */
    if (amdlibAllocatePhotometry(&dstPhot, finalNbFrames, nbBases, 
                                 nbWlen) != amdlibSUCCESS)
    {
        amdlibSetErrMsg("Could not allocate memory for photometry");
        return amdlibFAILURE;
    }
    /*  Allocate memory for vis */
    if (amdlibAllocateVis(&dstVis, finalNbFrames, nbBases, 
                          nbWlen) != amdlibSUCCESS)
    {
        amdlibSetErrMsg("Could not allocate memory for vis");
        return amdlibFAILURE;            
    }
    /*  Allocate memory for vis2 */
    if (amdlibAllocateVis2(&dstVis2, finalNbFrames, nbBases, 
                           nbWlen) != amdlibSUCCESS)
    {
        amdlibSetErrMsg("Could not allocate memory for vis2");
        return amdlibFAILURE;            
    }
    /*  Allocate memory for vis3 if necessary */
    if (nbBases > 1)
    { 
        if (amdlibAllocateVis3(&dstVis3, finalNbFrames, refVis3.nbClosures, 
                               nbWlen) != amdlibSUCCESS)
        {
            amdlibSetErrMsg("Could not allocate memory for vis3");
            return amdlibFAILURE;            
        }
        hasVis3 = 1;
    }
    if (hasAmberData == 1)
    {
        /*  Allocate memory for piston */
        if (amdlibAllocatePiston(&dstPst, finalNbFrames, 
                                 nbBases) != amdlibSUCCESS)
        {
            amdlibSetErrMsg("Could not allocate memory for opd");
            return amdlibFAILURE; 
        }
    }
    /* Loop on files and insert at end of final structure */
    for (iFile = 0, insertIndex=0 ; iFile < nbFiles; iFile++)
    {
        /* reload file */
       if (amdlibLoadOiFile(oiFitsFile[iFile], &srcCfg, &srcArray, &srcTarget, 
                             &srcPhot, &srcVis, &srcVis2, &srcVis3, &srcWave, 
                             &srcPst, &srcSpectrum, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not reload OI-FITS file '%s'", oiFitsFile[iFile]);
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE;
        }

        /* add spectrum to ref Spectrum*/
        if (srcSpectrum.nbTels != 0 && (hasSpectrum == 1))
        {
            if (amdlibAddSpectrum(&dstSpectrum, &srcSpectrum, errMsg)!= amdlibSUCCESS)
            {
                amdlibLogError("Could not add spectrum from file '%s'", oiFitsFile[iFile]);
                amdlibLogErrorDetail(errMsg);
                return amdlibFAILURE;
            }
        }
        /* Insert photometry (if any) */
        if ((srcPhot.nbFrames != 0) && (hasAmberData == 1))
        {
            if (amdlibInsertPhotometry(&dstPhot, 
                                       &srcPhot, insertIndex, errMsg) != amdlibSUCCESS)
            {
                amdlibLogError("Could not append photometry from file '%s'", 
                               oiFitsFile[iFile]);
                amdlibLogErrorDetail(errMsg);
                return amdlibFAILURE;
            }
        }

        /* Insert vis */
        if (amdlibInsertVis(&dstVis, &srcVis, insertIndex, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not insert vis from file '%s'",
                           oiFitsFile[iFile]);
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE;
        }
        /* Insert vis2 */
        if (amdlibInsertVis2(&dstVis2, &srcVis2, insertIndex, errMsg) != amdlibSUCCESS)
        {
            amdlibLogError("Could not insert vis2 from file '%s'",
                           oiFitsFile[iFile]);
            amdlibLogErrorDetail(errMsg);
            return amdlibFAILURE;
        }
        /* Insert vis3 */
        if (hasVis3 == 1)
        {
            if (amdlibInsertVis3(&dstVis3, &srcVis3, insertIndex, errMsg) != amdlibSUCCESS)
            {
                amdlibLogError("Could not insert vis3 from file '%s'",
                               oiFitsFile[iFile]);
                amdlibLogErrorDetail(errMsg);
                return amdlibFAILURE;
            }
        }
        /* Insert piston */
        if (hasAmberData == 1)
        {
            if (amdlibInsertPiston(&dstPst, &srcPst, insertIndex, errMsg) != amdlibSUCCESS)
            {
                amdlibLogError("Could not insert piston from file '%s'",
                               oiFitsFile[iFile]);
                amdlibLogErrorDetail(errMsg);
                return amdlibFAILURE;
            }
        }

        insertIndex += srcVis2.nbFrames;

        amdlibReleaseOiArray(&srcArray);
        amdlibReleaseOiTarget(&srcTarget);
        amdlibReleaseVis(&srcVis);
        amdlibReleaseVis2(&srcVis2);
        amdlibReleaseVis3(&srcVis3);
        amdlibReleaseWavelength(&srcWave);
        amdlibReleasePiston(&srcPst);
        amdlibReleasePhotometry(&srcPhot);
        amdlibReleaseSpectrum(&srcSpectrum);
 
        /*Add relevant Keyword(s)*/
        strncpy(fullFileName,oiFitsFile[iFile],1024);
        keyValBis = basename(fullFileName);
        sprintf(keywLine,"HIERARCH ESO DRS PROV%2.2d = \'%.50s\'",iFile,keyValBis);
        amdlibAddInsCfgKeyword(&insCfg,keywLine,errMsg); 
        /* If last file, update PBL* END with the ones of the last one */
        if (iFile==(nbFiles-1))
        {
            switch(nbBases)
            {
                case 3 :
                {
                    amdlibRemoveInsCfgKeyword(&insCfg,"HIERARCH ESO ISS PBLA13 END");
                    amdlibRemoveInsCfgKeyword(&insCfg,"HIERARCH ESO ISS PBLA23 END");
                    amdlibRemoveInsCfgKeyword(&insCfg,"HIERARCH ESO ISS PBL13 END");
                    amdlibRemoveInsCfgKeyword(&insCfg,"HIERARCH ESO ISS PBL23 END");
                }
                default :
                {
                    amdlibRemoveInsCfgKeyword(&insCfg,"HIERARCH ESO ISS PBLA12 END");
                    amdlibRemoveInsCfgKeyword(&insCfg,"HIERARCH ESO ISS PBL12 END");
                }
            }
            amdlibGetInsCfgKeyword(&srcCfg,"HIERARCH ESO ISS PBL12 END",keywValue,errMsg);
            sprintf(keywLine,"HIERARCH ESO ISS PBL12 END = %s / New End Value",keywValue);
            amdlibAddInsCfgKeyword(&insCfg,keywLine,errMsg);
            amdlibGetInsCfgKeyword(&srcCfg,"HIERARCH ESO ISS PBLA12 END",keywValue,errMsg);
            sprintf(keywLine,"HIERARCH ESO ISS PBLA12 END = %s / New End Value",keywValue);
            amdlibAddInsCfgKeyword(&insCfg,keywLine,errMsg);
            if (nbBases==3)
            {
                amdlibGetInsCfgKeyword(&srcCfg,"HIERARCH ESO ISS PBL23 END",keywValue,errMsg);
                sprintf(keywLine,"HIERARCH ESO ISS PBL23 END = %s / New End Value",keywValue);
                amdlibAddInsCfgKeyword(&insCfg,keywLine,errMsg);
                amdlibGetInsCfgKeyword(&srcCfg,"HIERARCH ESO ISS PBLA23 END",keywValue,errMsg);
                sprintf(keywLine,"HIERARCH ESO ISS PBLA23 END = %s / New End Value",keywValue);
                amdlibAddInsCfgKeyword(&insCfg,keywLine,errMsg);
                amdlibGetInsCfgKeyword(&srcCfg,"HIERARCH ESO ISS PBL13 END",keywValue,errMsg);
                sprintf(keywLine,"HIERARCH ESO ISS PBL13 END = %s / New End Value",keywValue);
                amdlibAddInsCfgKeyword(&insCfg,keywLine,errMsg);
                amdlibGetInsCfgKeyword(&srcCfg,"HIERARCH ESO ISS PBLA13 END",keywValue,errMsg);
                sprintf(keywLine,"HIERARCH ESO ISS PBLA13 END = %s / New End Value",keywValue);
                amdlibAddInsCfgKeyword(&insCfg,keywLine,errMsg);
            }
        }
        amdlibClearInsCfg(&srcCfg);
   }
    /* Save output file */
    if(amdlibSaveOiFile(outputFile, &insCfg, &refArray, &refTarget, 
                        &dstPhot, &dstVis, &dstVis2, 
                        (hasVis3==1)?&dstVis3:NULL, &refWave,
                        (hasAmberData==1)?&dstPst:NULL, 
                        (hasSpectrum==1)?&dstSpectrum:NULL, 
                        errMsg) != amdlibSUCCESS)
    {
        amdlibLogError("Could not save OI-FITS file '%s'", outputFile);
        amdlibLogErrorDetail(errMsg);
        return amdlibFAILURE;
    }

    amdlibReleaseVis(&dstVis);
    amdlibReleaseVis2(&dstVis2);
    amdlibReleaseVis3(&dstVis3);
    amdlibReleasePiston(&dstPst);
    amdlibReleasePhotometry(&dstPhot);
    amdlibReleaseSpectrum(&dstSpectrum);

    amdlibReleaseOiArray(&refArray);
    amdlibReleaseOiTarget(&refTarget);
    amdlibReleaseVis(&refVis);
    amdlibReleaseVis2(&refVis2);
    amdlibReleaseVis3(&refVis3);
    amdlibReleaseWavelength(&refWave);
    amdlibReleasePiston(&refPst);
    amdlibReleasePhotometry(&refPhot);
    amdlibReleaseSpectrum(&refSpectrum);

    return amdlibSUCCESS;
}
/*
 * Local functions
 */

#ifndef ESO_CPL_PIPELINE_VARIANT
/**
 * Add PRO keywords for P2VM 
 *
 * @param p2vmFile name of file containing P2VM
 * @param badPixelFile name of file containing bad pixel map
 * @param flatFieldFile name of file containing flat-field map
 * @param inputFiles names of the files containing AMBER data
 * @param nbInputFiles number of files containing AMBER data
 * @param newSpectralOffsets offset to be applied on photometric channels 
 * @param addOffsets true iif new spectral offsets are considered.
 * @param errMsg error description message returned if function fails
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibAppendKeywordListToP2VM(const char *p2vmFile,
                                               const char *badPixelFile,
                                               const char *flatFieldFile,
                                               const char **inputFiles,
                                               int nbInputFiles,
                                               amdlibDOUBLE newSpectralOffsets[],
                                               amdlibBOOLEAN addOffsets,
                                               amdlibERROR_MSG errMsg)
{
    char       keyName[36];
    char       keyVal[256], *keyValBis;
    char       fullFileName[1024];
    int        i;
    fitsfile   *filePtr;
    int        status = 0;
    char       fitsioMsg[256];
    int        nbTel;
    int        nbRaws;
    
    amdlibLogTrace("amdlibAppendKeywordListToFile()");
    
    if(fits_open_file(&filePtr, p2vmFile, READWRITE, &status))
    {
        amdlibReturnFitsError(p2vmFile);
    }

    if (fits_movabs_hdu(filePtr, 1, 0, &status) != 0)
    {
        amdlibGetFitsError("main header"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    if (fits_write_key(filePtr, TSTRING, "HIERARCH ESO PRO DID", "AMBER",
                       "Data dictionnary for PRO", &status) != 0)
    {
        amdlibGetFitsError("PRO DID"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    if (fits_write_key(filePtr, TSTRING, "HIERARCH ESO PRO CATG", 
                       "P2VM_REDUCED",
                       "Category of product frames", &status) != 0)
    {
        amdlibGetFitsError("PRO CATG"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    if (fits_write_key(filePtr, TSTRING, "HIERARCH ESO PRO TYPE", "REDUCED",
                       "Product type", &status) != 0)
    {
        amdlibGetFitsError("PRO TYPE"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    if (fits_write_key(filePtr, TSTRING, "HIERARCH ESO PRO REC1 ID", 
                       "amdlibComputeP2vm",
                       "Program identifier", &status) != 0)
    {
        amdlibGetFitsError("PRO REC1 ID"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
            
    /* Add information relative to bad pixel file */
    strcpy(keyVal, badPixelFile);
    strcpy(fullFileName, badPixelFile);
    keyValBis = basename(fullFileName);
    sprintf(keyVal, "%.40s", keyValBis);
    if (fits_write_key(filePtr, TSTRING, "HIERARCH ESO PRO REC1 RAW1 NAME", 
                       keyVal, "File name of raw frame", &status) != 0)
    {
        amdlibGetFitsError("PRO REC1 RAW1 NAME"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    if (fits_write_key(filePtr, TSTRING, "HIERARCH ESO PRO REC1 RAW1 CATG", 
                       "AMBER_BADPIX",
                       "Category of raw frame", &status) != 0)
    {
        amdlibGetFitsError("PRO REC1 RAW1 CATG"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    /* Add information relative to flat field file */
    strcpy(fullFileName, flatFieldFile);
    keyValBis = basename(fullFileName);
    sprintf(keyVal, "%.40s", keyValBis);
    if (fits_write_key(filePtr, TSTRING, "HIERARCH ESO PRO REC1 RAW2 NAME", 
                       keyVal, "File name of raw frame", &status) != 0)
    {
        amdlibGetFitsError("PRO REC1 RAW2 NAME"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    if (fits_write_key(filePtr, TSTRING, "HIERARCH ESO PRO REC1 RAW2 CATG", 
                       "AMBER_FLATFIELD",
                       "Category of raw frame", &status) != 0)
    {
        amdlibGetFitsError("PRO REC1 RAW2 CATG"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    if (nbInputFiles == 5)
    {
        nbTel = 2;
    }
    else if (nbInputFiles == 10)
    {
        nbTel = 3;
    }
    else
    {
        amdlibSetErrMsg("Invalid parameter nbInputFiles (%d) should be 5 or 10",
                        nbInputFiles);
        return amdlibFAILURE;
    }
    for (i = 0; i < nbInputFiles; i++)
    {
        /* Add information relative to input files name (PRO keywords) */
        sprintf(keyName, "HIERARCH ESO PRO REC1 RAW%d NAME", i+3);
        strcpy(fullFileName, inputFiles[i]);
        keyValBis = basename(fullFileName);
        sprintf(keyVal, "%.40s", keyValBis);
        if (fits_write_key(filePtr, TSTRING, keyName, keyVal,
                           "File name of raw frame", &status) != 0)
        {
            amdlibGetFitsError(keyName); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
        sprintf(keyName, "HIERARCH ESO PRO REC1 RAW%d CATG", i+3);
        if (fits_write_key(filePtr, TSTRING, keyName, "AMBER_2P2V",
                           "Category of raw frame", &status) != 0)
        {
            amdlibGetFitsError(keyName); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
    }
    
    nbRaws = i+2;
    if (fits_write_key(filePtr, TINT, "HIERARCH ESO PRO DATANCOM", &nbRaws,
                       "Number of combined frames", &status) != 0)
    {
        amdlibGetFitsError("PRO DATANCOM"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    
    if (addOffsets == amdlibTRUE)
    {
        if (fits_write_key(filePtr, TSTRING, 
                           "HIERARCH ESO PRO REC1 PARAM1 NAME", "-s",
                           "", &status) != 0)
        {
            amdlibGetFitsError("PRO REC1 PARAM1 NAME"); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
        if (nbTel == 2)
        {
            sprintf(keyVal, "%f,%f", newSpectralOffsets[0], 
                    newSpectralOffsets[1]);

        }
        else
        {
            sprintf(keyVal, "%f,%f,%f", newSpectralOffsets[0], 
                    newSpectralOffsets[1], newSpectralOffsets[2]);
        }
        if (fits_write_key(filePtr, TSTRING, 
                           "HIERARCH ESO PRO REC1 PARAM1 VALUE", keyVal,
                           "", &status) != 0)
        {
            amdlibGetFitsError("PRO REC1 PARAM1 VALUE"); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
    }

    /* Close FITS file */
    if (fits_close_file(filePtr, &status)) 
    {
        amdlibReturnFitsError(p2vmFile);
    }


    return amdlibSUCCESS;
}

/**
 * Add PRO keywords in insCfg structure for OI-FITS result file
 * 
 * @param oifitsFile name of the OI-FITS result file.
 * @param badPixelFile name of file containing bad pixel map
 * @param flatFieldFile name of file containing flat-field map
 * @param darkFile name of file containing dark (if any)
 * @param inputFile name of the input science file 
 * @param nbBinning number of binnings 
 * @param errorType indicates wether the noise figures are estimated 
 * statistically from the sequence of photometries during the bin 
 * (amdlibSTATISTICAL_ERROR) or theoretically by the poisson error on N photons
 * (amdlibTHEORETICAL_ERROR). The latter is forced obviously when the binsize
 * is 1. 
 * @param pistonType indicates wether the piston is to be measured by fitting
 * a slope in the phasors amdlibITERATIVE_PHASOR or in the differential phases
 * after dewrapping (amdlibUNWRAPPED_PHASE). 
 * @param noCheckP2vmId forces amdlib to use without wuestion the passed p2vm,
 * even if its magic number is not OK. Can happen if the P2VM has been 
 * observed AFTER the science observations. 
 * @param mergeBandsInOutputFile indicates if many files will be produced (a single 
 * one if TRUE or as much as there are bands otherwise).
 * @param selectionType name of the chosen selection criteria 
 * (amdlibNO_FRAME_SEL if no selection).
 * @param selectionRatio rato or threshold associated to the selection criteria.
 * @param errMsg error description message returned if function fails
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibAppendKeywordListToOIFITS(
                                const char *oifitsFile,
                                const char *badPixelFile,
                                const char *flatFieldFile,
                                const char *darkFile,
                                const char *inputFile, 
                                const int   nbBinning,
                                const amdlibERROR_TYPE errorType,
                                const amdlibPISTON_ALGORITHM pistonType,
                                const amdlibBOOLEAN noCheckP2vmId,
                                const amdlibBOOLEAN mergeBandsInOutputFile,
                                const amdlibFRAME_SELECTION selectionType,
                                const double selectionRatio,
                                const int nbRecords,
                                amdlibERROR_MSG errMsg)
{
    fitsfile *filePtr;
    int      status = 0;
    char     fitsioMsg[256];
    char     fullFileName[1024];
    char     keyName[36];
    char     keyVal[256], *keyValBis;
    char     comment[amdlibKEYW_CMT_LEN+1];
    int      index;
    char     dprCatg[256];
    char     proCatg[256];

    if(fits_open_file(&filePtr, oifitsFile, READWRITE, &status))
    {
        amdlibReturnFitsError(oifitsFile);
    }

    if (fits_movabs_hdu(filePtr, 1, 0, &status) != 0)
    {
        amdlibGetFitsError("main header"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    if (fits_write_key(filePtr, TSTRING, "HIERARCH ESO PRO DID", "AMBER",
                       "Data dictionnary for PRO", &status) != 0)
    {
        amdlibGetFitsError("PRO DID"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    /* Get DPR categrory (SCIENCE or CALIB) */
    if (fits_read_key(filePtr, TSTRING, "HIERARCH ESO DPR CATG", dprCatg, 
                      comment, &status))
    {
        status = 0;
        strcpy(dprCatg , "SCIENCE"); 
    }

    /* If there is only one records, assume it is averaged OI data */
    if (nbRecords != 1)
    {
        sprintf(proCatg, "%s_%s", dprCatg, "REDUCED");
    }
    else
    {
        sprintf(proCatg, "%s_%s", dprCatg, "AVERAGED");
    }

    if (fits_write_key(filePtr, TSTRING, "HIERARCH ESO PRO CATG", 
                       proCatg, "Category of product frames", &status) != 0)
    {
        amdlibGetFitsError("PRO CATG"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    if (fits_write_key(filePtr, TSTRING, "HIERARCH ESO PRO TYPE", "REDUCED",
                       "Product type", &status) != 0)
    {
        amdlibGetFitsError("PRO TYPE"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    if (fits_write_key(filePtr, TSTRING, "HIERARCH ESO PRO REC1 ID", 
                       "amdlibComputeOiData",
                       "Program identifier", &status) != 0)
    {
        amdlibGetFitsError("PRO REC1 ID"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
            
    /* Add information relative to bad pixel file */
    strcpy(fullFileName, badPixelFile);
    keyValBis = basename(fullFileName);
    sprintf(keyVal, "%.40s", keyValBis);
    if (fits_write_key(filePtr, TSTRING, "HIERARCH ESO PRO REC1 RAW1 NAME", 
                       keyVal, "File name of raw frame", &status) != 0)
    {
        amdlibGetFitsError("PRO REC1 RAW1 NAME"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    if (fits_write_key(filePtr, TSTRING, "HIERARCH ESO PRO REC1 RAW1 CATG", 
                       "AMBER_BADPIX",
                       "Category of raw frame", &status) != 0)
    {
        amdlibGetFitsError("PRO REC1 RAW1 CATG"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    /* Add information relative to flat field file */
    strcpy(fullFileName, flatFieldFile);
    keyValBis = basename(fullFileName);
    sprintf(keyVal, "%.40s", keyValBis);
    if (fits_write_key(filePtr, TSTRING, "HIERARCH ESO PRO REC1 RAW2 NAME", 
                       keyVal, "File name of raw frame", &status) != 0)
    {
        amdlibGetFitsError("PRO REC1 RAW2 NAME"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    if (fits_write_key(filePtr, TSTRING, "HIERARCH ESO PRO REC1 RAW2 CATG", 
                       "AMBER_FLATFIELD",
                       "Category of raw frame", &status) != 0)
    {
        amdlibGetFitsError("PRO REC1 RAW2 CATG"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    index = 2;
    /* Add information relative to dark file (if any) (PRO keywords) */
    if (strlen(darkFile) != 0)
    {
        index += 1;
        sprintf(keyName, "HIERARCH ESO PRO REC1 RAW%d NAME", index);
        strcpy(fullFileName, darkFile);
        keyValBis = basename(fullFileName);
        sprintf(keyVal, "%.40s", keyValBis);
        if (fits_write_key(filePtr, TSTRING, keyName, keyVal,
                           "File name of raw frame", &status) != 0)
        {
            amdlibGetFitsError(keyName); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
        sprintf(keyName, "HIERARCH ESO PRO REC1 RAW%d CATG", index);
        if (fits_write_key(filePtr, TSTRING, keyName, "AMBER_DARK",
                           "Category of raw frame", &status) != 0)
        {
            amdlibGetFitsError(keyName); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
    }
    
    
    /* Add information relative to input science file (PRO keywords) */
    index += 1;
    sprintf(keyName, "HIERARCH ESO PRO REC1 RAW%d NAME", index);
    strcpy(fullFileName, inputFile);
    keyValBis = basename(fullFileName);
    sprintf(keyVal, "%.40s", keyValBis);
    if (fits_write_key(filePtr, TSTRING, keyName, keyVal,
                       "File name of raw frame", &status) != 0)
    {
        amdlibGetFitsError(keyName); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    sprintf(keyName, "HIERARCH ESO PRO REC1 RAW%d CATG", index);
    if (fits_write_key(filePtr, TSTRING, keyName, "AMBER_SCIENCE",
                       "Category of raw frame", &status) != 0)
    {
        amdlibGetFitsError(keyName); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    if (fits_write_key(filePtr, TINT, "HIERARCH ESO PRO DATANCOM", &index,
                       "Number of combined frames", &status) != 0)
    {
        amdlibGetFitsError(keyName); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    /* Add information relative to parameters */
    index = 1;
    sprintf(keyName, "HIERARCH ESO PRO REC1 PARAM%d NAME", index);
    if (fits_write_key(filePtr, TSTRING, keyName, "-e",
                       "Parameter name", &status) != 0)
    {
        amdlibGetFitsError(keyName); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    sprintf(keyName, "HIERARCH ESO PRO REC1 PARAM%d VALUE", index);
    if (errorType == amdlibSTATISTICAL_ERROR)
    {
        strcpy(keyVal, "STATISTIC");
    }
    else if (errorType == amdlibTHEORETICAL_ERROR)
    {
        strcpy(keyVal, "THEORETIC");
    }
    if (fits_write_key(filePtr, TSTRING, keyName, keyVal,
                       "Parameter value", &status) != 0)
    {
        amdlibGetFitsError(keyName); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    index ++;

    sprintf(keyName, "HIERARCH ESO PRO REC1 PARAM%d NAME", index);
    if (fits_write_key(filePtr, TSTRING, keyName, "-p",
                       "Parameter name", &status) != 0)
    {
        amdlibGetFitsError(keyName); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    sprintf(keyName, "HIERARCH ESO PRO REC1 PARAM%d VALUE", index);
    if (pistonType == amdlibITERATIVE_PHASOR)
    {
        strcpy(keyVal, "PHASOR");
    }
    else if (pistonType == amdlibUNWRAPPED_PHASE)
    {
        strcpy(keyVal, "PHASE");    
    }
    if (fits_write_key(filePtr, TSTRING, keyName, keyVal,
                       "Parameter value", &status) != 0)
    {
        amdlibGetFitsError(keyName); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    index ++;

    sprintf(keyName, "HIERARCH ESO PRO REC1 PARAM%d NAME", index);
    if (mergeBandsInOutputFile == amdlibFALSE)
    {
        if (fits_write_key(filePtr, TSTRING, keyName, "-s",
                           "Parameter name", &status) != 0)
        {
            amdlibGetFitsError(keyName); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
        sprintf(keyName, "HIERARCH ESO PRO REC1 PARAM%d VALUE", index);
        if (fits_write_key(filePtr, TSTRING, keyName, "",
                           "Parameter value", &status) != 0)
        {
            amdlibGetFitsError(keyName); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
        index ++;
    }

    if (noCheckP2vmId == amdlibTRUE)
    {
        sprintf(keyName, "HIERARCH ESO PRO REC1 PARAM%d NAME", index);
        if (fits_write_key(filePtr, TSTRING, keyName, "-f",
                           "Parameter name", &status) != 0)
        {
            amdlibGetFitsError(keyName); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
        sprintf(keyName, "HIERARCH ESO PRO REC1 PARAM%d VALUE", index);
        if (fits_write_key(filePtr, TSTRING, keyName, "",
                           "Parameter value", &status) != 0)
        {
            amdlibGetFitsError(keyName); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
        index ++;
    }
    
    if (nbBinning != 999999)
    {
        int binning;
        sprintf(keyName, "HIERARCH ESO PRO REC1 PARAM%d NAME", index);
        if (fits_write_key(filePtr, TSTRING, keyName, "-b",
                           "Parameter name", &status) != 0)
        {
            amdlibGetFitsError(keyName); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
        sprintf(keyName, "HIERARCH ESO PRO REC1 PARAM%d VALUE", index);
        binning = nbBinning;
        if (fits_write_key(filePtr, TINT, keyName, &binning,
                           "Parameter value", &status) != 0)
        {
            amdlibGetFitsError(keyName); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
        index ++;
    }

    if (selectionType != amdlibNO_FRAME_SEL)
    {
        amdlibDOUBLE ratio;
        sprintf(keyName, "HIERARCH ESO PRO REC1 PARAM%d NAME", index);
        if (fits_write_key(filePtr, TSTRING, keyName, "-c",
                           "Parameter name", &status) != 0)
        {
            amdlibGetFitsError(keyName); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
        sprintf(keyName, "HIERARCH ESO PRO REC1 PARAM%d VALUE", index);
        if (selectionType == amdlibFLUX_SEL_PCG)
        {
            strcpy(keyVal, "FLUX_PCG");
        }
        else if    (selectionType == amdlibFLUX_SEL_THR)
        {
            strcpy(keyVal, "FLUX_THR");
        }
        else if (selectionType == amdlibFRG_CONTRAST_SEL_PCG)
        {
            strcpy(keyVal, "FRG_PCG");
        }
        else if (selectionType == amdlibFRG_CONTRAST_SEL_THR)
        {
            strcpy(keyVal, "FRG_THR");
        }
        else if (selectionType == amdlibOPD_SEL_PCG)
        {
            strcpy(keyVal, "OPD_PCG");
        }
        else if (selectionType == amdlibOPD_SEL_THR)
        {
            strcpy(keyVal, "OPD_THR");
        }
        if (fits_write_key(filePtr, TSTRING, keyName, keyVal,
                           "Parameter value", &status) != 0)
        {
            amdlibGetFitsError(keyName); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
        index ++;
    
        sprintf(keyName, "HIERARCH ESO PRO REC1 PARAM%d NAME", index);
        if (fits_write_key(filePtr, TSTRING, keyName, "-r",
                           "Parameter name", &status) != 0)
        {
            amdlibGetFitsError(keyName); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
        sprintf(keyName, "HIERARCH ESO PRO REC1 PARAM%d VALUE", index);
        ratio = selectionRatio;
        if (fits_write_key(filePtr, TDOUBLE, keyName, &ratio,
                           "Parameter value", &status) != 0)
        {
            amdlibGetFitsError(keyName); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
        index ++;
    }

    /* Close FITS file */
    if (fits_close_file(filePtr, &status)) 
    {
        amdlibReturnFitsError(oifitsFile);
    }

    return amdlibSUCCESS;
}
#endif

/**
 * Add PRO keywords in insCfg structure for OI-FITS result file after having
 * performed a frame selection.
 * 
 * @param oifitsFile name of the OI-FITS result file.
 * @param selFile name of the selection file.
 * @param inOiFits name of OI-FITS file from which a selection is performed.
 * @param selKeywList list of keywords contained in an input selection file was
 * given.
 * @param saveInOiFits true if OI-FITS file is saved.
 * @param selectionType name of the chosen selection criteria 
 * (amdlibNO_FRAME_SEL if no selection).
 * @param selectionRatio rato or threshold associated to the selection criteria.
 * @param errMsg error description message returned if function fails
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibAppendSelectionKeywords(
                                 const char *oifitsFile,
                                 const char *selFile,
                                 const char *inOiFits,
                                 amdlibINS_CFG *selKeywList,
                                 amdlibBOOLEAN saveInOiFits,
                                 const amdlibFRAME_SELECTION selectionType,
                                 const double selectionRatio,
                                 amdlibERROR_MSG errMsg)
{
    fitsfile *filePtr;
    int      status = 0;
    char     fitsioMsg[256];
    char     keyName[36];
    char     fullFileName[1024];
    char     name[36], suffix[36];
    char     keyVal[80], *keyValBis;
    char     keyCmt[amdlibKEYW_CMT_LEN+1];
    int      index;
    int      ind1, ind2, i;
    int      recNb;
    amdlibDOUBLE    ratio;
    amdlibKEYW_LINE keywLine;
    
    recNb = 0;
    if (saveInOiFits == amdlibTRUE)
    {
        if (fits_open_file(&filePtr, oifitsFile, READWRITE, &status))
        {
            amdlibReturnFitsError(oifitsFile);
        }
        /* Search for recipe number */
        index = 0;
        while (recNb == 0)
        {
            index ++;
            sprintf(keyName, "HIERARCH ESO PRO REC%d ID", index);
            if (fits_read_key(filePtr, TSTRING, keyName, keyVal, keyCmt, 
                              &status) != 0)
            {
                recNb = index;
                status = 0;
            }
        }
    }
    else
    {
        if (fits_open_file(&filePtr, selFile, READWRITE, &status))
        {
            amdlibReturnFitsError(oifitsFile);
        }
        recNb = 1;
    }

    /* Move to primary header */
    if (fits_movabs_hdu(filePtr, 1, 0, &status) != 0)
    {
        amdlibGetFitsError("main header"); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    
    /* Append keywords stored in selKeywList (if any) */
    if (selKeywList->nbKeywords != 0)
    {
        if (saveInOiFits == amdlibFALSE)
        {
            for (i = 0; i < selKeywList->nbKeywords; i++)
            {
                if (strstr(selKeywList->keywords[i].name, "DATE   ") == NULL)
                {
                    if ((strstr(selKeywList->keywords[i].name, 
                               "HIERARCH ESO PRO REC") != NULL) &&
                        (strstr(selKeywList->keywords[i].name, "ID") != NULL))
                    {
                        recNb++;
                    }
                    sprintf((char*)keywLine, "%s=%s/%s", 
                            selKeywList->keywords[i].name,
                            selKeywList->keywords[i].value, 
                            selKeywList->keywords[i].comment);
                    if (fits_write_record(filePtr, keywLine, &status) != 0)
                    {
                        fits_close_file(filePtr, &status);
                        amdlibSetErrMsg("Could not complete main header");
                        return amdlibFAILURE;
                    }
                }
            }
        }
        else
        {
            ind2 = 0;
            for (i = 0; i < selKeywList->nbKeywords; i++)
            {
                strcpy(name, selKeywList->keywords[i].name);
                if (strstr(name, "HIERARCH") != NULL)
                {
                    if (sscanf(name, "HIERARCH ESO PRO REC%d %[^^]", &ind1, 
                               suffix) != 0)
                    {
                        if ((ind2 != 0) && (ind1 != ind2))
                        {
                            recNb++;
                            ind2 = ind1;
                        }
                        else
                        {
                            ind2 = ind1;
                        }                            
                        sprintf(keyName, "HIERARCH ESO PRO REC%d %s", recNb, 
                                suffix);
                        amdlibStripQuotes(selKeywList->keywords[i].value);
                         
                        if (fits_write_key(filePtr, TSTRING, keyName, 
                                           selKeywList->keywords[i].value,
                                           selKeywList->keywords[i].comment,
                                           &status) != 0)
                        {
                            fits_close_file(filePtr, &status);
                            amdlibSetErrMsg("Could not complete main header");
                            return amdlibFAILURE;
                        }
                    }
                }
            }
            recNb++;
        }
    }
     

    /* Add information on the recipe itself */
    sprintf(keyName, "HIERARCH ESO PRO REC%d ID", recNb);
    if (fits_write_key(filePtr, TSTRING, keyName, 
                       "amdlibPerformFrameSelection",
                       "Program identifier", &status) != 0)
    {
        amdlibGetFitsError(keyName); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    
    /* Add information relative to parameters */
    if (saveInOiFits == amdlibFALSE)
    {
        sprintf(keyName, "HIERARCH ESO PRO REC%d RAW1 NAME", recNb);
	strcpy(fullFileName, inOiFits);
	keyValBis = basename(fullFileName);
        sprintf(keyVal, "%.40s", keyValBis);
        if (fits_write_key(filePtr, TSTRING, keyName, keyVal,
                           "File name of raw frame", &status) != 0)
        {
            amdlibGetFitsError(keyName); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
        sprintf(keyName, "HIERARCH ESO PRO REC%d RAW1 CATG", recNb);
        if (fits_write_key(filePtr, TSTRING, keyName, "SCIENCE_REDUCED",
                           "Category of raw frame", &status) != 0)
        {
            amdlibGetFitsError(keyName); 
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
    }
    sprintf(keyName, "HIERARCH ESO PRO REC%d PARAM1 NAME", recNb);
    if (fits_write_key(filePtr, TSTRING, keyName, "-c",
                       "Parameter name", &status) != 0)
    {
        amdlibGetFitsError(keyName); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    sprintf(keyName, "HIERARCH ESO PRO REC%d PARAM1 VALUE", recNb);
    if (selectionType == amdlibFLUX_SEL_PCG)
    {
        strcpy(keyVal, "FLUX_PCG");
    }
    else if    (selectionType == amdlibFLUX_SEL_THR)
    {
        strcpy(keyVal, "FLUX_THR");
    }
    else if (selectionType == amdlibFRG_CONTRAST_SEL_PCG)
    {
        strcpy(keyVal, "FRG_PCG");
    }
    else if (selectionType == amdlibFRG_CONTRAST_SEL_THR)
    {
        strcpy(keyVal, "FRG_THR");
    }
    else if (selectionType == amdlibOPD_SEL_PCG)
    {
        strcpy(keyVal, "OPD_PCG");
    }
    else if (selectionType == amdlibOPD_SEL_THR)
    {
        strcpy(keyVal, "OPD_THR");
    }
    if (fits_write_key(filePtr, TSTRING, keyName, keyVal, 
                       "Parameter value", &status) != 0)
    {
        amdlibGetFitsError(keyName); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
 
    sprintf(keyName, "HIERARCH ESO PRO REC%d PARAM2 NAME", recNb);    
    if (fits_write_key(filePtr, TSTRING, keyName, "-r",
                       "Parameter name", &status) != 0)
    {
        amdlibGetFitsError(keyName); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }
    sprintf(keyName, "HIERARCH ESO PRO REC%d PARAM2 VALUE", recNb);
    ratio = selectionRatio;
    if (fits_write_key(filePtr, TDOUBLE, keyName, &ratio,
                       "Parameter value", &status) != 0)
    {
        amdlibGetFitsError(keyName); 
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

    /* Close FITS file */
    if (fits_close_file(filePtr, &status)) 
    {
        amdlibReturnFitsError(oifitsFile);
    }

    return amdlibSUCCESS;
}


/*___oOo___*/
