/*******************************************************************************
 * JMMC project ( http://www.jmmc.fr ) - Copyright (C) CNRS.
 ******************************************************************************/
/**
 * @file
 * Functions to handle regions. 
 *
 * The amdlibREGION structures are these containing all information describing
 * regions of the detector and coming from either IMAGING_DETECTOR or 
 * IMAGING_DATA binary tables. 
 */

#define _POSIX_SOURCE 1

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

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

/* 
 * Protected functions 
 */
/**
 * Allocate memory for storing information of regions. 
 *
 * This function allocates memory for storing information (not the corresponding
 * data) of N regions. 
 *
 * @param regions array of amdlibREGION structures allocated.
 * @param nbRegions size of the regions array.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibAllocateRegions(amdlibREGION **regions,
                                       int          nbRegions)
{
    amdlibLogTrace("amdlibAllocateRegions()");

    /* First free previous allocated memory */
    amdlibFreeRegions(regions, nbRegions);

    /* Allocates memory to store region information (not data) */
    *regions = calloc(sizeof(amdlibREGION), nbRegions);
    if (*regions == NULL)
    {
        return amdlibFAILURE;
    }

    return amdlibSUCCESS;
}

/**
 * Free memory allocated for regions. 
 *
 * This function frees memory corresponding to the table of regions, with the 
 * associated memory containing the dectector data.
 * 
 * @param regions array of amdlibREGION structures to free.
 * @param nbRegions size of the regions array.
 */
void amdlibFreeRegions(amdlibREGION *regions[], int nbRegions )
{
    amdlibREGION *regionTab;

    amdlibLogTrace("amdlibFreeRegions()");
    
    /* Free allocated memory */
    if (*regions != NULL)
    {
        int i;
        regionTab = *regions; 
        for (i = 0; i < nbRegions; i++)
        {
            if (regionTab[i].data != NULL)
            {
                free(regionTab[i].data);
                regionTab[i].data = NULL;
            }
        }
        free(*regions);
        *regions = NULL;
    }
}

