/*******************************************************************************
 * JMMC project ( http://www.jmmc.fr ) - Copyright (C) CNRS.
 ******************************************************************************/
/**
 * @file
 * Functions to handle flat-field map. 
 *
 * The flat field map contains for each pixel a factor which gives the gain of
 * that pixel according to the average gain.
 */

#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"

/*
 * Global variables
 */
static amdlibFLAT_FIELD_MAP amdlibFlatFieldMap = {amdlibFALSE};

/* 
 * Public functions 
 */
/**
 * Set flat-field map to the given value. 
 *
 * This function set all gains of the flat-field map to the given value. 
 *
 * @param value gain value.
 *
 * @return
 * Always return amdlibSUCCESS
 */
amdlibCOMPL_STAT amdlibSetFlatFieldMap(double value)
{
    int x, y;

    amdlibLogTrace("amdlibSetFlatFieldMap()"); 
    
    /* Set all gains to the given value */
    for (y = 0; y < amdlibDET_SIZE_Y; y++)
    {
        for (x = 0; x < amdlibDET_SIZE_X; x++)
        {
            amdlibFlatFieldMap.data[y][x] = value;
        }
    }

    amdlibFlatFieldMap.mapIsInitialized = amdlibTRUE;

    return amdlibSUCCESS;
}

/**
 * Load flat-field map from file. 
 *
 * This function load the flat-field map from file. 
 *
 * @param filename name of the FITS file containing flat-field map.
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibLoadFlatFieldMap(const char           *filename,
                                        amdlibERROR_MSG       errMsg)
{
    struct stat  statBuf;
    char         fitsioMsg[256];
    fitsfile     *filePtr;
    int          status = 0, nbFound;
    long         nAxes[2];
    int          anynull = 0;
    amdlibDOUBLE        nullval;

    amdlibLogTrace("amdlibLoadFlatFieldMap()");
    
    /* Check the file exists */
    if (stat(filename, &statBuf) != 0)
    {
        amdlibSetErrMsg("File '%.80s' does not exist", filename);
        return amdlibFAILURE;
    }

     /* Open FITS file */
    if (fits_open_file(&filePtr, filename, READONLY, &status) != 0)
    {
        amdlibReturnFitsError(filename);
    }

    /* Check file type (if given in header) */
    char dprType[256];
    char comment[amdlibKEYW_CMT_LEN+1];
    if (fits_read_key(filePtr, TSTRING, "HIERARCH ESO DPR TYPE", dprType, 
                      comment, &status))
    {
        status = 0;
        strcpy(dprType , "FLATFIELD"); 
    }
    if (strncmp(dprType, "FLATFIELD", strlen("FLATFIELD")) != 0)
    {
        amdlibSetErrMsg("Invalid file type '%s' : must be FLATFIELD", dprType);
        return amdlibFAILURE; 
    }

    /* Read the NAXIS1 and NAXIS2 keyword to get image size */
    if (fits_read_keys_lng(filePtr, "NAXIS", 1, 2,
                           nAxes, &nbFound, &status) != 0)
    {
        amdlibGetFitsError("NAXIS");
        fits_close_file(filePtr, &status);
        return amdlibFAILURE; 
    }
   
    /* Check size of the flat field map */
    if (nAxes[0] != amdlibDET_SIZE_X)
    {
        fits_close_file(filePtr, &status);
        amdlibSetErrMsg("Invalid number of pixels in X direction %ld : "
                        "should be %d", nAxes[0], amdlibDET_SIZE_X);
        return amdlibFAILURE;
    }
    if (nAxes[1] != amdlibDET_SIZE_Y)
    {
        fits_close_file(filePtr, &status);
        amdlibSetErrMsg("Invalid number of pixels in Y direction %ld : "
                        "Should be %d", nAxes[1], amdlibDET_SIZE_Y);
        return amdlibFAILURE;
    }

    /* Read 'imaging detector header' */
    nullval  = 0;   /* Don't check for null values in the image */
    if (fits_read_img(filePtr, TDOUBLE, 1, nAxes[0]*nAxes[1], &nullval,
                      amdlibFlatFieldMap.data, &anynull, &status) != 0)
    {
        amdlibGetFitsError("Reading map");
        fits_close_file(filePtr, &status);
        return amdlibFAILURE; 
    }

    /* Close the FITS file */
    if (fits_close_file(filePtr, &status) != 0)
    {
        amdlibReturnFitsError(filename);
    }

    amdlibFlatFieldMap.mapIsInitialized = amdlibTRUE;
    return amdlibSUCCESS;
}

