/* librepo - A library providing (libcURL like) API to downloading repository
 * Copyright (C) 2014  Tomas Mlcoch
 * Copyright (C) 2014  Richard Hughes
 *
 * Licensed under the GNU Lesser General Public License Version 2.1
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <assert.h>
#include <math.h>
#include <string.h>
#include <errno.h>
#include "librepo.h"
#include "repoconf.h"
#include "repoconf_internal.h"
#include "cleanup.h"


static LrYumRepoFile *
lr_yum_repofile_init(const gchar *path, GKeyFile *keyfile)
{
    LrYumRepoFile *repofile = lr_malloc0(sizeof(*repofile));
    repofile->path = g_strdup(path);
    repofile->keyfile = keyfile;
    return repofile;
}

static void
lr_yum_repofile_free(LrYumRepoFile *repofile)
{
    if (!repofile)
        return;
    g_free(repofile->path);
    g_key_file_unref(repofile->keyfile);
    g_free(repofile);
}

static GKeyFile *
repofile_keyfile(LrYumRepoFile *repofile)
{
    return repofile->keyfile;
}

static void
repofile_set_modified(LrYumRepoFile *repofile,
                      gboolean val)
{
    repofile->modified = val;
}

static gboolean
repofile_modified(LrYumRepoFile *repofile)
{
    return repofile->modified;
}

static gboolean
repofile_save_keyfile(LrYumRepoFile *repofile,
                      GError **err)
{
    assert(!err || *err == NULL);

    gboolean ret;
    GError *tmp_err = NULL;

    if (!repofile_modified(repofile))
        // File hasn't been modified
        return TRUE;

    // Write the contents of the keyfile
    ret = lr_key_file_save_to_file(repofile->keyfile,
                                   repofile->path,
                                   &tmp_err);

    // Act upon the return status
    if (ret)
        repofile_set_modified(repofile, FALSE);
    else
        g_set_error(err, LR_REPOCONF_ERROR, LRE_KEYFILE,
                    "Cannot save conf to %s: %s",
                    repofile->path, tmp_err->message);

    return ret;
}

static LrYumRepoConf *
lr_yum_repoconf_init(LrYumRepoFile *repofile, const gchar *id)
{
    LrYumRepoConf *repoconf = lr_malloc0(sizeof(*repoconf));
    repoconf->file = repofile;
    repoconf->id = g_strdup(id);
    return repoconf;
}

static void
lr_yum_repoconf_free(LrYumRepoConf *repoconf)
{
    if (!repoconf)
        return;
    g_free(repoconf->id);
    g_free(repoconf);
}

static LrYumRepoFile *
repoconf_repofile(LrYumRepoConf *repoconf)
{
    return repoconf->file;
}

static GKeyFile *
repoconf_keyfile(LrYumRepoConf *repoconf)
{
    return repofile_keyfile(repoconf_repofile(repoconf));
}

static gchar *
repoconf_id(LrYumRepoConf *repoconf)
{
    return repoconf->id;
}

LrYumRepoConfs *
lr_yum_repoconfs_init(void)
{
    LrYumRepoConfs *repos = lr_malloc0(sizeof(*repos));
    return repos;
}

void
lr_yum_repoconfs_free(LrYumRepoConfs *repos)
{
    if (!repos)
        return;
    g_slist_free_full(repos->repos, (GDestroyNotify) lr_yum_repoconf_free);
    g_slist_free_full(repos->files, (GDestroyNotify) lr_yum_repofile_free);
    lr_free(repos);
}

GSList *
lr_yum_repoconfs_get_list(LrYumRepoConfs *repos, G_GNUC_UNUSED GError **err)
{
    return repos->repos;
}

/* This function is taken from libhif
 * Original author: Richard Hughes <richard at hughsie dot com>
 */