/**
 * Read information and store it into regions. 
 *
 * This function reads information about the regions from header of the 
 * IMAGING_DETECTOR binary table and store them into the corresponding data 
 * structures. This concerns, for each region, to :
 *      \li the region number
 *      \li the detector number
 *      \li the entrance ports 
 *      \li the correlation type
 *      \li the region name
 *      \li the corner position
 *      \li the gain
 *      \li the dimension of the data array
 *      \li the reference pixel information
 *
 * @param filePtr name of the FITS file containing raw data.
 * @param regions array of regions to store information.
 * @param nbRegions size of the regions array.
 * @param nbTel number of telescopes used.
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibReadRegionInfo(fitsfile        *filePtr, 
                                      amdlibREGION    **regions, 
                                      int             nbRegions,
                                      int             nbTel,
                                      amdlibERROR_MSG errMsg)
{
#define amdlibIMAGING_DETECTOR_NB_COLS 10
    int          status = 0;
    int          anynull = 0;
    int          intValues[256];
    double       doubleValues[256];
    char         fitsioMsg[256];
    int          i, j;
    int          nbFrames;
    int          colNum[amdlibIMAGING_DETECTOR_NB_COLS];
    char*        colName[amdlibIMAGING_DETECTOR_NB_COLS] = { 
        "REGION  ", "DETECTOR", "PORTS   ", "CORRELATION", 
        "REGNAME ", "CORNER  ", "GAIN    ", "NAXIS   ", 
        "CRVAL   ", "CRPIX   "}; 
        /*, "CTYPE   ", "CD      ", "DMP     ", "DMC     " */
    amdlibREGION *regionTab;

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

    /* Allocates memory for 'region information' */
    if (amdlibAllocateRegions(regions, nbRegions) != amdlibSUCCESS)
    {
        amdlibSetErrMsg("Could not allocate memory for regions");
        return amdlibFAILURE;       
    }
    regionTab = *regions; 

    /* Go to the IMAGING_DETECTOR binary table */
    if (fits_movnam_hdu(filePtr, BINARY_TBL, 
                        "IMAGING_DETECTOR", 0, &status) != 0)
    {
        amdlibReturnFitsError("IMAGING_DETECTOR");
    }
    /* Retreive the column number */
    for (i=0;i<amdlibIMAGING_DETECTOR_NB_COLS;i++) 
    {
        if (fits_get_colnum(filePtr,CASESEN,colName[i],&colNum[i],&status) != 0)
        {
            /* Special treatment for 'NAXIS' column: The name of the column of
             * the first files created by AMBER DCS is 'NAXES' instead of
             * 'NAXIS' */
            if (strncmp(colName[i], "NAXIS", 5) == 0)
            {
                status = 0;
                if (fits_get_colnum(filePtr,CASESEN, "NAXES" ,
                                    &colNum[i], &status) != 0)
                {
                    amdlibReturnFitsError(colName[i]);
                }
            }
            else
            {
                amdlibReturnFitsError(colName[i]);
            }
        } 
    }
    /* Get region numbers */
    if (fits_read_col(filePtr, TINT, colNum[0], 1, 1, nbRegions, NULL,
                      (void *)intValues, &anynull, &status) != 0)
    {
        amdlibReturnFitsError(colName[0]);
    } 
    for(i = 0; i < nbRegions; i++)
    {
        regionTab[i].regionNumber = intValues[i];
    }

    /* Get detector number */
    if (fits_read_col(filePtr, TINT,  colNum[1], 1, 1, nbRegions, NULL,
                      (void *)intValues, &anynull, &status) != 0)
    {
        amdlibReturnFitsError(colName[1]);
    } 
    for(i = 0; i < nbRegions; i++)
    {
        regionTab[i].detectorNumber = intValues[i];
    }

    /* Get entrance ports */
    if (fits_read_col(filePtr, TINT, colNum[2] , 1, 1, nbTel*nbRegions, NULL,
                      (void *)intValues, &anynull, &status) != 0)
    {
        amdlibReturnFitsError(colName[2]);
    } 
    for(i = 0; i < nbRegions; i++)
    {
        for (j = 0; j < amdlibNB_TEL; j++)
        {
            regionTab[i].ports[j] = 0;
        }
        for (j = 0; j < nbTel; j++)
        {
            regionTab[i].ports[j] = intValues[nbTel*i + j];
        }
    }

    /* Get correlation type */
    if (fits_read_col(filePtr, TINT,  colNum[3], 1, 1, nbRegions, NULL,
                      (void *)intValues, &anynull, &status) != 0)
    {
        amdlibReturnFitsError(colName[3]);
    } 
    for(i = 0; i < nbRegions; i++)
    {
        regionTab[i].correlation = intValues[i];
    }

    /* Get region name */
    for(i = 0; i < nbRegions; i++)
    {
        char  buffer[4096];
        char  *ptr[1];

        memset(buffer, '\0', sizeof(buffer));
        ptr[0] = buffer;
        if (fits_read_col(filePtr, TSTRING,  colNum[4], (i+1), 1, 1, (char *)"",
                          ptr, &anynull, &status) != 0) 
        {
            amdlibReturnFitsError(colName[4]);
        } 
        strncpy (regionTab[i].regionName,ptr[0], 15); 
    }

    /* Get corner position */
    if (fits_read_col(filePtr, TINT,  colNum[5], 1, 1, 2*nbRegions, NULL,
                      (void *)intValues, &anynull, &status) != 0)
    {
        amdlibReturnFitsError(colName[5]);
    } 
    for(i = 0; i < nbRegions; i++)
    {
        for (j = 0; j < 2; j++)
        {
            regionTab[i].corner[j] = intValues[2*i + j];
        }
    }

    /* Get gain or set gain if overriden by user */
    if (amdlibGetUserPref(amdlibUSE_GAIN).set==amdlibTRUE)
        for(i = 0; i < nbRegions; i++)
        {
            regionTab[i].gain = amdlibGetUserPref(amdlibUSE_GAIN).value;
        }
    else
    {
        if (fits_read_col(filePtr, TDOUBLE,  colNum[6], 1, 1, nbRegions, NULL,
                          (void *)doubleValues, &anynull, &status) != 0)
        {
            amdlibReturnFitsError(colName[6]);
        } 
        for(i = 0; i < nbRegions; i++)
        {
            regionTab[i].gain = doubleValues[i];
        }
    }

    /* Get dimension of the data array */
    if (fits_read_col(filePtr, TINT,  colNum[7], 1, 1, 2*nbRegions, NULL,
                      (void *)intValues, &anynull, &status) != 0)
    {
        amdlibReturnFitsError(colName[7]);
    } 
    for(i = 0; i < nbRegions; i++)
    {
        for (j = 0; j < 2; j++)
        {
            regionTab[i].dimAxis[j] = intValues[2*i + j];
        }
    }

    /* Get coordinates at the reference pixels */
    if (fits_read_col(filePtr, TDOUBLE,  colNum[8], 1, 1, 2*nbRegions, NULL,
                      (void *)doubleValues, &anynull, &status) != 0)
    {
        amdlibReturnFitsError(colName[8]);
    } 
    for(i = 0; i < nbRegions; i++)
    {
        for (j = 0; j < 2; j++)
        {
            regionTab[i].crVal[j] = doubleValues[2*i + j];
        }
    }
    /* Get the reference pixels */
    if (fits_read_col(filePtr, TDOUBLE,  colNum[9], 1, 1, 2*nbRegions, NULL,
                      (void *)doubleValues, &anynull, &status) != 0)
    {
        amdlibReturnFitsError(colName[9]);
    } 
    for(i = 0; i < nbRegions; i++)
    {
        for (j = 0; j < 2; j++)
        {
            regionTab[i].crPix[j] = doubleValues[2*i + j];
        }
    }

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

    /* Get the number of frames in the table */
    if (fits_read_key(filePtr, TINT, "NAXIS2", &nbFrames,
                      NULL, &status) != 0)
    {
        amdlibReturnFitsError("NAXIS2");
    }  
    for(i = 0; i < nbRegions; i++)
    {
        regionTab[i].dimAxis[2] = nbFrames;
    }

    return amdlibSUCCESS;
}

