/*!
   \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 seperate 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));
}
