/** @file  tst_psImage.c
 *
 *  @brief Contains the tests for psImage.[ch]
 *
 *
 *  @author Robert DeSonia, MHPCC
 *
 *  @version $Revision: 1.24 $ $Name:  $
 *  @date $Date: 2004/08/31 19:36:02 $
 *
 *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
 */

#include <math.h>
#include <float.h>
#include <string.h>
#include <stdlib.h>

#include "psTest.h"
#include "pslib.h"
#include "psType.h"

static int testImageAlloc(void);
static int testImageSubset(void);
static int testImageCopy(void);

testDescription tests[] = {
                              {
                                  testImageAlloc,546,"psImageAlloc",0,false
                              },
                              {
                                  testImageAlloc,548,"psImageFree",0,true
                              },
                              {
                                  testImageSubset,547,"psImageSubset",0,false
                              },
                              {
                                  testImageSubset,550,"psImageSubset",0,true
                              },
                              {
                                  testImageCopy,551,"psImageCopy",0,false
                              },
                              {
                                  NULL
                              }
                          };

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

    return ! runTestSuite(stderr,"psImage",tests,argc,argv);
}

int testImageAlloc(void)
{
    psImage* image = NULL;
    unsigned int sizes = 6;
    unsigned int numCols[] = {
                                 0,1,1,100,100,150
                             };
    unsigned int numRows[] = {
                                 0,1,100,1,150,100
                             };
    unsigned int types = 13;
    psElemType type[] = { PS_TYPE_S8, PS_TYPE_S16, PS_TYPE_S32, PS_TYPE_S64,
                          PS_TYPE_U8, PS_TYPE_U16, PS_TYPE_U32, PS_TYPE_U64,
                          PS_TYPE_F32, PS_TYPE_F64, PS_TYPE_C32, PS_TYPE_C64,
                          PS_TYPE_PTR };

    psLogMsg(__func__,PS_LOG_INFO,"#546 - psImageAlloc shall allocate memory for a psImage structure");

    for (unsigned int t=0;t<types;t++) {
        psLogMsg(__func__,PS_LOG_INFO,"Testing psImage with type %xh",type[t]);

        for (unsigned int i=0;i<sizes;i++) {

            if (numRows[i] == 0 || numCols[i] == 0) {
                psLogMsg(__func__,PS_LOG_INFO,"Following should be an error.");
            }

            image = psImageAlloc(numCols[i],numRows[i],type[t]);

            if (image == NULL) {
                if (numRows[i] == 0 || numCols[i] == 0) {
                    continue;
                }
                psError(__func__,"psImageAlloc returned NULL for type %x, size %dx%d.",
                        type[t], numCols[i], numRows[i]);
                psFree(image);
                return 1;
            }

            if (image->type.dimen != PS_DIMEN_IMAGE || image->type.type != type[t]) {
                psError(__func__,"psImageAlloc allocated wrong dimen/type (%d/%d), should be "
                        "PS_IMAGE_DIMEN/%d", image->type.dimen, image->type.type, type[t]);
                psFree(image);
                return 2;
            }

            if (image->numCols != numCols[i] || image->numRows != numRows[i]) {
                psError(__func__,"psImageAlloc allocated wrong size %dx%d, should be %dx%d (type = %d)",
                        image->numCols, image->numRows, numCols[i], numRows[i],type[t]);
                psFree(image);
                return 3;
            }

            if (image->col0 != 0 || image->row0 != 0) {
                psError(__func__,"psImageAlloc returned row0/col0 of %d/%d.  Should be 0/0.",
                        image->row0, image->row0);
                psFree(image);
                return 4;
            }

            if (image->parent != NULL) {
                psError(__func__,"psImageAlloc returned non-NULL parent");
                psFree(image);
                return 5;
            }

            if (image->nChildren != 0) {
                psError(__func__,"psImageAlloc returned non-zero number of children");
                psFree(image);
                return 6;
            }

            if (image->children != NULL) {
                psError(__func__,"psImageAlloc returned non-NULL children vector");
                psFree(image);
                return 7;
            }

            switch (type[t]) {
            case PS_TYPE_U16: {
                    unsigned int rows = numRows[i];
                    unsigned int cols = numCols[i];

                    for (int r=0;r<rows;r++) {
                        for (int c=0;c<cols;c++) {
                            image->data.U16[r][c] = 2*c+r;
                        }
                    }
                    for (int r=0;r<rows;r++) {
                        for (int c=0;c<cols;c++) {
                            if (image->data.U16[r][c] != 2*c+r) {
                                psError(__func__,"Could not set all pixels in uint16 image at (%d,%d)",c,r);
                                psFree(image);
                                return 8;
                            }
                        }
                    }
                }
                break;
            case PS_TYPE_F32: {
                    unsigned int rows = numRows[i];
                    unsigned int cols = numCols[i];

                    for (int r=0;r<rows;r++) {
                        for (int c=0;c<cols;c++) {
                            image->data.F32[r][c] = 2.0f*c+r;
                        }
                    }
                    for (int r=0;r<rows;r++) {
                        for (int c=0;c<cols;c++) {
                            if (fabsf(image->data.F32[r][c] - (2.0f*c+r)) > FLT_EPSILON) {
                                psError(__func__,"Could not set all pixels in float image at (%d,%d)",c,r);
                                psFree(image);
                                return 8;
                            }
                        }
                    }
                }
                break;
            case PS_TYPE_F64: {
                    unsigned int rows = numRows[i];
                    unsigned int cols = numCols[i];

                    for (int r=0;r<rows;r++) {
                        for (int c=0;c<cols;c++) {
                            image->data.F64[r][c] = 2.0f*c+r;
                        }
                    }
                    for (int r=0;r<rows;r++) {
                        for (int c=0;c<cols;c++) {
                            if (fabs(image->data.F64[r][c] - (2.0f*c+r)) > DBL_EPSILON) {
                                psError(__func__,"Could not set all pixels in double image at (%d,%d)",c,r);
                                psFree(image);
                                return 8;
                            }
                        }
                    }
                }
                break;
            case PS_TYPE_C32: {
                    unsigned int rows = numRows[i];
                    unsigned int cols = numCols[i];

                    for (int r=0;r<rows;r++) {
                        for (int c=0;c<cols;c++) {
                            image->data.C32[r][c] = r + I * c;
                        }
                    }
                    for (int r=0;r<rows;r++) {
                        for (int c=0;c<cols;c++) {
                            if (fabsf(crealf(image->data.C32[r][c]) - r) > FLT_EPSILON ||
                                    fabsf(cimagf(image->data.C32[r][c]) - c) > FLT_EPSILON ) {
                                psError(__func__,"Could not set all pixels in complex image at (%d,%d)",c,r);
                                psFree(image);
                                return 8;
                            }
                        }
                    }
                }
                break;
            case PS_TYPE_PTR: {
                    unsigned int rows = numRows[i];
                    unsigned int cols = numCols[i];
                    psImage* temp = psImageAlloc(1,1,PS_TYPE_F32);

                    for (int r=0;r<rows;r++) {
                        for (int c=0;c<cols;c++) {
                            image->data.PTR[r][c] = psMemIncrRefCounter(temp);
                        }
                    }
                    for (int r=0;r<rows;r++) {
                        for (int c=0;c<cols;c++) {
                            if (image->data.PTR[r][c] != temp) {
                                psError(__func__, "Could not set pixel in pointer image at (%d,%d): %x %x",
                                        c,r,image->data.PTR[r][c],temp);
                                psFree(image);
                                psFree(temp);
                                return 8;
                            }
                        }
                    }
                    psFree(temp);
                }
                break;
            default: {
                    // ignore type and just use as byte bucket.
                    unsigned int rows = numRows[i];
                    unsigned int cols = numCols[i]*PSELEMTYPE_SIZEOF(type[t]);

                    for (int r=0;r<rows;r++) {
                        for (int c=0;c<cols;c++) {
                            image->data.U8[r][c] = (uint8_t)(r + c);
                        }
                    }
                    for (int r=0;r<rows;r++) {
                        for (int c=0;c<cols;c++) {
                            if (image->data.U8[r][c] != (uint8_t)(r + c)) {
                                psError(__func__,"Could not set all pixels in image (type=%d) at (%d,%d)",
                                        type[t],c,r);
                                psFree(image);
                                return 8;
                            }
                        }
                    }
                }
            }

            // #548: Verify no memory leaks or corruption are detected after a valid psImage structure with no
            // children is freed.
            psFree(image);
        }
    }

    // #548: Verify program execution doesn't stop, if the input psImage structure pointer is null.
    psFree(NULL);

    // #548: Verify no memory leaks or corruption are detected after a valid psImage structure with multiple
    // children isfreed.
    image = psImageAlloc(100,100,PS_TYPE_F32);
    psImageSubset(image,50,50,0,0);
    psImageSubset(image,50,50,20,20);

    psFree(image);

    return 0;
}

