/*!
  \file lib/gis/env.c

  \brief GIS library - environment routines
  
  (C) 2001-2023 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 Original author CERL
  \author Updated for GRASS7 by Glynn Clements
*/

#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <unistd.h>		/* for sleep() */
#include <string.h>
#include <grass/gis.h>
#include <grass/glocale.h>

struct bind {
    int loc;
    char *name;
    char *value;
};

struct env {
    struct bind *binds;
    int count;
    int size;
};

static struct state {
    struct env env;
    struct env env2;
    char *gisrc;
    int varmode;
    int init[2];
} state;

static struct state *st = &state;

static int read_env(int);
static int set_env(const char *, const char *, int);
static int unset_env(const char *, int);
static const char *get_env(const char *, int);
static void write_env(int);
static void parse_env(FILE *, int);
static void force_read_env(int);
static FILE *open_env(const char *, int);

/*!
  \brief Set where to find/store variables
  
  Modes:
   - G_GISRC_MODE_FILE
   - G_GISRC_MODE_MEMORY 

   \param mode mode to find/store variables (G_GISRC_MODE_FILE by default)
*/
void G_set_gisrc_mode(int mode)
{
    st->varmode = mode;
}

/*!
  \brief Get info where variables are stored
  
  \return mode
*/
int G_get_gisrc_mode(void)
{
    return (st->varmode);
}

/*!
  \brief Initialize variables
  
  \return
*/
void G_init_env(void)
{
    read_env(G_VAR_GISRC);
    read_env(G_VAR_MAPSET);
}

/*!
 * \brief Force to read the mapset environment file VAR
 * 
 * The mapset specific VAR file of the mapset set with G_setenv()
 * will be read into memory, ignoring if it was readed before. 
 * Existing values will be overwritten, new values appended.
 * 
 * \return
 */ 
void G__read_mapset_env(void)
{
    force_read_env(G_VAR_MAPSET);
}

/*!
 * \brief Force to read the GISRC environment file
 * 
 * The GISRC file 
 * will be read into memory, ignoring if it was readed before. 
 * Existing values will be overwritten, new values appended.
 * 
 * \return
 */ 
void G__read_gisrc_env(void)
{
    force_read_env(G_VAR_GISRC);
}

/*!
 * \brief Read or read again the GISRC (session) environment variable
 *
 * The GISRC environment variable will be read and its value
 * stored, ignoring if it was read before.
 *
 * Calls G_fatal_error when the GISRC variable is not set.
 */
void G__read_gisrc_path(){
    st->gisrc = getenv("GISRC");
    if (!st->gisrc) {
        G_fatal_error(_("No active GRASS session: "
                        "GISRC environment variable not set"));
    }
}

static void parse_env(FILE *fd, int loc)
{
    /* Account for long lines up to GPATH_MAX. 
       E.g. "GISDBASE: GPATH_MAX\n\0" */
    char buf[GPATH_MAX + 16];
    char *name;
    char *value;

    while (G_getl2(buf, sizeof buf, fd)) {
	    for (name = value = buf; *value; value++)
		if (*value == ':')
		    break;
	    if (*value == 0)
		continue;

	    *value++ = 0;
	    G_strip(name);
	    G_strip(value);
	    if (*name && *value)
		set_env(name, value, loc);
	}
}

static int read_env(int loc)
{

    FILE *fd;

    if (loc == G_VAR_GISRC && st->varmode == G_GISRC_MODE_MEMORY)
	return 0;		/* don't use file for GISRC */

    if (G_is_initialized(&st->init[loc]))
	return 1;

    if ((fd = open_env("r", loc))) {
        parse_env(fd, loc);
        fclose(fd);
    }

    G_initialize_done(&st->init[loc]);
    return 0;
}

/*!
 * \brief Force the reading or the GISRC or MAPSET/VAR files
 * and overwrite/append the specified variables
 * 
 */ 
static void force_read_env(int loc)
{
    FILE *fd;
    if ((fd = open_env("r", loc))) {
        parse_env(fd, loc);
        fclose(fd);
    }
}