static GKeyFile *
lr_load_multiline_key_file(const char *filename,
                           GError **err)
{
    GKeyFile *file = NULL;
    gboolean ret;
    gsize len;
    guint i;
    _cleanup_error_free_ GError *tmp_err = NULL;
    _cleanup_free_ gchar *data = NULL;
    _cleanup_string_free_ GString *string = NULL;
    _cleanup_strv_free_ gchar **lines = NULL;

    // load file
    if (!g_file_get_contents (filename, &data, &len, &tmp_err)) {
        g_set_error(err, LR_REPOCONF_ERROR, LRE_FILE,
                    "Cannot load content of %s: %s",
                    filename, tmp_err->message);
        return NULL;
    }

    // split into lines
    string = g_string_new ("");
    lines = g_strsplit (data, "\n", -1);
    for (i = 0; lines[i] != NULL; i++) {

        // convert tabs to spaces
        g_strdelimit (lines[i], "\t", ' ');

        // if a line starts with whitespace, then append it on
        // the previous line
        if (lines[i][0] == ' ' && string->len > 0) {

            // remove old newline from previous line
            g_string_set_size (string, string->len - 1);

            // whitespace strip this new line
            g_strchug (lines[i]);

            // only add a ';' if we have anything after the '='
            if (string->str[string->len - 1] == '=') {
                g_string_append_printf (string, "%s\n", lines[i]);
            } else {
                g_string_append_printf (string, ";%s\n", lines[i]);
            }
        } else {
            g_string_append_printf (string, "%s\n", lines[i]);
        }
    }

    // remove final newline
    if (string->len > 0)
        g_string_set_size (string, string->len - 1);

    // load modified lines
    file = g_key_file_new ();
    ret = g_key_file_load_from_data (file,
                                     string->str,
                                     -1,
                                     G_KEY_FILE_KEEP_COMMENTS,
                                     &tmp_err);
    if (!ret) {
        g_key_file_free (file);
        g_set_error(err, LR_REPOCONF_ERROR, LRE_KEYFILE,
                    "Cannot parse key file %s: %s", filename, tmp_err->message);
        return NULL;
    }
    return file;
}

static gboolean
lr_key_file_get_boolean(GKeyFile *keyfile,
                        const gchar *groupname,
                        const gchar *key,
                        gboolean default_value,
                        GError **err)
{
    _cleanup_free_ gchar *string = NULL;
    _cleanup_free_ gchar *string_lower = NULL;
    string = g_key_file_get_string(keyfile, groupname, key, err);
    if (!string)
        return default_value;
    string_lower = g_ascii_strdown (string, -1);
    if (!g_strcmp0(string_lower, "1") ||
        !g_strcmp0(string_lower, "yes") ||
        !g_strcmp0(string_lower, "true"))
        return TRUE;
    return FALSE;
}

static long
lr_key_file_get_boolean_long(GKeyFile *keyfile,
                             const gchar *groupname,
                             const gchar *key,
                             gboolean default_value,
                             GError **err)
{
    if (lr_key_file_get_boolean(keyfile, groupname, key, default_value, err))
        return 1;
    return 0;
}

static gchar **
lr_key_file_get_string_list(GKeyFile *keyfile,
                            const gchar *groupname,
                            const gchar *key,
                            GError **err)
{
    gchar **list = NULL;
    _cleanup_free_ gchar *string = NULL;
    string = g_key_file_get_string(keyfile, groupname, key, err);
    if (!string)
        return list;
    list = g_strsplit_set(string, " ,;", 0);
    gint i=0;
    for (; list && list[i]; i++)
        g_strstrip(list[i]);

    if (((i-1) > 0) && !g_strcmp0(list[i-1], "")) {
        // If the last element of the list is an empty string remove it
        // If could be cause when a list is defined like: option = val1; val2;
        g_free(list[i-1]);
        list[i-1] = NULL;
    }
    return list;
}

static LrIpResolveType
lr_key_file_get_ip_resolve(GKeyFile *keyfile,
                            const gchar *groupname,
                            const gchar *key,
                            LrIpResolveType default_value,
                            GError **err)
{
    _cleanup_free_ gchar *string = NULL;
    _cleanup_free_ gchar *string_lower = NULL;
    string = g_key_file_get_string(keyfile, groupname, key, err);
    if (!string)
        return default_value;
    string_lower = g_ascii_strdown(string, -1);
    if (!g_strcmp0(string_lower, "ipv4"))
        return LR_IPRESOLVE_V4;
    else if (!g_strcmp0(string_lower, "ipv6"))
        return LR_IPRESOLVE_V6;
    else if (!g_strcmp0(string_lower, "whatever"))
        return LR_IPRESOLVE_WHATEVER;
    else
        g_set_error(err, LR_REPOCONF_ERROR, LRE_VALUE,
                    "Unknown ip_resolve value '%s'", string);
    return default_value;
}

static void
lr_key_file_set_ip_resolve(GKeyFile *keyfile,
                           const gchar *groupname,
                           const gchar *key,
                           LrIpResolveType resolve_type)
{
    if (resolve_type == LR_IPRESOLVE_WHATEVER)
        g_key_file_set_string(keyfile, groupname, key, "whatever");
    else if (resolve_type == LR_IPRESOLVE_V4)
        g_key_file_set_string(keyfile, groupname, key, "ipv4");
    else if (resolve_type == LR_IPRESOLVE_V6)
        g_key_file_set_string(keyfile, groupname, key, "ipv6");
}