// #547: psImageSubset shall create child image of a specified size from a parent psImage structure
int testImageSubset(void)
{
    psImage preSubsetStruct;
    psImage* original;
    psImage* subset1 = NULL;
    psImage* subset2 = NULL;
    psImage* subset3 = NULL;
    int c = 128;
    int r = 256;

    original = psImageAlloc(c,r,PS_TYPE_U32);
    for (int row=0;row<r;row++) {
        for (int col=0;col<c;col++) {
            original->data.F32[row][col] = row*1000+col;
        }
    }

    memcpy(&preSubsetStruct,original,sizeof(psImage));

    subset2 = psImageSubset(original,c/2,r/2,c/4,r/4);

    subset3 = psImageSubset(original,c/2,r/2,0,0);

    psLogMsg(__func__,PS_LOG_INFO,"Verify the returned psImage structure contains expected values in the "
             "row member, if the input psImage structure image contains known values.");

    for (int row=0;row<r/2;row++) {
        for (int col=0;col<c/2;col++) {
            if (subset2->data.U32[row][col] != original->data.U32[row+r/4][col+c/4]) {
                psError(__func__,"psImageSubset output #1 was wrong at %dx%d (%d vs %d).",
                        row,col,subset2->data.U32[row][col], original->data.U32[row+r/4][col+c/4]);
                return 3;
            }
            if (subset3->data.U32[row][col] != original->data.U32[row][col]) {
                psError(__func__,"psImageSubset output #1 was wrong at %dx%d (%d vs %d).",
                        row,col,subset2->data.U32[row][col], original->data.U32[row][col]);
                return 4;
            }
        }
    }

    psLogMsg(__func__,PS_LOG_INFO,"Verify the returned psImage structure members nrow and ncol are equal to "
             "the input parameter nrow and ncol respectively.");

    if (subset3->numCols != c/2 || subset3->numRows != r/2) {
        psError(__func__,"psImageSubset output size was not proper(%dx%d, should be %dx%d).",
                subset3->numCols, subset3->numRows, c/2,r/2);
        return 5;
    }

    psLogMsg(__func__,PS_LOG_INFO,"Verify the returned psImage structure member type is equal to the input "
             "psImage structure member type.");

    if (subset2->type.type != PS_TYPE_U32) {
        psError(__func__,"psImageSubset output type was not proper(%d, should be %d).",
                subset2->type.type, PS_TYPE_U32);
        return 6;
    }
    if (subset3->type.type != PS_TYPE_U32) {
        psError(__func__,"psImageSubset output type was not proper(%d, should be %d).",
                subset3->type.type, PS_TYPE_U32);
        return 7;
    }

    psLogMsg(__func__,PS_LOG_INFO,"Verify the returned psImage structure members row0 and col0 are equal to "
             "the input parameters row0 and col0 respectively.");

    if (subset2->col0 != c/4 || subset2->row0 != r/4) {
        psError(__func__,"psImageSubset didn't set col0/row0 for subset2 (%d/%d, should be %d/%d).",
                subset2->col0,subset2->row0,c/4,r/4);
        return 8;
    }
    if (subset3->col0 != 0 || subset3->row0 != 0) {
        psError(__func__,"psImageSubset didn't set col0/row0 for subset3 (%d/%d, should be %d/%d).",
                subset3->col0,subset3->row0,0,0);
        return 9;
    }

    psLogMsg(__func__,PS_LOG_INFO,"Verify the returned psImage structure member parent is equal to the "
             "input psImage structure pointer image.");

    if (subset2->parent != original || subset3->parent != original) {
        psError(__func__,"psImageSubset didn't set parent.");
        return 10;
    }

    psLogMsg(__func__,PS_LOG_INFO,"Verify the returned psImage structure member Nchildren is equal to "
             "zero.");

    if (subset2->nChildren != 0 || subset3->nChildren != 0) {
        psError(__func__,"psImageSubset didn't set nChildren to zero.");
        return 11;
    }

    psLogMsg(__func__,PS_LOG_INFO,"Verify the returned psImage structure member children is null.");

    if (subset2->children != NULL || subset3->children != NULL) {
        psError(__func__,"psImageSubset didn't set children to NULL.");
        return 11;
    }

    psLogMsg(__func__,PS_LOG_INFO,"Verify the input psImage structure image only has the following members "
             "changed: 1) Nchildren is increased by one. 2) parent contains pointer psImage structure "
             "out at parent[Nchildren-1].");

    if (original->nChildren != preSubsetStruct.nChildren+2) {
        psError(__func__,"psImageSubset didn't increment nChildren by one per subset.");
        return 12;
    }
    if (original->children[0] != subset2 || original->children[1] != subset3) {
        psError(__func__,"psImageSubset didn't properly store the children pointers.");
        return 13;
    }

    psLogMsg(__func__,PS_LOG_INFO,"Verify the returned psImage structure pointer is null and program "
             "execution doesn't stop, if the input parameter image is null. Also verified the input "
             "psImage structure is not modified.");

    psLogMsg(__func__,PS_LOG_INFO,"An error should follow...");
    subset1 = psImageSubset(NULL,c/2,r/2,0,0);
    if (subset1 != NULL) {
        psError(__func__,"psImageSubset didn't return NULL when input image was NULL.");
        return 14;
    }

    psLogMsg(__func__,PS_LOG_INFO,"Verify the returned psImage structure pointer is null and program "
             " execution doesn't stop, if the input parameters nrow and/or ncol are zero. Also verify "
             "input psImage structure is not modified.");

    memcpy(&preSubsetStruct,original,sizeof(psImage));
    psLogMsg(__func__,PS_LOG_INFO,"An error should follow...");
    subset1 = psImageSubset(original,c/2,0,0,0);
    if (subset1 != NULL) {
        psError(__func__,"psImageSubset didn't return NULL when numRows=0.");
        return 15;
    }
    psLogMsg(__func__,PS_LOG_INFO,"An error should follow...");
    subset1 = psImageSubset(original,0,r/2,0,0);
    if (subset1 != NULL) {
        psError(__func__,"psImageSubset didn't return NULL when numCols=0.");
        return 16;
    }
    if (memcmp(original,&preSubsetStruct,sizeof(psImage)) != 0) {
        psError(__func__,"psImageSubset changed the original struct though it failed to subset.");
        return 17;
    }


    psLogMsg(__func__,PS_LOG_INFO,"Verify the returned psImage structure pointer is null and program "
             "execution doesn't stop, if the input parameters row0 and col0 are not within the range of "
             "values of psImage structure image.");

    psLogMsg(__func__,PS_LOG_INFO,"An error should follow...");
    subset1 = psImageSubset(original,c/2,r/2,c,0);
    if (subset1 != NULL) {
        psError(__func__,"psImageSubset didn't return NULL when subset origin was outside of "
                "image (via cols).");
        return 18;
    }
    psLogMsg(__func__,PS_LOG_INFO,"An error should follow...");
    subset1 = psImageSubset(original,c/2,r/2,0,r);
    if (subset1 != NULL) {
        psError(__func__,"psImageSubset didn't return NULL when subset origin was outside of "
                "image (via rows).");
        return 19;
    }
    psLogMsg(__func__,PS_LOG_INFO,"An error should follow...");
    subset1 = psImageSubset(original,c/2,r/2,-1,0);
    if (subset1 != NULL) {
        psError(__func__,"psImageSubset didn't return NULL when subset origin was outside of "
                "image (col0=-1).");
        return 20;
    }
    psLogMsg(__func__,PS_LOG_INFO,"An error should follow...");
    subset1 = psImageSubset(original,c/2,r/2,0,-1);
    if (subset1 != NULL) {
        psError(__func__,"psImageSubset didn't return NULL when subset origin was outside of "
                "image (row0=-1).");
        return 21;
    }

    psLogMsg(__func__,PS_LOG_INFO,"Verify the returned psImage structure pointer is null and program "
             "execution doesn't stop if the input parameters nrow, ncol, row0 and col0 specify a range of "
             "data not within the input psImage structure image.  Also verify the input psImage structure "
             "is not modified.");

    psLogMsg(__func__,PS_LOG_INFO,"An error should follow...");
    subset1 = psImageSubset(original,c/2,r/2,c/2,0);
    if (subset1 != NULL) {
        psError(__func__,"psImageSubset didn't return NULL when subset was outside of image (via rows).");
        return 22;
    }
    psLogMsg(__func__,PS_LOG_INFO,"An error should follow...");
    subset1 = psImageSubset(original,c/2,r/2,0,r/2);
    if (subset1 != NULL) {
        psError(__func__,"psImageSubset didn't return NULL when subset was outside of image (via cols).");
        return 23;
    }
    psLogMsg(__func__,PS_LOG_INFO,"An error should follow...");
    subset1 = psImageSubset(original,c/2,r/2,c/2,r/2);
    if (subset1 != NULL) {
        psError(__func__,"psImageSubset didn't return NULL when subset was outside of image (via row+cols).");
        return 24;
    }

    psLogMsg(__func__, PS_LOG_INFO, "psImageFreeChildren shall deallocate any children images of a "
             "psImage structure");

    memcpy(&preSubsetStruct,original,sizeof(psImage));

    psImageFreeChildren(original);

    // Verify the returned psImage structure member Nchildren is set to zero.
    if (original->nChildren != 0) {
        psError(__func__,"psImageFreeChildren didn't set nChildren to zero.");
        return 25;
    }

    // Verify the returned psImage structure member children pointer is set to null.
    if (original->children != NULL) {
        psError(__func__,"psImageFreeChildren didn't set children ptr to NULL.");
        return 26;
    }

    //Verify the returned psImage structure members type, nrow, ncol, row0, col0, rows and parent are not
    // modified.
    if (preSubsetStruct.numRows != original->numRows ||
            preSubsetStruct.numCols != original->numCols ||
            preSubsetStruct.row0 != original->row0 ||
            preSubsetStruct.col0 != original->col0) {

        psError(__func__,"psImageFreeChildren modified parent's non-children elements.");
        return 27;
    }

    psFree(original);

    return 0;
}

