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

   \brief OGSF library - dataset loading and management (lower level functions)

   GRASS OpenGL gsurf OGSF Library

   The idea here is to treat datasets as separate objects, which SHOULD:
   - allow easier reuse of data for different attributes.
   - allow a mechanism for changing data and have changes reflected
   in each attribute using that data.
   - allow a mechanism to automatically update data when the data source
   is changed.
   - allow easier weaning from GRASS.
   - allow easier use of shared memory between processes.

   These structures are defined in gstypes.h:

   <code>
   typedef struct{
   float *fb;
   int *ib;
   short *sb;
   char *cb;
   struct BM *bm;
   } typbuff;
   </code>

   How about adding a transform func here, so GET_MAPATT would do an
   on-the-fly transformation? Or even a transform func LIST!

   <code>
   typedef struct{
   int data_id;
   int dims[MAXDIMS];
   int ndims;
   int numbytes;
   char unique_name[80];
   typbuff databuff;
   int changed;
   int need_reload;
   } dataset;
   </code>

   (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 Bill Brown UI GMS Lab
   \author Doxygenized by Martin Landa <landa.martin gmail.com> (May 2008)
 */

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

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

#define LUCKY  33
#define BLOC   20
#define MAX_DS 100

static int init_gsds(void);
static int check_numsets(void);
static dataset *get_dataset(int);
static int get_type(dataset *);

static dataset *Data[MAX_DS];
static dataset Ds[MAX_DS]; /* trying to avoid allocation */

static int Numsets = 0;

static int Cur_id = LUCKY;
static int Cur_max;
static size_t Tot_mem = 0;

/*!
   \brief Initialize gsds
 */
static int init_gsds(void)
{
    int i;

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

    Cur_max = MAX_DS;

    return (1);
}

/*!
   \brief Check numsets

   \return 0 numset < cur_max
 */
static int check_numsets(void)
{
    if (Numsets < Cur_max) {
        return (0);
    }

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

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

/*!
   \brief Get dataset

   \param id data id

   \return pointer to dataset struct
   \return NULL dataset not found
 */
static dataset *get_dataset(int id)
{
    int i;

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

    return (NULL);
}

/*!
   \brief Get type

   \param ds pointer to dataset struct

   \return type code
   \return -1 unsupported type
 */
static int get_type(dataset *ds)
{
    if (ds) {
        if (ds->databuff.bm) {
            return (ATTY_MASK);
        }

        if (ds->databuff.cb) {
            return (ATTY_CHAR);
        }

        if (ds->databuff.sb) {
            return (ATTY_SHORT);
        }

        if (ds->databuff.ib) {
            return (ATTY_INT);
        }

        if (ds->databuff.fb) {
            return (ATTY_FLOAT);
        }
    }

    return (-1);
}

/*!
   \brief Get handle to gsds.

   Successive calls will continue search until "begin" is set
   (problem here is, unique_name no longer uniquely identifies
   dataset, since changes may be made; but unique_name should still
   be useful for reloading dataset)
   changes & types are set to actual for dataset if found.

   \param name
   \param changes,types  acceptable changes & types, flags may be or'd
   not changed is assumed to always be acceptable
   \param begin flag to indicate search from beginning

   \return data id
   \return -1 not found
 */
int gsds_findh(const char *name, IFLAG *changes, IFLAG *types, int begin)
{
    static int i;
    int start;

    start = begin ? 0 : i + 1;

    for (i = start; i < Numsets; i++) {
        if (!strcmp(Data[i]->unique_name, name)) {
            if ((Data[i]->changed & *changes) || !(Data[i]->changed)) {
                if (get_type(Data[i]) & *types) {
                    *changes = Data[i]->changed;
                    *types = get_type(Data[i]);

                    return (Data[i]->data_id);
                }
            }
        }
    }

    return (-1);
}

/*!
   \brief Get handle to gsds

   \param name raster map name

   \return -1 on failure
   \return data id
 */
int gsds_newh(const char *name)
{
    dataset *new;
    static int first = 1;
    int i;

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

        first = 0;
    }
    else if (0 > check_numsets()) {
        return (-1);
    }

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

    new = Data[Numsets];

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

        for (i = 0; i < MAXDIMS; i++) {
            new->dims[i] = 0;
        }

        new->unique_name = G_store(name);
        new->databuff.fb = NULL;
        new->databuff.ib = NULL;
        new->databuff.sb = NULL;
        new->databuff.cb = NULL;
        new->databuff.bm = NULL;
        new->databuff.nm = NULL;
        new->databuff.k = 0.0;
        new->changed = 0;
        new->ndims = 0;
        new->need_reload = 1;

        return (new->data_id);
    }

    return (-1);
}

