/** @file  tst_psImageIO.c
 *
 *  @brief Contains the tests for psImageIO.[ch]
 *
 *
 *  @author Robert DeSonia, MHPCC
 *
 *  @version $Revision: 1.14 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2005-04-07 20:27:42 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
 */

#include <math.h>
#include <float.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fitsio.h>

#include "psTest.h"
#include "pslib_strict.h"

#define GENIMAGE(img,c,r,TYP, valueFcn) \
img = psImageAlloc(c,r,PS_TYPE_##TYP); \
for (psU32 row=0;row<r;row++) { \
    ps##TYP* imgRow = img->data.TYP[row]; \
    for (psU32 col=0;col<c;col++) { \
        imgRow[col] = (ps##TYP)(valueFcn); \
    } \
}

static psS32 testImageRead(void);
static psS32 testImageWrite(void);

testDescription tests[] = {
                              {
                                  testImageRead,567,"psImageReadSection",0,false
                              },
                              {
                                  testImageWrite,569,"psImageWriteSection",0,false
                              },
                              {
                                  NULL
                              }
                          };

psS32 main(psS32 argc, char* argv[])
{
    psLogSetLevel(PS_LOG_INFO);

    if (! runTestSuite(stderr,"psImage",tests,argc,argv)) {
        psAbort(__FILE__,"One or more tests failed");
    }
    return 0;
}

psS32 testImageRead(void)
{
    psS32 N = 256;
    psS32 M = 128;
    psImage* image = NULL;

    /*
        This function shall open the specified FITS file, read the specified data
        and place the data into a psImage structure. This function shall generate
        an error message and return NULL if any of the input parameters are out of
        range, image file doesn't exist or image is zero or one dimensional.

        Verify the returned psImage structure contains expected values, if the input
        parameter filename specifies an available FITS file with known 2dimensional
        data, input parameters col, row, ncol, nrow, z specify data range with the
        FITS file. Cases should include 1x1, Nx1, 1xN, NxN and MxN sub images and
        total FITS file image. (done in macro)

        Verify the returned psImage structure is equal to the input parameter
        'output', if specified. (done in macro)

        */

    /* generate FITS file to read */

    #define testReadTypeSize(m, n, readM0, readN0, readM, readN, TYP, filename) \
    { \
        psImage* img = NULL; \
        psImage* img2 = NULL; \
        psImage* img3 = NULL; \
        psImage* img4 = NULL; \
        /*        psImage* img_ref = NULL; */ \
        \
        GENIMAGE(img,m,n,TYP,row+2*col); \
        img2 = psImageCopy(img2,img,PS_TYPE_##TYP); \
        GENIMAGE(img3,m,n,TYP,row+2*col); \
        psImageClip(img3,32.0,32.0,120.0,120.0); \
        img4 = psImageCopy(img4,img3,PS_TYPE_##TYP); \
        remove(filename); \
        if (! psImageWriteSection(img3,0,0,1,NULL,0,filename)) { \
            psError(PS_ERR_UNKNOWN, true,"Failed to write test image %s",filename); \
            return 1; \
        } \
        if (! psImageWriteSection(img,0,0,0,NULL,0,filename)) { \
            psError(PS_ERR_UNKNOWN, true,"Failed to write test image %s",filename); \
            return 2; \
        } \
        if (! psImageWriteSection(img,0,0,1,NULL,1,filename)) { \
            psError(PS_ERR_UNKNOWN, true,"Failed to write test image %s",filename); \
            return 3; \
        } \
        if (! psImageWriteSection(img3,0,0,0,NULL,1,filename)) { \
            psError(PS_ERR_UNKNOWN, true,"Failed to write test image %s",filename); \
            return 4; \
        } \
        psFree(img); \
        img = NULL; \
        /*        img_ref = img = psImageAlloc(2,2,PS_TYPE_F32); */ \
        psFree(img3); \
        img3 = NULL; \
        img = psImageReadSection(img,readM0,readN0,readM,readN,0,NULL,0,filename); \
        /*        if (img_ref != img) { */ \
        /*            psError(PS_ERR_UNKNOWN, true,"psImageReadSection didn't recycle the psImage?"); */ \
        /*            return 5; */ \
        /*        } */ \
        img3 = psImageReadSection(img3,readM0,readN0,readM,readN,1,NULL,0,filename); \
        if (img3 == NULL) { \
            psError(PS_ERR_UNKNOWN, true,"Failed to read test image %s",filename); \
            return 6; \
        } \
        for (psU32 row = readN0; row < readN; row++) { \
            ps##TYP* imgRow = img->data.TYP[row-readN0]; \
            ps##TYP* img2Row = img2->data.TYP[row]; \
            ps##TYP* img3Row = img3->data.TYP[row-readN0]; \
            ps##TYP* img4Row = img4->data.TYP[row]; \
            for (psU32 col = readM0; col < readM; col++) { \
                if (fabsf(imgRow[col-readM0]-img2Row[col]) > FLT_EPSILON) { \
                    psError(PS_ERR_UNKNOWN, true,"Image changed in I/O operation at %d,%d,0 (%.2f vs %.2f) for %s", \
                            col,row,(psF32)imgRow[col-readM0],(psF32)img2Row[col],filename); \
                    return 7; \
                } \
                if (fabsf(img3Row[col-readM0]-img4Row[col]) > FLT_EPSILON) { \
                    psError(PS_ERR_UNKNOWN, true,"Image changed in I/O operation at %d,%d,1 (%.2f vs %.2f) for %s", \
                            col,row,(psF32)img3Row[col-readM0],(psF32)img4Row[col],filename); \
                    return 8; \
                } \
            } \
        } \
        psFree(img); \
        img = NULL; \
        psFree(img3); \
        img3 = NULL; \
        img3 = psImageReadSection(img3,readM0,readN0,readM,readN,0,NULL,1,filename); \
        img = psImageReadSection(img,readM0,readN0,readM,readN,1,NULL,1,filename); \
        if (img == NULL) { \
            psError(PS_ERR_UNKNOWN, true,"Failed to read test image %s",filename); \
            return 9; \
        } \
        for (psU32 row = readN0; row < readN; row++) { \
            ps##TYP* imgRow = img->data.TYP[row-readN0]; \
            ps##TYP* img2Row = img2->data.TYP[row]; \
            ps##TYP* img3Row = img3->data.TYP[row-readN0]; \
            ps##TYP* img4Row = img4->data.TYP[row]; \
            for (psU32 col = readM0; col < readM; col++) { \
                if (fabsf(imgRow[col-readM0]-img2Row[col]) > FLT_EPSILON) { \
                    psError(PS_ERR_UNKNOWN, true,"Image changed in I/O operation at %d,%d,0 (%.2f vs %.2f) for %s", \
                            col,row,(psF32)imgRow[col-readM0],(psF32)img2Row[col],filename); \
                    return 10; \
                } \
                if (fabsf(img3Row[col-readM0]-img4Row[col]) > FLT_EPSILON) { \
                    psError(PS_ERR_UNKNOWN, true,"Image changed in I/O operation at %d,%d,1 (%.2f vs %.2f) for %s", \
                            col,row,(psF32)img3Row[col-readM0],(psF32)img4Row[col],filename); \
                    return 11; \
                } \
            } \
        } \
        psFree(img); \
        psFree(img2); \
        psFree(img3); \
        psFree(img4); \
    }

    #define testReadType(TYP,filename) \
    mkdir("tmpImages",0777); \
    testReadTypeSize(1,1,0,0,1,1,TYP,"tmpImages/1x1_" filename); \
    testReadTypeSize(M,1,M/4,0,M/2,1,TYP,"tmpImages/Mx1_" filename); \
    testReadTypeSize(1,N,0,N/4,1,N/2,TYP,"tmpImages/1xN_" filename); \
    testReadTypeSize(M,N,M/4,N/4,M/2,N/2,TYP,"tmpImages/MxN_" filename);

    testReadType(U8,"U8.fits");
    //    testReadType(S8,"S8.fits");   Not a requirement
    testReadType(S16,"S16.fits");
    //    testReadType(U16,"U16.fits"); Not a requirement
    testReadType(S32,"S32.fits");
    //    testReadType(U32,"U32.fits"); Not a requirement
    testReadType(F32,"F32.fits");
    testReadType(F64,"F64.fits");

    /*
    Verify the returned psImage structure pointer is null, error log message is
    generated and program execution doesn't stop, if input parameter filename
    specifies a file that doesn't exist.
    */

    psLogMsg(__func__,PS_LOG_INFO,"Following should be an error as file doesn't exist.");
    image = psImageReadSection(image,0,0,M,N,0,NULL,0,"foobar.fits");
    if (image != NULL) {
        psError(PS_ERR_UNKNOWN, true,"Image returned though filename did not exist.");
        return 12;
    }

    /*
    Verify the returned psImage structure pointer is null, error log message is
    generatedand program execution doesn't stop, if input parameter filename
    is null.Verify the returned psImage structurepointer is null, error log
    message is generated and program execution doesn't stop, if input
    parameters col,row, ncol, nrow and/or z specify a data range which is not
    within the data range of the FITS file.
    */

    psLogMsg(__func__,PS_LOG_INFO,"Following should be an error as filename is NULL.");
    image = psImageReadSection(image,0,0,M,N,0,NULL,0,NULL);
    if (image != NULL) {
        psError(PS_ERR_UNKNOWN, true,"Image returned though filename was NULL.");
        return 13;
    }

    /*
    Verify the returned psImage structure pointer is null, error log message is
    generated and program execution doesn't stop,if input parameters extname
    and/or extnum specify an extension which is not valid in the FITS file.
    */

    psLogMsg(__func__,PS_LOG_INFO,"Following should be an error as extnum is invalid.");
    image = psImageReadSection(image,0,0,M,N,0,NULL,4,"tmpImages/MxN_F32.fits");
    if (image != NULL) {
        psError(PS_ERR_UNKNOWN, true,"Image returned though extnum was invalid.");
        return 14;
    }
    psLogMsg(__func__,PS_LOG_INFO,"Following should be an error as extname is invalid.");
    image = psImageReadSection(image,0,0,M,N,0,"bogus",0,"tmpImages/MxN_F32.fits");
    if (image != NULL) {
        psError(PS_ERR_UNKNOWN, true,"Image returned though extnum was invalid.");
        return 15;
    }

    /*
    Verify the returned psImage structure pointer is null, error log message is
    generated and program execution doesn't stop,if input parameter filename
    specifies a FITS file that is zero or one dimensional.

    XXX
    N.B: since I can't write anything but 2 or 3 dimensional image, I can't
    do this programatically at this time.  I verified that this functionality
    exists in the source code via inspection. (line 101 in psImageIO.c for v1.3)
    -rdd
    */

    return 0;
}

psS32 testImageWrite(void)
{
    psImage* img = NULL;
    psImage* img2 = NULL;
    psS32 m = 64;
    psS32 n = 96;

    /*
    This function shall write the specified section within a psImage structure
    to a FITS file. If the specifiedfile exists, then data should overwrite the
    section to write. If the specified file doesn't exist, it shall be created.
    If an extenstion is specified, then a basic primary header data unit shall
    be created.
    */

    /*
    Verify a FITS file named filename is generated and contains expected
    values, if the input parameter input contains known data values, input
    parameters col, row, ncol, nrow specify a valid data region within psImage
    structure.

    Verify a FITS file named filename is generated and contains a primary
    header data unit with extension with expected values, if the input
    parameter input contains known data values, input parameters col, row,
    ncol, nrow specify a valid data region within psImage structure and
    extname and/or extnum specify an extenstion to write.

    N.B. : these are done in testImageRead tests, see above.
    */

    /*
    Verify a FITS file named filename is overwritten and contains
    expected values, if the input parameter input contains known data values,
    input parameters col, row, ncol, nrow specify a valid data region within
    psImage structure.
    */

    GENIMAGE(img,m,n,F32,0);
    GENIMAGE(img2,m,n,F32,row+2*col);
    mkdir("tmpImages",0777);
    remove
        ("writeTest.fits")
        ;
    if (! psImageWriteSection(img,0,0,0,NULL,0,"tmpImages/writeTest.fits")) {
        psError(PS_ERR_UNKNOWN, true,"Couldn't write writeTest.fits.");
        return 14;
    }
    if (! psImageWriteSection(img2,0,0,0,NULL,0,"tmpImages/writeTest.fits")) {
        psError(PS_ERR_UNKNOWN, true,"Couldn't overwrite writeTest.fits.");
        return 15;
    }
    psFree(img);
    psFree(img2);

    // Did it really overwrite the pixel values?  Let's read it in and see.
    img = psImageReadSection(NULL,0,0,m,n,0,NULL,0,"tmpImages/writeTest.fits");
    if (img == NULL) {
        psError(PS_ERR_UNKNOWN, true,"Could not read in writeTest.fits.");
        return 16;
    }
    for (psU32 row=0;row<n;row++) {
        psF32* imgRow = img->data.F32[row];
        for (psU32 col=0;col<m;col++) {
            if (fabsf(imgRow[col] - (row+2*col)) > FLT_EPSILON) {
                psError(PS_ERR_UNKNOWN, true,"The image values were not overwritten at %d,%d (%.2f vs %.2f)",
                        col,row,imgRow[col],(row+2*col));
                return 17;
            }
        }
    }

    psFree(img);

    /*
    Verify false is returned and program execution is not stopped, if the input image
    is null.
    */
    psLogMsg(__func__,PS_LOG_INFO,"Following should generate an error message because input image is null.");
    if ( psImageWriteSection(NULL,0,0,0,NULL,0,"tmpImages/writeTest.fits")) {
        psError(PS_ERR_UNKNOWN, true,"psImageWriteSection did not return false when input image is NULL.");
        return 20;
    }

    /*
    Verify error message generated when invalid path specified for output file.
    */
    psLogMsg(__func__,PS_LOG_INFO,"Following should be an error for passing invalid image type.");
    GENIMAGE(img,32,32,U8,row+2*col);
    if ( psImageWriteSection(img,0,0,0,NULL,0,"panstarrs/writeFits.fits")) {
        psError(PS_ERR_UNKNOWN, true,"psImageWriteSection did not return false for invalid path name.");
        return 22;
    }

    /*
    Verify error message generated when an invalid FITs file is specified
    */
    FILE *fileptr = fopen("tmpImages/writeInvalidFile.fits","w");
    fprintf(fileptr," ");
    fclose(fileptr);
    psLogMsg(__func__,PS_LOG_INFO,"Following should be an error for passing invalid FITS file.");
    if ( psImageWriteSection(img,0,0,0,NULL,0,"tmpImages/writeInvalidFile.fits")) {
        psError(PS_ERR_UNKNOWN, true,"psImageWriteSection did not return false for invalid FITS file.");
        return 22;
    }
    remove
        ("tmpImages/writeInvalidFile.fits");

    /*
    Verify error message when invalid extension name is provided
    */
    psLogMsg(__func__,PS_LOG_INFO,"Following should be an error for invalid extension name.");
    if ( psImageWriteSection(img,0,0,0,"ext2",0,"tmpImages/writeTest.fits")) {
        psError(PS_ERR_UNKNOWN, true,"psImageWriteSection did not return false for invalid extension name.");
        return 23;
    }

    /*
    Verified error message when invalid extension number is provided
    */
    psLogMsg(__func__,PS_LOG_INFO,"Following should be an error for invalid extension number.");
    if ( psImageWriteSection(img,0,0,0,NULL,99,"tmpImages/writeTest.fits")) {
        psError(PS_ERR_UNKNOWN, true,"psImageWriteSection did not return false for invalid extension number.");
        return 24;
    }

    psFree(img);

    return 0;
}
