#include <limits.h>
#include <float.h>
#include <math.h>
#include <grass/gis.h>
#include <grass/raster.h>
#include "tinf.h"

/* To add a new multitype function, use the function below to initialize
 * the function pointer to each of the three typed functions.  The function
 * pointers and the function prototypes are defined in a header file.
 * The actual functions follow. */

int (*is_null)(void *);
size_t (*bpe)(void);
void *(*get_max)(void *, void *);
void *(*get_min)(void *, void *);
void (*get_row)(int, void *, int);
void *(*get_buf)(void);
void (*put_row)(int, void *);
double (*slope)(void *, void *, double);
void (*set_min)(void *);
void (*set_max)(void *);
void (*diff)(void *, void *);
void (*sum)(void *, void *);
void (*quot)(void *, void *);
void (*prod)(void *, void *);

void set_func_pointers(int in_type)
{
    switch (in_type) {
    case CELL_TYPE:
        is_null = is_null_c;
        bpe = bpe_c;
        get_max = get_max_c;
        get_min = get_min_c;
        get_row = get_row_c;
        get_buf = get_buf_c;
        put_row = put_row_c;
        slope = slope_c;
        set_min = set_min_c;
        set_max = set_max_c;
        diff = diff_c;
        sum = sum_c;
        quot = quot_c;
        prod = prod_c;

        break;

    case FCELL_TYPE:
        is_null = is_null_f;
        bpe = bpe_f;
        get_max = get_max_f;
        get_min = get_min_f;
        get_row = get_row_f;
        get_buf = get_buf_f;
        put_row = put_row_f;
        slope = slope_f;
        set_min = set_min_f;
        set_max = set_max_f;
        diff = diff_f;
        sum = sum_f;
        quot = quot_f;
        prod = prod_f;

        break;

    case DCELL_TYPE:
        is_null = is_null_d;
        bpe = bpe_d;
        get_max = get_max_d;
        get_min = get_min_d;
        get_row = get_row_d;
        get_buf = get_buf_d;
        put_row = put_row_d;
        slope = slope_d;
        set_min = set_min_d;
        set_max = set_max_d;
        diff = diff_d;
        sum = sum_d;
        quot = quot_d;
        prod = prod_d;
    }

    return;
}

/* check for null values */
int is_null_c(void *value)
{
    return Rast_is_c_null_value((CELL *)value);
}

int is_null_f(void *value)
{
    return Rast_is_f_null_value((FCELL *)value);
}

int is_null_d(void *value)
{
    return Rast_is_d_null_value((DCELL *)value);
}

/* return the size of the current type */
size_t bpe_c(void)
{
    return sizeof(CELL);
}

size_t bpe_f(void)
{
    return sizeof(FCELL);
}

size_t bpe_d(void)
{
    return sizeof(DCELL);
}

/* return the pointer that points to the smaller of two value */
void *get_min_c(void *v1, void *v2)
{
    void *rc;

    rc = v2;
    if (*(CELL *)v1 < *(CELL *)v2)
        rc = v1;
    return rc;
}

void *get_min_f(void *v1, void *v2)
{
    void *rc;

    rc = v2;
    if (*(FCELL *)v1 < *(FCELL *)v2)
        rc = v1;
    return rc;
}

void *get_min_d(void *v1, void *v2)
{
    void *rc;

    rc = v2;
    if (*(DCELL *)v1 < *(DCELL *)v2)
        rc = v1;
    return rc;
}

/* return the pointer that points to the larger value */
void *get_max_c(void *v1, void *v2)
{
    void *rc;

    rc = v2;
    if (*(CELL *)v1 > *(CELL *)v2)
        rc = v1;
    return rc;
}

void *get_max_f(void *v1, void *v2)
{
    void *rc;

    rc = v2;
    if (*(FCELL *)v1 > *(FCELL *)v2)
        rc = v1;
    return rc;
}

void *get_max_d(void *v1, void *v2)
{
    void *rc;

    rc = v2;
    if (*(DCELL *)v1 > *(DCELL *)v2)
        rc = v1;
    return rc;
}

/* Read one line from a raster map */
void get_row_c(int fd, void *row, int n)
{
    Rast_get_c_row(fd, (CELL *)row, n);
}

void get_row_f(int fd, void *row, int n)
{
    Rast_get_f_row(fd, (FCELL *)row, n);
}

void get_row_d(int fd, void *row, int n)
{
    Rast_get_d_row(fd, (DCELL *)row, n);
}

/* Write one row to a raster map */
void put_row_c(int fd, void *row)
{
    Rast_put_c_row(fd, (CELL *)row);
}

void put_row_f(int fd, void *row)
{
    Rast_put_f_row(fd, (FCELL *)row);
}

