
/**
 * @file
 * Functions to read/write IO-FITS file
 */
#define _POSIX_SOURCE 1

static char *rcsId="@(#) $Id: esolibamdlibOi.c,v 1.5 2011-09-26 12:54:06 agabasch Exp $"; 
static void *use_rcsId = (0 ? &use_rcsId : (void*)&rcsId);

/* 
   System Headers
*/

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

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




/* Local function declaration */
static amdlibCOMPL_STAT amdlibWriteOiArray(fitsfile        *filePtr,
                                           amdlibOI_ARRAY  *array,
                                           amdlibERROR_MSG errMsg, int lmin, int lmax);
static amdlibCOMPL_STAT amdlibWriteOiTarget(fitsfile        *filePtr,
                                            amdlibOI_TARGET *target,
                                            amdlibERROR_MSG errMsg, int lmin, int lmax);


static amdlibCOMPL_STAT amdlibWriteOiWavelength(fitsfile         *filePtr,
                                                char             *insName,
                                                amdlibWAVELENGTH *wave, 
                                                amdlibERROR_MSG  errMsg, int lmin, int lmax);
static amdlibCOMPL_STAT amdlibWriteOiVis(fitsfile        *filePtr, 
                                         char            *insName,
                                         char            *arrName,
                                         amdlibVIS       *vis, 
                                         amdlibERROR_MSG errMsg, int lmin, int lmax);
static amdlibCOMPL_STAT amdlibWriteOiVis2(fitsfile        *filePtr, 
                                          char            *insName,
                                          char            *arrName,
                                          amdlibVIS2      *vis2, 
                                          amdlibERROR_MSG errMsg, int lmin, int lmax);

static amdlibCOMPL_STAT amdlibWriteOiVis3(fitsfile        *filePtr, 
                                          char            *insName,
                                          char            *arrName,
                                          amdlibVIS3      *vis3, 
                                          amdlibERROR_MSG errMsg, int lmin, int lmax);

static amdlibCOMPL_STAT amdlibWriteAmberData(fitsfile         *filePtr,
                                             char             *insName,
                                             amdlibPHOTOMETRY *photometry,
                                             amdlibVIS        *vis,
                                             amdlibPISTON     *pst,
                                             amdlibWAVELENGTH *wave, 
                                             amdlibERROR_MSG  errMsg, int lmin, int lmax);
static amdlibCOMPL_STAT esolibamdlibWriteAmberSpectrum(fitsfile         *filePtr,
                                          amdlibWAVELENGTH *wave,
                                          amdlibSPECTRUM   *spc,
                                          amdlibERROR_MSG  errMsg, int lmin, int lmax);


/*
 * Public functions 
 */
/**
 * Write OI-FITS file.
 *
 * This function create OI-FITS file containing the OI_ARRAY, OI_TARGET, OI_VIS,
 * OI_VIS2, OI_T3 and OI_WAVELENGTH binary tables defined in the IAU standard,
 * and the AMBER_DATA binary table which is specific to AMBER. The AMBER_DATA
 * binary table contains photometry and piston data.
 *
 * @note
 * If the file exists, it is overwritten.
 *
 * @param filename name of the OI-FITS file to create
 * @param insCfg array containing keywords of the primary header.
 * @param array structure containing information of OI_ARRAY binary table 
 * @param target structure containing information of OI_TARGET binary table 
 * @param photometry structure containing photometry which is stored in
 * AMBER_DATA binary table.
 * @param vis structure containing information of OI_VIS binary table 
 * @param vis2 structure containing information of OI_VIS2 binary table 
 * @param vis3 structure containing information of OI_T3 binary table 
 * @param wave structure containing information of OI_WAVELENGTH binary table 
 * @param pst structure containing piston which is stored in AMBER_DATA binary
 * table.
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibSaveOiFile_waveselected(const char           *filename,
                                  amdlibINS_CFG        *insCfg, 
                                  amdlibOI_ARRAY       *array,
                                  amdlibOI_TARGET      *target,
                                  amdlibPHOTOMETRY     *photometry,
                                  amdlibVIS            *vis,
                                  amdlibVIS2           *vis2,
                                  amdlibVIS3           *vis3,
                                  amdlibWAVELENGTH     *wave,
                                  amdlibPISTON         *pst,
                                  amdlibSPECTRUM       *spectrum,
                                  amdlibERROR_MSG       errMsg,
                                  int                   lmin,
                                  int                   lmax)
{
    int         status = 0;
    fitsfile    *filePtr;
    int         i;
#ifndef ESO_CPL_PIPELINE_VARIANT
    struct stat statBuf;
    time_t      timeSecs;
    struct tm   *timeNow;
    char        strTime[amdlibKEYW_VAL_LEN+1];
#endif
    char        fitsioMsg[256];
    char        insName[amdlibKEYW_VAL_LEN+1];
    char        arrName[amdlibKEYW_VAL_LEN+1];
    char        version[32];

    amdlibLogTrace("amdlibSaveOiFile()");

#ifndef ESO_CPL_PIPELINE_VARIANT
    /* 
     * amdlib will create the file on its own
     */
    /* First remove previous IO file (if any) */
    if (stat(filename, &statBuf) == 0)
    {
        if (remove(filename) != 0)
        {
            amdlibSetErrMsg("Could not overwrite file %s", filename); 
            return amdlibFAILURE;
        }
    }

    /* Create new FITS file */
    if (fits_create_file(&filePtr, filename, &status))
    {
        amdlibReturnFitsError("opening file");
    }
#else
    /* 
     *  The ESO pipeline will create that file and prepare the header
     *  with DMD compliant keywords. Hence amdlib shall just append to it
     */ 

    /* Append to FITS file */
    if (fits_open_file(&filePtr, filename, READWRITE, &status))
    {
        printf("%s->fits_open_file cannot open in append mode\n", filename  );
        amdlibReturnFitsError("opening file");
    }
    else
    {
        printf("%s->fits_open_file append OK\n", filename);
    }
