/** @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.3 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2005-11-18 19:43:14 $
 *
 *  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"
#include "pmSubtractBias.h"

//XXX: REmove, autoconf is broken.
#define PS_WARN_PTR_NON_NULL(NAME) \
if ((NAME) == NULL) { \
    psLogMsg(__func__, PS_LOG_WARN, "WARNING: %s is NULL.", #NAME); \
} \


/******************************************************************************
GrowPixel(inMask, row, col, radius, maskVal): This private routine takes an
input image mask and a pixel location, then sets (logical or) all pixels with
parameter radius if that pixel to maskVal parameter.
*****************************************************************************/
psBool GrowPixel(
    psImage *inMask,
    psS32 col,
    psS32 row,
    psS32 radius,
    psU32 maskVal)
{
    psS32 rowMin = PS_MAX(row-radius, 0);
    psS32 rowMax = PS_MIN(row+radius+1, inMask->numRows);
    psS32 colMin = PS_MAX(col-radius, 0);
    psS32 colMax = PS_MIN(col+radius+1, inMask->numCols);
    psF32 squareRadius = PS_SQR(radius);


    for(psS32 i=rowMin; i<rowMax; i++) {
        for(psS32 j=colMin; j<colMax; j++) {
            // Old code:
            //            psF32 rRound = 0.5 + sqrtf((psF32) (((col-j)*(col-j)) + ((row-i)*(row-i))));
            //            if(rRound <= radius) {
            psF32 squareDis = (psF32) (((col-j)*(col-j)) + ((row-i)*(row-i)));
            if (squareDis <= squareRadius) {
                inMask->data.U8[i][j] |= maskVal;
            }
        }
    }
    return(true);
}

/******************************************************************************
GetRadius(inImg, col, row, sat, growVal, grow): This private routine takes an
input image and pixel location and determines what radius that pixel must grow
by.
 
//XXX: Inline this or macro it for speed.
 *****************************************************************************/
static psS32 GetRadius(
    psImage *inImg,
    psS32 col,
    psS32 row,
    psF32 sat,
    psU32 growVal,
    psS32 grow)
{
    psS32 growRadius = 0;
    if (inImg->type.type == PS_TYPE_F32) {
        if(inImg->data.F32[row][col] == growVal) {
            growRadius = grow;
        }
        if (inImg->data.F32[row][col] > sat) {
            growRadius = PS_MAX(growRadius+1, 2);
        }
    } else if (inImg->type.type == PS_TYPE_S32) {
        if(inImg->data.S32[row][col] == growVal) {
            growRadius = grow;
        }
        if (inImg->data.S32[row][col] > sat) {
            growRadius = PS_MAX(growRadius+1, 2);
        }
    } else if (inImg->type.type == PS_TYPE_U16) {
        if(inImg->data.U16[row][col] == growVal) {
            growRadius = grow;
        }
        if (inImg->data.U16[row][col] > sat) {
            growRadius = PS_MAX(growRadius+1, 2);
        }
    } else {
        psError( PS_ERR_BAD_PARAMETER_TYPE, true,
                 PS_ERRORTEXT_pmMaskBadPixels_TYPE_MASK_IMAGE,
                 inImg->type.type);
    }
    return(growRadius);
}


bool pmMaskBadPixels(
    pmReadout *in,
    const pmReadout *mask,
    unsigned int maskVal,
    float sat,
    unsigned int growVal,
    int grow)
{
    // XXX: Review this code then put all asserts at the top.
    PS_ASSERT_PTR_NON_NULL(in, true);
    PS_ASSERT_PTR_NON_NULL(in->image, false);
    PS_WARN_PTR_NON_NULL(in->parent);
    if (in->parent != NULL) {
        PS_WARN_PTR_NON_NULL(in->parent->concepts);
    }
    PS_ASSERT_PTR_NON_NULL(mask, false);
    int i = 0;
    int j = 0;
    int totOffCol = 0;
    int totOffRow = 0;
    psElemType inType;
    psElemType maskType;
    psImage *inMask = NULL;

    //
    // Determine trimmed image from metadata.
    //
    psImage *trimmedImg = p_psDetermineTrimmedImage(in);
    if(in->mask == NULL) {
        in->mask = psImageAlloc(trimmedImg->numCols, trimmedImg->numRows, PS_TYPE_MASK);
        PS_IMAGE_SET_U8(in->mask, 0);
    }
    inMask = in->mask;

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

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

    // Check that offsets are within image limits
    if(totOffRow>=mask->image->numRows || totOffCol>=mask->image->numCols) {
        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
                 PS_ERRORTEXT_pmMaskBadPixels_OFFSET_MASK_IMAGE,
                 totOffRow, totOffCol, mask->image->numRows, mask->image->numCols);
        return false;
    } else if(totOffRow>=trimmedImg->numRows || totOffCol>=trimmedImg->numCols) {
        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
                 PS_ERRORTEXT_pmMaskBadPixels_OFFSET_INPUT_IMAGE,
                 totOffRow, totOffCol, trimmedImg->numRows, trimmedImg->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 = trimmedImg->type.type;
    maskType = mask->image->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;
    }

    //
    // This is the main loop which examines each pixel and masks pixels if
    //    A: The mask matches the maskValue
    //    B: The pixel is larger than the saturation value
    //    C: The pixel equals the grow value (in which case a circle is masked)
    //
    for(i=totOffRow; i<trimmedImg->numRows; i++) {
        for(j=totOffCol; j<trimmedImg->numCols; j++) {
            //
            // A: Pixels which satisfy maskVal shall be masked
            //
            if (mask->image->data.U8[i][j] == maskVal) {
                in->mask->data.U8[i][j] |= maskVal;
            }

            //
            // We first determine how much we need to grow by and store this in
            // growRadius.
            //
            psS32 growRadius = GetRadius(trimmedImg, j, i, sat, growVal, grow);

            //
            // Grow the pixel mask if necessary.
            //
            if (growRadius != 0) {
                GrowPixel(in->mask, j, i, growRadius, maskVal);
            }
        }
    }

    return true;
}
