/*!
   \file lib/ogsf/gvl_file.c

   \brief OGSF library - loading and manipulating volumes (lower level
   functions)

   GRASS OpenGL gsurf OGSF Library

   (C) 1999-2008 by the GRASS Development Team

   This program is free software under the
   GNU General Public License (>=v2).
   Read the file COPYING that comes with GRASS
   for details.

   \author Tomas Paudits (February 2004)
   \author Doxygenized by Martin Landa <landa.martin gmail.com> (May 2008)
 */

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

#include <grass/gis.h>
#include <grass/ogsf.h>
#include <grass/raster3d.h>
#include <grass/glocale.h>

#define LUCKY        33

#define MODE_DIRECT  0
#define MODE_SLICE   1
#define MODE_FULL    2
#define MODE_PRELOAD 3

#define MODE_DEFAULT 0

#define STATUS_READY 0
#define STATUS_BUSY  1

/*!
   \brief structure for slice mode reading from volume file
 */
typedef struct {
    int num, skip;
    int crnt, base;

    void *slice[MAX_VOL_SLICES];
} slice_data;

static geovol_file *Data[MAX_VOL_FILES];
static geovol_file Df[MAX_VOL_FILES]; /* trying to avoid allocation */

static int Numfiles = 0;

static int Cur_id = LUCKY;
static int Cur_max;

static int Rows, Cols, Depths;

/* local functions prototypes */
void *open_g3d_file(const char *, IFLAG *, double *, double *);
int close_g3d_file(void *);

/*!
   \brief Initialize volume files

   \return 1
 */
static int init_volfiles(void)
{
    int i;
    RASTER3D_Region *w3;

    for (i = 0; i < MAX_VOL_FILES; i++) {
        /* avoiding dynamic allocation */
        Data[i] = &(Df[i]);
    }

    Cur_max = MAX_VOL_FILES;

    /* get window */
    w3 = GVL_get_window();

    /* set cols, rows, depths from window */
    Cols = w3->cols;
    Rows = w3->rows;
    Depths = w3->depths;

    return (1);
}

/*!
   \brief Check number of files

   \return 0
 */
static int check_num_volfiles(void)
{
    if (Numfiles < Cur_max) {
        return (0);
    }

    G_fatal_error(_("Maximum number of datafiles exceeded"));

    /* This return statement keeps compilers happy, it is never executed */
    return (0);
}

/*!
   \brief Get geovol_file structure for given handle

   \param id

   \return pointer to geovol_file struct
   \return NULL on failure
 */
geovol_file *gvl_file_get_volfile(int id)
{
    int i;

    for (i = 0; i < Numfiles; i++) {
        if (Data[i]->data_id == id) {
            return (Data[i]);
        }
    }

    return (NULL);
}

/*!
   \brief Find file with name and type in geovol_file array an return handle

   \param name file name
   \param begin

   \param data id
   \param -1 not found
 */
int find_datah(const char *name, IFLAG type, int begin)
{
    static int i;
    int start;

    start = begin ? 0 : i + 1;

    for (i = start; i < Numfiles; i++) {
        if (!strcmp(Data[i]->file_name, name)) {
            if (Data[i]->file_type == type) {
                return (Data[i]->data_id);
            }
        }
    }

    return (-1);
}

/*!
   \brief Get file name for given handle

   \param id handle id

   \return file name
   \return NULL on failure
 */
char *gvl_file_get_name(int id)
{
    int i;
    geovol_file *fvf;
    static char retstr[GPATH_MAX];

    for (i = 0; i < Numfiles; i++) {
        if (Data[i]->data_id == id) {
            fvf = Data[i];
            strcpy(retstr, fvf->file_name);

            return (retstr);
        }
    }

    return (NULL);
}

/*!
   \brief Get file type for given handle

   \param vf pointer to geovol_file struct

   \return file type
 */
int gvl_file_get_file_type(geovol_file *vf)
{
    return (vf->file_type);
}

/*!
   \brief Get data type for given handle

   \param vf pointer to geovol_file struct

   \return data type
 */
int gvl_file_get_data_type(geovol_file *vf)
{
    return (vf->data_type);
}

