/** @file  pmMaskBadPixels.c
 *
 *  @brief Given an input image, a bad pixel mask, a corresponding value in the bad pixel mask to mask, a
 *  saturation level, and a growing radius, mask in the input image those pixels in the bad pixel mask that
 *  match the value to mask.
 *
 *  Given an input image, in, a bad pixel mask, a corresponding value in the bad pixel mask to mask in the
 * input image, maskVal, a saturation level, and a growing radius, pmMaskBadPixels shall mask in the input
 * image those pixels in the bad pixel mask that match the value to mask. Note that the input image, in, is
 * modified in-place. All pixels in the mask which satisfy the maskVal shall have their corresponding pixels
 * masked in the input image, in. All pixels which satisfy the growVal shall have their corresponding
 * pixels, along with all pixels within the grow radius masked. Pixels which have flux greater than sat shall
 * also be masked, but not grown. Note that the input image, in, and the mask need not be the same size, since
 * the input image may already have been trimmed (following overscan subtraction), but the function shall use
 * the offsets in the image (in->x0 and in->y0) to determine the appropriate offsets to obtain the correct
 * pixel on the mask. In the event that the mask image is too small (i.e., pixels on the input image refer to
 * pixels outside the range of the mask image), the function shall generate an error.
 
 *  @author Ross Harman, MHPCC
 *
 *  @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2004-11-04 02:02:10 $
 *
 *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
 */

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include<stdio.h>
#include<math.h>
#include<string.h>

#include "pmMaskBadPixels.h"
#include "pmMaskBadPixelsErrors.h"

bool pmMaskBadPixels(psReadout *in, const psImage *mask, unsigned int maskVal, float sat,
                     unsigned int growVal, int grow)
{
    int i = 0;
    int j = 0;
    int jj = 0;
    int ii = 0;
    int rRound = 0;
    int rowMin = 0;
    int rowMax = 0;
    int colMin = 0;
    int colMax = 0;
    int totOffCol = 0;
    int totOffRow = 0;
    float r = 0.0f;
    psElemType inType;
    psElemType maskType;
    psImage *inImage = NULL;
    psImage *inMask = NULL;


    // Check for nulls
    if (in == NULL) {
        return true;   // Readout may not have data in it
    } else if(mask==NULL) {
        psError( PS_ERR_BAD_PARAMETER_NULL, true,
                 PS_ERRORTEXT_pmMaskBadPixels_NULL_MASK_IMAGE);
        return false;
    }

    inImage = in->image;
    if (inImage == NULL) {
        psError( PS_ERR_BAD_PARAMETER_NULL, true,
                 PS_ERRORTEXT_pmMaskBadPixels_NULL_INPUT_IMAGE);
        return false;
    } else if(in->mask == NULL) {
        in->mask = psImageAlloc(inImage->numCols, inImage->numRows, PS_TYPE_MASK);
        memset(in->mask->data.V[0], 0, inImage->numCols*inImage->numRows*PSELEMTYPE_SIZEOF(PS_TYPE_MASK));
    }
    inMask = in->mask;

    // Check input image and its mask are not larger than mask
    if(inImage->numRows > mask->numRows || inImage->numCols > mask->numCols) {
        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
                 PS_ERRORTEXT_pmMaskBadPixels_SIZE_INPUT_IMAGE,
                 inImage->numRows, inImage->numCols, mask->numRows, mask->numCols);
        return false;
    } else if(inMask->numRows>mask->numRows || inMask->numCols>mask->numCols) {
        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
                 PS_ERRORTEXT_pmMaskBadPixels_SIZE_MASK_IMAGE,
                 inMask->numRows, inMask->numCols, mask->numRows, mask->numCols);
        return false;
    }

    // Determine total offset based on image offset with chip offset
    totOffCol = inImage->col0 + in->col0;
    totOffRow = inImage->row0 + in->row0;

    // Check that offsets are within image limits
    if(totOffRow>=mask->numRows || totOffCol>=mask->numCols) {
        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
                 PS_ERRORTEXT_pmMaskBadPixels_OFFSET_MASK_IMAGE,
                 totOffRow, totOffCol, mask->numRows, mask->numCols);
        return false;
    } else if(totOffRow>=inImage->numRows || totOffCol>=inImage->numCols) {
        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
                 PS_ERRORTEXT_pmMaskBadPixels_OFFSET_INPUT_IMAGE,
                 totOffRow, totOffCol, inImage->numRows, inImage->numCols);
        return false;
    } else if(totOffRow>=inMask->numRows || totOffCol>=inMask->numCols) {
        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
                 PS_ERRORTEXT_pmMaskBadPixels_OFFSET_INPUT_IMAGE_MASK,
                 totOffRow, totOffCol, inMask->numRows, inMask->numCols);
        return false;
    }

    // Check for incorrect types
    inType = inImage->type.type;
    maskType = mask->type.type;
    if(PS_IS_PSELEMTYPE_COMPLEX(inType)) {
        psError( PS_ERR_BAD_PARAMETER_TYPE, true,
                 PS_ERRORTEXT_pmMaskBadPixels_TYPE_INPUT_IMAGE,
                 inType);
        return false;
    } else if(maskType!=PS_TYPE_MASK) {
        psError( PS_ERR_BAD_PARAMETER_TYPE, true,
                 PS_ERRORTEXT_pmMaskBadPixels_TYPE_MASK_IMAGE,
                 maskType);
        return false;
    }

    // Macro for all PS types
    #define PM_BAD_PIXELS(TYPE)                                                                              \