static gboolean
lr_convert_interval_to_seconds(const char *str,
                               gint64 *out,
                               GError **err)
{
    gdouble value = 0.0;
    gdouble mult = 1.0;
    gchar *endptr = NULL;

    *out = G_GINT64_CONSTANT(0);

    // Initial sanity checking
    if (!str) {
        g_set_error(err, LR_REPOCONF_ERROR, LRE_BADFUNCARG,
                    "No time interval value specified");
        return FALSE;
    }

    // Initial conversion
    value = g_ascii_strtod(str, &endptr);
    if (value == HUGE_VAL && errno == ERANGE) {
        g_set_error(err, LR_REPOCONF_ERROR, LRE_VALUE,
                    "Too big time interval value '%s'", str);
        return FALSE;
    }

    // String doesn't start with numbers
    if (endptr == str) {
        g_set_error(err, LR_REPOCONF_ERROR, LRE_VALUE,
                    "Couldn't convert '%s' to seconds", str);
        return FALSE;
    }

    // Process multiplier (if supplied)
    if (endptr && *endptr) {
        if (strlen(endptr) != 1) {
            g_set_error(err, LR_REPOCONF_ERROR, LRE_VALUE,
                        "Unknown time interval unit '%s'", endptr);
            return FALSE;
        }

        gchar unit = g_ascii_tolower(*endptr);
        if (unit == 's') {
            mult = 1.0;
        } else if (unit == 'm') {
            mult = 60.0;
        } else if (unit == 'h') {
            mult = 60.0 * 60.0;
        } else if (unit == 'd') {
            mult = 60.0 * 60.0 * 24.0;
        } else {
            g_set_error(err, LR_REPOCONF_ERROR, LRE_VALUE,
                        "Unknown time interval unit '%s'", endptr);
            return FALSE;
        }
    }

    // Convert result to seconds
    value = value * mult;

    // Return result as integer
    *out = (gint64) value;
    return TRUE;
}

static gint64
lr_key_file_get_metadata_expire(GKeyFile *keyfile,
                                const gchar *groupname,
                                const gchar *key,
                                gint64 default_value,
                                GError **err)
{
    gint64 res = G_GINT64_CONSTANT(-1);
    _cleanup_free_ gchar *string = NULL;
    string = g_key_file_get_string(keyfile, groupname, key, err);
    if (!string)
        return default_value;
    if (!lr_convert_interval_to_seconds(string, &(res), err))
        return G_GINT64_CONSTANT(-1);
    return res;
}

static gboolean
lr_convert_bandwidth_to_bytes(const char *str,
                              guint64 *out,
                              GError **err)
{
    gdouble dbytes = 0.0;
    gdouble mult = 1.0;
    gchar *endptr = NULL;

    *out = G_GUINT64_CONSTANT(0);

    // Initial sanity checking
    if (!str) {
        g_set_error(err, LR_REPOCONF_ERROR, LRE_BADFUNCARG,
                    "No bandwidth value specified");
        return FALSE;
    }

    // Initial conversion
    dbytes = g_ascii_strtod(str, &endptr);
    if (dbytes == HUGE_VAL && errno == ERANGE) {
        g_set_error(err, LR_REPOCONF_ERROR, LRE_VALUE,
                    "Too big bandwidth value '%s'", str);
        return FALSE;
    }

    // String doesn't start with numbers
    if (endptr == str) {
        g_set_error(err, LR_REPOCONF_ERROR, LRE_VALUE,
                    "Couldn't convert '%s' to number", str);
        return FALSE;
    }

    // Process multiplier (if supplied)
    if (endptr && *endptr) {
        if (strlen(endptr) != 1) {
            g_set_error(err, LR_REPOCONF_ERROR, LRE_VALUE,
                        "Unknown unit '%s'", endptr);
            return FALSE;
        }

        gchar unit = g_ascii_tolower(*endptr);
        if (unit == 'k') {
            mult = 1024.0;
        } else if (unit == 'm') {
            mult = 1024.0 * 1024.0;
        } else if (unit == 'g') {
            mult = 1024.0 * 1024.0 * 1024.0;
        } else {
            g_set_error(err, LR_REPOCONF_ERROR, LRE_VALUE,
                        "Unknown unit '%s'", endptr);
            return FALSE;
        }
    }

    // Convert result to bytes
    dbytes = dbytes * mult;
    if (dbytes < 0.0) {
        g_set_error(err, LR_REPOCONF_ERROR, LRE_VALUE,
                    "Bytes value may not be negative '%s'", str);
        return FALSE;
    }

    // Return result as integer
    *out = (guint64) dbytes;
    return TRUE;
}

static guint64
lr_key_file_get_bandwidth(GKeyFile *keyfile,
                          const gchar *groupname,
                          const gchar *key,
                          guint64 default_value,
                          GError **err)
{
    guint64 res = G_GUINT64_CONSTANT(0);
    _cleanup_free_ gchar *string = NULL;
    string = g_key_file_get_string(keyfile, groupname, key, err);
    if (!string)
        return default_value;
    if (!lr_convert_bandwidth_to_bytes(string, &(res), err))
        return G_GUINT64_CONSTANT(0);
    return res;
}