/*!
   \brief Get data buffer

   Doesn't prevent writing a buff thats's been gotten with change_flag
   == 0 (could return a copy, but willing to trust calling func for
   now)

   \param id dataset id
   \param change_flag set changed flag

   \return pointer to typbuff struct
   \return NULL on failure
 */
typbuff *gsds_get_typbuff(int id, IFLAG change_flag)
{
    dataset *ds;

    if ((ds = get_dataset(id))) {
        ds->changed = ds->changed | change_flag;
        ds->need_reload = 0;

        return (&(ds->databuff));
    }

    return (NULL);
}

/*!
   \brief Get name

   \param id

   \return name
   \return NULL on failure
 */
char *gsds_get_name(int id)
{
    int i;
    dataset *fds;
    static char retstr[GPATH_MAX];

    for (i = 0; i < Numsets; i++) {
        if (Data[i]->data_id == id) {
            fds = Data[i];
            strcpy(retstr, fds->unique_name);

            return (retstr);
        }
    }

    return (NULL);
}

/*!
   \brief Free allocated dataset

   \param id

   \return 0 not found
   \return 1 found
 */
int gsds_free_datah(int id)
{
    int i, j, found = 0;
    dataset *fds;

    G_debug(3, "gsds_free_datah");

    for (i = 0; i < Numsets; i++) {
        if (Data[i]->data_id == id) {
            found = 1;
            fds = Data[i];
            free_data_buffs(fds, ATTY_ANY);
            G_free((void *)fds->unique_name);
            fds->unique_name = NULL;
            fds->data_id = 0;

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

            Data[j] = fds;
        }
    }

    if (found) {
        --Numsets;
    }

    return (found);
}

/*!
   \brief Free allocated buffer

   \param id dataset id
   \param typ data type

   \return 0 not found
   \return 1 found
 */
int gsds_free_data_buff(int id, int typ)
{
    int i, found = 0;
    dataset *fds;

    for (i = 0; i < Numsets; i++) {
        if (Data[i]->data_id == id) {
            found = 1;
            fds = Data[i];
            free_data_buffs(fds, typ);
        }
    }

    return (found);
}

/*!
   \brief Free data buffer

   \param ds pointer to dataset struct
   \param typ data type

   \return freed size
 */
size_t free_data_buffs(dataset *ds, int typ)
{
    int i;
    size_t siz, nsiz = 1, freed = 0;

    for (i = 0; i < ds->ndims; i++) {
        nsiz *= ds->dims[i];
    }

    if (typ & ATTY_NULL) {
        if (ds->databuff.nm) {
            siz = BM_get_map_size(ds->databuff.nm);
            BM_destroy(ds->databuff.nm);
            ds->databuff.nm = NULL;
            freed += siz;
        }
    }

    if (typ & ATTY_MASK) {
        if (ds->databuff.bm) {
            siz = BM_get_map_size(ds->databuff.bm);
            BM_destroy(ds->databuff.bm);
            ds->databuff.bm = NULL;
            freed += siz;
        }
    }

    if (typ & ATTY_CHAR) {
        if (ds->databuff.cb) {
            siz = nsiz * sizeof(char);
            free(ds->databuff.cb);
            ds->databuff.cb = NULL;
            freed += siz;
        }
    }

    if (typ & ATTY_SHORT) {
        if (ds->databuff.sb) {
            siz = nsiz * sizeof(short);
            free(ds->databuff.sb);
            ds->databuff.sb = NULL;
            freed += siz;
        }
    }

    if (typ & ATTY_INT) {
        if (ds->databuff.ib) {
            siz = nsiz * sizeof(int);
            free(ds->databuff.ib);
            ds->databuff.ib = NULL;
            freed += siz;
        }
    }

    if (typ & ATTY_FLOAT) {
        if (ds->databuff.fb) {
            siz = nsiz * sizeof(float);
            free(ds->databuff.fb);
            ds->databuff.fb = NULL;
            freed += siz;
        }
    }

    Tot_mem -= freed;
    ds->numbytes -= freed;

    if (freed) {
        G_debug(5, "free_data_buffs(): freed data from id no. %d", ds->data_id);
        G_debug(5, "free_data_buffs(): %.3f Kbytes freed, current total = %.3f",
                freed / 1000., Tot_mem / 1000.);
    }

    return (freed);
}

