/** @file  psPixels.c
 *
 *  @brief Contains psPixel related functions
 *
 *  @ingroup Image
 *
 *  @author Robert DeSonia, MHPCC
 *
 *  @version $Revision: 1.29 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2006-10-13 21:13:48 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
 */

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

#include "psPixels.h"
#include "psMemory.h"
#include "psAssert.h"
#include "psError.h"


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

static void pixelsFree(psPixels* pixels)
{
    psFree(pixels->data);
}

// for use by qsort, etc.
static int comparePixelCoord(psPixelCoord* coord1,
                             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(long nalloc)
{
    psPixels* out = psAlloc(sizeof(psPixels));

    if (nalloc > 0) {
        out->data = psAlloc(sizeof(psPixelCoord)*nalloc);
    } else {
        out->data = NULL;
    }
    out->n = 0;
    P_PSPIXELS_SET_NALLOC(out,nalloc);

    psMemSetDeallocator(out, (psFreeFunc)pixelsFree);

    return out;
}


bool psMemCheckPixels(psPtr ptr)
{
    PS_ASSERT_PTR(ptr, false);
    return ( psMemGetDeallocator(ptr) == (psFreeFunc)pixelsFree );
}


psPixels* psPixelsRealloc(psPixels* pixels,
                          long nalloc)
{
    if (pixels == NULL) {
        return psPixelsAlloc(nalloc);
    }

    if (nalloc > 0) {
        pixels->data = psRealloc(pixels->data, sizeof(psPixelCoord)*nalloc);
    } else {
        psFree(pixels->data);
        pixels->data = NULL;
    }

    P_PSPIXELS_SET_NALLOC(pixels,nalloc);

    if (pixels->n > pixels->nalloc) {
        pixels->n = pixels->nalloc;
    }

    return pixels;
}

psPixels* p_psPixelsAppend(psPixels* pixels,
                           long growth,
                           float x,
                           float y)
{
    if (growth < 1) {
        growth = 10;
    }

    if ( (pixels == NULL) || (pixels->n >= pixels->nalloc) ) {
        pixels=psPixelsRealloc(pixels, pixels->nalloc+growth);
    }

    long n = pixels->n;

    pixels->data[n].x = x;
    pixels->data[n].y = y;

    pixels->n++;

    return pixels;
}

psPixels* psPixelsCopy(psPixels* out,
                       const psPixels* pixels)
{
    if (pixels == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL,true,_("Input psPixels can not be NULL."));
        psFree(out);
        return NULL;
    }

    out = psPixelsRealloc(out, pixels->n);

    memcpy(out->data,pixels->data, pixels->n*sizeof(psPixelCoord));
    out->n = pixels->n;

    return out;
}

psImage *psPixelsToMask(psImage *out,
                        const psPixels *pixels,
                        psRegion region,
                        psMaskType maskVal)
{
    // check that the input pixel vector is valid
    if (pixels == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("Input psPixels can not be NULL."));
        psFree(out);
        return NULL;
    }
    psPixelCoord* data = pixels->data;
    if (data == NULL) {
        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
                _("Input psPixels contains no data."));
        psFree(out);
        return NULL;
    }

    float x0 = region.x0;
    float x1 = region.x1;
    float y0 = region.y0;
    float y1 = region.y1;

    if ( (x0 < 0 || x1 < 0) || (y0 < 0 || y1 < 0) ) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                _("Specified psRegion, [%d:%d,%d:%d], does not specify a valid region."),
                (int)y0,(int)y1,(int)x0,(int)x1);
        psFree(out);
        return NULL;
    }

    // determine the output image size
    int numRows = y1-y0;
    int numCols = x1-x0;
    numRows += 1;
    numCols += 1;
    if (numRows < 1 || numCols < 1) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                _("Specified psRegion, [%d:%d,%d:%d], does not specify a valid region."),
                (int)y0,(int)y1,(int)x0,(int)x1);
        psFree(out);
        return NULL;
    }

    //  allocate the output image
    out = psImageRecycle(out, numCols, numRows, PS_TYPE_MASK);
    if (out == NULL) {
        psError(PS_ERR_UNKNOWN, false,
                _("Failed to create image of size %dx%d."),
                numCols, numRows);
        return NULL;
    }
    *(psS32*)&out->row0 = (int)y0;
    *(psS32*)&out->col0 = (int)x0;

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

    // determine the length of the pixel vector
    long length = pixels->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++) {
        float x = data[p].x;
        float y = data[p].y;
        // pixel in region?
        if (x >= x0 && x <= x1 && y >= y0 && y <= y1) {
            outData[(int)(y-y0)][(int)(x-x0)] |= maskVal;
        }
    }

    return out;
}

