/** @file  psPixels.c
 *
 *  @brief Contains psPixel related functions
 *
 *  @ingroup Image
 *
 *  @author Robert DeSonia, MHPCC
 *
 *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2005-04-22 00:06:41 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
 */

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

#include "psPixels.h"
#include "psMemory.h"

typedef struct
{
    psS32 x;
    psS32 y;
}
p_psPixelCoord;

typedef int(*qsortCompareFcn)(const void *, const void *);

static void pixelsFree(psPixels* pixels)
{
    if (pixels != NULL) {
        psFree(pixels->x);
        psFree(pixels->y);
    }
}

// for use by qsort, etc.
static int comparePixelCoord(p_psPixelCoord* coord1, p_psPixelCoord* coord2)
{
    // check row first
    if (coord1->y < coord2->y) {
        return -1;
    }

    if (coord1->y > coord2->y) {
        return 1;
    }

    // rows are the same, so check column
    if (coord1->x < coord2->x) {
        return -1;
    }

    if (coord1->x > coord2->x) {
        return 1;
    }

    return 0;
}

psPixels* psPixelsAlloc(int size)
{
    psPixels* out = psAlloc(sizeof(psPixels));

    if (size > 0) {
        out->x = psVectorAlloc(size, PS_TYPE_S32);
        out->y = psVectorAlloc(size, PS_TYPE_S32);
    } else {
        out->x = NULL;
        out->y = NULL;
    }

    psMemSetDeallocator(out, (psFreeFcn)pixelsFree);

    return NULL;
}

psPixels* psPixelsRealloc(psPixels* pixels, int size)
{
    if (pixels == NULL) {
        return psPixelsAlloc(size);
    }

    pixels->x = psVectorRealloc(pixels->x, size);
    pixels->y = psVectorRealloc(pixels->y, size);

    return pixels;
}

psImage *psPixelsToMask(psImage *out, const psPixels *pixels, const psRegion *region, unsigned int maskVal)
{
    // check that the input pixel vector is valid
    if (pixels == NULL) {
        // XXX: Error message
        psFree(out);
        return NULL;
    }
    psVector* xVec = pixels->x;
    psVector* yVec = pixels->y;
    if (xVec == NULL || yVec == NULL) {
        // XXX: Error message
        psFree(out);
        return NULL;
    }
    if (xVec->type.type != PS_TYPE_S32) {
        // XXX: Error message
        psFree(out);
        return NULL;
    }
    if (yVec->type.type != PS_TYPE_S32) {
        // XXX: Error message
        psFree(out);
        return NULL;
    }

    // check if the input region is valid
    if (region == NULL) {
        // XXX: Error message
        psFree(out);
        return NULL;
    }
    int x0 = region->x0;
    int x1 = region->x1;
    int y0 = region->y0;
    int y1 = region->y1;

    // determine the output image size
    int numRows = x1-x0;
    int numCols = y1-y0;
    if (numRows < 1 || numCols < 1) {
        // XXX: Error message
        psFree(out);
        return NULL;
    }

    //  allocate the output image
    out = psImageRecycle(out, numCols, numRows, PS_TYPE_MASK);
    if (out == NULL) {
        // XXX: Error message
        return NULL;
    }
    *(psS32*)&out->row0 = x0;
    *(psS32*)&out->col0 = y0;

    // initialize image to all zeros
    int columnByteSize = sizeof(PS_TYPE_MASK)*numCols;
    for (int row = 0; row < numRows; row++) {
        memset(out->data.U8[row],0,columnByteSize);
    }

    // determine the length of the pixel vector
    int length = pixels->x->n;
    if (pixels->y->n != length) {
        // XXX: warning message
        if (pixels->y->n < length) {
            length = pixels->y->n;
        }
    }

    // cycle through the vector of pixels and insert pixels into image
    psMaskType** outData = out->data.PS_TYPE_MASK_DATA;
    for (int p = 0; p < length; p++) {
        psS32 x = xVec->data.S32[p];
        psS32 y = yVec->data.S32[p];
        // pixel in region?
        if (x >= x0 && x < x1 && y >= y0 && y < y1) {
            outData[x-x0][y-y0] |= maskVal;
        }
    }

    return out;
}