/*!
   \brief Get minimum and maximum value in volume file

   \param vf pointer to geovol_file struct
   \param[out] min min value
   \param[out] max max value
 */
void gvl_file_get_min_max(geovol_file *vf, double *min, double *max)
{
    *min = vf->min;
    *max = vf->max;
}

/*!
   \brief Open 3d raster file

   \param name file name
   \param file_type file type
   \param data_type data type
   \param[out] min min value
   \param[out] max max value

   \return pointer to file
   \return NULL on failure
 */
void *open_volfile(const char *name, IFLAG file_type, IFLAG *data_type,
                   double *min, double *max)
{
    if (file_type == VOL_FTYPE_RASTER3D) {
        return open_g3d_file(name, data_type, min, max);
    }

    return (NULL);
}

/*!
   \brief Close volume file

   \param map volume filename
   \param type file type

   \return
   \return -1 on failure
 */
int close_volfile(void *map, IFLAG type)
{
    if (type == VOL_FTYPE_RASTER3D) {
        return close_g3d_file(map);
    }

    return (-1);
}

/*!
   \brief Get handle for given file name and type

   \param name volume filename
   \param file_type file type

   \return data id
   \return -1 on failure
 */
int gvl_file_newh(const char *name, IFLAG file_type)
{
    geovol_file *new;
    static int first = 1;
    int i, id;
    void *m;
    IFLAG data_type;
    double min, max;

    if (first) {
        if (0 > init_volfiles()) {
            return (-1);
        }

        first = 0;
    }

    if (0 <= (id = find_datah(name, file_type, 1))) {
        for (i = 0; i < Numfiles; i++) {
            if (Data[i]->data_id == id) {
                new = Data[i];
                new->count++;

                return (id);
            }
        }
    }

    if (0 > check_num_volfiles()) {
        return (-1);
    }

    if (!name) {
        return (-1);
    }

    if ((m = open_volfile(name, file_type, &data_type, &min, &max)) == NULL) {
        return (-1);
    }

    new = Data[Numfiles];

    if (new) {
        Numfiles++;
        new->data_id = Cur_id++;

        new->file_name = G_store(name);
        new->file_type = file_type;
        new->count = 1;
        new->map = m;
        new->min = min;
        new->max = max;
        new->data_type = data_type;

        new->status = STATUS_READY;
        new->buff = NULL;

        new->mode = 255;
        gvl_file_set_mode(new, MODE_DEFAULT);

        return (new->data_id);
    }

    return (-1);
}

/*!
   \brief Free allocated buffers

   \param vf pointer to geovol_file struct

   \return 1
 */
int free_volfile_buffs(geovol_file *vf)
{
    if (vf->mode == MODE_SLICE) {
        G_free(vf->buff);
        vf->buff = NULL;
    }

    if (vf->mode == MODE_PRELOAD) {
        G_free(vf->buff);
        vf->buff = NULL;
    }

    return (1);
}

/*!
   \brief Free geovol_file structure for given handle

   \param id

   \return
 */
int gvl_file_free_datah(int id)
{
    int i, j, found = -1;
    geovol_file *fvf;

    G_debug(5, "gvl_file_free_datah(): id=%d", id);

    for (i = 0; i < Numfiles; i++) {
        if (Data[i]->data_id == id) {
            found = 1;
            fvf = Data[i];

            if (fvf->count > 1) {
                fvf->count--;
            }
            else {
                close_volfile(fvf->map, fvf->file_type);
                free_volfile_buffs(fvf);

                G_free(fvf->file_name);
                fvf->file_name = NULL;

                fvf->data_id = 0;

                for (j = i; j < (Numfiles - 1); j++) {
                    Data[j] = Data[j + 1];
                }

                Data[j] = fvf;

                --Numfiles;
            }
        }
    }

    return (found);
}

/******************************************************************/
/* reading from RASTER3D raster volume files */

/******************************************************************/

/*!
   \brief Open 3d raster file

   \param filename file name
   \param type data type
   \param[out] min min value
   \param[out] max max value

   \returns pointer to data
 */
