/*******************************************************************************
 * JMMC project ( http://www.jmmc.fr ) - Copyright (C) CNRS.
 ******************************************************************************/
/**
 * @file
 * Functions to handle bad pixel map. 
 *
 * The bad pixel map contains for each pixel a flag (1 = good pixel, 0 = bad
 * pixel). All pixel marked as bad has to be ignored but the values for that
 * pixels can be interpolated from the values of the surrounding pixels.
 */

#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 amdlibBAD_PIXEL_MAP amdlibBadPixelMap = {amdlibFALSE};

/* 
 * Public functions 
 */
/**
 * Set bad pixel map to the given value. 
 *
 * This function set all flags of the bad pixel map to the given value; if set
 * to true (amdlibTRUE) all pixels are marked as good, otherwise they are marked
 * as bad. 
 *
 * @param flag indicates whether all pixels are good or bad.
 *
 * @return
 * Always return amdlibSUCCESS
 */
amdlibCOMPL_STAT amdlibSetBadPixelMap(amdlibBOOLEAN flag)
{
    int x, y;
    int pixelFlag;
    
    amdlibLogTrace("amdlibSetBadPixelMap()"); 

    if (flag == amdlibTRUE)
    {
        pixelFlag = amdlibGOOD_PIXEL_FLAG;
    }
    else
    {
        pixelFlag = amdlibBAD_PIXEL_FLAG;
    }

    /* Set all pixel flags to the given value */
    for (y = 0; y < amdlibDET_SIZE_Y; y++)
    {
        for (x = 0; x < amdlibDET_SIZE_X; x++)
        {
            amdlibBadPixelMap.data[y][x] = pixelFlag;
        }
    }
    amdlibBadPixelMap.mapIsInitialized = amdlibTRUE;

    return amdlibSUCCESS;
}

/**
 * Load bad pixel map from file. 
 *
 * This function load the bad pixel map from file. 
 *
 * @param filename name of the FITS file containing bad pixel map.
 * @param errMsg error description message returned if function fails.
 *
 * @return
 * amdlibSUCCESS on successful completion. Otherwise amdlibFAILURE is returned.
 */
amdlibCOMPL_STAT amdlibLoadBadPixelMap(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("amdlibLoadBadPixelMap()"); 

    /* 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 , "BADPIX"); 
    }
    if (strncmp(dprType, "BADPIX", strlen("BADPIX")) != 0)
    {
        amdlibSetErrMsg("Invalid file type '%s' : must be BADPIX", 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 bad pixel map size */
    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,
                       amdlibBadPixelMap.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);
    }

    amdlibBadPixelMap.mapIsInitialized = amdlibTRUE;
    return amdlibSUCCESS;
}

/* 
 * Protected functions 
 */
/**
 * Get the current bad pixel map.
 * 
 * This function returns the bad pixel map. If there is no bad pixel map loaded
 * or set, a perfect (all pixel set to good) bad pixel map is loaded.
 *
 * @return pointer to the current bad pixel or NULL if no "real" bad pixel map
 * is available
 */
amdlibBAD_PIXEL_MAP *amdlibGetBadPixelMap(void)
{
    amdlibLogTrace("amdlibGetBadPixelMap()"); 

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

/**
 * Get bad pixel flags for a given region.
 * 
 * This function returns from the bad pixel map, the flags for the given region.
 * If there is no bad pixel map loaded or set, a perfect (all pixel set to good)
 * bad pixel map is loaded.
 *
 * @param startPixelX[0..511] origin of the region along X axis
 * @param startPixelY[0..511] 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 bad pixel flags for the given region.
 */
amdlibDOUBLE **amdlibGetBadPixelMapRegion(int             startPixelX, 
                                          int             startPixelY, 
                                          int             nbPixelX, 
                                          int             nbPixelY,
                                          amdlibERROR_MSG errMsg)
{
    static amdlibDOUBLE **badPixelRegion = NULL;
    int   x;
    int   y;
   
    amdlibLogTrace("amdlibGetBadPixelMapRegion()"); 

    /* Check origin [0..511] */
    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 pixels are good */
    if (amdlibBadPixelMap.mapIsInitialized == amdlibFALSE)
    {
        if (amdlibSetBadPixelMap(amdlibGOOD_PIXEL_FLAG) != amdlibSUCCESS)
        {
            return NULL;
        }
        else
        {
            amdlibBadPixelMap.mapIsInitialized = amdlibTRUE;
        }
    }
    
    /* Allocate memory to store bad pixel flags corresponding to the given
     * region */
    badPixelRegion = amdlibAlloc2DArrayDouble(nbPixelX, nbPixelY, errMsg);
    if (badPixelRegion != NULL)
    {
        /* Get flags for the pixels belonging to the region */
        for (y = 0; y < nbPixelY; y++)
        {
            for (x = 0; x < nbPixelX; x++)
            {
                badPixelRegion[y][x] = 
                    amdlibBadPixelMap.data[startPixelY + y][startPixelX + x];
            }
        }
    }
    return badPixelRegion;
}

/**
 * Update bad pixel flags for a given region.
 * 
 * This function modifies in the current bad pixel map the flags for
 * the given region. If there is no bad pixel map loaded or set, a
 * perfect (all pixel set to good) bad pixel map is created, and the
 * region is updated.
 *
 * @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 mask a 2d array of double values 0(bad) and 1(good) for the region 
 * @param errMsg error description message returned if function fails.
 *
 * @return Completion Status.
 */
 amdlibCOMPL_STAT amdlibUpdateBadPixelMap(int             startPixelX, 
                                          int             startPixelY, 
                                          int             nbPixelX, 
                                          int             nbPixelY,
                                          amdlibDOUBLE    **mask,    
                                          amdlibERROR_MSG errMsg)
{
    int   x;
    int   y;
   
    amdlibLogTrace("amdlibUpdateBadPixelMap()"); 

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

    /* 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 amdlibFAILURE;
    }
    if ((nbPixelY < 0) || (startPixelY + nbPixelY) > amdlibDET_SIZE_Y) 
    {
        amdlibSetErrMsg("Invalid region height %d : should be in [0..%d]", 
                        nbPixelY, (amdlibDET_SIZE_Y - startPixelY));
        return amdlibFAILURE;
    }

    /* If map is not loaded, create one with all pixels good */
    if (amdlibBadPixelMap.mapIsInitialized == amdlibFALSE)
    {
        if (amdlibSetBadPixelMap(amdlibGOOD_PIXEL_FLAG) != amdlibSUCCESS)
        {
            return amdlibFAILURE;
        }
        else
        {
            amdlibBadPixelMap.mapIsInitialized = amdlibTRUE;
        }
    }
    
    /* Update bad pixel flags in the given region */
    for (y = 0; y < nbPixelY; y++)
    {
        for (x = 0; x < nbPixelX; x++)
        {
            if (mask[y][x]==0.0)
            {
                /* if(amdlibBadPixelMap.data[startPixelY + y][startPixelX + x]==0.0) amdlibLogInfo("bad pixel confirmed at %d %d",startPixelX + x+1,startPixelY + y +1); */
                amdlibBadPixelMap.data[startPixelY + y][startPixelX + x]=0.0;
            } 
        }
    }
    return amdlibSUCCESS;
}

/*___oOo___*/