static void
lr_key_file_set_string(GKeyFile *keyfile,
                       const gchar *groupname,
                       const gchar *key,
                       const gchar *str)
{
    if (!str) {
        g_key_file_remove_key(keyfile, groupname, key, NULL);
        return;
    }

    g_key_file_set_string(keyfile, groupname, key, str);
}

static void
lr_key_file_set_string_list(GKeyFile *keyfile,
                            const gchar *groupname,
                            const gchar *key,
                            const gchar **list)
{
    gsize len = 0;

    if (list)
        len = g_strv_length((gchar **) list);

    if (len == 0) {
        g_key_file_remove_key(keyfile, groupname, key, NULL);
        return;
    }

    g_key_file_set_string_list(keyfile, groupname, key, (const gchar * const*) list, len);
}

gboolean
lr_yum_repoconfs_add_empty_conf(LrYumRepoConfs *repos,
                                const char *filename,
                                const char **ids,
                                GError **err)
{
    GKeyFile *keyfile;
    LrYumRepoFile *repofile;

    assert(!err || *err == NULL);

    if (!repos) {
        g_set_error(err, LR_REPOCONF_ERROR, LRE_BADFUNCARG,
                    "No yumrepoconfs arg specified");
        return FALSE;
    }

    if (!filename) {
        g_set_error(err, LR_REPOCONF_ERROR, LRE_BADFUNCARG,
                    "No filename specified");
        return FALSE;
    }

    keyfile = g_key_file_new();
    repofile = lr_yum_repofile_init(filename, keyfile);
    repos->files = g_slist_append(repos->files, repofile);

    for (guint i = 0; ids[i]; i++) {
        LrYumRepoConf *repoconf = NULL;
        repoconf = lr_yum_repoconf_init(repofile, ids[i]);
        if (!repoconf)
            return FALSE;
        repos->repos = g_slist_append(repos->repos, repoconf);
    }

    return TRUE;
}

gboolean
lr_yum_repoconfs_parse(LrYumRepoConfs *repos,
                       const char *filename,
                       GError **err)
{
    _cleanup_strv_free_ gchar **groups = NULL;
    LrYumRepoFile *repofile = NULL;
    GKeyFile *keyfile = NULL;

    // Load key file content
    keyfile = lr_load_multiline_key_file(filename, err);
    if (!keyfile)
        return FALSE;

    // Create LrYumRepoFile object
    repofile = lr_yum_repofile_init(filename, keyfile);
    repos->files = g_slist_append(repos->files, repofile);

    // Create LrYumRepoConf objects
    groups = g_key_file_get_groups (keyfile, NULL);
    for (guint i = 0; groups[i]; i++) {
        LrYumRepoConf *repoconf = NULL;
        repoconf = lr_yum_repoconf_init(repofile, groups[i]);
        if (!repoconf)
            return FALSE;
        repos->repos = g_slist_append(repos->repos, repoconf);
    }

    return TRUE;
}

gboolean
lr_yum_repoconfs_load_dir(LrYumRepoConfs *repos,
                          const char *path,
                          GError **err)
{
    const gchar *file;
    _cleanup_error_free_ GError *tmp_err = NULL;
    _cleanup_dir_close_ GDir *dir = NULL;

    // Open dir
    dir = g_dir_open(path, 0, &tmp_err);
    if (!dir) {
        g_set_error(err, LR_REPOCONF_ERROR, LRE_KEYFILE,
                    "Cannot open dir %s: %s", path, tmp_err->message);
        return FALSE;
    }

    // Find all the .repo files
    while ((file = g_dir_read_name(dir))) {
        _cleanup_free_ gchar *path_tmp = NULL;
        if (!g_str_has_suffix(file, ".repo"))
            continue;
        path_tmp = g_build_filename(path, file, NULL);
        if (!lr_yum_repoconfs_parse(repos, path_tmp, err))
            return FALSE;
    }

    return TRUE;
}

gboolean
lr_yum_repoconfs_save(LrYumRepoConfs *confs,
                      GError **err)
{
    assert(!err || *err == NULL);

    if (!confs) {
        g_set_error(err, LR_REPOCONF_ERROR, LRE_BADFUNCARG,
                    "No yumrepoconfs arg specified");
        return FALSE;
    }

    for (GSList *elem = confs->repos; elem; elem = g_slist_next(elem)) {
        LrYumRepoConf *repoconf = elem->data;
        if (!lr_yum_repoconf_save(repoconf, err))
            return FALSE;
    }

    return TRUE;
}