void *open_g3d_file(const char *filename, IFLAG *type, double *min, double *max)
{
    const char *mapset;
    int itype;
    void *map;

    /* search for g3d file a return his mapset */
    mapset = G_find_raster3d(filename, "");
    if (!mapset) {
        G_warning(_("3D raster map <%s> not found"), filename);
        return (NULL);
    }

    /* open g3d file */
    map = Rast3d_open_cell_old(filename, mapset, RASTER3D_DEFAULT_WINDOW,
                               RASTER3D_TILE_SAME_AS_FILE,
                               RASTER3D_USE_CACHE_DEFAULT);
    if (!map) {
        G_warning(_("Unable to open 3D raster map <%s>"), filename);
        return (NULL);
    }

    /* load range into range structure of map */
    if (!Rast3d_range_load(map)) {
        G_warning(_("Unable to read range of 3D raster map <%s>"), filename);
        return (NULL);
    }

    Rast3d_range_min_max(map, min, max);

    /* get file data type */
    itype = Rast3d_file_type_map(map);
    if (itype == FCELL_TYPE)
        *type = VOL_DTYPE_FLOAT;
    if (itype == DCELL_TYPE)
        *type = VOL_DTYPE_DOUBLE;

    return (map);
}

/*!
   \brief Close g3d file

   \param map 3d raster map

   \return -1 on failure
   \return 1 on success
 */
int close_g3d_file(void *map)
{
    /* close opened g3d file */
    if (Rast3d_close((RASTER3D_Map *)map) != 1) {
        G_warning(_("Unable to close 3D raster map <%s>"),
                  ((RASTER3D_Map *)map)->fileName);
        return (-1);
    }

    return (1);
}

/*!
   \brief Eead value from g3d file

   \param type data type
   \param map 3D raster map
   \param x,y,z real coordinates
   \param[out] value data value

   \return -1 on failure
   \return 1 on success
 */
int read_g3d_value(IFLAG type, void *map, int x, int y, int z, void *value)
{
    switch (type) {
        /* float data type */
    case (VOL_DTYPE_FLOAT):
        *((float *)value) = Rast3d_get_float(map, x, y, z);
        break;

        /* double data type */
    case (VOL_DTYPE_DOUBLE):
        *((double *)value) = Rast3d_get_double(map, x, y, z);
        break;

        /* unsupported data type */
    default:
        return (-1);
    }

    return (1);
}

/*!
   \brief Read slice of values at level from g3d file

   \param type data type
   \param map 3D raster map
   \param level
   \param[out] data

   \return -1 on failure
   \return 0 on success
 */
int read_g3d_slice(IFLAG type, void *map, int level, void *data)
{
    int x, y;

    switch (type) {
        /* float data type */
    case (VOL_DTYPE_FLOAT):
        for (x = 0; x < Cols; x++) {
            for (y = 0; y < Rows; y++) {
                ((float *)data)[x + y * Cols] =
                    Rast3d_get_float(map, x, y, level);
            }
        }

        break;

        /* double data type */
    case (VOL_DTYPE_DOUBLE):
        for (x = 0; x < Cols; x++) {
            for (y = 0; y < Rows; y++) {
                ((double *)data)[x + y * Cols] =
                    Rast3d_get_double(map, x, y, level);
            }
        }

        break;

        /* unsupported data type */
    default:
        return (-1);
    }

    return (1);
}

/*!
   \brief Read all values from g3d file

   \param type data type
   \param map 3D raster map
   \param[out] data data buffer

   \return -1 on failure
   \return 1 on success
 */
int read_g3d_vol(IFLAG type, void *map, void *data)
{
    int x, y, z;

    switch (type) {
        /* float data type */
    case (VOL_DTYPE_FLOAT):
        for (x = 0; x < Cols; x++) {
            for (y = 0; y < Rows; y++) {
                for (z = 0; z < Depths; z++) {
                    ((float *)data)[x + y * Cols + z * Rows * Cols] =
                        Rast3d_get_float(map, x, y, z);
                }
            }
        }

        break;

        /* double data type */
    case (VOL_DTYPE_DOUBLE):
        for (x = 0; x < Cols; x++) {
            for (y = 0; y < Rows; y++) {
                for (z = 0; z < Depths; z++) {
                    ((double *)data)[x + y * Cols + z * Rows * Cols] =
                        Rast3d_get_double(map, x, y, z);
                }
            }
        }

        break;

        /* unsupported data type */
    default:
        return (-1);
    }

    return (1);
}