static int set_env(const char *name, const char *value, int loc)
{
    int n;
    int empty;
    char *tv;

    /* if value is NULL or empty string, convert into an unsetenv() */
    if (!value || !strlen(value)) {
	unset_env(name, loc);
	return 0;
    }

    tv = G_store(value);
    G_strip(tv);
    if (*tv == 0) {
	G_free(tv);
	unset_env(name, loc);
	return 1;
    }

    /*
     * search the array
     *   keep track of first empty slot
     *   and look for name in the environment
     */
    empty = -1;
    for (n = 0; n < st->env.count; n++) {
	struct bind *b = &st->env.binds[n];
	if (!b->name)	/* mark empty slot found */
	    empty = n;
	else if (strcmp(b->name, name) == 0 && b->loc == loc) {
	    b->value = tv;
	    return 1;
	}
    }

    /* add name to env: to empty slot if any */
    if (empty >= 0) {
	struct bind *b = &st->env.binds[empty];
	b->loc = loc;
	b->name = G_store(name);
	b->value = tv;
	return 0;
    }

    /* must increase the env list and add in */
    if (st->env.count >= st->env.size) {
	st->env.size += 20;
	st->env.binds = G_realloc(st->env.binds, st->env.size * sizeof(struct bind));
    }

    {
	struct bind *b = &st->env.binds[st->env.count++];

	b->loc = loc;
	b->name = G_store(name);
	b->value = tv;
    }

    return 0;
}

static int unset_env(const char *name, int loc)
{
    int n;

    for (n = 0; n < st->env.count; n++) {
	struct bind *b = &st->env.binds[n];
	if (b->name && strcmp(b->name, name) == 0 && b->loc == loc) {
	    G_free(b->name);
	    b->name = 0;
	    return 1;
	}
    }

    return 0;
}

static const char *get_env(const char *name, int loc)
{
    int n;

    for (n = 0; n < st->env.count; n++) {
	struct bind *b = &st->env.binds[n];
	if (b->name && (strcmp(b->name, name) == 0) &&
	    b->loc == loc)
	    return b->value;
    }

    return NULL;
}

static void write_env(int loc)
{
    FILE *fd;
    int n;
    char dummy[2];
    void (*sigint)(int);
#ifdef SIGQUIT
    void (*sigquit)(int);
#endif

    if (loc == G_VAR_GISRC && st->varmode == G_GISRC_MODE_MEMORY)
	return;		/* don't use file for GISRC */

    /*
     * THIS CODE NEEDS TO BE PROTECTED FROM INTERRUPTS
     * If interrupted, it can wipe out the GISRC file
     */
    sigint = signal(SIGINT, SIG_IGN);
#ifdef SIGQUIT
    sigquit = signal(SIGQUIT, SIG_IGN);
#endif
    if ((fd = open_env("w", loc))) {
	for (n = 0; n < st->env.count; n++) {
	    struct bind *b = &st->env.binds[n];
	    if (b->name && b->value && b->loc == loc
		&& (sscanf(b->value, "%1s", dummy) == 1))
		fprintf(fd, "%s: %s\n", b->name, b->value);
	}
	fclose(fd);
    }

    signal(SIGINT, sigint);
#ifdef SIGQUIT
    signal(SIGQUIT, sigquit);
#endif
}

static FILE *open_env(const char *mode, int loc)
{
    char buf[GPATH_MAX];

    if (loc == G_VAR_GISRC) {
	if (!st->gisrc)
	    G__read_gisrc_path();

	if (!st->gisrc) {
	    return NULL;
	}
	strcpy(buf, st->gisrc);
    }
    else if (loc == G_VAR_MAPSET) {
	/* Warning: G_VAR_GISRC must be previously read -> */
	/* TODO: better place ? */
	read_env(G_VAR_GISRC);

	sprintf(buf, "%s/%s/VAR", G_location_path(), G_mapset());
    }

    return fopen(buf, mode);
}

/*!
  \brief Get environment variable
  
  G_fatal_error() is called when variable is not found.
  
  \param name variable name
  
  \return char pointer to value for name
*/
const char *G_getenv(const char *name)
{
    const char *value = G_getenv_nofatal(name);

    if (value)
	return value;

    G_fatal_error(_("Incomplete GRASS session: Variable '%s' not set"), name);
    return NULL;
}

/*!
  \brief Get variable from specific place
  
  Locations:
   - G_VAR_GISRC
   - G_VAR_MAPSET

  G_fatal_error() is called when variable is not found.
  
  \param name variable name
  \param loc location (G_VAR_GISRC, G_VAR_MAPSET)
   
  \return variable value
  \return NULL if not found
*/
const char *G_getenv2(const char *name, int loc)
{
    const char *value = G_getenv_nofatal2(name, loc);

    if (value)
	return value;

    G_fatal_error(_("Incomplete GRASS session: Variable '%s' not set"), name);
    return NULL;
}