#endif    

    /* Build a uniq INSNAME using P2VM id (if possible), else use default */
    sprintf(insName, "AMBER");

    /* Retrieve ARRNAME if possible, else use default.*/
    if (array != NULL)
    {
        strncpy(arrName, array->arrayName, amdlibKEYW_VAL_LEN+1);
    }
    else
    {
        sprintf(arrName, "UNKNOWN");
    }    

    /* Write OI_ARRAY binary table */
    if (array != NULL)
    {
        if (amdlibWriteOiArray(filePtr, array,  errMsg, lmin, lmax) != amdlibSUCCESS)
        {
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
    }

    /* Write OI_TARGET binary table */
    if (target != NULL)
    {
        if (amdlibWriteOiTarget(filePtr, target, errMsg, lmin, lmax) != amdlibSUCCESS)
        {
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
    }

    /* Write OI_WAVELENGTH binary table */
    if (wave != NULL)
    {
        if (amdlibWriteOiWavelength(filePtr, insName, 
                                    wave, errMsg, lmin, lmax) != amdlibSUCCESS)
        {
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
    }

    /* Write OI_VIS binary table */
    if (vis != NULL)
    {
        if (amdlibWriteOiVis(filePtr, insName, arrName, 
                             vis, errMsg, lmin, lmax) != amdlibSUCCESS)
        {
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
    }

    /* Write OI_VIS2 binary table */
    if (vis2 != NULL)
    {    
        if (amdlibWriteOiVis2(filePtr, insName, arrName, 
                              vis2, errMsg, lmin, lmax) != amdlibSUCCESS)
        {
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
    }
    /* Write OI_T3 binary table */
    if (vis3 != NULL)
    {
        if (amdlibWriteOiVis3(filePtr, insName, arrName, 
                              vis3, errMsg, lmin, lmax) != amdlibSUCCESS)
        {
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
    }

    /* Write AMBER_DATA binary table */
    if ((photometry != NULL) && (vis != NULL) && (pst != NULL))
    {
        if (vis->nbFrames != 1)
        {
            if (amdlibWriteAmberData(filePtr, insName, 
                                     photometry, vis, pst, wave,
                                     errMsg, lmin, lmax) != amdlibSUCCESS)
            {
                fits_close_file(filePtr, &status);
                return amdlibFAILURE;
            }
        }
    }

    /* Write AMBER_SPECTRUM binary table */
    if (spectrum != NULL)
    {
        if (esolibamdlibWriteAmberSpectrum(filePtr, wave, spectrum,
                                     errMsg, lmin, lmax) != amdlibSUCCESS)
        {
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }
    }

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


#ifndef ESO_CPL_PIPELINE_VARIANT
    /* Complete main header */
    if (insCfg != NULL)
    {
        /* Add DATE */
        timeSecs = time(NULL);
        timeNow = gmtime(&timeSecs);
        strftime(strTime, sizeof(strTime), "%Y-%m-%dT%H:%M:%S", timeNow);
        if (fits_write_key(filePtr, TSTRING, "DATE", strTime,
                           "Date this file was written", &status) != 0)
        {
            amdlibGetFitsError("completing main header");
            fits_close_file(filePtr, &status);
            return amdlibFAILURE;
        }

        /* Add other keywords */
        for (i = 0; i < insCfg->nbKeywords; i++)
        {
            amdlibKEYW_LINE keywLine;
            if ((strstr(insCfg->keywords[i].name, "SIMPLE") == NULL) &&
                (strstr(insCfg->keywords[i].name, "BITPIX") == NULL) &&
                (strstr(insCfg->keywords[i].name, "NAXIS ") == NULL) &&
                (strstr(insCfg->keywords[i].name, "EXTEND") == NULL) &&
                (strstr(insCfg->keywords[i].name, "DATE  ") == NULL))
            {
                sprintf((char*)keywLine, "%s=%s/%s", insCfg->keywords[i].name,
                        insCfg->keywords[i].value, 
                        insCfg->keywords[i].comment);
                if (fits_write_record(filePtr, keywLine, &status) != 0)
                {
                    amdlibGetFitsError("completing main header");
                    fits_close_file(filePtr, &status);
                    return amdlibFAILURE;
                }
            }
        }
    }
#else
    /* Complete main header */
    if (insCfg != NULL)
    {
        /* Add QC keywords  - spectral shifts */
        for (i = 0; i < insCfg->nbKeywords; i++)
        {
            amdlibKEYW_LINE keywLine;
            if ((strstr(insCfg->keywords[i].name, 
                        "HIERARCH ESO QC P1 ") != NULL) ||
                (strstr(insCfg->keywords[i].name, 
                        "HIERARCH ESO QC P2 ") != NULL) ||
                (strstr(insCfg->keywords[i].name, 
                        "HIERARCH ESO QC P3 ") != NULL)) 
            {
                sprintf((char*)keywLine, "%s=%s/%s", insCfg->keywords[i].name,
                        insCfg->keywords[i].value, 
                        insCfg->keywords[i].comment);
                if (fits_write_record(filePtr, keywLine, &status) != 0)
                {
                    amdlibGetFitsError("completing main header");
                    fits_close_file(filePtr, &status);
                    return amdlibFAILURE;
                }
            }
        }
    }
#endif
    
    /* Add amdlib version */
    amdlibGetVersion(version);
    if (fits_update_key(filePtr, TSTRING, "HIERARCH ESO OCS DRS VERSION", 
                        version,
                        "Data Reduction SW version", &status) != 0)
    {
        amdlibGetFitsError("HIERARCH ESO OCS DRS VERSION");
        fits_close_file(filePtr, &status);
        return amdlibFAILURE;
    }

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

    return amdlibSUCCESS;
}


/*
 * Protected functions
 */

/*
 * Local functions
 */
/**
 * Write OI_ARRAY table in OI-FITS file
 *
 * This function writes the OI_ARRAY binary table defined in the IAU standard
 * in the OI-FITS file given as parameter.
 *
 * @param filePtr pointer on the file to be created
 * @param array OI_ARRAY produced
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibWriteOiArray(fitsfile        *filePtr,
                                    amdlibOI_ARRAY  *array,
                                    amdlibERROR_MSG errMsg, int lmin, int lmax)
{
    const int  tfields = 5;
    char       *ttype[] = {"TEL_NAME", "STA_NAME", "STA_INDEX", "DIAMETER", 
        "STAXYZ"};
    char       *tform[] = {"8A", "8A", "I", "E", "3D"};
    char       *tunit[] = {"\0", "\0", "\0", "m", "m"};
    char       extname[] = "OI_ARRAY";
    int        status = 0;
    int        iRow;
    char       fitsioMsg[256];
    int        revision = amdlib_OI_REVISION;
    char       *str;

    amdlibLogTrace("amdlibWriteOiArray()");

    /* Algorithm */

    /* Create binary table */
    if (fits_create_tbl(filePtr, BINARY_TBL, 1, tfields, ttype, tform, tunit,
                       extname, &status)) 
    {
        amdlibReturnFitsError("Creating Binary Table");
    }

    /* Write revision number */
    if (fits_write_key(filePtr, TINT, "OI_REVN", &revision,
                       "Revision number of the table definition", &status))
    {
        amdlibReturnFitsError("OI_REVN");
    }

    /* Write array name */
    if (fits_write_key(filePtr, TSTRING, "ARRNAME", array->arrayName,
                       "Array name", &status))
    {
        amdlibReturnFitsError("ARRNAME");
    }

    /* Write coordinate frame */
    if (fits_write_key(filePtr, TSTRING, "FRAME", array->coordinateFrame,
                       "Coordinate frame", &status))
    {
        amdlibReturnFitsError("FRAME");
    }

    /* Write array center x coordinate */
    if (fits_write_key(filePtr, TDOUBLE, "ARRAYX", 
                       &array->arrayCenterCoordinates[0],
                       "Array centre x coordinate", &status))
    {
        amdlibReturnFitsError("ARRAYX");
    }

    if (fits_write_key_unit(filePtr, "ARRAYX", "m", &status))
    {
        amdlibReturnFitsError("ARRAYX");
    }

    /* Write array center y coordinate */
    if (fits_write_key(filePtr, TDOUBLE, "ARRAYY", 
                       &array->arrayCenterCoordinates[1],
                       "Array centre y coordinate", &status))
    {
        amdlibReturnFitsError("ARRAYY");
    }

    if (fits_write_key_unit(filePtr, "ARRAYY", "m", &status))
    {
        amdlibReturnFitsError("ARRAYY");
    }

    /* Write array center z coordinate */
    if (fits_write_key(filePtr, TDOUBLE, "ARRAYZ", 
                       &array->arrayCenterCoordinates[2],
                       "Array centre z coordinate", &status)) 
    {
        amdlibReturnFitsError("ARRAYZ");
    }

    if (fits_write_key_unit(filePtr, "ARRAYZ", "m", &status)) 
    {
        amdlibReturnFitsError("ARRAYZ");
    }

    /* Write information on instrument */
    for (iRow = 1; iRow <= array->nbStations; iRow++) 
    {
        str = array->element[iRow-1].telescopeName;
        if (fits_write_col(filePtr, TSTRING, 1, iRow, 1, 1, &str, &status))
        {
            amdlibReturnFitsError("telescopeName");
        }
        str = array->element[iRow-1].stationName;
        if (fits_write_col(filePtr, TSTRING, 2, iRow, 1, 1, &str, &status))
        {
            amdlibReturnFitsError("stationName");
        }
        if (fits_write_col(filePtr, TINT, 3, iRow, 1, 1, 
                           &array->element[iRow-1].stationIndex, &status))
        {
            amdlibReturnFitsError("stationIndex");
        }
        if (fits_write_col(filePtr, TDOUBLE, 4, iRow, 1, 1, 
                           &array->element[iRow-1].elementDiameter, &status))
        {
            amdlibReturnFitsError("diameter");
        }
        if (fits_write_col(filePtr, TDOUBLE, 5, iRow, 1, 3, 
                           array->element[iRow-1].stationCoordinates, &status))
        {
            amdlibReturnFitsError("staXYZ");
        }
    }
    return amdlibSUCCESS;
}


/**
 * Write OI_TARGET table in OI-FITS file.
 *
 * This function writes the OI_TARGET binary table defined in the IAU standard
 * in the OI-FITS file given as parameter.
 *
 * @param filePtr pointer on the file to be created.
 * @param target OI_TARGET produced.
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibWriteOiTarget(fitsfile        *filePtr,
                                     amdlibOI_TARGET *target,
                                     amdlibERROR_MSG errMsg, int lmin, int lmax)
{
    const int  tfields = 17;
    char       *ttype[] = {"TARGET_ID", "TARGET", "RAEP0", "DECEP0", "EQUINOX",
        "RA_ERR", "DEC_ERR", "SYSVEL", "VELTYP", "VELDEF", "PMRA", "PMDEC", 
        "PMRA_ERR", "PMDEC_ERR", "PARALLAX", "PARA_ERR", "SPECTYP"};
    char       *tform[] = {"I", "16A", "D", "D", "E", "D", "D", "D", "8A", 
        "8A", "D", "D", "D", "D", "E", "E", "16A"};
    char       *tunit[] = {"\0", "\0", "deg", "deg", "year", "deg", "deg", 
        "m/s", "\0", "\0", "deg/year", "deg/year", "deg/year", "deg/year",
        "deg", "deg", "\0"};
    char       extname[] = "OI_TARGET";
    char       fitsioMsg[256];
    int        status = 0;
    int        revision = amdlib_OI_REVISION;
    int        iRow;
    char       *str;

    amdlibLogTrace("amdlibWriteOiTarget()");

    /* Create binary table */
    if (fits_create_tbl(filePtr, BINARY_TBL, 1, tfields, ttype, tform, tunit,
                       extname, &status)) 
    {
        amdlibReturnFitsError("Creating Binary Table");
    }

    /* Write revision number of the table definition */
    if (fits_write_key(filePtr, TINT, "OI_REVN", &revision,
                       "Revision number of the table definition", &status))
    {
        amdlibReturnFitsError("OI_REVN"); 
    }

    for (iRow = 1; iRow <= target->nbTargets; iRow++) 
    {
        /* Write target identity */
        if (fits_write_col(filePtr, TINT, 1, iRow, 1, 1, 
                          &target->element[iRow-1].targetId, &status))
        {
            amdlibReturnFitsError("targetId");
        }

        str = target->element[iRow-1].targetName;
    
        /* Write target name */
        if (fits_write_col(filePtr, TSTRING, 2, iRow, 1, 1, &str, &status))
        {
            amdlibReturnFitsError("targetName");
        }

        /* Write right ascension at mean equinox */
        if (fits_write_col(filePtr, TDOUBLE, 3, iRow, 1, 1,
                           &target->element[iRow-1].raEp0, &status))
        {
            amdlibReturnFitsError("raEp0");
        }
        /* Write declinaison at mean equinox */
        if (fits_write_col(filePtr, TDOUBLE, 4, iRow, 1, 1,
                           &target->element[iRow-1].decEp0, &status))
        {
            amdlibReturnFitsError("decEp0");
        }
        /* Write equinox of raEp0 and decEp0 */
        if (fits_write_col(filePtr, TDOUBLE, 5, iRow, 1, 1,
                           &target->element[iRow-1].equinox, &status))
        {
            amdlibReturnFitsError("equinox");
        }
        /* Write error in apparent RA */
        if (fits_write_col(filePtr, TDOUBLE, 6, iRow, 1, 1, 
                           &target->element[iRow-1].raErr, &status))
        {
            amdlibReturnFitsError("raErr");
        }
        /* Write error in apparent DEC */
        if (fits_write_col(filePtr, TDOUBLE, 7, iRow, 1, 1,
                           &target->element[iRow-1].decErr, &status))
        {
            amdlibReturnFitsError("decErr");
        }
        /* Write systemic radial velocity */
        if (fits_write_col(filePtr, TDOUBLE, 8, iRow, 1, 1,
                           &target->element[iRow-1].sysVel, &status))
        {
            amdlibReturnFitsError("sysVel");
        }

        str = target->element[iRow-1].velTyp;
        /* Write velocity type of sysVel */
        if (fits_write_col(filePtr, TSTRING, 9, iRow, 1, 1, &str, &status))
        {
            amdlibReturnFitsError("velTyp");
        }

        str = target->element[iRow-1].velDef;
        /* Write definition of radial velocity : "OPTICAL" or "RADIO" */
        if (fits_write_col(filePtr, TSTRING, 10, iRow, 1, 1, &str, &status))
        {
            amdlibReturnFitsError("velDef");
        }

        /* Write proper motion in RA */
        if (fits_write_col(filePtr, TDOUBLE, 11, iRow, 1, 1, 
                           &target->element[iRow-1].pmRa, &status))
        {
            amdlibReturnFitsError("pmRa");
        }
        /* Write proper motion in DEC */
        if (fits_write_col(filePtr, TDOUBLE, 12, iRow, 1, 1, 
                           &target->element[iRow-1].pmDec, &status))
        {
            amdlibReturnFitsError("pmDec");
        }
        /* Write error on pmRa */
        if (fits_write_col(filePtr, TDOUBLE, 13, iRow, 1, 1, 
                           &target->element[iRow-1].pmRaErr, &status))
        {
            amdlibReturnFitsError("pmRaErr");
        }
        /* Write error on pmDec */
        if (fits_write_col(filePtr, TDOUBLE, 14, iRow, 1, 1, 
                           &target->element[iRow-1].pmDecErr, &status))
        {
            amdlibReturnFitsError("pmDecErr");
        }
        /* Write parallax value */
        if (fits_write_col(filePtr, TDOUBLE, 15, iRow, 1, 1, 
                           &target->element[iRow-1].parallax, &status))
        {
            amdlibReturnFitsError("parallax");
        }
        /* Write error in parallax value */
        if (fits_write_col(filePtr, TDOUBLE, 16, iRow, 1, 1, 
                           &target->element[iRow-1].paraErr, &status))
        {
            amdlibReturnFitsError("paraErr");
        }
        /* Write spectral type */
        str = target->element[iRow-1].specTyp;
        if (fits_write_col(filePtr, TSTRING, 17, iRow, 1, 1, &str, &status))
        {
            amdlibReturnFitsError("specTyp");
        }
    }
    return amdlibSUCCESS;
}


/**
 * Write OI_WAVELENGTH table in OI-FITS file.
 *
 * This function writes the OI_WAVELENGTH binary table defined in the IAU 
 * standard in the OI-FITS file given as parameter.
 *
 * @param filePtr pointer on the file to be created.
 * @param insName name of the instrument.
 * @param wave OI_WAVELENGTH produced.
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibWriteOiWavelength(fitsfile         *filePtr,
                                         char             *insName,
                                         amdlibWAVELENGTH *wave,
                                         amdlibERROR_MSG  errMsg, int lmin, int lmax)
{
    int        status = 0;
    char       fitsioMsg[256];
    const int  tfields = 2;
    char       *ttype[] = {"EFF_WAVE", "EFF_BAND"};
    char       *tform[] = {"E", "E"};
    char       *tunit[] = {"m", "m"};
    char       extname[] = "OI_WAVELENGTH";
    int        revision = amdlib_OI_REVISION;
    int        i;
    amdlibDOUBLE      waveInM, bwInM;

    amdlibLogTrace("amdlibWriteOiWavelength()");

    /* Create binary table */
    if (fits_create_tbl (filePtr, BINARY_TBL, 1, tfields, ttype, tform, tunit,
                        extname, &status)) 
    {
        amdlibReturnFitsError("Creating Binary Table");
    }

    /* Write revision number of the table definition */
    if (fits_write_key (filePtr, TINT, "OI_REVN", &revision,
                       "Revision number of the table definition", &status)) 
    {
        amdlibReturnFitsError("OI_REVN");
    }

    /* Write spectral setup unique identifier */
    if (fits_write_key (filePtr, TSTRING, "INSNAME", insName,
                       "Instrument name", &status)) 
    {
        amdlibReturnFitsError("INSNAME");
    }

    for (i = 1; i <= wave->nbWlen-(lmin+lmax); i ++)
    {
        waveInM = (wave->wlen[i-1+lmin]) * amdlibNM_TO_M; 
        bwInM = (wave->bandwidth[i-1+lmin]) * amdlibNM_TO_M; 
        /* Write EFFective_WAVElengh table */
        if (fits_write_col (filePtr, TDOUBLE, 1, i, 1, 1, &waveInM, &status)) 
        {
            amdlibReturnFitsError("EFFective_WAVElengh");
        }

        /* Write EFFective_BANDwidth table */
        if (fits_write_col (filePtr, TDOUBLE, 2, i, 1, 1, &bwInM, &status)) 
        {
            amdlibReturnFitsError("EFFective_BANDwidth");
        }
    }

    return amdlibSUCCESS;
}

#define amdlibWriteOiVis_FREEALL()  free(visError); free(flag); free(convertToDeg);

/**
 * Write OI_VIS table in OI-FITS file
 *
 * This function writes the OI_VIS binary table defined in the IAU 
 * standard in the OI-FITS file given as parameter.
 *
 * @param filePtr pointer on the file to be created
 * @param insName name of the detector
 * @param arrName array name 
 * @param vis OI_VIS produced
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibWriteOiVis(fitsfile        *filePtr, 
                                  char            *insName,
                                  char            *arrName,
                                  amdlibVIS       *vis, 
                                  amdlibERROR_MSG errMsg, int lmin, int lmax)

{
    /* Local Declarations */

    int           nbWlen = vis->nbWlen-(lmin+lmax);
    int           lVis;
    double        *convertToDeg;
    amdlibCOMPLEX *visError;
    unsigned char *flag;
    amdlibDOUBLE amdlibBval=amdlibBLANKING_VALUE;

    int           status = 0;
    char          fitsioMsg[256];
    const int     tfields = 14;
    char          *ttype[] = {"TARGET_ID", "TIME", "MJD", "INT_TIME",
        "VISDATA", "VISERR", "VISAMP", "VISAMPERR", "VISPHI", "VISPHIERR",
        "UCOORD", "VCOORD", "STA_INDEX", "FLAG"};
    char          *Tform[] = {"I", "D", "D", "D", "?C", "?C", "?D", "?D", 
        "?D", "?D", "1D", "1D", "2I", "?L"};
    char          *tform[tfields];
    char          *tunit[] = {"\0", "s", "day", "s", "\0", "\0", "\0", "\0", 
        "deg", "deg", "m", "m", "\0", "\0"};
    char          extname[] = "OI_VIS";
    int           revision = amdlib_OI_REVISION;
    char          tmp[16];
    int           iFrame = 0, iBase = 0, iVis = 0, i = 0;
    double        expTime;

    amdlibLogTrace("amdlibWriteOiVis()");

    /* Algorithm */
    if (vis->thisPtr != vis) 
    {
        amdlibSetErrMsg("Unitialized vis structure");
        return amdlibFAILURE; 
    }

    if (vis->nbFrames < 1)
    {
        return amdlibSUCCESS;
    }

    /* Create table structure: Make up Tform: substitute nbWlen for '?' */
    for (i = 0; i < tfields; i++) 
    {
        if (Tform[i][0] == '?')
        {
            sprintf(tmp, "%d%s", nbWlen, &Tform[i][1]);
            tform[i] = calloc((strlen(tmp)+1), sizeof(char));
            strcpy(tform[i], tmp);
        }
        else
        {
            tform[i] = calloc((strlen(Tform[i])+1), sizeof(char));
            strcpy(tform[i], Tform[i]);
        }
    }
    if (fits_create_tbl(filePtr, BINARY_TBL, 0, tfields, ttype, 
                       tform, tunit, extname, &status)) 
    {
        amdlibReturnFitsError("BINARY_TBL");
    }

    for (i = 0; i < tfields; i++)
    {
        free(tform[i]);
    }

    /* Write revision number of the table definition */
    if (fits_write_key(filePtr, TINT, "OI_REVN", &revision, 
                      "Revision number of the table definition", &status))
    {
        amdlibReturnFitsError("OI_REVN");
    }

    /* Give date */
    if (fits_write_key(filePtr, TSTRING, "DATE-OBS", vis->dateObs,
                      "UTC start date of observations", &status))
    {
        amdlibReturnFitsError("DATE-OBS");
    }

    /* Write names of array and detector */
    if (fits_write_key (filePtr, TSTRING, "ARRNAME", arrName,
                       "Array Name", &status))
    {
        amdlibReturnFitsError("ARRNAME");
    }
    if (fits_write_key(filePtr, TSTRING, "INSNAME", insName, 
                      "Instrument name", &status))
    {
        amdlibReturnFitsError("INSNAME");
    }
    
    visError  = calloc(nbWlen, sizeof(amdlibCOMPLEX));
    convertToDeg  = calloc(nbWlen, sizeof(double));
    flag = calloc(nbWlen, sizeof(unsigned char));

    /* Write columns */
    for (iFrame = 0; iFrame < vis->nbFrames; iFrame++) 
    {
        for (iBase = 0; iBase < vis->nbBases; iBase++)
        {
            iVis++;

            /* Write TARGET_IDentity as an index into IO_TARGET table */
            if (fits_write_col(filePtr, TINT, 1, iVis, 1, 1, 
                              &(vis->table[iVis-1].targetId), &status)) 
            {
                amdlibWriteOiVis_FREEALL();
                amdlibReturnFitsError("TARGET_IDentity");
            }

            /* Write utc TIME of observation */
            if (fits_write_col(filePtr, TDOUBLE, 2, iVis, 1, 1,
                              &(vis->table[iVis-1].time), &status))
            {
                amdlibWriteOiVis_FREEALL();
                amdlibReturnFitsError("TIME");
            }

            /* Write observation date in ModifiedJulianDay */
            if (fits_write_col(filePtr, TDOUBLE, 3, iVis, 1, 1,
                              &(vis->table[iVis-1].dateObsMJD), &status))
            {
                amdlibWriteOiVis_FREEALL();
                amdlibReturnFitsError("ModifiedJulianDay");
            }

            /* Write INTegration_TIME (seconds) */
            expTime = (double)vis->table[iVis-1].expTime;
            if (fits_write_col(filePtr, TDOUBLE, 4, iVis, 1, 1,
                              &expTime, &status))
            {
                amdlibWriteOiVis_FREEALL();
                amdlibReturnFitsError("INTegration_TIME");
            }

            /* Writing ComPleXVISibility (no unit) */
            if (fits_write_col(filePtr,TDBLCOMPLEX, 5, iVis, 1, nbWlen,
                              (double*)vis->table[iVis-1].vis, &status))
            {
                amdlibWriteOiVis_FREEALL();
                amdlibReturnFitsError("ComPleXVISibility");
            }

            /* Write ComPleXVISibilityERRor (no unit) */
            /* Need to write Error, NOT sigma2 */
            for (lVis = 0; lVis < nbWlen; lVis++)
            {
                visError[lVis].re = 
                    amdlibSignedSqrt(vis->table[iVis-1].sigma2Vis[lVis+lmin].re);
                visError[lVis].im = 
                    amdlibSignedSqrt(vis->table[iVis-1].sigma2Vis[lVis+lmin].im);
            }
          
            if (fits_write_col(filePtr,TDBLCOMPLEX,  6, iVis, 1,
                              nbWlen, (double *)visError, &status)) 
            { 
                amdlibWriteOiVis_FREEALL();
                amdlibReturnFitsError("ComPleXVISibilityERRor"); 
            }

            /* Write VISibilityAMPlitude (no unit) */
            if (fits_write_col(filePtr, TDOUBLE, 7, iVis, 1, nbWlen,
                               vis->table[iVis-1].diffVisAmp, &status))
            {
                amdlibWriteOiVis_FREEALL();
                amdlibReturnFitsError("VISibilityAMPlitude");
            }

            /* Write VISibilityAMPlitudeERRor (no unit) */
            if (fits_write_col(filePtr, TDOUBLE, 8, iVis, 1, nbWlen,
                               vis->table[iVis-1].diffVisAmpErr, &status))
            {
                amdlibWriteOiVis_FREEALL();
                amdlibReturnFitsError("VISibilityAMPlitudeERRor");
            }

            /* Write VISibilityPHI (degrees) */
            for(lVis = 0; lVis < nbWlen; lVis++)
            {
                if(vis->table[iVis-1].diffVisPhi[lVis+lmin]!=amdlibBLANKING_VALUE)
                {
                    convertToDeg[lVis] = 180.0 / M_PI * 
                    vis->table[iVis-1].diffVisPhi[lVis+lmin];
                }
                else
                {
                    convertToDeg[lVis] = amdlibBLANKING_VALUE;
                }
            }

            if (fits_write_colnull(filePtr, TDOUBLE, 9, iVis, 1, nbWlen,
                               convertToDeg, &amdlibBval, &status))
            {
                amdlibWriteOiVis_FREEALL();
                amdlibReturnFitsError("VISibilityPHI");
            }

            /* Write VISibilityPHIERRor (degrees) */
            for(lVis = 0; lVis < nbWlen; lVis++)
            {
                if(vis->table[iVis-1].diffVisPhiErr[lVis+lmin]!=amdlibBLANKING_VALUE)
                {
                    convertToDeg[lVis] = 180.0 / M_PI * 
                    vis->table[iVis-1].diffVisPhiErr[lVis+lmin];
                }
                else
                {
                    convertToDeg[lVis] = amdlibBLANKING_VALUE;
                }
            }
            if (fits_write_colnull(filePtr, TDOUBLE, 10, iVis, 1, nbWlen,
                               convertToDeg, &amdlibBval, &status))
            {
                amdlibWriteOiVis_FREEALL();
                amdlibReturnFitsError("VISibilityPHIERRor");
            }
            /* Write UCOORDinate (meters) */
            if (fits_write_col(filePtr, TDOUBLE, 11, iVis, 1, 1,
                              &(vis->table[iVis-1].uCoord), &status))
            {
                amdlibWriteOiVis_FREEALL();
                amdlibReturnFitsError("UCOORDinate");
            }

            /* Write VCOORDinates (meters) */
            if (fits_write_col(filePtr, TDOUBLE, 12, iVis, 1, 1,
                              &(vis->table[iVis-1].vCoord), &status))
            {
                amdlibWriteOiVis_FREEALL();
                amdlibReturnFitsError("VCOORDinates");
            }

            /* Write STAtion_INDEX contributing to the data */
            if (fits_write_col(filePtr, TINT, 13, iVis, 1, 2,
                              vis->table[iVis-1].stationIndex, &status))
            {
                amdlibWriteOiVis_FREEALL();
                amdlibReturnFitsError("STAtion_INDEX");
            }
            
            /* Write flag as unsigned char -- does not work with booleans */
            for (lVis = 0; lVis < nbWlen; lVis++)
            {
                flag[lVis] = vis->table[iVis-1].flag[lVis+lmin];
            }
          
            if (fits_write_col(filePtr, TLOGICAL, 14, iVis, 1, nbWlen, 
                               flag, &status))
            {
                amdlibWriteOiVis_FREEALL();
                amdlibReturnFitsError("FLAG");
            }
        }
    }
    /* free allocated space at end of job */
    amdlibWriteOiVis_FREEALL();
    return amdlibSUCCESS;
}
#undef   amdlibWriteOiVis_FREEALL

#define amdlibWriteOiVis2_FREEALL()  free(flag);
/**
 * Write OI_VIS2 table in OI-FITS file
 *
 * This function writes the OI_VIS2 binary table defined in the IAU 
 * standard in the OI-FITS file given as parameter.
 *
 * @param filePtr pointer on the file to be created
 * @param insName name of the detector
 * @param arrName array name 
 * @param vis2 OI_VIS2 produced
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibWriteOiVis2( fitsfile        *filePtr,
                                    char            *insName,
                                    char            *arrName,
                                    amdlibVIS2      *vis2,
                                    amdlibERROR_MSG errMsg, int lmin, int lmax)
{
    int        nbWlen = vis2->nbWlen-(lmin+lmax);
    int        lVis;
    int        status = 0;
    char       fitsioMsg[256];
    const int  tfields = 10;
    char       *ttype[] = {"TARGET_ID", "TIME", "MJD", "INT_TIME", "VIS2DATA", 
        "VIS2ERR", "UCOORD", "VCOORD", "STA_INDEX", "FLAG"};
    char       *Tform[] = {"I", "D", "D", "D", "?D", "?D", "1D", "1D", "2I", 
        "?L"};
    char       *tform[tfields];
    char       *tunit[] = {"\0", "s", "day", "s", "\0", "\0", "m", "m", "\0",
        "\0"};
    char       extname[] = "OI_VIS2";
    int        revision = amdlib_OI_REVISION;
    int        iFrame = 0, iBase = 0, iVis = 0, i = 0;
    char       tmp[16];
    double     expTime;
    unsigned char *flag;

    amdlibLogTrace("amdlibWriteOiVis2()");
 
    if (vis2->thisPtr != vis2)
    {
        amdlibSetErrMsg("Unitialized vis2 structure");
        return amdlibFAILURE; 
    }
    if (vis2->nbFrames < 1)
    {
        return amdlibSUCCESS;
    }

    /* Create table structure. Make up Tform: substitute nbWlen for '?' */
    for (i = 0; i < tfields; i++) 
    {
        if (Tform[i][0] == '?') 
        {
            sprintf(tmp, "%d%s", nbWlen, &Tform[i][1]);
            tform[i] = calloc((strlen(tmp)+1), sizeof(char));
            strcpy(tform[i], tmp);
        }
        else
        {
            tform[i] = calloc((strlen(Tform[i])+1), sizeof(char));
            strcpy(tform[i], Tform[i]);
        }
    }

    if (fits_create_tbl (filePtr, BINARY_TBL, 0, tfields,
                         ttype, tform, tunit, extname, &status))
    {
        amdlibReturnFitsError("BINARY_TBL");
    }

    for (i = 0; i < tfields; i++)
    {
        free(tform[i]);
    }

    /* Write revision number of the table definition */
    if (fits_write_key (filePtr, TINT, "OI_REVN", &revision,
                        "Revision number of the table definition", &status))
    {
        amdlibReturnFitsError("OI_REVN");
    }

    /* Give date */
    if (fits_write_key(filePtr, TSTRING,"DATE-OBS", vis2->dateObs,
                       "UTC start date of observations", &status))
    {
        amdlibReturnFitsError("DATE-OBS");
    }

    /* Write array and detector names */
    if (fits_write_key (filePtr, TSTRING, "ARRNAME", arrName,
                        "Array Name", &status))
    {
        amdlibReturnFitsError("ARRNAME");
    }
    if (fits_write_key(filePtr, TSTRING, "INSNAME", insName, 
                       "Instrument name", &status))
    {
        amdlibReturnFitsError("INSNAME");
    }

    flag = calloc(nbWlen, sizeof(unsigned char));

    /* Write columns */
    for (iFrame = 0; iFrame < vis2->nbFrames; iFrame++) 
    {
        for (iBase = 0; iBase < vis2->nbBases; iBase++)
        {
            iVis++;
            /* Write TARGET_IDentity */
            if (fits_write_col (filePtr, TINT, 1, iVis, 1, 1,
                                &(vis2->table[iVis-1].targetId), &status))
            {
	       amdlibWriteOiVis2_FREEALL();
               amdlibReturnFitsError("TARGET_IDentity");
            }

            /* Write time */
            if (fits_write_col (filePtr, TDOUBLE, 2, iVis, 1, 1,
                                &(vis2->table[iVis-1].time), &status))
            {
                amdlibWriteOiVis2_FREEALL();
                amdlibReturnFitsError("Time");
            }
            if (fits_write_col (filePtr, TDOUBLE, 3, iVis, 1, 1,
                                &(vis2->table[iVis-1].dateObsMJD), &status))
            {
                amdlibWriteOiVis2_FREEALL();
                amdlibReturnFitsError("dateObsMJD");
            }
            expTime = (double)vis2->table[iVis-1].expTime;
            if (fits_write_col (filePtr, TDOUBLE, 4, iVis, 1, 1,
                                &expTime, &status))
            {
                amdlibWriteOiVis2_FREEALL();
                amdlibReturnFitsError("expTime");
            }

            /* Write squared visibility */
            if (fits_write_col(filePtr, TDOUBLE, 5, iVis, 1, nbWlen,
                               vis2->table[iVis-1].vis2+ lmin, &status))
            {
                amdlibWriteOiVis2_FREEALL();
                amdlibReturnFitsError("vis2");
            }
            /* Write error in the squared visibility */
            if (fits_write_col(filePtr, TDOUBLE, 6, iVis, 1, nbWlen,
                               vis2->table[iVis-1].vis2Error+ lmin, &status))
            {
                amdlibWriteOiVis2_FREEALL();
                amdlibReturnFitsError("vis2Error");
            }

            /* Write U and V coordinates on the data */
            if (fits_write_col (filePtr, TDOUBLE, 7, iVis, 1, 1,
                                &(vis2->table[iVis-1].uCoord), &status))
            {
                amdlibWriteOiVis2_FREEALL();
                amdlibReturnFitsError("uCoord");
            }
            if (fits_write_col (filePtr, TDOUBLE, 8, iVis, 1, 1,
                                &(vis2->table[iVis-1].vCoord), &status))
            {
                amdlibWriteOiVis2_FREEALL();
                amdlibReturnFitsError("vCoord");
            }

            /* Write station index */
            if (fits_write_col (filePtr, TINT, 9, iVis, 1, 2,
                                vis2->table[iVis-1].stationIndex, &status))
            {
                amdlibWriteOiVis2_FREEALL();
                amdlibReturnFitsError("stationIndex");
            }
            /* Write flag as unsigned char -- does not work with booleans */
            for (lVis = 0; lVis < nbWlen; lVis++)
            {
                flag[lVis] = vis2->table[iVis-1].flag[lVis+ lmin];
            }
            if (fits_write_col(filePtr, TLOGICAL, 10, iVis, 1, nbWlen, 
                               flag, &status))
            {
                amdlibWriteOiVis2_FREEALL();
                amdlibReturnFitsError("FLAG");
            }

        }
    }
    /* free allocated space at end of job */
    amdlibWriteOiVis2_FREEALL();
    return amdlibSUCCESS;
}