case PS_TYPE_##TYPE:                                                                                         \
    for(j=totOffRow; j<inImage->numRows; j++) {                                                              \
        for(i=totOffCol; i<inImage->numCols; i++) {                                                          \
            \
            /* Pixels with flux greater than sat shall be masked */                                          \
            if(inImage->data.TYPE[j][i] > sat) {                                                             \
                inMask->data.PS_TYPE_MASK_DATA[j][i] |= PM_MASK_SAT;                                         \
            }                                                                                                \
            \
            /* Pixels which satisfy maskVal shall be masked */                                               \
            inMask->data.PS_TYPE_MASK_DATA[j][i] |= (mask->data.PS_TYPE_MASK_DATA[j][i]&maskVal);            \
            \
            /* Pixels which satisfy growVal and within the grow radius shall be masked */                    \
            if(mask->data.PS_TYPE_MASK_DATA[j][i] & growVal) {                                               \
                rowMin = MAX(j-grow, 0);                                                                     \
                rowMax = MIN(j+grow+1, inImage->numRows);                                                    \
                colMin = MAX(i-grow, 0);                                                                     \
                colMax = MIN(i+grow+1, inImage->numCols);                                                    \
                for(jj=rowMin; jj<rowMax; jj++) {                                                            \
                    for(ii=colMin; ii<colMax; ii++) {                                                        \
                        r = sqrtf((ii-i)*(ii-i)+(jj-j)*(jj-j));                                              \
                        rRound = r + 0.5;                                                                    \
                        if(rRound <= grow) {                                                                 \
                            inMask->data.PS_TYPE_MASK_DATA[jj][ii] |=                                        \
                                    (mask->data.PS_TYPE_MASK_DATA[j][i]&growVal);                            \
                        }                                                                                    \
                    }                                                                                        \
                }                                                                                            \
            }                                                                                                \
        }                                                                                                    \
    }                                                                                                        \
    break;

    // Switch to call bad pixel masking macro defined above
    switch(inType) {
        PM_BAD_PIXELS(U8);
        PM_BAD_PIXELS(U16);
        PM_BAD_PIXELS(U32);
        PM_BAD_PIXELS(U64);
        PM_BAD_PIXELS(S8);
        PM_BAD_PIXELS(S16);
        PM_BAD_PIXELS(S32);
        PM_BAD_PIXELS(S64);
        PM_BAD_PIXELS(F32);
        PM_BAD_PIXELS(F64);
    default:
        psError( PS_ERR_BAD_PARAMETER_TYPE, true,
                 PS_ERRORTEXT_pmMaskBadPixels_TYPE_UNSUPPORTED,
                 inType);
    }

    return false;
}