gboolean
lr_yum_repoconf_save(LrYumRepoConf *repoconf,
                     GError **err)
{
    assert(!err || *err == NULL);

    if (!repoconf) {
        g_set_error(err, LR_REPOCONF_ERROR, LRE_BADFUNCARG,
                    "No config specified");
        return FALSE;
    }

    LrYumRepoFile *repofile = repoconf_repofile(repoconf);
    return repofile_save_keyfile(repofile, err);
}

gboolean
lr_yum_repoconf_getinfo(LrYumRepoConf *repoconf,
                        GError **err,
                        LrYumRepoConfOption option,
                        ...)
{
    GError *tmp_err = NULL;
    va_list arg;

    char **str;
    char ***strv;
    long *lnum;

    assert(!err || *err == NULL);

    if (!repoconf) {
        g_set_error(err, LR_REPOCONF_ERROR, LRE_BADFUNCARG,
                    "No config specified");
        return FALSE;
    }

    // Shortcuts
    GKeyFile *keyfile = repoconf_keyfile(repoconf);
    gchar *id = repoconf_id(repoconf);

    // Basic sanity checks
    if (!keyfile) {
        g_set_error(err, LR_REPOCONF_ERROR, LRE_BADFUNCARG,
                    "No keyfile available in yumrepoconf");
        return FALSE;
    }

    va_start(arg, option);

    switch (option) {

    case LR_YRC_ID:              /*!< (char *) ID (short name) of the repo */
        str = va_arg(arg, char **);
        *str = g_strdup(id);
        break;

    case LR_YRC_NAME:            /*!< (char *) Pretty name of the repo */
        str = va_arg(arg, char **);
        *str = g_key_file_get_string(keyfile, id, "name", &tmp_err);
        break;

    case LR_YRC_ENABLED:         /*!< (long 1 or 0) Is repo enabled? */
        lnum = va_arg(arg, long *);
        *lnum = lr_key_file_get_boolean_long(keyfile, id, "enabled", TRUE, &tmp_err);
        break;

    case LR_YRC_BASEURL:         /*!< (char **) List of base URLs */
        strv = va_arg(arg, char ***);
        *strv = lr_key_file_get_string_list(keyfile, id, "baseurl", &tmp_err);
        break;

    case LR_YRC_MIRRORLIST:      /*!< (char *) Mirrorlist URL */
        str = va_arg(arg, char **);
        *str = g_key_file_get_string(keyfile, id, "mirrorlist", &tmp_err);
        break;

    case LR_YRC_METALINK:        /*!< (char *) Metalink URL */
        str = va_arg(arg, char **);
        *str = g_key_file_get_string(keyfile, id, "metalink", &tmp_err);
        break;

    case LR_YRC_MEDIAID:         /*!< (char *) Media ID */
        str = va_arg(arg, char **);
        *str = g_key_file_get_string(keyfile, id, "mediaid", &tmp_err);
        break;

    case LR_YRC_GPGKEY:          /*!< (char **) URL of GPG key */
        strv = va_arg(arg, char ***);
        *strv = lr_key_file_get_string_list(keyfile, id, "gpgkey", &tmp_err);
        break;

    case LR_YRC_GPGCAKEY:        /*!< (char **) GPG CA key */
        strv = va_arg(arg, char ***);
        *strv = lr_key_file_get_string_list(keyfile, id, "gpgcakey", &tmp_err);
        break;

    case LR_YRC_EXCLUDE:         /*!< (char **) List of excluded packages */
        strv = va_arg(arg, char ***);
        *strv = lr_key_file_get_string_list(keyfile, id, "exclude", &tmp_err);
        break;

    case LR_YRC_INCLUDE:         /*!< (char **) List of included packages */
        strv = va_arg(arg, char ***);
        *strv = lr_key_file_get_string_list(keyfile, id, "include", &tmp_err);
        break;

    case LR_YRC_FASTESTMIRROR:  /*!< (long 1 or 0) Fastest mirror determination */
        lnum = va_arg(arg, long *);
        *lnum = lr_key_file_get_boolean_long(keyfile, id, "fastestmirror", TRUE, &tmp_err);
        break;

    case LR_YRC_PROXY:          /*!< (char *) Proxy address */
        str = va_arg(arg, char **);
        *str = g_key_file_get_string(keyfile, id, "proxy", &tmp_err);
        break;

    case LR_YRC_PROXY_USERNAME: /*!< (char *) Proxy username */
        str = va_arg(arg, char **);
        *str = g_key_file_get_string(keyfile, id, "proxy_username", &tmp_err);
        break;

    case LR_YRC_PROXY_PASSWORD: /*!< (char *) Proxy password */
        str = va_arg(arg, char **);
        *str = g_key_file_get_string(keyfile, id, "proxy_password", &tmp_err);
        break;

    case LR_YRC_USERNAME:       /*!< (char *) Username */
        str = va_arg(arg, char **);
        *str = g_key_file_get_string(keyfile, id, "username", &tmp_err);
        break;

    case LR_YRC_PASSWORD:       /*!< (char *) Password */
        str = va_arg(arg, char **);
        *str = g_key_file_get_string(keyfile, id, "password", &tmp_err);
        break;

    case LR_YRC_GPGCHECK:       /*!< (long 1 or 0) GPG check for packages */
        lnum = va_arg(arg, long *);
        *lnum = lr_key_file_get_boolean_long(keyfile, id, "gpgcheck", TRUE, &tmp_err);
        break;

    case LR_YRC_REPO_GPGCHECK:  /*!< (long 1 or 0) GPG check for repodata */
        lnum = va_arg(arg, long *);
        *lnum = lr_key_file_get_boolean_long(keyfile, id, "repo_gpgcheck", TRUE, &tmp_err);
        break;

    case LR_YRC_ENABLEGROUPS:   /*!< (long 1 or 0) Use groups */
        lnum = va_arg(arg, long *);
        *lnum = lr_key_file_get_boolean_long(keyfile, id, "enablegroups", TRUE, &tmp_err);
        break;

    case LR_YRC_BANDWIDTH:      /*!< (guint64) Bandwidth - Number of bytes */
    {
        guint64 *num = va_arg(arg, guint64 *);
        *num = lr_key_file_get_bandwidth(keyfile, id, "bandwidth", G_GUINT64_CONSTANT(0), &tmp_err);
        break;
    }

    case LR_YRC_THROTTLE:       /*!< (char *) Throttle string */
        str = va_arg(arg, char **);
        *str = g_key_file_get_string(keyfile, id, "throttle", &tmp_err);
        break;

    case LR_YRC_IP_RESOLVE:     /*!< (LrIpResolveType) Ip resolve type */
    {
        LrIpResolveType *val = va_arg(arg, LrIpResolveType *);
        *val = lr_key_file_get_ip_resolve(keyfile, id, "ip_resolve", LR_YUMREPOCONF_IP_RESOLVE_DEFAULT, &tmp_err);
        break;
    }

    case LR_YRC_METADATA_EXPIRE:/*!< (gint64) Interval in secs for metadata expiration */
    {
        gint64 *num = va_arg(arg, gint64 *);
        *num = lr_key_file_get_metadata_expire(keyfile,
                                               id,
                                               "metadata_expire",
                                               LR_YUMREPOCONF_METADATA_EXPIRE_DEFAULT,
                                               &tmp_err);
        break;
    }

    case LR_YRC_COST:           /*!< (gint) Repo cost */
    {
        gint *num = va_arg(arg, gint *);
        *num = g_key_file_get_integer(keyfile, id, "cost", &tmp_err);
        break;
    }

    case LR_YRC_PRIORITY:       /*!< (gint) Repo priority */
    {
        gint *num = va_arg(arg, gint *);
        *num = g_key_file_get_integer(keyfile, id, "priority", &tmp_err);
        break;
    }

    case LR_YRC_SSLCACERT:      /*!< (gchar *) SSL Certification authority cert */
        str = va_arg(arg, char **);
        *str = g_key_file_get_string(keyfile, id, "sslcacert", &tmp_err);
        break;

    case LR_YRC_SSLVERIFY:      /*!< (long 1 or 0) SSL verification */
        lnum = va_arg(arg, long *);
        *lnum = lr_key_file_get_boolean_long(keyfile, id, "sslverify", TRUE, &tmp_err);
        break;

    case LR_YRC_SSLCLIENTCERT:  /*!< (gchar *) SSL Client certificate */
        str = va_arg(arg, char **);
        *str = g_key_file_get_string(keyfile, id, "sslclientcert", &tmp_err);
        break;

    case LR_YRC_SSLCLIENTKEY:   /*!< (gchar *) SSL Client key */
        str = va_arg(arg, char **);
        *str = g_key_file_get_string(keyfile, id, "sslclientkey", &tmp_err);
        break;

    case LR_YRC_DELTAREPOBASEURL:/*!< (char **) Deltarepo mirror URLs */
        strv = va_arg(arg, char ***);
        *strv = lr_key_file_get_string_list(keyfile, id, "deltarepobaseurl", &tmp_err);
        break;

    case LR_YRC_FAILOVERMETHOD:   /*!< (gchar *) Failover method */
        str = va_arg(arg, char **);
        *str = g_key_file_get_string(keyfile, id, "failovermethod", &tmp_err);
        break;

    case LR_YRC_SKIP_IF_UNAVAILABLE:/*!< (long 1 or 0) Skip if unavailable */
        lnum = va_arg(arg, long *);
        *lnum = lr_key_file_get_boolean_long(keyfile, id, "skip_if_unavailable", TRUE, &tmp_err);
        break;

    }

    va_end(arg);

    if (tmp_err) {
        if (tmp_err->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND
                || tmp_err->code == G_KEY_FILE_ERROR_GROUP_NOT_FOUND)
            g_set_error(err, LR_REPOCONF_ERROR, LRE_NOTSET,
                        "Value of option %d is not set",
                        option);
        else
            g_set_error(err, LR_REPOCONF_ERROR, LRE_KEYFILE,
                        "Cannot get value of option %d: %s",
                        option, tmp_err->message);

        g_error_free(tmp_err);
        return FALSE;
    }

    return TRUE;
}

