/** @file  psImageStructManip.c
 *
 *  @brief Contains basic image structure manipulations, as specified in the
 *         PSLIB SDRS sections "Image Structure Manipulation".
 *
 *  @ingroup Image
 *
 *  @author Robert DeSonia, MHPCC
 *
 *  @version $Revision: 1.13 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2007-01-09 22:38:52 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
 *
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <string.h>

#include "psMemory.h"
#include "psImageStructManip.h"
#include "psError.h"



static psImage* imageSubset(psImage* out,
                            psImage* image,
                            psS32 col0,
                            psS32 row0,
                            psS32 col1,
                            psS32 row1)
{
    psU32 elementSize;          // size of image element in bytes
    psS32 inputColOffset;       // offset in bytes to first subset pixel in input row

    if (image == NULL || image->data.V == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("Can not operate on a NULL psImage."));
        return NULL;
    }

    if ( col0 < image->col0 || row0 < image->row0 ) {
        //        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
        //                _("Specified subset range, [%d:%d,%d:%d], is invalid or outside input psImage's boundaries, [0:%d,0:%d]."));
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                _("Specified subset range, [%d:%d,%d:%d], is invalid or outside input psImage's boundaries, [0:%d,0:%d]."),
                col0, col1-1, row0, row1-1,
                image->numCols-1, image->numRows-1);
        return NULL;
    }

    if (image->type.dimen != PS_DIMEN_IMAGE) {
        psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                _("The input psImage must have a PS_DIMEN_IMAGE dimension type."));
        return NULL;
    }

    if (col1 < 1) {
        col1 = image->col0 + image->numCols + col1;
    }
    if (row1 < 1) {
        row1 = image->row0 + image->numRows + row1;
    }

    if (    col1 <= col0 ||
            row1 <= row0 ||
            col0 >= image->col0 + image->numCols ||
            row0 >= image->row0 + image->numRows ||
            col1 > image->col0 + image->numCols ||
            row1 > image->row0 + image->numRows ) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                _("Specified subset range, [%d:%d,%d:%d], is invalid or outside input psImage's boundaries, [0:%d,0:%d]."),
                col0, col1-1, row0, row1-1,
                image->numCols-1, image->numRows-1);
        return NULL;
    }



    psS32 numRows = row1-row0;
    psS32 numCols = col1-col0;

    elementSize = PSELEMTYPE_SIZEOF(image->type.type);

    if (image->parent != NULL) { // if this is a child, we need to start working with parent.
        // XXX EAM : we now treat the region as parent coordinates
        // col0 += image->col0;
        // col1 += image->col0;
        // row0 += image->row0;
        // row1 += image->row0;
        image = (psImage*)image->parent;
    }

    // increment the raw data buffer before freeing anything in the 'out'
    psPtr rawData = psMemIncrRefCounter(image->p_rawDataBuffer);

    if (out != NULL) {
        // if a child, need to orphan (disassociate from parent) first
        if (out->parent != NULL) {
            psArrayRemoveData(out->parent->children,psMemIncrRefCounter(out));
            // remove from parent's knowledge without triggering a free
            out->parent = NULL; // break link to parent
        }

        psFree(out->p_rawDataBuffer); // free the previous data reference
    } else {
        out = psAlloc(sizeof(psImage));
        out->data.V = NULL;
    }

    out->data.V = psRealloc(out->data.V,sizeof(psPtr)*numRows); // resize row pointer array
    P_PSIMAGE_SET_TYPE(out, image->type);
    P_PSIMAGE_SET_NUMCOLS(out, numCols);
    P_PSIMAGE_SET_NUMROWS(out, numRows);

    out->row0 = row0;
    out->col0 = col0;
    out->parent = image;
    out->children = NULL;
    out->p_rawDataBuffer = rawData;

    // set the new psImage's deallocator to the same as the input image
    psMemSetDeallocator(out,psMemGetDeallocator(image));

    inputColOffset = elementSize * col0;
    for (psS32 row = 0; row < numRows; row++) {
        out->data.V[row] = image->data.U8[row0 + row] + inputColOffset;
    }

    // add output image as a child of the input image.
    image->children = psArrayAdd(image->children,16,out);
    psMemDecrRefCounter(out); // don't count the reference held by parent as a true reference

    return (out);
}

psImage* psImageSubset(psImage* image,
                       psRegion region)
{
    return imageSubset(NULL,image,region.x0, region.y0,
                       region.x1, region.y1);
}

psImage* psImageCopy(psImage* output,
                     const psImage* input,
                     psElemType type)
{
    psElemType inDatatype;
    psS32 elementSize;
    psS32 elements;
    psS32 numRows;
    psS32 numCols;

    if (input == NULL || input->data.V == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("Can not operate on a NULL psImage."));
        psFree(output);
        return NULL;
    }

    if (input == output) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("Specified input and output psImage can not reference the same psImage."));
        psFree(output);
        return NULL;
    }

    if (input->type.dimen != PS_DIMEN_IMAGE) {
        psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                _("The input psImage must have a PS_DIMEN_IMAGE dimension type."));
        psFree(output);
        return NULL;
    }

    inDatatype = input->type.type;
    numRows = input->numRows;
    numCols = input->numCols;
    elements = numRows * numCols;
    elementSize = PSELEMTYPE_SIZEOF(inDatatype);

    output = psImageRecycle(output, numCols, numRows, type);
    output->col0 = input->col0;
    output->row0 = input->row0;

    // cover the trival case of copy of the same
    // datatype.
    if (type == inDatatype) {
        for (psS32 row=0;row<numRows;row++) {
            memcpy(output->data.V[row], input->data.V[row], elementSize * numCols);
        }
        return output;
    }

    #define PSIMAGE_ELEMENT_COPY(IN,INTYPE,OUT,OUTTYPE,ELEMENTS) { \
        ps##INTYPE *in; \
        ps##OUTTYPE *out; \
        for(psS32 row=0;row<numRows;row++) { \
            in = IN->data.INTYPE[row]; \
            out = OUT->data.OUTTYPE[row]; \
            for (psS32 col=0;col<numCols;col++) { \
                *(out++) = *(in++); \
            } \
        } \
    }

    #define PSIMAGE_COPY_CASE(OUT,OUTTYPE) { \
        switch (inDatatype) { \
        case PS_TYPE_S8: \
            PSIMAGE_ELEMENT_COPY(input,S8,OUT,OUTTYPE,elements); \
            break; \
        case PS_TYPE_S16: \
            PSIMAGE_ELEMENT_COPY(input,S16,OUT,OUTTYPE,elements); \
            break; \
        case PS_TYPE_S32: \
            PSIMAGE_ELEMENT_COPY(input,S32,OUT,OUTTYPE,elements); \
            break; \
        case PS_TYPE_S64: \
            PSIMAGE_ELEMENT_COPY(input,S64,OUT,OUTTYPE,elements); \
            break; \
        case PS_TYPE_U8: \
            PSIMAGE_ELEMENT_COPY(input,U8,OUT,OUTTYPE,elements); \
            break; \
        case PS_TYPE_U16: \
            PSIMAGE_ELEMENT_COPY(input,U16,OUT,OUTTYPE,elements); \
            break; \
        case PS_TYPE_U32: \
            PSIMAGE_ELEMENT_COPY(input,U32,OUT,OUTTYPE,elements); \
            break; \
        case PS_TYPE_U64: \
            PSIMAGE_ELEMENT_COPY(input,U64,OUT,OUTTYPE,elements); \
            break; \
        case PS_TYPE_F32: \
            PSIMAGE_ELEMENT_COPY(input,F32,OUT,OUTTYPE,elements); \
            break; \
        case PS_TYPE_F64: \
            PSIMAGE_ELEMENT_COPY(input,F64,OUT,OUTTYPE,elements); \
            break; \
        case PS_TYPE_C32: \
            PSIMAGE_ELEMENT_COPY(input,C32,OUT,OUTTYPE,elements); \
            break; \
        case PS_TYPE_C64: \
            PSIMAGE_ELEMENT_COPY(input,C64,OUT,OUTTYPE,elements); \
            break; \
        default: \
            break; \
        } \
    }

    switch (type) {
    case PS_TYPE_S8:
        PSIMAGE_COPY_CASE(output, S8);
        break;
    case PS_TYPE_S16:
        PSIMAGE_COPY_CASE(output, S16);
        break;
    case PS_TYPE_S32:
        PSIMAGE_COPY_CASE(output, S32);
        break;
    case PS_TYPE_S64:
        PSIMAGE_COPY_CASE(output, S64);
        break;
    case PS_TYPE_U8:
        PSIMAGE_COPY_CASE(output, U8);
        break;
    case PS_TYPE_U16:
        PSIMAGE_COPY_CASE(output, U16);
        break;
    case PS_TYPE_U32:
        PSIMAGE_COPY_CASE(output, U32);
        break;
    case PS_TYPE_U64:
        PSIMAGE_COPY_CASE(output, U64);
        break;
    case PS_TYPE_F32:
        PSIMAGE_COPY_CASE(output, F32);
        break;
    case PS_TYPE_F64:
        PSIMAGE_COPY_CASE(output, F64);
        break;
    case PS_TYPE_C32:
        PSIMAGE_COPY_CASE(output, C32);
        break;
    case PS_TYPE_C64:
        PSIMAGE_COPY_CASE(output, C64);
        break;
    default: {
            char* typeStr;
            PS_TYPE_NAME(typeStr,type);
            psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                    _("Specified psImage type, %s, is not supported."),
                    typeStr);
            psFree(output);

            break;
        }
    }
    return output;
}

psImage* psImageTrim(psImage* image,
                     psRegion region)
{
    if (image == NULL || image->data.V == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("Can not operate on a NULL psImage."));
        return NULL;
    }

    if (image->parent != NULL) {
        return imageSubset(image,
                           (psImage*)image->parent,
                           region.x0+image->col0,
                           region.y0+image->row0,
                           region.x1+image->col0,
                           region.y1+image->row0);
    }

    int col0 = region.x0;
    int row0 = region.y0;
    int col1 = region.x1;
    int row1 = region.y1;

    if (col1 < 1) {
        col1 += image->numCols;
    }

    if (row1 < 1) {
        row1 += image->numRows;
    }

    if (    col0 < 0 ||
            row0 < 0 ||
            col1 > image->numCols ||
            row1 > image->numRows ||
            col0 >= col1 ||
            row0 >= row1 ) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                _("Specified subset range, [%d:%d,%d:%d], is invalid or outside input psImage's boundaries, [0:%d,0:%d]."),
                col0, col1-1, row0, row1-1,
                image->numCols-1, image->numRows-1);
        psFree(image);
        return NULL;
    }

    psImageFreeChildren(image);

    psU32 elementSize = PSELEMTYPE_SIZEOF(image->type.type);
    psU32 numCols = col1-col0;
    psU32 numRows = row1-row0;
    psU32 rowSize = elementSize*numCols;
    psU32 colOffset = elementSize * col0;
    psU8* imageData = image->p_rawDataBuffer;
    for (psS32 row = row0; row < row1; row++) {
        memmove(imageData,image->data.U8[row] + colOffset,rowSize);
        imageData += rowSize;
    }

    P_PSIMAGE_SET_NUMCOLS(image, numCols);
    P_PSIMAGE_SET_NUMROWS(image, numRows);

    // XXX: should I really resize the buffers?
    image->data.V = psRealloc(image->data.V,sizeof(psPtr)*numRows);
    image->p_rawDataBuffer = psRealloc(image->p_rawDataBuffer,rowSize*numRows);

    image->data.V[0] = image->p_rawDataBuffer;
    for (psS32 r = 1; r < numRows; r++) {
        image->data.U8[r] = image->data.U8[r-1] + rowSize;
    }

    return (image);
}