/*!
   \brief Check for null value

   \param type data type
   \param value

   \return 1 if value is null
   \return 0 if value is not null
   \return -1 on failure (unsupported data type
 */
int is_null_g3d_value(IFLAG type, void *value)
{
    switch (type) {
        /* float data type */
    case (VOL_DTYPE_FLOAT):
        return Rast3d_is_null_value_num(value, FCELL_TYPE);
        break;

        /* double data type */
    case (VOL_DTYPE_DOUBLE):
        return Rast3d_is_null_value_num(value, DCELL_TYPE);
        break;

        /* unsupported data type */
    default:
        return (-1);
    }

    return (-1);
}

/******************************************************************/
/* reading from buffer */

/******************************************************************/

/*!
   \brief Get value from buffer

   \param type data type
   \param data data buffer
   \param offset
   \param value

   \return -1 on failure (unsupported data type)
   \return 1 on success
 */
int get_buff_value(IFLAG type, void *data, int offset, void *value)
{
    switch (type) {
        /* float data type */
    case (VOL_DTYPE_FLOAT):
        *((float *)value) = ((float *)data)[offset];
        break;

        /* double data type */
    case (VOL_DTYPE_DOUBLE):
        *((double *)value) = ((double *)data)[offset];
        break;

        /* unsupported data type */
    default:
        return (-1);
    }

    return (1);
}

/******************************************************************/
/* direct mode reading from volume file */

/******************************************************************/

/*!
   \brief Read value direct from volume file

   \param vf pointer to geovol_file struct
   \param x,y,z real point
   \param[out] value data value

   \return -1 on failure
   \return 1 on success
 */
int get_direct_value(geovol_file *vf, int x, int y, int z, void *value)
{
    switch (vf->file_type) {
        /* RASTER3D file type */
    case (VOL_FTYPE_RASTER3D):
        if (0 > read_g3d_value(vf->data_type, vf->map, x, y, z, value))
            return (-1);
        break;

    default:
        return (-1);
    }

    return (1);
}

/******************************************************************/
/* full mode reading from volume file */

/******************************************************************/

/*!
   \brief Allocate buffer memory for full mode reading

   \param vf pointer to geovol_file

   \return -1 on failure
   \return 1 on success
 */
int alloc_vol_buff(geovol_file *vf)
{
    switch (vf->data_type) {
        /* float data type */
    case (VOL_DTYPE_FLOAT):
        if ((vf->buff = (float *)G_malloc(sizeof(float) * Cols * Rows *
                                          Depths)) == NULL)
            return (-1);
        break;

        /* double data type */
    case (VOL_DTYPE_DOUBLE):
        if ((vf->buff = (double *)G_malloc(sizeof(double) * Cols * Rows *
                                           Depths)) == NULL)
            return (-1);
        break;

        /* unsupported data type */
    default:
        return (-1);
    }

    return (1);
}

/*!
   \brief Free memory buffer memory

   \param vf pointer to geovol_file struct

   \return 1
 */
int free_vol_buff(geovol_file *vf)
{
    G_free(vf->buff);

    return (1);
}

/*!
   \brief Read all values from volume file

   \param vf pointer to geovol_file struct

   \return -1 on failure
   \return 1 on success
 */
int read_vol(geovol_file *vf)
{
    switch (vf->file_type) {
        /* RASTER3D file format */
    case (VOL_FTYPE_RASTER3D):
        if (0 > read_g3d_vol(vf->data_type, vf->map, vf->buff))
            return (-1);
        break;
        /* unsupported file format */
    default:
        return (-1);
    }

    return (1);
}

/*!
   \brief Get value from volume buffer

   \param vf pointer to geovol_file struct
   \param x,y,z real point
   \param[out] value data value

   \return 1
 */
int get_vol_value(geovol_file *vf, int x, int y, int z, void *value)
{
    get_buff_value(vf->data_type, vf->buff, z * Rows * Cols + y * Cols + x,
                   value);

    return (1);
}

/******************************************************************/
/* slice mode reading from volume file */

/******************************************************************/

