# include <strings.h>  // for strncasecmp
# include <pslib.h>
# include "psLibUtils.h"

// XXX EAM : this is NOT included in the C99 headers ??
FILE *fdopen(int fildes, const char *mode);

// XXX EAM : these utility functions should be added back into PSLib

static psHash *timers = NULL;

/* XXX: remove
bool psTimerClear (char *name) {
 
  bool status;
 
  if (name == NULL) return false;
 
  status = psHashRemove (timers, name);
  return (status);
}
*/

void psTimerFree ()
{

    psFree (timers);
    p_psTimeFinalize();
    return;
}

// start/restart a named timer
bool psTimerStart (char *name)
{

    psTime *start;

    if (timers == NULL)
        timers = psHashAlloc (16);

    start = psTimeGetNow (PS_TIME_UTC);
    psHashAdd (timers, name, start);
    psFree (start);
    return (TRUE);
}

// get current elapsed time on named timer
psF64 psTimerMark (char *name)
{

    psTime *start;
    psTime *mark;
    psF64   delta;

    if (timers == NULL)
        return (0);

    start = psHashLookup (timers, name);
    if (start == NULL)
        return (0);

    mark = psTimeGetNow (PS_TIME_UTC);
    delta = psTimeDelta (mark, start);
    psFree (mark);
    // psFree (start); -- XXX is psHashLookup not psMemCopying?

    return (delta);
}

/* XXX: remove
// find the location of the specified argument
int psArgumentGet (int argc, char **argv, char *arg) {
 
    int i;
 
    for (i = 0; i < argc; i++) {
 if (!strcmp(argv[i], arg))
     return (i);
    }
  
    return ((int)NULL);
}
*/

/* XXX: remove
// remove the specified argument (by location)
int psArgumentRemove (int N, int *argc, char **argv) {
 
    int i;
 
    if ((N != (int)NULL) && (N != 0)) {
 (*argc)--;
 for (i = N; i < *argc; i++) {
     argv[i] = argv[i+1];
 }
    }
 
    return (N);
}
*/

// we have log levels 1 (Error), 2 (Warning), 3 (Info), 4 (Details), 5 (Minutiae)
// 2 = default, -v = 3, -vv = 4, -vvv = 5
psS32 psLogArguments (int *argc, char **argv)
{

    int N, level;

    // default log level is 2
    level = 2;

    // set in order, so that -vvv overrides -vv overrides -v
    if ((N = psArgumentGet (*argc, argv, "-v"))) {
        psArgumentRemove (N, argc, argv);
        level = 3;
    }
    if ((N = psArgumentGet (*argc, argv, "-vv"))) {
        psArgumentRemove (N, argc, argv);
        level = 4;
    }
    if ((N = psArgumentGet (*argc, argv, "-vvv"))) {
        psArgumentRemove (N, argc, argv);
        level = 5;
    }

    if ((N = psArgumentGet (*argc, argv, "-logfmt"))) {
        if (*argc < N + 2) {
            psAbort ("psLogArguments", "USAGE: -logfmt (format)");
        }
        psArgumentRemove (N, argc, argv);
        psLogSetFormat (argv[N]); // XXX EAM : this function should return an error if the log format is invalid
        psArgumentRemove (N, argc, argv);
    }

    // set the level, return the level
    psLogSetLevel (level);
    return (level);
}

// set trace levels by facility
psS32 psTraceArguments (int *argc, char **argv)
{

    int N;

    // argument format is: -trace (facil) (level)
    while ((N = psArgumentGet (*argc, argv, "-trace"))) {
        if (*argc < N + 3) {
            psAbort ("psTraceArguments", "USAGE: -trace (facility) (level)");
        }
        psArgumentRemove (N, argc, argv);
        psTraceSetLevel (argv[N], atoi(argv[N+1]));
        psArgumentRemove (N, argc, argv);
        psArgumentRemove (N, argc, argv);
    }
    if ((N = psArgumentGet (*argc, argv, "-trace-levels"))) {
        psTracePrintLevels ();
        exit (2);
    }
    return (TRUE);
}

# if (0)
    // alternate implementation of this function from pmObjects.c
    // now defined in psLib SDRS as psImageRow
    psVector *psGetRowVectorFromImage(psImage *image,
                                      psU32 row)
{
    PS_ASSERT_IMAGE_NON_NULL(image, NULL);
    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, NULL);

    psVector *tmpVector = psVectorAlloc(image->numCols, PS_TYPE_F32);
    memcpy (tmpVector->data.F32, image->data.F32[row], image->numCols*sizeof(psF32));
    return(tmpVector);
}
# endif

