/*
 * gnc-environment.c:
 *
 * Copyright (C) 2013 Geert Janssens <geert@kobaltwit.be>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, contact:
 *
 * Free Software Foundation           Voice:  +1-617-542-5942
 * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652
 * Boston, MA  02110-1301,  USA       gnu@gnu.org
 */

#include <glib.h>
#include <stdbool.h>
#include <stdio.h>

#include <string.h>
#include "gnc-environment.h"
#include "gnc-path.h"

static gchar  *environment_expand(gchar *param)
{
    gchar *search_start;
    gchar *opening_brace;
    gchar *closing_brace;
    gchar *result;
    gchar *tmp;
    gchar *expanded = NULL;

    if (!param)
        return NULL;

    /* Set an initial return value, so we can always use g_strconcat below) */
    result = g_strdup ("x");

    /* Look for matching pairs of { and }. Anything in between should be expanded */
    search_start = param;
    opening_brace = g_strstr_len (search_start, -1, "{");
    closing_brace = g_strstr_len (search_start, -1, "}");

    /* Note: the test on valid braces is fairly simple:
     *       * if no pair of opening/closing braces is found, no expansion occurs
     *       * braces can't be nested, this will give unexpected results
     *       * the string should contain no other braces than those used to mark
     *         expandable variables, or unexpected results will be returned.
     */
    while ( opening_brace && closing_brace && (closing_brace > opening_brace) )
    {
        /* Found a first matching pair */
        gchar *to_expand;
        const gchar *env_val;

        /* If the string had characters before the opening {, copy them first */
        if (opening_brace > search_start)
        {
            gchar *prefix = g_strndup (search_start, opening_brace - search_start);

            tmp = g_strconcat (result, prefix, NULL);
            g_free (result);
            result = tmp;
            g_free (prefix);
        }

        /* Expand the variable  we found and append it to the result */
        to_expand = g_strndup (opening_brace + 1, closing_brace - opening_brace - 1);
        env_val = g_getenv (to_expand);
        tmp = g_strconcat (result, env_val, NULL);
        g_free (result);
        result = tmp;
        g_free (to_expand);

        /* Look for matching pairs of { and }. Anything in between should be expanded */
        search_start = closing_brace + 1;
        opening_brace = g_strstr_len (search_start, -1, "{");
        closing_brace = g_strstr_len (search_start, -1, "}");
    }

    /* No more braces found, append the remaining characters */
    tmp = g_strconcat (result, search_start, NULL);
    g_free (result);
    result = tmp;

    /* Remove the "x" from our result */
    if (g_strcmp0 (result, "x"))
        expanded = g_strdup (result + 1);
    g_free (result);

    return expanded;
}

static bool
char_array_contains(const char **array, unsigned num_keys, const char *key)
{
    for (unsigned i = 0; i < num_keys; ++i)
    {
        if (strcmp(key, array[i]) == 0)
            return true;
    }
    return false;
}

static void
gnc_environment_parse_one (const gchar *env_path)
{
    GKeyFile    *keyfile = g_key_file_new();
    gchar **env_vars;
    gsize param_count;
    gint i;
    gboolean got_keyfile;
    const unsigned num_keys = 6;
    const char *reserved_keys[] = {"GNC_HOME", "GNC_BIN", "GNC_LIB",
        "GNC_DATA", "GNC_CONF", "SYS_LIB"};

    got_keyfile = g_key_file_load_from_file (keyfile, env_path, G_KEY_FILE_NONE, NULL);
    if ( !got_keyfile )
    {
        g_key_file_free(keyfile);
        return;
    }

    /* Read the environment overrides and apply them */
    env_vars = g_key_file_get_keys(keyfile, "Variables", &param_count, NULL);
    for ( i = 0; i < param_count; i++ )
    {
        gchar **val_list;
        gsize val_count;
        gint j;
        gchar *new_val = NULL, *tmp_val;
        if (char_array_contains(reserved_keys, num_keys, env_vars[i]))
            continue;

        /* for each variable, read its new value, optionally expand it and set/unset it */
        val_list = g_key_file_get_string_list (keyfile, "Variables",
                                               env_vars[i], &val_count,
                                               NULL);
        if ( val_count == 0 )
            g_unsetenv (env_vars[i]);
        else
        {
            /* Set an initial return value, so we can always use g_build_path below) */
            tmp_val = g_strdup ("x");
            for ( j = 0; j < val_count; j++ )
            {
                gchar *expanded = environment_expand (val_list[j]);
                if (expanded && strlen(expanded))
                {
                    new_val = g_build_path (G_SEARCHPATH_SEPARATOR_S, tmp_val, expanded, NULL);
                    g_free (tmp_val);
                    g_free(expanded);
                    tmp_val = new_val;
                }
            }
            g_strfreev (val_list);

            /* Remove the "x" from our result */
            if (g_strcmp0 (tmp_val, "x"))
            {
                new_val = g_strdup (tmp_val + sizeof (G_SEARCHPATH_SEPARATOR_S));
                g_free (tmp_val);
            }
            if (!g_setenv (env_vars[i], new_val, TRUE))
                g_warning ("Couldn't properly override environment variable \"%s\". "
                "This may lead to unexpected results", env_vars[i]);
            g_free(new_val);
        }
    }

    g_strfreev(env_vars);
    g_key_file_free(keyfile);
}

void
gnc_environment_setup (void)
{
    gchar *config_path;
    gchar *env_path;
    gchar *env_parm;

    /* Export default parameters to the environment */
    env_parm = gnc_path_get_prefix();
    if (!g_setenv("GNC_HOME", env_parm, TRUE))
        g_warning ("Couldn't set/override environment variable GNC_HOME.");
    g_free (env_parm);
    env_parm = gnc_path_get_bindir();
    if (!g_setenv("GNC_BIN", env_parm, TRUE))
        g_warning ("Couldn't set/override environment variable GNC_BIN.");
    g_free (env_parm);
    env_parm = gnc_path_get_pkglibdir();
    if (!g_setenv("GNC_LIB", env_parm, TRUE))
        g_warning ("Couldn't set/override environment variable GNC_LIB.");
    g_free (env_parm);
    env_parm = gnc_path_get_pkgdatadir();
    if (!g_setenv("GNC_DATA", env_parm, TRUE))
        g_warning ("Couldn't set/override environment variable GNC_DATA.");
    g_free (env_parm);
    env_parm = gnc_path_get_pkgsysconfdir();
    if (!g_setenv("GNC_CONF", env_parm, TRUE))
        g_warning ("Couldn't set/override environment variable GNC_CONF.");
    g_free (env_parm);
    env_parm = gnc_path_get_libdir();
    if (!g_setenv("SYS_LIB", env_parm, TRUE))
        g_warning ("Couldn't set/override environment variable SYS_LIB.");
    g_free (env_parm);

    config_path = gnc_path_get_pkgsysconfdir();
#ifdef G_OS_WIN32
    {
        /* unhide files without extension */
        gchar *pathext = g_build_path(";", ".", g_getenv("PATHEXT"),
                                      (gchar*) NULL);
        g_setenv("PATHEXT", pathext, TRUE);
        g_free(pathext);
    }
#endif

    /* Parse the environment file that got installed with gnucash */
    env_path = g_build_filename (config_path, "environment", NULL);
    gnc_environment_parse_one(env_path);
    g_free (env_path);

    /* Parse local overrides for this file */
    env_path = g_build_filename (config_path, "environment.local", NULL);
    gnc_environment_parse_one(env_path);
    g_free (env_path);
    g_free (config_path);
}