/**
 * Get the current flat-field map.
 * 
 * This function returns the flat-field map. If there is no flat-field map
 * loaded or set, a perfect (all gains to 1.0) flat-field map is loaded.
 *
 * @return pointer to the current flat field map or NULL if no "real" flatfield
 * map is available
 */
amdlibFLAT_FIELD_MAP *amdlibGetFlatFieldMap(void)
{
    amdlibLogTrace("amdlibGetFlatFieldMap()"); 

    /* If map is not loaded, assume that all pixels are good */
    if (amdlibFlatFieldMap.mapIsInitialized == amdlibFALSE)
    {
        if (amdlibSetFlatFieldMap(1.0) != amdlibSUCCESS)
        {
            return NULL;
        }
        else
        {
            amdlibFlatFieldMap.mapIsInitialized = amdlibTRUE;
        }
    }
    
    return &amdlibFlatFieldMap;
}

/**
 * Get flat-field for a given region.
 * 
 * This function returns from the flat-field map, the gains for the given
 * region. If there is no flat-field map loaded or set, a perfect (all gains to
 * 1.0) flat-field map is loaded.
 *
 * @param startPixelX origin of the region along X axis
 * @param startPixelY origin of the region along Y axis
 * @param nbPixelX width of the region
 * @param nbPixelY height of the region
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibDOUBLE **amdlibGetFlatFieldMapRegion(int             startPixelX, 
                                    int             startPixelY, 
                                    int             nbPixelX, 
                                    int             nbPixelY,
                                    amdlibERROR_MSG errMsg)
{
    static amdlibDOUBLE **flatFieldRegion = NULL;
    int   x;
    int   y;
   
    amdlibLogTrace("amdlibGetFlatFieldMapRegion()"); 

    /* Check origin */
    if ((startPixelX < 0) || (startPixelX >= amdlibDET_SIZE_X) ||
        (startPixelY < 0) || (startPixelY >= amdlibDET_SIZE_Y))
    {
        amdlibSetErrMsg("Origin (%d, %d) is out of range",
                        startPixelX, startPixelY);
        return NULL;
    }

    /* Check region dimension */
    if ((nbPixelX < 0) || (startPixelX + nbPixelX) > amdlibDET_SIZE_X) 
    {
        amdlibSetErrMsg("Invalid region width %d : should be in [0..%d]", 
                        nbPixelX, (amdlibDET_SIZE_X - startPixelX));
        return NULL;
    }
    if ((nbPixelY < 0) || (startPixelY + nbPixelY) > amdlibDET_SIZE_Y) 
    {
        amdlibSetErrMsg("Invalid region height %d : should be in [0..%d]",
                        nbPixelY, (amdlibDET_SIZE_Y - startPixelY));
        return NULL;
    }

    /* If map is not loaded, assume that all gains are 1.0 */
    if (amdlibFlatFieldMap.mapIsInitialized == amdlibFALSE)
    {
        if (amdlibSetFlatFieldMap(1.0) != amdlibSUCCESS)
        {
            return NULL;
        }
        else
        {
            amdlibFlatFieldMap.mapIsInitialized = amdlibTRUE;
        }
    }
    
    /* Allocate memory to store gains corresponding to the given region */
    flatFieldRegion = amdlibAlloc2DArrayDouble(nbPixelX, nbPixelY, errMsg);
    if (flatFieldRegion != NULL)
    {
        /* Get gains for the pixels belonging to the region */
        for (y = 0; y < nbPixelY; y++)
        {
            for (x = 0; x < nbPixelX; x++)
            {
                flatFieldRegion[y][x] = 
                    amdlibFlatFieldMap.data[startPixelY + y][startPixelX + x];
            }
        }
    }
    return flatFieldRegion;
}

/*___oOo___*/