/**
 * Read detector data and store them into regions. 
 *
 * This function reads the detector data, in the IMAGING_DATA binary table, 
 * for all regions and store them into the corresponding data structures.

 * @param filePtr name of the FITS file containing raw data.
 * @param regions array of regions to store information.
 * @param nbRegions size of the regions array.
 * @param firstFrame first frame to read.
 * @param nbFrames number of frames.
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibReadRegionData (fitsfile        *filePtr, 
                                      amdlibREGION    *regions, 
                                      int             nbRegions,
                                      int             firstFrame,
                                      int             nbFrames,
                                      amdlibERROR_MSG errMsg)
{
    int        status = 0;
    int        anynull = 0;
    char       fitsioMsg[256];
    int        i, j;
    int        nbFields;
    char       fieldName[32];
    char       key[16];

    amdlibLogTrace("amdlibReadRegionData()");
    
    /* Check first frame index and number of frames to be read */
    if ((firstFrame < 1) || (firstFrame > regions[0].dimAxis[2]))
    {
        amdlibSetErrMsg("Invalid first frame index '%d'"
                        " Should be in [1..%d] range", firstFrame, 
                        regions[0].dimAxis[2]);
        return amdlibFAILURE;
    }
    if ((nbFrames < 0) || (nbFrames > (regions[0].dimAxis[2] - firstFrame + 1)))
    {
        amdlibSetErrMsg("Invalid number of frames to be "
                        "read '%d'. Should be in [1..%d] range", nbFrames, 
                        (regions[0].dimAxis[2] - firstFrame + 1));
        return amdlibFAILURE;
    }

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

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

    /* Get the number of fields in the table */
    if (fits_read_key(filePtr, TINT, "TFIELDS", &nbFields,
                      NULL, &status) != 0)
    {
        amdlibReturnFitsError("TFIELDS");
    }   

    /* For each field in the table */
    for(i = 0; i < nbFields; i++)
    {
        /* Get the name of the field */
        sprintf(key, "TTYPE%d", i + 1);
        status = 0;
        if (fits_read_key(filePtr, TSTRING, key, fieldName,
                          NULL, &status) != 0)
        {
            amdlibReturnFitsError(key);
        }

        /* If this name matches with a region name */
        for (j=0; j < nbRegions; j++)
        {
            if (strcmp(regions[j].regionName, fieldName) == 0)
            {
                int imageSize;
                int nbOfElements;

                /* Compute the size of each image */
                imageSize = regions[j].dimAxis[0] * regions[j].dimAxis[1];
                
                /* Compute the number of elements in the data array */
                nbOfElements = imageSize * nbFrames;

                /* Allocate memory for data */
                regions[j].data = calloc(sizeof(amdlibDOUBLE), nbOfElements);
                if ( regions[j].data == NULL)
                {
                    amdlibSetErrMsg("Could not allocate memory for regions");
                    return amdlibFAILURE;
                }
                
                /* Load the corresponding data */
                if (fits_read_col
                    (filePtr, TDOUBLE, i+1, firstFrame, 1, nbOfElements, NULL,
                     (void *)regions[j].data, &anynull, &status) != 0)
                {
                    amdlibReturnFitsError("DATAi");
                }

                /* Set again the number of frames. It can differ from the
                 * number of frames available in file */
                regions[j].dimAxis[2] = nbFrames;

                break;
            }
        }
    }

    return amdlibSUCCESS;
}