/*!
   \brief Allocate buffer for slice mode reading

   \param vf pointer to geovol_file struct

   \return -1 on failure
   \return 1 on success
 */
int alloc_slice_buff(geovol_file *vf)
{
    int i;
    slice_data *sd = (slice_data *)vf->buff;

    switch (vf->data_type) {
        /* float data type */
    case (VOL_DTYPE_FLOAT):
        for (i = 0; i < sd->num; i++) {
            if ((sd->slice[i] =
                     (float *)G_malloc(sizeof(float) * Cols * Rows)) == NULL)
                return (-1);
        }
        break;

        /* double data type */
    case (VOL_DTYPE_DOUBLE):
        for (i = 0; i < sd->num; i++) {
            if ((sd->slice[i] =
                     (double *)G_malloc(sizeof(double) * Cols * Rows)) == NULL)
                return (-1);
        }
        break;

        /* unsupported data type */
    default:
        return (-1);
    }

    return (1);
}

/*!
   \brief Free buffer for slice mode reading

   \param vf pointer to geovol_file struct

   \return 1
 */
int free_slice_buff(geovol_file *vf)
{
    int i;
    slice_data *sd = (slice_data *)vf->buff;

    for (i = 0; i < sd->num; i++) {
        G_free(sd->slice[i]);
    }

    return (1);
}

/*!
   \brief Read slice of values at level from volume file

   \param vf pointer to geovol_file struct
   \param s
   \param l

   \return -1 on failure
   \return 1 on success
 */
int read_slice(geovol_file *vf, int s, int l)
{
    /* get slice structure */
    slice_data *sd = (slice_data *)vf->buff;

    switch (vf->file_type) {
        /* RASTER3D file format */
    case (VOL_FTYPE_RASTER3D):
        if (0 > read_g3d_slice(vf->data_type, vf->map, l, sd->slice[s]))
            return (-1);
        break;
        /* unsupported file format */
    default:
        return (-1);
    }

    return (1);
}

/*!
   \brief Read new slice into buffer

   \param vf pointer to geovol_file struct
 */
void shift_slices(geovol_file *vf)
{
    void *tmp;
    int i;

    slice_data *sd = (slice_data *)vf->buff;

    /* change pointers to slices */
    tmp = sd->slice[0];
    for (i = 0; i < sd->num - 1; i++) {
        sd->slice[i] = sd->slice[i + 1];
    }
    sd->slice[sd->num - 1] = tmp;

    /* read new slice data */
    read_slice(vf, sd->num, sd->crnt + 1 + (sd->num - sd->base));

    /* increase current slice value */
    sd->crnt++;
}

/*!
   \brief Get value from slice buffer

   \param vf pointer to geovol_file struct
   \param x,y,z real point
   \param[out] value data value

   \return -1 on failure
   \return 1 on success
 */
int get_slice_value(geovol_file *vf, int x, int y, int z, void *value)
{
    slice_data *sd = (slice_data *)vf->buff;

    /* value is in loaded slices */
    if ((z >= sd->crnt - (sd->base - 1)) &&
        (z <= sd->crnt + sd->num - sd->base)) {

        get_buff_value(vf->data_type, sd->slice[(z - sd->crnt)], y * Cols + x,
                       value);
    }

    /* if value isn't in loaded slices, need read new data slice */
    else if (z == sd->crnt - (sd->base - 1) + 1) {
        shift_slices(vf);
        get_buff_value(vf->data_type, sd->slice[(z - sd->crnt)], y * Cols + x,
                       value);
    }

    /* value out of range */
    else {
        return (-1);
    }

    return (1);
}

/******************************************************************/
/* reading from volume file */

/******************************************************************/

/*!
   \brief Start read - allocate memory buffer a read first data into buffer

   \param vf pointer to geovol_file struct

   \return -1 on failure
   \return 1 on success
 */