int testImageCopy(void)
{
    psImage* img = NULL;
    psImage* img2 = NULL;
    psImage* img3 = NULL;
    unsigned int c = 128;
    unsigned int r = 256;

    img = psImageAlloc(c,r,PS_TYPE_F32);
    for (unsigned row=0;row<r;row++) {
        psF32* imgRow = img->data.F32[row];
        for (unsigned col=0;col<c;col++) {
            imgRow[col] = (psF32)(row+col);
        }
    }
    img2 = psImageAlloc(c,r,PS_TYPE_F32);
    for (unsigned row=0;row<r;row++) {
        psF32* img2Row = img2->data.F32[row];
        for (unsigned col=0;col<c;col++) {
            img2Row[col] = 0.0f;
        }
    }

    img3 = psImageCopy(img2,img,PS_TYPE_F32);

    // Verify the returned psImage structure pointer is equal to the input parameter output.
    if (img2 != img3) {
        psError(__func__,"the image given for recycled wasn't");
        return 1;
    }

    // Verify the returned psImage structure member are equal to the values in
    // the input psImage structure input.

    #define testImageCopyType(IN,OUT) \
    img = psImageRecycle(img,c,r,PS_TYPE_##IN); \
    for (unsigned row=0;row<r;row++) { \
        ps##IN* imgRow = img->data.IN[row]; \
        for (unsigned col=0;col<c;col++) { \
            imgRow[col] = (ps##IN)(row+col); \
        } \
    } \
    img2 = psImageCopy(img2,img,PS_TYPE_##OUT); \
    if (img2 == NULL) { \
        psError(__func__,"psImageCopy failed to copy U8."); \
        return 2; \
    } \
    for (unsigned int row=0;row<r;row++) { \
        ps##IN* imgRow = img->data.IN[row]; \
        ps##OUT* img2Row = img2->data.OUT[row]; \
        for (unsigned int col=0;col<c;col++) { \
            if (abs(imgRow[col] - (ps##IN)(row+col)) > 0.5) { \
                psError(__func__,"Input image was changed at %d,%d!", \
                        col,row); \
                return 2; \
            } \
            if (abs(img2Row[col] - (ps##OUT)(imgRow[col])) > 0.5) { \
                psError(__func__,"returned psImage values after copy don't match at %d,%d " \
                        "(%d vs %d)",\
                        col,row,img2Row[col], (ps##OUT)(imgRow[col])); \
                return 2; \
            } \
        } \
    }

    #define testImageCopyTypes(IN) \
    printf("to psF32\n"); \
    testImageCopyType(IN,F32);\
    printf("to psF64\n"); \
    testImageCopyType(IN,F64); \
    printf("to psU8\n"); \
    testImageCopyType(IN,U8); \
    printf("to psU16\n"); \
    testImageCopyType(IN,U16); \
    printf("to psU32\n"); \
    testImageCopyType(IN,U32); \
    printf("to psS8\n"); \
    testImageCopyType(IN,S8);\
    printf("to psS16\n"); \
    testImageCopyType(IN,S16);\
    printf("to psS32\n"); \
    testImageCopyType(IN,S32);

    psLogMsg(__func__,PS_LOG_INFO,"Image Copy Test for psU8");
    testImageCopyTypes(U8);
    psLogMsg(__func__,PS_LOG_INFO,"Image Copy Test for psU16");
    testImageCopyTypes(U16);
    psLogMsg(__func__,PS_LOG_INFO,"Image Copy Test for psU32");
    testImageCopyTypes(U32);
    psLogMsg(__func__,PS_LOG_INFO,"Image Copy Test for psS8");
    testImageCopyTypes(S8);
    psLogMsg(__func__,PS_LOG_INFO,"Image Copy Test for psS16");
    testImageCopyTypes(S16);
    psLogMsg(__func__,PS_LOG_INFO,"Image Copy Test for psS32");
    testImageCopyTypes(S32);
    psLogMsg(__func__,PS_LOG_INFO,"Image Copy Test for psF32");
    testImageCopyTypes(F32);
    psLogMsg(__func__,PS_LOG_INFO,"Image Copy Test for psF64");
    testImageCopyTypes(F64);

    // Verify the returned psImage structure pointer is null and program
    // execution doesn't stop, if the input parameter input is null.
    psLogMsg(__func__,PS_LOG_INFO,"An error should follow...");
    img3 = psImageCopy(NULL,NULL,PS_TYPE_F32);
    if (img3 != NULL) {
        psError(__func__,"psImageCopy didn't return NULL when input image was NULL.");
        return 3;
    }

    psFree(img);
    psFree(img2);

    return 0;
}