/**
 * Sets regions data in IMAGING_DATA binary table of the specified file. 
 *
 * This function simply substitutes the detector data stored in the 
 * IMAGING_DATA binary table of the specified file, with the data contained 
 * in the region data structure. 
 *
 * @warning No check is performed concerning the dimension of data array.
 * 
 * @param filePtr name of the FITS file to be modified.
 * @param regions array of regions where information is.
 * @param nbRegions size of the regions array.
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibWriteRegionData (fitsfile        *filePtr, 
                                      amdlibREGION    *regions, 
                                      int             nbRegions,
                                      amdlibERROR_MSG errMsg)
{
    int     status = 0;
    char    fitsioMsg[256];
    int     i, j;
    int     nbFields;
    char    fieldName[32];
    char    key[16];
    int     nbFrames = 0;
    int     pCount,tableWidth;
    
    amdlibLogTrace("amdlibWriteRegionData()");
    
    /* Reset error message */
    memset(errMsg, '\0', sizeof(amdlibERROR_MSG));

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

    if (fits_read_key(filePtr, TINT, "TFIELDS", &nbFields,
                      NULL, &status) != 0)
    {
        amdlibReturnFitsError("TFIELDS");
    }   

    /* For each field in the table */
    for(i = 0; i < nbFields; i++)
    {
        /* Get the name of the field */
        sprintf(key, "TTYPE%d", i + 1);
        status = 0;
        if (fits_read_key(filePtr, TSTRING, key, fieldName,
                          NULL, &status) != 0)
        {
            amdlibReturnFitsError(key);
        }

        /* If this name matches with a region name */
        for (j=0; j < nbRegions; j++)
        {
            if (strcmp(regions[j].regionName, fieldName) == 0)
            {
                int nbOfElements;

                /* Compute the number of elements in the data array */
                nbOfElements = regions[j].dimAxis[0] *
                    regions[j].dimAxis[1] *
                    regions[j].dimAxis[2];

                /* Check data pointer */
                if (regions[j].data == NULL)
                {
                    amdlibSetErrMsg("The pointer to " 
                                    "the data of region #%d is invalid", j); 
                    return amdlibFAILURE;
                }

                /* Load the corresponding data */
                if (fits_write_col
                    (filePtr, TDOUBLE, i+1, 1, 1, nbOfElements, 
                     (void *)regions[j].data, &status) != 0)
                {
                    amdlibReturnFitsError("DATAi");
                }

                break;
            }
        }
    }
    /* Update the number of frames in the table, in case NULL frames were 
     * supressed */
    /* Get the number of frames in the table */
    if (fits_read_key(filePtr, TINT, "NAXIS2", &nbFrames,
                      NULL, &status) != 0)
    {
        amdlibReturnFitsError("NAXIS2");
    }  
    if (nbFrames!=regions[0].dimAxis[2])
    {
        amdlibLogWarning("Shortening the output file");
        /* trick to write in place: Update PCOUNT to good value to fill up 
         * table until next*/
        if (fits_read_key(filePtr, TINT, "NAXIS1", &tableWidth,
                          NULL, &status) != 0)
        {
            amdlibReturnFitsError("NAXIS1");
        }  
        if (fits_read_key(filePtr, TINT, "PCOUNT", &pCount,
                          NULL, &status) != 0)
        {
            amdlibReturnFitsError("PCOUNT");
        }  
        pCount+=(nbFrames-regions[0].dimAxis[2])*tableWidth;
        nbFrames=regions[0].dimAxis[2];
        if (fits_modify_key_lng(filePtr, "NAXIS2", nbFrames, NULL, 
                                &status) != 0)
        {
            amdlibReturnFitsError("NAXIS2");
        }  
        if (fits_modify_key_lng(filePtr, "PCOUNT", pCount, NULL, &status) != 0)
        {
            amdlibReturnFitsError("NAXIS2");
        }  
    }
    
    return amdlibSUCCESS;
}

/*___oOo___*/