psPixels* psPixelsFromMask(psPixels* out,
                           const psImage* mask,
                           psMaskType maskVal)
{
    if (mask == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("Specified mask can not be NULL."));
        psFree(out);
        return NULL;
    }
    if (mask->type.type != PS_TYPE_MASK) {
        char* typeStr;
        PS_TYPE_NAME(typeStr,mask->type.type);
        psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                _("Specified mask's type, %s, is invalid.  Should be PS_TYPE_MASK."),
                typeStr);
        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
    long minPixels = numRows*numCols/100; // initial guess, 1% of pixels masked
    if (minPixels < 32) { // enforce a minimum size
        minPixels = 32;
    }

    // check out's validity (allocated/minimum size)
    if (out == NULL || out->data == NULL || out->nalloc < minPixels) {
        out = psPixelsRealloc(out,minPixels);
    }

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

    // find the mask pixels in the image
    long numPixels = 0;
    psPixelCoord* data = out->data;
    int nalloc = out->nalloc;
    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 (nalloc <= numPixels) {
                    out = psPixelsRealloc(out, 2*nalloc);
                    nalloc = out->nalloc;
                    data = out->data;
                }

                data[numPixels].x = col;
                data[numPixels].y = row;
                numPixels++;
            }
        }
    }
    out->n = numPixels;

    return out;
}

psPixels* psPixelsConcatenate(psPixels *out,
                              const psPixels *pixels)
{
    if (pixels == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true,
                _("Input psPixels can not be NULL."));
        psFree(out);
        return NULL;
    }

    long pixelsN = pixels->n;
    psPixelCoord* pixelsData = pixels->data;

    if (out == NULL) {
        // simple copy pixels
        out = psPixelsCopy(out,pixels);
        return out;
    }

    // make sure the out is large enough to fit the result
    long outN = out->n;
    out = psPixelsRealloc(out,outN + pixelsN);
    psPixelCoord* outData = out->data;

    // sort the OUT array to help in searching for duplicates later
    qsort(outData, outN, sizeof(psPixelCoord),
          (qsortCompareFcn)comparePixelCoord);

    // add non-duplicate values in pixels to out
    psPixelCoord pCoord;
    long end = outN;
    for (long n = 0; n < pixelsN; n++) {
        pCoord = pixelsData[n];
        if (bsearch(&pCoord, outData, outN, sizeof(psPixelCoord),
                    (qsortCompareFcn)comparePixelCoord) == NULL) {
            // no match in OUT array of this value
            outData[end++] = pCoord;
        }
    }
    out->n = end; // set number of elements to reflect added data

    return out;
}

bool p_psPixelsPrint (FILE *fd,
                      psPixels* pixels,
                      const char *name)
{
    if (fd == NULL) {
        fd = stdout;
    } else {
        if ( fprintf(fd, "\n") < 0 ) {
            psError(PS_ERR_IO, true,
                    "Invalid file pointer in p_psPixelsPrint.  Could not write to fd.\n");
            return false;
        }
    }

    if (name != NULL) {
        fprintf (fd, "psPixels: %s\n", name);
    }

    if (pixels == NULL) {
        fprintf(fd,"NULL\n\n");
        return true;
    }

    long n = pixels->n;
    psPixelCoord* data = pixels->data;

    if (data == NULL || n == 0) {
        fprintf(fd,"EMPTY\n\n");
        return true;
    }

    for (long i = 0; i < n; i++) {
        fprintf (fd, "(%f,%f)\n", data[i].x, data[i].y);
    }
    fprintf (fd, "\n");
    return (true);
}

bool psPixelsSet(psPixels *pixels,
                 long position,
                 psPixelCoord value)
{
    if (pixels == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true, _("Input psPixels can not be NULL."));
        return false;
    }
    if (position > pixels->n) {
        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Invalid position.  Number too large\n");
        return false;
    }
    if(position < 0) {
        position += pixels->n;
    }
    if(position < 0) {
        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Invalid position.  Negative number too large\n");
        return false;
    }
    if (position == pixels->n) {
        if (position >= pixels->nalloc) {
            psError(PS_ERR_BAD_PARAMETER_NULL, true,
                    "Specified position, %ld, is greater than n+1 of the pixels, %ld.",
                    position, pixels->nalloc);
            return false;
        }
        pixels->n++;
    }
    pixels->data[position].x = value.x;
    pixels->data[position].y = value.y;

    return true;
}

psPixelCoord psPixelsGet(const psPixels *pixels,
                         long position)
{
    psPixelCoord out;
    if (pixels == NULL) {
        psError(PS_ERR_BAD_PARAMETER_NULL, true, _("Input psPixels can not be NULL."));
        out.x = NAN; //XXX: should be NAN when changed to float
        out.y = NAN; //XXX: should be NAN when changed to float
        return out;
    }
    if (position >= pixels->n) {
        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Invalid position.  Number too large\n");
        out.x = NAN; //XXX: should be NAN when changed to float
        out.y = NAN; //XXX: should be NAN when changed to float
        return out;
    }
    if (position < 0) {
        position += pixels->n;
    }
    if (position < 0) {
        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Invalid position.  Negative number too large\n");
        out.x = NAN; //XXX: should be NAN when changed to float
        out.y = NAN; //XXX: should be NAN when changed to float
        return out;
    }
    out.x = pixels->data[position].x;
    out.y = pixels->data[position].y;
    return out;
}

long psPixelsLength(const psPixels *pixels)
{
    if ( !psMemCheckPixels((psPixels*)pixels) ) {
        psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                "Error:  Specified pixels is not a valid psPixels \n");
        return -1;
    }
    return (pixels->n);
}