/*!
   \brief Allocates correct buffer according to type, keeps track of total mem

   \todo add ATTY_CONST

   \param id dataset id
   \param dims array of dimensions
   \param ndims number of dimensions
   \param type data type

   \return amount of allocated memory
 */
size_t gsds_alloc_typbuff(int id, int *dims, int ndims, int type)
{
    dataset *ds;
    int i;
    size_t siz = 1;

    if ((ds = get_dataset(id))) {
        /*
           free_data_buffs(ds);
           careful here - allowing > 1 type to coexist (for float -> color
           conv.) now also use this to allocate a null mask (then if not used,
           use gsds_free_data_buff(id, ATTY_NULL))
         */

        for (i = 0; i < ndims; i++) {
            ds->dims[i] = dims[i];
            siz *= dims[i];
        }

        switch (type) {
        case ATTY_NULL:
            if (ndims != 2) {
                /* higher dimension bitmaps not supported */
                return 0;
            }

            if (NULL == (ds->databuff.nm = BM_create(dims[1], dims[0]))) {
                return 0;
            }

            siz = BM_get_map_size(ds->databuff.nm);

            break;

        case ATTY_MASK:
            if (ndims != 2) {
                /* higher dimension bitmaps not supported */
                return (-1);
            }

            if (NULL == (ds->databuff.bm = BM_create(dims[1], dims[0]))) {
                return 0;
            }

            siz = BM_get_map_size(ds->databuff.bm);

            break;

        case ATTY_CHAR:
            siz *= sizeof(char);

            if (siz) {
                if (NULL ==
                    (ds->databuff.cb = (unsigned char *)G_malloc(siz))) {
                    return 0;
                }
            }
            else {
                return 0;
            }

            break;

        case ATTY_SHORT:
            siz *= sizeof(short);

            if (siz) {
                if (NULL == (ds->databuff.sb = (short *)G_malloc(siz))) {
                    return 0;
                }
            }
            else {
                return 0;
            }

            break;

        case ATTY_INT:
            siz *= sizeof(int);

            if (siz) {
                if (NULL == (ds->databuff.ib = (int *)G_malloc(siz))) {
                    return 0;
                }
            }
            else {
                return 0;
            }

            break;

        case ATTY_FLOAT:
            siz *= sizeof(float);

            if (siz) {
                if (NULL == (ds->databuff.fb = (float *)G_malloc(siz))) {
                    return 0;
                }
            }
            else {
                return 0;
            }

            break;

        default:
            return 0;
        }

        ds->changed = 0; /* starting with clean slate */
        ds->need_reload = 1;
        ds->numbytes += siz;
        ds->ndims = ndims;
        Tot_mem += siz;

        G_debug(5,
                "gsds_alloc_typbuff(): %f Kbytes allocated, current total = %f",
                siz / 1000., Tot_mem / 1000.);

        return (siz);
    }

    return 0;
}

/*!
   \brief ADD

   \param id

   \return -1 on error
   \return
 */
int gsds_get_changed(int id)
{
    dataset *ds;

    if ((ds = get_dataset(id))) {
        return ((int)ds->changed);
    }

    return (-1);
}

/*!
   \brief ADD

   \param id
   \param reason

   \return -1 on error
   \return
 */
int gsds_set_changed(int id, IFLAG reason)
{
    dataset *ds;

    if ((ds = get_dataset(id))) {
        ds->changed = reason;
    }

    return (-1);
}

/*!
   \brief ADD

   \param id

   \return
 */
int gsds_get_type(int id)
{
    dataset *ds;

    ds = get_dataset(id);

    return (get_type(ds));
}