psPixels *psMaskToPixels(psPixels *out, const psImage *mask, unsigned int maskVal)
{
    if (mask == NULL) {
        // XXX: Error message
        psFree(out);
        return NULL;
    }
    if (mask->type.type != PS_TYPE_MASK) {
        // XXX: Error message
        psFree(out);
        return NULL;
    }
    int numRows = mask->numRows;
    int numCols = mask->numCols;

    // assumption: number of masked pixels is relatively small compared to
    // total pixels, so it is best to just start with a guess and resize if
    // necessary
    int minPixels = numRows*numCols/100; // initial guess, 1% of pixels masked
    if (minPixels < 32) { // enforce a minimum size
        minPixels = 32;
    }
    if (out == NULL) {
        out = psPixelsAlloc(minPixels);
    }
    psVector* xVec = out->x;
    psVector* yVec = out->y;

    // check the x and y vector validity (type/minimum size)
    if (xVec == NULL || xVec->type.type != PS_TYPE_S32 || xVec->nalloc < minPixels) {
        xVec = psVectorRecycle(xVec,minPixels,PS_TYPE_S32);
    }
    if (yVec == NULL || yVec->type.type != PS_TYPE_S32 || yVec->nalloc < minPixels) {
        yVec = psVectorRecycle(yVec, minPixels,PS_TYPE_S32);
    }

    // start with a blank list of pixels
    xVec->n = 0;
    yVec->n = 0;

    // find the mask pixels in the image
    int numPixels = 0;
    for (int row=0; row<numRows; row++) {
        psMaskType* maskRow = mask->data.PS_TYPE_MASK_DATA[row];
        for (int col=0; col<numCols; col++) {
            if ( (maskRow[col] | maskVal) != 0 ) {
                // check the vector sizes, and expand if necessary
                if (xVec->nalloc >= numPixels) {
                    xVec = psVectorRealloc(xVec, 2*xVec->nalloc);
                }
                if (yVec->nalloc >= numPixels) {
                    yVec = psVectorRealloc(yVec, 2*yVec->nalloc);
                }

                xVec->data.S32[numPixels] = col;
                yVec->data.S32[numPixels] = row;
                numPixels++;
            }
        }
    }

    // return the vectors to the psPixels struct
    // (n.b., not assuming psVectorRealloc/psVectorRecycle, etc., didn't
    //  relocate the vectors)
    out->x = xVec;
    out->y = yVec;

    return out;
}

psPixels* psPixelsConcatenate(psPixels *out,const psPixels *pixels)
{
    if (pixels == NULL) {
        // XXX: Error message
        return NULL;
    }
    psVector* xPixels = pixels->x;
    psVector* yPixels = pixels->y;

    // verify that pixels has well formed vectors (type)
    if (xPixels->type.type != PS_TYPE_S32 ||
            yPixels->type.type != PS_TYPE_S32) {
        // XXX: Error message
        return NULL;
    }

    // determine the length of the pixel vector
    int pixelsLen = xPixels->n;
    if (yPixels->n != pixelsLen) {
        // XXX: warning message
        if (yPixels->n < pixelsLen) {
            pixelsLen = yPixels->n;
        }
    }

    if (out == NULL) {
        // simple copy of pixels
        out = psPixelsAlloc(0); // let psVectorCopy allocate the vector
        out->x = psVectorCopy(out->x,pixels->x,PS_TYPE_S32);
        out->y = psVectorCopy(out->y,pixels->y,PS_TYPE_S32);

        return out;
    }

    // make sure the out vectors are allocated
    psVector* xVec = out->x;
    psVector* yVec = out->y;
    if (xVec == NULL) {
        out->x = xVec = psVectorAlloc(pixelsLen,PS_TYPE_S32);
        xVec->n = 0;
    }
    if (yVec == NULL) {
        out->y = yVec = psVectorAlloc(pixelsLen,PS_TYPE_S32);
        yVec->n = 0;
    }

    // verify that out has well formed vectors (type/size)
    if (xVec->type.type != PS_TYPE_S32 ||
            yVec->type.type != PS_TYPE_S32) {
        // XXX: Error message
        return NULL;
    }
    if (xVec->n != yVec->n) {
        // XXX: Error message
        return NULL;
    }
    int outLen = xVec->n;


    // populate an array of psPixelCoord structs with the out values
    p_psPixelCoord* coordinates = psAlloc(sizeof(p_psPixelCoord)*(pixelsLen+outLen));
    psS32* outXData = xVec->data.S32;
    psS32* outYData = yVec->data.S32;
    for (int n = 0; n < outLen; n++) {
        coordinates[n].x = outXData[n];
        coordinates[n].y = outYData[n];
    }

    // sort the coordinates array
    qsort(coordinates, sizeof(p_psPixelCoord), outLen,
          (qsortCompareFcn)comparePixelCoord);

    // search out for coordinates in pixels
    int end = outLen;
    psS32* pixelsXData = xPixels->data.S32;
    psS32* pixelsYData = yPixels->data.S32;
    p_psPixelCoord pCoord;
    for (int n = 0; n < pixelsLen; n++) {
        pCoord.x = pixelsXData[n];
        pCoord.y = pixelsYData[n];
        if (bsearch(&pCoord, coordinates, sizeof(p_psPixelCoord), outLen,
                    (qsortCompareFcn)comparePixelCoord) == NULL) {
            coordinates[end++] = pCoord;
        }
    }

    // transfer the coordinates data back to psPixels
    out = psPixelsRealloc(out, end);
    outXData = xVec->data.S32;
    outYData = yVec->data.S32;
    for (int n = 0; n < end; n++) {
        outXData[n] = coordinates[n].x;
        outYData[n] = coordinates[n].y;
    }

    psFree(coordinates);

    return out;
}