// XXX EAM : this is now in psLib
void psImageSmooth_EAM (psImage *image, float sigma, float Nsigma)
{

    int Nx, Ny, Npixel, Nrange;
    float factor, g, s;
    psVector *temp;

    // relevant terms
    Nrange = sigma*Nsigma + 0.5;
    Npixel = 2*Nrange + 1;
    factor = -0.5/(sigma*sigma);

    Nx = image->numCols;
    Ny = image->numRows;

    // generate gaussian
    psVector *gaussnorm = psVectorAlloc (Npixel, PS_TYPE_F32);
    for (int i = -Nrange; i < Nrange + 1; i++) {
        gaussnorm->data.F32[i+Nrange] = exp (factor*i*i);
    }
    psF32 *gauss = &gaussnorm->data.F32[Nrange];

    // smooth in X direction
    temp = psVectorAlloc (Nx, PS_TYPE_F32);
    for (int j = 0; j < Ny; j++) {
        psF32 *vi = image->data.F32[j];
        psF32 *vo = temp->data.F32;
        for (int i = 0; i < Nx; i++) {
            g = s = 0;
            for (int n = -Nrange; n < Nrange + 1; n++) {
                if (i+n < 0)
                    continue;
                if (i+n >= Nx)
                    continue;
                s += gauss[n]*vi[i+n];
                g += gauss[n];
            }
            vo[i] = s / g;
        }
        memcpy (image->data.F32[j], temp->data.F32, Nx*sizeof(psF32));
    }
    psFree (temp);

    // smooth in Y direction
    temp = psVectorAlloc (image->numRows, PS_TYPE_F32);
    for (int i = 0; i < Nx; i++) {
        psF32  *vo = temp->data.F32;
        psF32 **vi = image->data.F32;
        for (int j = 0; j < Ny; j++) {
            g = s = 0;
            for (int n = -Nrange; n < Nrange + 1; n++) {
                if (j+n < 0)
                    continue;
                if (j+n >= Ny)
                    continue;
                s += gauss[n]*vi[j+n][i];
                g += gauss[n];
            }
            vo[j] = s / g;
        }
        // replace temp in image
        for (int j = 0; j < Ny; j++) {
            vi[j][i] = vo[j];
        }
    }
    psFree (temp);
    psFree (gaussnorm);
}

bool psImageInit (psImage *image,...)
{

    va_list argp;
    psU8  vU8;
    psF32 vF32;
    psF64 vF64;

    if (image == NULL)
        return (false);

    va_start (argp, image);

    switch (image->type.type) {
    case PS_TYPE_U8:
        vU8 = va_arg (argp, psU32);

        for (int iy = 0; iy < image->numRows; iy++) {
            for (int ix = 0; ix < image->numCols; ix++) {
                image->data.U8[iy][ix] = vU8;
            }
        }
        break;

    case PS_TYPE_F32:
        vF32 = va_arg (argp, psF64);

        for (int iy = 0; iy < image->numRows; iy++) {
            for (int ix = 0; ix < image->numCols; ix++) {
                image->data.F32[iy][ix] = vF32;
            }
        }
        return (true);

    case PS_TYPE_F64:
        vF64 = va_arg (argp, psF64);

        for (int iy = 0; iy < image->numRows; iy++) {
            for (int ix = 0; ix < image->numCols; ix++) {
                image->data.F64[iy][ix] = vF64;
            }
        }
        return (true);

    default:
        psAbort ("psphot.psUtils", "datatype %d not defined in psImageInit\n", image->type);
        return (false);
    }
    return (false);
}

/* XXX: remove
// count number of pixels with given mask value
int psImageCountPixelMask (psImage *mask, psU8 value) 
{
    int Npixels = 0;
  
    for (int i = 0; i < mask->numRows; i++) {
 for (int j = 0; j < mask->numCols; j++) {
     if (mask->data.U8[i][j] & value) {
  Npixels ++;
     }
 }
    }
    return (Npixels);
}
*/

// define a square region centered on the given coordinate
// XXX EAM : this is now in psLib
# if (0)
    psRegion *psRegionSquare (psF32 x, psF32 y, psF32 radius)
{
    psRegion *region;
    region = psRegionAlloc (x - radius, x + radius + 1,
                            y - radius, y + radius + 1);
    return (region);
}
# endif