#define amdlibWriteOiVis3_FREEALL() free(flag); free(convertToDeg);
/**
 * Write OI_T3 table in OI-FITS file.
 *
 * This function writes the OI_T3 binary table defined in the IAU 
 * standard in the OI-FITS file given as parameter.
 *
 * @param filePtr pointer on the file to be created.
 * @param insName name of the detector.
 * @param arrName array name.
 * @param vis3 OI_T3 produced.
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibWriteOiVis3(fitsfile        *filePtr,
                                   char            *insName,
                                   char            *arrName,
                                   amdlibVIS3      *vis3,
                                   amdlibERROR_MSG errMsg, int lmin, int lmax)
{
    int        nbWlen =vis3->nbWlen-(lmin+lmax);
    int        lVis;
    int        status = 0;
    char       fitsioMsg[256];
    const int  tfields = 14;
    char       *ttype[] = {"TARGET_ID", "TIME", "MJD", "INT_TIME", "T3AMP", 
        "T3AMPERR", "T3PHI", "T3PHIERR", "U1COORD", "V1COORD", "U2COORD", 
        "V2COORD", "STA_INDEX", "FLAG"};
    char       *Tform[] = {"I", "D", "D", "D", "?D", "?D", "?D", "?D", "1D", 
        "1D", "1D", "1D", "3I", "?L"};
    char       *tform[tfields];
    char       *tunit[] = {"\0", "s", "day", "s", "\0", "\0", "deg", "deg",
        "m", "m", "m", "m", "\0", "\0"};
    char       extname[] = "OI_T3";
    int        revision = amdlib_OI_REVISION;
    int        i;
    char       tmp[16];
    double     expTime;
    double    *convertToDeg;
    unsigned char *flag;
    amdlibDOUBLE amdlibBval=amdlibBLANKING_VALUE;
    
    int        iFrame = 0, iClos = 0, iT3 = 0;

    amdlibLogTrace("amdlibWriteOiVis3()");

    /*Vis3 may not be initialized (only 1 baseline), this is not a defect*/
    if (vis3->thisPtr != vis3)
    {
        return amdlibSUCCESS;
    }
    /*If initialized but empty, do nothing gracefully*/
    if (vis3->nbFrames < 1)
    {
        return amdlibSUCCESS;
    }

    /* Create table structure. Make up Tform: substitute nbWlen for '?' */
    for (i = 0; i < tfields; i++) 
    {
        if (Tform[i][0] == '?') 
        {
            sprintf(tmp, "%d%s", nbWlen, &Tform[i][1]);
            tform[i] = calloc((strlen(tmp)+1), sizeof(char));
            strcpy(tform[i], tmp);
        }
        else
        {
            tform[i] = calloc((strlen(Tform[i])+1), sizeof(char));
            strcpy(tform[i], Tform[i]);
        }
    }
    if (fits_create_tbl(filePtr, BINARY_TBL, 0, tfields, ttype, tform, tunit,
                        extname, &status)) 
    {
        amdlibReturnFitsError("Creating Binary Table");
    }

    for (i = 0; i < tfields; i++) 
    {
        free(tform[i]);
    }

    /* Write revision number of the table definition */
    if (fits_write_key (filePtr, TINT, "OI_REVN", &revision,
                        "Revision number of the table definition", &status))
    {
        amdlibReturnFitsError("OI_REVN");
    }

    /* Give date */
    if (fits_write_key(filePtr, TSTRING,"DATE-OBS", vis3->dateObs,
                       "UTC start date of observations", &status))
    {
        amdlibReturnFitsError("DATE-OBS");
    }

    /* Write names of detector and array */
    if (fits_write_key (filePtr, TSTRING, "ARRNAME", arrName,
                        "Array Name", &status))
    {
        amdlibReturnFitsError("ARRNAME");
    }
    if (fits_write_key(filePtr, TSTRING, "INSNAME", insName, 
                       "Instrument name", &status))
    {
        amdlibReturnFitsError("INSNAME");
    }

    convertToDeg  = calloc(nbWlen, sizeof(double));
    flag = calloc(nbWlen, sizeof(unsigned char));

    /* Write columns */
    for (iFrame = 0; iFrame < vis3->nbFrames; iFrame++) 
    {
        for (iClos = 0; iClos < vis3->nbClosures; iClos++)
        {            
            /* Write dtarget identity */ 
            if (fits_write_col(filePtr, TINT, 1, iT3+1, 1, 1,
                               &(vis3->table[iT3].targetId), &status))
            {
                amdlibWriteOiVis3_FREEALL();
                amdlibReturnFitsError("TARGET_ID");
            }

            /* Write time */
            if (fits_write_col(filePtr, TDOUBLE, 2, iT3+1, 1, 1,
                               &(vis3->table[iT3].time), &status))
            {
                amdlibWriteOiVis3_FREEALL();
                amdlibReturnFitsError("TIME");
            }
            if (fits_write_col(filePtr, TDOUBLE, 3, iT3+1, 1, 1,
                               &(vis3->table[iT3].dateObsMJD), &status))
            {
                amdlibWriteOiVis3_FREEALL();
                amdlibReturnFitsError("DATEOBS");
            }
            expTime = (double)vis3->table[iT3].expTime;
            if (fits_write_col(filePtr, TDOUBLE, 4, iT3+1, 1, 1,
                               &expTime, &status))
            {
                amdlibWriteOiVis3_FREEALL();
                amdlibReturnFitsError("EXPTIME");
            }

            /* Write visibility amplitude and its associated error */
            if (fits_write_col(filePtr, TDOUBLE, 5, iT3+1, 1, nbWlen,
                               vis3->table[iT3].vis3Amplitude+ lmin, &status))
            {
                amdlibWriteOiVis3_FREEALL();
                amdlibReturnFitsError("VIS3AMP");
            }
            if (fits_write_col(filePtr, TDOUBLE, 6, iT3+1, 1, nbWlen,
                               vis3->table[iT3].vis3AmplitudeError+ lmin,
                               &status))
            {
                amdlibWriteOiVis3_FREEALL();
                amdlibReturnFitsError("VIS3AMPERR");
            }

            /* Write visibility phase and its associated error,
             * converted in degrees */
            for (lVis = 0; lVis < nbWlen; lVis++)
            {
                if(vis3->table[iT3].vis3Phi[lVis+lmin]!=amdlibBLANKING_VALUE)
                {
                    convertToDeg[lVis] = 180.0 / M_PI * 
                    vis3->table[iT3].vis3Phi[lVis+lmin];
                }
                else
                {
                    convertToDeg[lVis] =amdlibBLANKING_VALUE;
                }
                
            }
            if (fits_write_colnull(filePtr, TDOUBLE, 7, iT3+1, 1, nbWlen,
                               convertToDeg, &amdlibBval, &status))
            {
                amdlibWriteOiVis3_FREEALL();
                amdlibReturnFitsError("VIS3PHI");
            }
            for(lVis = 0; lVis < nbWlen; lVis++)
            {
                if(vis3->table[iT3].vis3PhiError[lVis + lmin]!=amdlibBLANKING_VALUE)
                {
                    convertToDeg[lVis] = 180.0 / M_PI * 
                    vis3->table[iT3].vis3PhiError[lVis + lmin];
                }
                else
                {
                    convertToDeg[lVis] =amdlibBLANKING_VALUE;
                }
            }
            if (fits_write_colnull(filePtr, TDOUBLE, 8, iT3+1, 1, nbWlen,
                               convertToDeg, &amdlibBval, &status))
            {
                amdlibWriteOiVis3_FREEALL();
                amdlibReturnFitsError("VIS3PHIERR");
            }

            /* Write U and V coordinates of the data */
            if (fits_write_col(filePtr, TDOUBLE, 9, iT3+1, 1, 1,
                               &(vis3->table[iT3].u1Coord), &status))
            {
                amdlibWriteOiVis3_FREEALL();
                amdlibReturnFitsError("U1COORD");
            }
            if (fits_write_col(filePtr, TDOUBLE, 10, iT3+1, 1, 1,
                               &(vis3->table[iT3].v1Coord), &status))
            {
                amdlibWriteOiVis3_FREEALL();
                amdlibReturnFitsError("V1COORD");
            }
            if (fits_write_col(filePtr, TDOUBLE, 11, iT3+1, 1, 1,
                               &(vis3->table[iT3].u2Coord), &status))
            {
                amdlibWriteOiVis3_FREEALL();
                amdlibReturnFitsError("U2COORD");
            }
            if (fits_write_col(filePtr, TDOUBLE, 12, iT3+1, 1, 1,
                               &(vis3->table[iT3].v2Coord), &status))
            {
                amdlibWriteOiVis3_FREEALL();
                amdlibReturnFitsError("V2COORD");
            }

            /* Write station index */
            if (fits_write_col(filePtr, TINT, 13, iT3+1, 1, 3,
                               vis3->table[iT3].stationIndex, &status))
            {
                amdlibWriteOiVis3_FREEALL();
                amdlibReturnFitsError("STATINDEX");
            }
            
            /* Write flag as unsigned char -- does not work with booleans */
            for (lVis = 0; lVis < nbWlen; lVis++)
            {
                flag[lVis] = vis3->table[iT3].flag[lVis + lmin];
            }
            if (fits_write_col(filePtr, TLOGICAL, 14, iT3+1, 1, nbWlen, 
                               flag, &status))
            {
                amdlibWriteOiVis3_FREEALL();
                amdlibReturnFitsError("FLAG");
            }

            iT3++;

        }
    }
    amdlibWriteOiVis3_FREEALL();

    return amdlibSUCCESS;
}