/*!
  \brief Get environment variable
  
  \param name variable name
  
  \return char pointer to value for name
  \return NULL if name not set
*/
const char *G_getenv_nofatal(const char *name)
{
    if (strcmp(name, "GISBASE") == 0)
	return getenv(name);

    read_env(G_VAR_GISRC);

    return get_env(name, G_VAR_GISRC);
}

/*!
  \brief Get environment variable from specific place
  
  \param name variable name
  \param loc location (G_VAR_GISRC, G_VAR_MAPSET)
  
  \return char pointer to value for name
  \return NULL if name not set
*/
const char *G_getenv_nofatal2(const char *name, int loc)
{
    if (strcmp(name, "GISBASE") == 0)
	return getenv(name);

    read_env(loc);

    return get_env(name, loc);
}

/*!
  \brief Set environment variable (updates .gisrc)

  If value is NULL, becomes an G_unsetenv().
    
  \param name variable name
  \param value variable value
*/
void G_setenv(const char *name, const char *value)
{
    read_env(G_VAR_GISRC);
    set_env(name, value, G_VAR_GISRC);
    write_env(G_VAR_GISRC);
}

/*!
  \brief Set environment variable from specific place (updates .gisrc)
  
  If value is NULL, becomes an G_unsetenv().
  
  \param name variable name
  \param value variable value
  \param loc location (G_VAR_GISRC, G_VAR_MAPSET)

*/
void G_setenv2(const char *name, const char *value, int loc)
{
    read_env(loc);
    set_env(name, value, loc);
    write_env(loc);
}

/*!
  \brief Set environment name to value (doesn't update .gisrc)
  
  \param name variable name
  \param value variable value
*/
void G_setenv_nogisrc(const char *name, const char *value)
{
    read_env(G_VAR_GISRC);
    set_env(name, value, G_VAR_GISRC);
}

/*!
  \brief Set environment name to value from specific place (doesn't update .gisrc)
  
  \param name variable name
  \param value variable value
  \param loc location (G_VAR_GISRC, G_VAR_MAPSET)
*/
void G_setenv_nogisrc2(const char *name, const char *value, int loc)
{
    read_env(loc);
    set_env(name, value, loc);
}

/*!
  \brief Remove name from environment
  
  Updates .gisrc
  
  \param name variable name
*/
void G_unsetenv(const char *name)
{
    read_env(G_VAR_GISRC);
    unset_env(name, G_VAR_GISRC);
    write_env(G_VAR_GISRC);
}

/*!
  \brief Remove name from environment from specific place
  
  Updates .gisrc
  
  \param name variable name
  \param loc location (G_VAR_GISRC, G_VAR_MAPSET)
*/
void G_unsetenv2(const char *name, int loc)
{
    read_env(loc);
    unset_env(name, loc);
    write_env(loc);
}

/*!
  \brief Writes current environment to .gisrc
*/
void G__write_env(void)
{
    if (st->init[G_VAR_GISRC])
	write_env(G_VAR_GISRC);
}

/*!
  \brief Get variable name for index n.
  
  For example:

  \code
   for (n = 0; ; n++)
      if ((name = G_get_env_name(n)) == NULL)
        break;
  \endcode

  \param n index of variable
  
  \return pointer to variable name
  \return NULL not found
*/
const char *G_get_env_name(int n)
{
    int i;

    read_env(G_VAR_GISRC);
    if (n >= 0)
	for (i = 0; i < st->env.count; i++)
	    if (st->env.binds[i].name && *st->env.binds[i].name && (n-- == 0))
		return st->env.binds[i].name;
    return NULL;
}

/*!
  \brief Initialize init array for G_VAR_GISRC.
*/
void G__read_env(void)
{
    st->init[G_VAR_GISRC] = 0;
}

/*!
  \brief Set up alternative environment variables
*/
void G_create_alt_env(void)
{
    int i;

    /* copy env to env2 */
    st->env2 = st->env;
    
    st->env.count = 0;
    st->env.size = 0;
    st->env.binds = NULL;
    
    for (i = 0; i < st->env2.count; i++) {
	struct bind *b = &st->env2.binds[i];
	if (b->name)
	    set_env(b->name, b->value, G_VAR_GISRC);
    }
}

/*!
  \brief Switch environments
*/
void G_switch_env(void)
{
    struct env tmp;

    tmp = st->env;
    st->env = st->env2;
    st->env2 = tmp;
}