// set actual region based on image parameters:
// compensate for negative upper limits
// XXX this is inconsistent: the coordindates should always be in the parent
//     frame, which means the negative values should subtract from Nx,Ny of
//     the parent, not the child.  but, we don't carry the dimensions of the
//     parent in the psImage container.  for now, use the child Nx,Ny
//     force range to be on this subimage
// XXX EAM : this needs to be changed to use psRegion rather than psRegion*
// XXX EAM : this is now in psLib
# if (0)
    psRegion *psRegionForImage (psRegion *out, psImage *image, psRegion *in)
{

    // x0,y0, x1,y1 are in *parent* units

    if (out == NULL) {
        out = psRegionAlloc(in->x0, in->x1, in->y0, in->y1);
    } else {
        *out = *in;
    }
    // XXX these are probably wrong (see above)
    if (out->x1 <= 0) {
        out->x1 = image->col0 + image->numCols + out->x1;
    }
    if (out->y1 <= 0) {
        out->y1 = image->row0 + image->numRows + out->y1;
    }

    // force the lower-limits to be on the child
    out->x0 = PS_MAX(image->col0, out->x0);
    out->y0 = PS_MAX(image->row0, out->y0);

    // force the upper-limits to be on the child
    out->x1 = PS_MIN(image->col0 + image->numCols, out->x1);
    out->y1 = PS_MIN(image->row0 + image->numRows, out->y1);
    return (out);
}
# endif

// mask the area contained by the region
// the region is defined wrt the parent image
// XXX EAM : this is now in psLib
# if (0)
    void psImageMaskRegion (psImage *image, psRegion *region, bool logical_and, int maskValue)
{

    for (int iy = 0; iy < image->numRows; iy++) {
        for (int ix = 0; ix < image->numCols; ix++) {
            if (ix + image->col0 <  region->x0)
                continue;
            if (ix + image->col0 >= region->x1)
                continue;
            if (iy + image->row0 <  region->y0)
                continue;
            if (iy + image->row0 >= region->y1)
                continue;
            if (logical_and) {
                image->data.U8[iy][ix] &= maskValue;
            } else {
                image->data.U8[iy][ix] |= maskValue;
            }
        }
    }
}
# endif

// mask the area not contained by the region
// the region is defined wrt the parent image
// XXX EAM : this is now in psLib
# if (0)
    void psImageKeepRegion (psImage *image, psRegion *region, bool logical_and, int maskValue)
{

    for (int iy = 0; iy < image->numRows; iy++) {
        for (int ix = 0; ix < image->numCols; ix++) {
            if (ix + image->col0 <  region->x0)
                goto maskit;
            if (ix + image->col0 >= region->x1)
                goto maskit;
            if (iy + image->row0 <  region->y0)
                goto maskit;
            if (iy + image->row0 >= region->y1)
                goto maskit;
            continue;
maskit:
            if (logical_and) {
                image->data.U8[iy][ix] &= maskValue;
            } else {
                image->data.U8[iy][ix] |= maskValue;
            }
        }
    }
}
# endif

// mask the area contained by the region
// the region is defined wrt the parent image
// XXX EAM : this is now in psLib
# if (0)
    void psImageMaskCircle (psImage *image, double x, double y, double radius, bool logical_and, int maskValue)
{

    double dx, dy, r2, R2;

    R2 = PS_SQR(radius);

    for (int iy = 0; iy < image->numRows; iy++) {
        for (int ix = 0; ix < image->numCols; ix++) {
            dx = ix + image->col0 - x;
            dy = iy + image->row0 - y;
            r2 = PS_SQR(dx) + PS_SQR(dy);
            if (r2 > R2)
                continue;
            if (logical_and) {
                image->data.U8[iy][ix] &= maskValue;
            } else {
                image->data.U8[iy][ix] |= maskValue;
            }
        }
    }
}
# endif

// mask the area contained by the region
// the region is defined wrt the parent image
// XXX EAM : this is now in psLib
# if (0)
    void psImageKeepCircle (psImage *image, double x, double y, double radius, bool logical_and, int maskValue)
{

    double dx, dy, r2, R2;

    R2 = PS_SQR(radius);

    for (int iy = 0; iy < image->numRows; iy++) {
        for (int ix = 0; ix < image->numCols; ix++) {
            dx = ix + image->col0 - x;
            dy = iy + image->row0 - y;
            r2 = PS_SQR(dx) + PS_SQR(dy);
            if (r2 < R2)
                continue;
            if (logical_and) {
                image->data.U8[iy][ix] &= maskValue;
            } else {
                image->data.U8[iy][ix] |= maskValue;
            }
        }
    }
}
# endif

/* XXX: remove
psVector *psVectorCreate (double lower, double upper, double delta, psElemType type) {
 
  int nBin = (upper - lower) / delta;
 
  psVector *out = psVectorAlloc (nBin, type);
  
  for (int i = 0; i < nBin; i++) {
    out->data.F64[i] = lower + i * delta;
  }
 
  return (out);
}
*/

// XXX EAM a utility function
bool p_psVectorPrintRow (int fd, psVector *a, char *name)
{

    FILE *f;
    f = fdopen(fd, "a+");
    fprintf (f, "vector: %s\n", name);

    for (int i = 0; i < a[0].n; i++) {
        fprintf (f, "%f  ", p_psVectorGetElementF64(a, i));
    }
    fprintf (f, "\n");
    fclose(f);
    return (true);
}
// XXX EAM is the use of fdopen here a good way to do this?