#define amdlibWriteAmberData_FREEALL() free(errTempVal);          \
    free(tempBandNumber); 
/**
 * Write AMBER_DATA table in OI-FITS file.
 *
 * This function writes the AMBER_DATA binary table in the OI-FITS file 
 * given as parameter.
 *
 * @param filePtr pointer on the file to be created.
 * @param insName name of the detector.
 * @param photometry photometry data.
 * @param vis AMBER_DATA produced.
 * @param pst piston data.
 * @param wave structure containing the wavelengths.
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibWriteAmberData(fitsfile         *filePtr,
                                      char             *insName,
                                      amdlibPHOTOMETRY *photometry,
                                      amdlibVIS        *vis,
                                      amdlibPISTON     *pst,
                                      amdlibWAVELENGTH *wave, 
                                      amdlibERROR_MSG  errMsg, int lmin, int lmax)
{
    int        nbWlen = photometry->nbWlen-(lmin + lmax);
    int        status = 0;
    char       fitsioMsg[256];
    const int  tfields = 13;
    amdlibDOUBLE amdlibBval=amdlibBLANKING_VALUE;
    char       *Ttype[] = 
    {
        "TARGET_ID", "TIME", "MJD", "INT_TIME", 
        "FLUX_SUM", "FLUX_SUM_CORRECTION", "FLUX_RATIO", 
        "FLUX_RATIO_CORRECTION", "FLUX_PRODUCT", 
        "OPD", "OPD_ERR", "FRINGE_SNR", "STA_INDEX"
    };
    
    char       *Tform[] = {"I", "D", "D", "D", "?D", "?D", "?D", "?D", "?D", 
        "?E", "?E", "?D", "2I"};
    
    char       *Tunit[] = {"\0", "s", "day", "s", "e-", "e-", "\0", "\0",
        "e-^2", "m", "m", "\0", "\0"};
    
    char       *ttype[tfields];
    char       *tform[tfields];
    char       *tunit[tfields];
    
    char       extname[] = "AMBER_DATA";
    int        revision = amdlib_OI_REVISION;
    int        colNum;
    int        i;
    int        lVis;
    char       tmp[16];
    int        iFrame = 0, iBase = 0, iVis = 0;
    double     *errTempVal;
    int        *tempBandNumber;
    int        band, iBand;
    int        nbBands = 0;
    amdlibBAND_DESC *bandDesc;
    amdlibDOUBLE      bound;
    char       keyName[amdlibKEYW_NAME_LEN+1];
    double     expTime;
    amdlibDOUBLE      pistonTmpVal[amdlibNB_BANDS];
    amdlibDOUBLE      pstErrTmpVal[amdlibNB_BANDS];
    double     frgTmpVal[amdlibNB_BANDS];

    amdlibLogTrace("amdlibWriteAmberData()");

    if (vis->thisPtr != vis) 
    {
        amdlibSetErrMsg("Unitialized vis structure");
        return amdlibFAILURE; 
    }

    /*If initialized but empty, do nothing gracefully*/
    if (vis->nbFrames < 1)
    {
        return amdlibSUCCESS;
    }
    
    if (photometry->thisPtr != photometry) 
    {
        amdlibSetErrMsg("Unitialized photometry structure");
        return amdlibFAILURE; 
    }
    /*If initialized but empty, do nothing gracefully*/
    if (photometry->nbFrames < 1)
    {
        return amdlibSUCCESS;
    }

    if (pst->thisPtr != pst) 
    {
        amdlibSetErrMsg("Unitialized piston structure");
        return amdlibFAILURE; 
    }
    /*If initialized but empty, do nothing gracefully*/
    if (pst->nbFrames < 1)
    {
        return amdlibSUCCESS;
    }

    /* Create table structure. Make up Tform: substitute nbWlen or
     * nbBands for '?' */
    /* (informational) check number of bands */
    nbBands=0;
    for (band = amdlibJ_BAND; band <= amdlibK_BAND; band++)
    {
        if (pst->bandFlag[band] == amdlibTRUE)
        {
            nbBands++;
        }
    }    
    for (i = 0; i < tfields; i++) 
    {
        ttype[i] = calloc(strlen(Ttype[i])+1, sizeof(char));
        strcpy(ttype[i],Ttype[i]);
        tunit[i] = calloc(strlen(Tunit[i])+1, sizeof(char));
        strcpy(tunit[i],Tunit[i]);
        
        if (Tform[i][0] == '?') 
        {
            if ((i == 9) || (i == 10) || (i == 11))
            {
                sprintf(tmp, "%d%s", nbBands, &Tform[i][1]);
            }
            else
            {
                sprintf(tmp, "%d%s", nbWlen, &Tform[i][1]);
            }
            tform[i] = calloc((strlen(tmp)+1), sizeof(char));
            strcpy(tform[i], tmp);
        }
        else
        {
            tform[i] = calloc((strlen(Tform[i])+1), sizeof(char));
            strcpy(tform[i], Tform[i]);
        }
    }

    if (fits_create_tbl(filePtr, BINARY_TBL, 0, tfields, ttype, tform, tunit,
                        extname, &status)) 
    {
        amdlibReturnFitsError("Creating Binary Table");
    }

    for (i = 0; i < tfields; i++)
    {
        free(ttype[i]);
        free(tform[i]);
        free(tunit[i]);
    }

    /* Give date */
    if (fits_write_key(filePtr, TSTRING, "DATE-OBS", vis->dateObs,
                       "UTC start date of observations", &status))
    {
        amdlibReturnFitsError("DATE-OBS");
    }

    /* Write detector name */
    if (fits_write_key (filePtr, TSTRING, "INSNAME", insName,
                        "Instrument name", &status))
    {
        amdlibReturnFitsError("INSNAME");
    }

    /* Write revision number of the table definition */
    if (fits_write_key (filePtr, TINT, "AMB_REVN", &revision,
                        "Revision number of the table definition", &status))
    {
        amdlibReturnFitsError("AMB_REVN");
    }

    /* Write information on spectral bands. */
    if (fits_write_key (filePtr, TINT, "HIERARCH ESO QC NBBANDS", 
                        &nbBands, "Number of spectral bands", &status))
    {
        amdlibReturnFitsError("HIERARCH ESO QC NBBANDS");
    }

    iBand = 1;
    for (band = amdlibJ_BAND; band <= amdlibK_BAND; band++)
    {
        if (pst->bandFlag[band] == amdlibTRUE)
        {
            bandDesc = amdlibGetBandDescription(band);
            sprintf(keyName, "HIERARCH ESO QC BAND%d NAME", iBand);
            if (fits_write_key(filePtr, TSTRING, keyName, bandDesc->name, 
                               "Name of spectral band", &status))
            {
                amdlibReturnFitsError(keyName);
            }
            memset(keyName, '\0', (amdlibKEYW_NAME_LEN+1) * sizeof(char));
            sprintf(keyName, "HIERARCH ESO QC BAND%d LOWBOUND", iBand);
            bound = bandDesc->lowerBound/1000;
            if (fits_write_key(filePtr, TDOUBLE, keyName, &bound,
                               "Lower bound of spectral band", &status))
            {
                amdlibReturnFitsError(keyName);
            }
            memset(keyName, '\0', (amdlibKEYW_NAME_LEN+1) * sizeof(char));
            sprintf(keyName, "HIERARCH ESO QC BAND%d UPBOUND", iBand);
            bound = bandDesc->upperBound/1000;
            if (fits_write_key(filePtr, TDOUBLE, keyName, &bound, 
                               "Upper bound of spectral band", &status))
            {
                amdlibReturnFitsError(keyName);
            }
            iBand++;
        }

    }

    errTempVal = calloc(nbWlen, sizeof(double));
    tempBandNumber = calloc(nbWlen, sizeof(int));
    /* Retrieve Band number for each Wlen */
    for (lVis = 0; lVis < nbWlen; lVis++)
    {
        tempBandNumber[lVis] = amdlibGetBand(wave->wlen[lVis+lmin]);
    }

    /* Write columns */
    for (iFrame = 0; iFrame < vis->nbFrames; iFrame++) 
    {
        for (iBase = 0; iBase < vis->nbBases; iBase++)
        {
            colNum = 1;
            iVis++;
            /* Write target identity */
            if (fits_write_col(filePtr, TINT, colNum, iVis, 1, 1,
                               &(vis->table[iVis-1].targetId), &status))
            {
                amdlibWriteAmberData_FREEALL();
                amdlibReturnFitsError("Target Id");
            }
            colNum++;

            /* Write time */
            if (fits_write_col(filePtr, TDOUBLE, colNum, iVis, 1, 1,
                               &(vis->table[iVis-1].time), &status))
            {
                amdlibWriteAmberData_FREEALL();
                amdlibReturnFitsError("Time");
            }
            colNum++;
            
            if (fits_write_col(filePtr, TDOUBLE, colNum, iVis, 1, 1,
                               &(vis->table[iVis-1].dateObsMJD), &status))
            {
                amdlibWriteAmberData_FREEALL();
                amdlibReturnFitsError("dateObsMJD");
            }
            colNum++;

            expTime = (double)vis->table[iVis-1].expTime;
            if (fits_write_col(filePtr, TDOUBLE, 4, iVis, 1, 1,
                               &expTime, &status))
            {
                amdlibWriteAmberData_FREEALL();
                amdlibReturnFitsError("expTime");
            }
            colNum++;
            
            /* Write information on photometry */
            /* Write fluxSumPiPj (number of electrons collected in the spectral
             * channel) and associated error */
            if (fits_write_colnull(filePtr, TDOUBLE, colNum, iVis, 1, nbWlen,
                               photometry->table[iVis-1].fluxSumPiPj+lmin,
				   &amdlibBval, &status))
            {
                amdlibWriteAmberData_FREEALL();
                amdlibReturnFitsError("baseFluxSum");
            }
            colNum++;
            
            for (lVis = 0; lVis < nbWlen; lVis++)
            {
                if (!(photometry->table[iVis-1].sigma2FluxSumPiPj[lVis+lmin] == 
                         amdlibBLANKING_VALUE))
                {
                    errTempVal[lVis] = 
                        sqrt(photometry->table[iVis-1].sigma2FluxSumPiPj[lVis+lmin]);
                }
                else 
                {
                    errTempVal[lVis] = amdlibBLANKING_VALUE;
                }
            }

            if (fits_write_colnull(filePtr, TDOUBLE, colNum, iVis, 1, nbWlen,
                               errTempVal, &amdlibBval, &status))
            {
                amdlibWriteAmberData_FREEALL();
                amdlibReturnFitsError("baseFluxSumCorrection");
            }
            colNum++;

            /* Write flux ratio in the spectral channel and associated error */
            if (fits_write_colnull(filePtr, TDOUBLE, colNum, iVis, 1, nbWlen,
                               photometry->table[iVis-1].fluxRatPiPj+lmin,
				   &amdlibBval, &status))
            {
                amdlibWriteAmberData_FREEALL();
                amdlibReturnFitsError("baseFluxRatio");
            }
            colNum++;

            for (lVis = 0; lVis < nbWlen; lVis++)
            {
                if (!(photometry->table[iVis-1].sigma2FluxRatPiPj[lVis+lmin] == 
                         amdlibBLANKING_VALUE)) 
                {
                    errTempVal[lVis]=
                        sqrt(photometry->table[iVis-1].sigma2FluxRatPiPj[lVis+lmin]);
                }
                else 
                {
                    errTempVal[lVis] = amdlibBLANKING_VALUE;
                }
            }
            if (fits_write_colnull(filePtr, TDOUBLE, colNum, iVis, 1, nbWlen,
                               errTempVal, &amdlibBval, &status))
            {
                amdlibWriteAmberData_FREEALL();
                amdlibReturnFitsError("baseFluxRatioCorrection");
            }
            colNum++;
            
            /* Write flux product */
            if (fits_write_colnull(filePtr, TDOUBLE, colNum, iVis, 1, nbWlen,
                               photometry->table[iVis-1].PiMultPj+lmin, 
				   &amdlibBval, &status))
            {
                amdlibWriteAmberData_FREEALL();
                amdlibReturnFitsError("baseFluxProduct");
            }
            colNum++;
            
            /* Write piston value and associated error */       
            int i = 0;
            for (band = amdlibJ_BAND; band <= amdlibK_BAND; band++)
            {
                if (pst->bandFlag[band] == amdlibTRUE )
                {
                    if (!amdlibCompareDouble(pst->pistonOPDArray[band][iVis-1],amdlibBLANKING_VALUE))
                    {
                        pistonTmpVal[i] = pst->pistonOPDArray[band][iVis-1] * 
                        amdlibNM_TO_M;
                        pstErrTmpVal[i] = pst->sigmaPistonArray[band][iVis-1] * 
                        amdlibNM_TO_M;
                    }
                    else
                    {
                        pistonTmpVal[i] = amdlibBLANKING_VALUE;
                        pstErrTmpVal[i] = amdlibBLANKING_VALUE;
                    }
                    frgTmpVal[i] = vis->table[iVis-1].frgContrastSnrArray[band];
                    i++;
                }
            }
            if (fits_write_colnull(filePtr, TDOUBLE, colNum, iVis, 1, nbBands,
                               pistonTmpVal, &amdlibBval, &status))
            {
                amdlibWriteAmberData_FREEALL();
                amdlibReturnFitsError("piston");
            }
            colNum++;
            
            if (fits_write_colnull(filePtr, TDOUBLE, colNum, iVis, 1, nbBands,
                               pstErrTmpVal, &amdlibBval, &status))
            {
                amdlibWriteAmberData_FREEALL();
                amdlibReturnFitsError("pistonErr");
            }
            colNum++;

            /* Write fringe contrast SNR */       
            if (fits_write_col(filePtr, TDOUBLE, colNum, iVis, 1, nbBands,
                               frgTmpVal, &status))
            {
                amdlibWriteAmberData_FREEALL();
                amdlibReturnFitsError("frgContrastSnr");
            }
            colNum++;
            
            /* Write station idexes corresponding to the baseline */       
            if (fits_write_col(filePtr, TINT, colNum, iVis, 1, 2,
                               vis->table[iVis-1].stationIndex, &status))
            {
                amdlibWriteAmberData_FREEALL();
                amdlibReturnFitsError("stationIndex");
            }
            colNum++;
        }
    }
    amdlibWriteAmberData_FREEALL();
    return amdlibSUCCESS;
}
#undef   amdlibWriteAmberData_FREEALL