gboolean
lr_yum_repoconf_setopt(LrYumRepoConf *repoconf,
                       GError **err,
                       LrYumRepoConfOption option,
                       ...)
{
    GError *tmp_err = NULL;
    va_list arg;

    char *str;
    char **strv;
    long lnum;

    assert(!err || *err == NULL);

    if (!repoconf) {
        g_set_error(err, LR_REPOCONF_ERROR, LRE_BADFUNCARG,
                    "No config specified");
        return FALSE;
    }

    // Shortcuts
    LrYumRepoFile *repofile = repoconf_repofile(repoconf);
    GKeyFile *keyfile = repoconf_keyfile(repoconf);
    gchar *id = repoconf_id(repoconf);

    // Basic sanity checks
    if (!keyfile) {
        g_set_error(err, LR_REPOCONF_ERROR, LRE_BADFUNCARG,
                    "No keyfile available in yumrepoconf");
        return FALSE;
    }

    va_start(arg, option);

    switch (option) {

    case LR_YRC_ID:              /*!<  0 (char *) ID (short name) of the repo */
        g_set_error(&tmp_err, LR_REPOCONF_ERROR, LRE_BADOPTARG,
                    "ID is read only option");
        break;

    case LR_YRC_NAME:            /*!<  1 (char *) Pretty name of the repo */
        str = va_arg(arg, char *);
        lr_key_file_set_string(keyfile, id, "name", str);
        break;

    case LR_YRC_ENABLED:         /*!<  2 (long 1 or 0) Is repo enabled? */
        lnum = va_arg(arg, long);
        g_key_file_set_boolean(keyfile, id, "enabled", lnum ? TRUE : FALSE);
        break;

    case LR_YRC_BASEURL:         /*!<  3 (char **) List of base URLs */
        strv = va_arg(arg, char **);
        lr_key_file_set_string_list(keyfile, id, "baseurl", (const gchar **) strv);
        break;

    case LR_YRC_MIRRORLIST:      /*!<  4 (char *) Mirrorlist URL */
        str = va_arg(arg, char *);
        lr_key_file_set_string(keyfile, id, "mirrorlist", str);
        break;

    case LR_YRC_METALINK:        /*!<  5 (char *) Metalink URL */
        str = va_arg(arg, char *);
        lr_key_file_set_string(keyfile, id, "metalink", str);
        break;

    case LR_YRC_MEDIAID:         /*!<  6 (char *) Media ID */
        str = va_arg(arg, char *);
        lr_key_file_set_string(keyfile, id, "mediaid", str);
        break;

    case LR_YRC_GPGKEY:          /*!<  7 (char **) URL of GPG key */
        strv = va_arg(arg, char **);
        lr_key_file_set_string_list(keyfile, id, "gpgkey", (const gchar **) strv);
        break;

    case LR_YRC_GPGCAKEY:        /*!<  8 (char **) GPG CA key */
        strv = va_arg(arg, char **);
        lr_key_file_set_string_list(keyfile, id, "gpgcakey", (const gchar **) strv);
        break;

    case LR_YRC_EXCLUDE:         /*!<  9 (char **) List of exluded packages */
        strv = va_arg(arg, char **);
        lr_key_file_set_string_list(keyfile, id, "exclude", (const gchar **) strv);
        break;

    case LR_YRC_INCLUDE:         /*!< 10 (char **) List of included packages */
        strv = va_arg(arg, char **);
        lr_key_file_set_string_list(keyfile, id, "include", (const gchar **) strv);
        break;

    case LR_YRC_FASTESTMIRROR:   /*!< 11 (long 1 or 0) Fastest mirror determination */
        lnum = va_arg(arg, long);
        g_key_file_set_boolean(keyfile, id, "fastestmirror", lnum);
        break;

    case LR_YRC_PROXY:           /*!< 12 (char *) Proxy address */
        str = va_arg(arg, char *);
        lr_key_file_set_string(keyfile, id, "proxy", str);
        break;

    case LR_YRC_PROXY_USERNAME:  /*!< 13 (char *) Proxy username */
        str = va_arg(arg, char *);
        lr_key_file_set_string(keyfile, id, "proxy_username", str);
        break;

    case LR_YRC_PROXY_PASSWORD:  /*!< 14 (char *) Proxy password */
        str = va_arg(arg, char *);
        lr_key_file_set_string(keyfile, id, "proxy_password", str);
        break;

    case LR_YRC_USERNAME:        /*!< 15 (char *) Username */
        str = va_arg(arg, char *);
        lr_key_file_set_string(keyfile, id, "username", str);
        break;

    case LR_YRC_PASSWORD:        /*!< 16 (char *) Password */
        str = va_arg(arg, char *);
        lr_key_file_set_string(keyfile, id, "password", str);
        break;

    case LR_YRC_GPGCHECK:        /*!< 17 (long 1 or 0) GPG check for packages */
        lnum = va_arg(arg, long);
        g_key_file_set_boolean(keyfile, id, "gpgcheck", (gboolean) lnum);
        break;

    case LR_YRC_REPO_GPGCHECK:   /*!< 18 (long 1 or 0) GPG check for repodata */
        lnum = va_arg(arg, long);
        g_key_file_set_boolean(keyfile, id, "repo_gpgcheck", (gboolean) lnum);
        break;

    case LR_YRC_ENABLEGROUPS:    /*!< 19 (long 1 or 0) Use groups */
        lnum = va_arg(arg, long);
        g_key_file_set_boolean(keyfile, id, "enablegroups", (gboolean) lnum);
        break;

    case LR_YRC_BANDWIDTH:       /*!< 20 (guint64) Bandwidth - Number of bytes */
    {
        guint64 val = va_arg(arg, guint64);
        g_key_file_set_uint64(keyfile, id, "bandwidth", val);
        break;
    }

    case LR_YRC_THROTTLE:        /*!< 21 (char *) Throttle string */
        str = va_arg(arg, char *);
        lr_key_file_set_string(keyfile, id, "throttle", str);
        break;

    case LR_YRC_IP_RESOLVE:      /*!< 22 (LrIpResolveType) Ip resolve type */
    {
        LrIpResolveType val = va_arg(arg, LrIpResolveType);
        lr_key_file_set_ip_resolve(keyfile, id, "ip_resolve", val);
        break;
    }

    case LR_YRC_METADATA_EXPIRE: /*!< 23 (gint64) Interval in secs for metadata expiration */
    {
        // TODO: Store value in appropriate units
        gint64 val = va_arg(arg, gint64);
        g_key_file_set_int64(keyfile, id, "metadata_expire", val);
        break;
    }

    case LR_YRC_COST:            /*!< 24 (gint) Repo cost */
    {
        gint val = va_arg(arg, gint);
        g_key_file_set_integer(keyfile, id, "cost", val);
        break;
    }

    case LR_YRC_PRIORITY:        /*!< 25 (gint) Repo priority */
    {
        guint val = va_arg(arg, gint);
        g_key_file_set_integer(keyfile, id, "priority", val);
        break;
    }

    case LR_YRC_SSLCACERT:       /*!< 26 (gchar *) SSL Certification authority cert */
        str = va_arg(arg, char *);
        lr_key_file_set_string(keyfile, id, "sslcacert", str);
        break;

    case LR_YRC_SSLVERIFY:       /*!< 27 (long 1 or 0) SSL verification */
        lnum = va_arg(arg, long);
        g_key_file_set_boolean(keyfile, id, "sslverify", (gboolean) lnum);
        break;

    case LR_YRC_SSLCLIENTCERT:   /*!< 28 (gchar *) SSL Client certificate */
        str = va_arg(arg, char *);
        lr_key_file_set_string(keyfile, id, "sslclientcert", str);
        break;

    case LR_YRC_SSLCLIENTKEY:    /*!< 29 (gchar *) SSL Client key */
        str = va_arg(arg, char *);
        lr_key_file_set_string(keyfile, id, "sslclientkey", str);
        break;

    case LR_YRC_DELTAREPOBASEURL:/*!< 30 (char **) Deltarepo mirror URLs */
        strv = va_arg(arg, char **);
        lr_key_file_set_string_list(keyfile, id, "deltarepobaseurl", (const gchar **) strv);
        break;

    case LR_YRC_FAILOVERMETHOD:   /*!< 31 (gchar *) Failover method */
        str = va_arg(arg, char *);
        lr_key_file_set_string(keyfile, id, "failovermethod", str);
        break;

    case LR_YRC_SKIP_IF_UNAVAILABLE: /*!< 32 (long 1 or 0) Skip if unavailable */
        lnum = va_arg(arg, long);
        g_key_file_set_boolean(keyfile, id, "skip_if_unavailable", (gboolean) lnum);
        break;
    }

    va_end(arg);

    if (tmp_err) {
        g_propagate_error(err, tmp_err);
        return FALSE;
    }

    repofile_set_modified(repofile, TRUE);

    return TRUE;
}