void put_row_d(int fd, void *row)
{
    Rast_put_d_row(fd, (DCELL *)row);
}

/* Allocate memory for one line of data */
void *get_buf_c(void)
{
    return (void *)Rast_allocate_c_buf();
}

void *get_buf_f(void)
{
    return (void *)Rast_allocate_f_buf();
}

void *get_buf_d(void)
{
    return (void *)Rast_allocate_d_buf();
}

/* initialize memory to a minimum value */
void set_min_c(void *v)
{
    *(CELL *)v = INT_MIN;
}

void set_min_f(void *v)
{
    *(FCELL *)v = FLT_MIN;
}

void set_min_d(void *v)
{
    *(DCELL *)v = DBL_MIN;
}

/* initialize memory to a maximum value */
void set_max_c(void *v)
{
    *(CELL *)v = INT_MAX;
}

void set_max_f(void *v)
{
    *(FCELL *)v = FLT_MAX;
}

void set_max_d(void *v)
{
    *(DCELL *)v = DBL_MAX;
}

/* get the difference between two values, returned in the first pointer */
void diff_c(void *v1, void *v2)
{
    *(CELL *)v1 -= *(CELL *)v2;
}

void diff_f(void *v1, void *v2)
{
    *(FCELL *)v1 -= *(FCELL *)v2;
}

void diff_d(void *v1, void *v2)
{
    *(DCELL *)v1 -= *(DCELL *)v2;
}

/* get the sum of two values, returned in the first pointer */
void sum_c(void *v1, void *v2)
{
    *(CELL *)v1 += *(CELL *)v2;
}

void sum_f(void *v1, void *v2)
{
    *(FCELL *)v1 += *(FCELL *)v2;
}

void sum_d(void *v1, void *v2)
{
    *(DCELL *)v1 += *(DCELL *)v2;
}

/* get the quotient of two values, returned in the first pointer */
void quot_c(void *v1, void *v2)
{
    *(CELL *)v1 /= *(CELL *)v2;
}

void quot_f(void *v1, void *v2)
{
    *(FCELL *)v1 /= *(FCELL *)v2;
}

void quot_d(void *v1, void *v2)
{
    *(DCELL *)v1 /= *(DCELL *)v2;
}

/* get the product of two values, returned in the first pointer */
void prod_c(void *v1, void *v2)
{
    *(CELL *)v1 *= *(CELL *)v2;
}

void prod_f(void *v1, void *v2)
{
    *(FCELL *)v1 *= *(FCELL *)v2;
}

void prod_d(void *v1, void *v2)
{
    *(DCELL *)v1 *= *(DCELL *)v2;
}

/* probably not a function of general interest */
/* calculate the slope between two cells, returned as a double  */
double slope_c(void *line1, void *line2, double cnst)
{
    double rc;
    CELL *pedge;

    rc = -HUGE_VAL;
    pedge = (CELL *)line2;
    if (!Rast_is_c_null_value(pedge)) {
        rc = (*(CELL *)line1 - *pedge) / cnst;
    }
    return rc;
}

double slope_f(void *line1, void *line2, double cnst)
{
    double rc;
    FCELL *pedge;

    rc = -HUGE_VAL;
    pedge = (FCELL *)line2;
    if (!Rast_is_f_null_value(pedge)) {
        rc = (*(FCELL *)line1 - *pedge) / cnst;
    }
    return rc;
}

double slope_d(void *line1, void *line2, double cnst)
{
    double rc;
    DCELL *pedge;

    rc = -HUGE_VAL;
    pedge = (DCELL *)line2;
    if (!Rast_is_d_null_value(pedge)) {
        rc = (*(DCELL *)line1 - *pedge) / cnst;
    }
    return rc;
}

/* read a line and update a three-line buffer */
/* moving forward through a file */
int advance_band3(int fh, struct band3 *bnd)
{
    int rc;
    void *hold;

    hold = bnd->b[0];
    bnd->b[0] = bnd->b[1];
    bnd->b[1] = bnd->b[2];
    bnd->b[2] = hold;
    if (fh == 0)
        rc = 0;
    else
        rc = read(fh, bnd->b[2], bnd->sz);
    return rc;
}

/* read a line and update a three-line buffer */
/* moving backward through a file */
int retreat_band3(int fh, struct band3 *bnd)
{
    int rc;
    void *hold;

    hold = bnd->b[2];
    bnd->b[2] = bnd->b[1];
    bnd->b[1] = bnd->b[0];
    bnd->b[0] = hold;
    if (fh == 0)
        rc = 0;
    else {
        rc = read(fh, bnd->b[0], bnd->sz);
        lseek(fh, (off_t)-2 * bnd->sz, SEEK_CUR);
    }
    return rc;
}