/**
 * Write AMBER_SPECTRUM table in OI-FITS file.
 *
 * This function writes the AMBER_SPECTRUM binary table in the OI-FITS file
 * given as parameter.
 *
 * @param filePtr pointer on the file to be created.
 * @param wave OI_WAVELENGTH produced.
 * @param spc amdlibSPECTRUM produced.
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
static amdlibCOMPL_STAT esolibamdlibWriteAmberSpectrum(fitsfile         *filePtr,
                                          amdlibWAVELENGTH *wave,
                                          amdlibSPECTRUM   *spc,
                                          amdlibERROR_MSG  errMsg, int lmin, int lmax)
{
    int        status = 0;
    char       fitsioMsg[256];
    const int  tfields = 4;
    char       *Ttype[] = {"EFF_WAVE", "EFF_BAND", "SPECTRUM", "SPECTRUM_ERROR"};
    char       *Tform[] = {"E", "E", "?D", "?D"};
    char       *Tunit[] = {"m", "m", "\0", "\0"};
    char       *ttype[tfields];
    char       *tform[tfields];
    char       *tunit[tfields];
    
    char       extname[] = "AMBER_SPECTRUM";
    int        revision  = amdlib_OI_REVISION;
    int        i, l;
    char       tmp[16];
    double     specDouble[amdlibNB_TEL];
    double     specErrDouble[amdlibNB_TEL];
    char       insName[amdlibKEYW_VAL_LEN+1];

    amdlibLogTrace("amdlibWriteAmberSpectrum()");

    if (spc->thisPtr != spc) 
    {
        amdlibSetErrMsg("Unitialized spc structure");
        return amdlibFAILURE;
    }

    /* If initialized but empty, do nothing gracefully */
    if (spc->nbWlen < 1)
    {
        return amdlibSUCCESS;
    }
    
    /* Create table structure. Make up Tform: substitute spc->nbTels for '?' */
    for (i = 0; i < tfields; i++) 
    {
        ttype[i] = calloc(strlen(Ttype[i]) + 1, sizeof(char));
        strcpy(ttype[i], Ttype[i]);
        tunit[i] = calloc(strlen(Tunit[i]) + 1, sizeof(char));
        strcpy(tunit[i], Tunit[i]);
        
        if (Tform[i][0] == '?') 
        {
            sprintf(tmp, "%d%s", spc->nbTels, &Tform[i][1]);
        }
        else
        {
            strcpy(tmp, Tform[i]);
        }
        tform[i] = calloc((strlen(tmp) + 1), sizeof(char));
        strcpy(tform[i], tmp);
    }

    if (fits_create_tbl(filePtr, BINARY_TBL, 0, tfields, ttype, tform, tunit,
                        extname, &status)) 
    {
        amdlibReturnFitsError("Creating Binary Table");
    }

    for (i = 0; i < tfields; i++)
    {
        free(ttype[i]);
        free(tform[i]);
        free(tunit[i]);
    }
    
    /* Write revision number of the table definition */
    if (fits_write_key (filePtr, TINT, "AMB_REVN", &revision,
                        "Revision number of the table definition", &status))
    {
        amdlibReturnFitsError("AMB_REVN");
    }

    /* Write spectral setup unique identifier */
    sprintf(insName, "AMBER");
    if (fits_write_key (filePtr, TSTRING, "INSNAME", insName,
                        "Instrument name", &status)) 
    {
        amdlibReturnFitsError("INSNAME");
    }

    for (l=0; l < wave->nbWlen-(lmin+lmax); l++)
    {
        amdlibDOUBLE waveInM;
        waveInM = wave->wlen[l+lmin] * amdlibNM_TO_M; /* Convert nm to m */
        if (fits_write_col(filePtr, TDOUBLE, 1, l+1, 1, 1, &waveInM, &status))
        {
            amdlibReturnFitsError("EFF_WAVE");
        }

        amdlibDOUBLE bandInM;
        bandInM = wave->bandwidth[l+lmin] * amdlibNM_TO_M; /* Convert nm to m */
        if (fits_write_col(filePtr, TDOUBLE, 2, l+1, 1, 1, &bandInM, &status))
        {
            amdlibReturnFitsError("EFF_BAND");
        }

        for (i=0; i < spc->nbTels; i++)
        { 
            specDouble[i] = spc->spec[i][l+lmin];
            specErrDouble[i] = spc->specErr[i][l+lmin];
        }
        if (fits_write_col(filePtr, TDOUBLE, 3, l+1, 1, spc->nbTels,
                           specDouble, &status))
        {
            amdlibReturnFitsError("SPECTRUM");
        }
        
        if (fits_write_col(filePtr, TDOUBLE, 4, l+1, 1, spc->nbTels,
                           specErrDouble, &status))
        {
            amdlibReturnFitsError("SPECTRUM_ERROR");
        }
    }

    return amdlibSUCCESS;
}