int gvl_file_start_read(geovol_file *vf)
{
    int i;
    slice_data *sd;

    /* check status */
    if (vf->status == STATUS_BUSY)
        return (-1);

    switch (vf->mode) {
        /* read whole volume into memory */
    case (MODE_FULL):
        /* allocate memory */
        if (0 > alloc_vol_buff(vf))
            return (-1);

        /* read volume */
        read_vol(vf);
        break;

        /* read first data slices into memory */
    case (MODE_SLICE):
        /* allocate slices buffer memory */
        if (0 > alloc_slice_buff(vf))
            return (-1);

        /* read volume */
        sd = (slice_data *)vf->buff;
        /* set current slice to 0 */
        sd->crnt = 0;

        /* read first slices into buffer */
        for (i = 0; i < (sd->num - sd->base + 1); i++)
            read_slice(vf, (sd->base - 1) + i, i);
        break;
    }

    /* set status */
    vf->status = STATUS_BUSY;

    return (1);
}

/*!
   \brief End read - free buffer memory

   \param vf pointer to geovol_file struct

   \return -1 on failure
   \return 1 on success
 */
int gvl_file_end_read(geovol_file *vf)
{
    /* check status */
    if (vf->status == STATUS_READY)
        return (-1);

    switch (vf->mode) {
    case (MODE_FULL):
        if (0 > free_vol_buff(vf))
            return (-1);
        break;

    case (MODE_SLICE):
        /* allocate slices buffer memory */
        if (0 > free_slice_buff(vf))
            return (-1);
    }

    /* set status */
    vf->status = STATUS_READY;

    return (1);
}

/*!
   \brief Get value for volume file at x, y, z

   \param vf pointer to geovol_file struct

   \return -1 on failure
   \return 1 on success
 */
int gvl_file_get_value(geovol_file *vf, int x, int y, int z, void *value)
{
    /*  check status */
    if (vf->status != STATUS_BUSY) {
        return (-1);
    }

    switch (vf->mode) {
        /* read value direct from g3d file */
    case (MODE_DIRECT):
        if (0 > get_direct_value(vf, x, y, z, value))
            return (-1);
        break;

    case (MODE_SLICE):
        if (0 > get_slice_value(vf, x, y, z, value))
            return (-1);
        break;

    case (MODE_FULL):
    case (MODE_PRELOAD):
        if (0 > get_vol_value(vf, x, y, z, value))
            return (-1);
        break;
    }
    return (1);
}

/*!
   \brief Check for null value

   \param vf pointer to geovol_file struct
   \param value data value

   \return -1 on failure
   \return 1 on success
 */
int gvl_file_is_null_value(geovol_file *vf, void *value)
{
    switch (vf->file_type) {
        /* RASTER3D file format */
    case (VOL_FTYPE_RASTER3D):
        return is_null_g3d_value(vf->file_type, value);
        break;
        /* unsupported file format */
    default:
        return (-1);
    }

    return (-1);
}

/******************************************************************/
/* set parameters for reading volumes */

/******************************************************************/

/*!
   \brief Set read mode

   \param vf pointer to geovol_file struct
   \param mode read mode

   \return -1 on failure
   \return 1 on success
 */
int gvl_file_set_mode(geovol_file *vf, IFLAG mode)
{
    slice_data *sd;

    if (vf->status == STATUS_BUSY)
        return (-1);

    if (vf->mode == mode)
        return (1);

    if (vf->mode == MODE_SLICE)
        G_free(vf->buff);

    if (vf->mode == MODE_PRELOAD)
        G_free(vf->buff);

    if (mode == MODE_SLICE) {
        if ((vf->buff = (slice_data *)G_malloc(sizeof(slice_data))) == NULL)
            return (-1);

        sd = (slice_data *)vf->buff;
        sd->num = 1;
        sd->crnt = 0;
        sd->base = 1;
    }

    if (mode == MODE_PRELOAD) {
        /* allocate memory */
        if (0 > alloc_vol_buff(vf))
            return (-1);

        /* read volume */
        read_vol(vf);
    }

    vf->mode = mode;

    return (1);
}

/*!
   \brief Set parameters for slice reading

   \param vf pointer to geovol_file struct
   \param n
   \param b

   \return -1 on failure
   \return 1 on success
 */
int gvl_file_set_slices_param(geovol_file *vf, int n, int b)
{
    slice_data *sd;

    if (vf->status == STATUS_BUSY)
        return (-1);

    if (!(vf->mode == MODE_SLICE))
        return (-1);

    sd = (slice_data *)vf->buff;
    sd->num = n;
    sd->base = b;

    return (1);
}
