/*  Copyright (c) 2003-2014 Xfce Development Team
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <sys/stat.h>

#include <libxfce4util/libxfce4util.h>
#include <libxfce4ui/libxfce4ui.h>
#include <xfconf/xfconf.h>

#include <libxml/parser.h>
#include <libxml/tree.h>
#include <json-c/json_tokener.h>

#include "weather-parsers.h"
#include "weather-data.h"
#include "weather.h"

#include "weather-translate.h"
#include "weather-summary.h"
#include "weather-config.h"
#include "weather-icon.h"
#include "weather-scrollbox.h"
#include "weather-debug.h"

#include "weather-config_ui.h"

#define XFCEWEATHER_ROOT "weather"
#define CACHE_FILE_MAX_AGE (48 * 3600)
#define BORDER (8)
#define CONN_TIMEOUT (10)        /* connection timeout in seconds */
#define CONN_MAX_ATTEMPTS (3)    /* max retry attempts using small interval */
#define CONN_RETRY_INTERVAL_SMALL (10)
#define CONN_RETRY_INTERVAL_LARGE (10 * 60)

/* power saving update interval in seconds used as a precaution to
   deal with suspend/resume events etc., when nothing needs to be
   updated earlier: */
#define POWERSAVE_UPDATE_INTERVAL (30)

/* standard update interval in seconds used as a precaution to deal
   with suspend/resume events etc., when nothing needs to be updated
   earlier: */
#define UPDATE_INTERVAL (10)

#define DATA_AND_UNIT(var, item)                        \
    value = get_data(conditions, data->units, item,     \
                     data->round, data->night_time);    \
    unit = get_unit(data->units, item);                 \
    var = g_strdup_printf("%s%s%s",                     \
                          value,                        \
                          strcmp(unit, "°") ? " " : "", \
                          unit);                        \
    g_free(value);

#define CACHE_APPEND(str, val)                  \
    if (val)                                    \
        g_string_append_printf(out, str, val);

#define CACHE_FREE_VARS()                       \
    g_free(locname);                            \
    g_free(lat);                                \
    g_free(lon);                                \
    g_free(offset);                             \
    if (keyfile)                                \
        g_key_file_free(keyfile);

#define CACHE_READ_STRING(var, key)                         \
    var = g_key_file_get_string(keyfile, group, key, NULL); \

#define SCHEDULE_WAKEUP_COMPARE(var, reason)        \
    if (difftime(var, now_t) < diff) {              \
        data->next_wakeup = var;                    \
        diff = difftime(data->next_wakeup, now_t);  \
        data->next_wakeup_reason = reason;          \
    }


gboolean debug_mode = FALSE;


static void write_cache_file(plugin_data *data);

static void schedule_next_wakeup(plugin_data *data);


void
weather_http_queue_request(SoupSession *session,
                           const gchar *uri,
                           SoupSessionCallback callback_func,
                           gpointer user_data)
{
    SoupMessage *msg;

    msg = soup_message_new("GET", uri);
    soup_session_queue_message(session, msg, callback_func, user_data);
}


static gchar *
make_label(const plugin_data *data,
           data_types type)
{
    xml_time *conditions;
    const gchar *lbl, *unit;
    gchar *str, *value;

    switch (type) {
    case TEMPERATURE:
        /* TRANSLATORS: Keep in sync with labeloptions in weather-config.c */
        lbl = _("T");
        break;
    case PRESSURE:
        lbl = _("P");
        break;
    case WIND_SPEED:
        lbl = _("WS");
        break;
    case WIND_BEAUFORT:
        lbl = _("WB");
        break;
    case WIND_DIRECTION:
        lbl = _("WD");
        break;
    case WIND_DIRECTION_DEG:
        lbl = _("WD");
        break;
    case HUMIDITY:
        lbl = _("H");
        break;
    case DEWPOINT:
        lbl = _("D");
        break;
    case APPARENT_TEMPERATURE:
        lbl = _("A");
        break;
    case CLOUDS_LOW:
        lbl = _("CL");
        break;
    case CLOUDS_MID:
        lbl = _("CM");
        break;
    case CLOUDS_HIGH:
        lbl = _("CH");
        break;
    case CLOUDINESS:
        lbl = _("C");
        break;
    case FOG:
        lbl = _("F");
        break;
    case PRECIPITATION:
        lbl = _("R");
        break;
    default:
        lbl = "?";
        break;
    }

    /* get current weather conditions */
    conditions = get_current_conditions(data->weatherdata);
    unit = get_unit(data->units, type);
    value = get_data(conditions, data->units, type,
                     data->round, data->night_time);

    if (data->labels->len > 1)
        str = g_strdup_printf("%s: %s%s%s", lbl, value,
                              strcmp(unit, "°") || strcmp(unit, "")
                              ? " " : "", unit);
    else
        str = g_strdup_printf("%s%s%s", value,
                              strcmp(unit, "°") || strcmp(unit, "")
                              ? " " : "", unit);
    g_free(value);
    return str;
}


static update_info *
make_update_info(const guint check_interval)
{
    update_info *upi = g_slice_new0(update_info);
    memset(&upi->last, 0, sizeof(upi->last));
    upi->next = time(NULL);
    upi->check_interval = check_interval;
    return upi;
}


static void
init_update_infos(plugin_data *data)
{
    if (G_LIKELY(data->astro_update))
        g_slice_free(update_info, data->astro_update);
    if (G_LIKELY(data->weather_update))
        g_slice_free(update_info, data->weather_update);
    if (G_LIKELY(data->conditions_update))
        g_slice_free(update_info, data->conditions_update);

    data->astro_update = make_update_info(24 * 3600);
    data->weather_update = make_update_info(60 * 60);
    data->conditions_update = make_update_info(5 * 60);
}


/*
 * Return the weather plugin cache directory, creating it if
 * necessary. The string returned does not contain a trailing slash.
 */
gchar *
get_cache_directory(void)
{
    gchar *dir = g_strconcat(g_get_user_cache_dir(), G_DIR_SEPARATOR_S,
                             "xfce4", G_DIR_SEPARATOR_S, "weather",
                             NULL);
    g_mkdir_with_parents(dir, 0755);
    return dir;
}


static gint
get_tooltip_icon_size(plugin_data *data)
{
    switch (data->tooltip_style) {
    case TOOLTIP_SIMPLE:
        return 96;
    case TOOLTIP_VERBOSE:
    default:
        return 128;
    }
}


void
update_timezone(plugin_data *data)
{
    if (data->timezone && strlen(data->timezone) > 0)
        g_setenv("TZ", data->timezone, TRUE);
    else {
        if (data->timezone_initial && strlen(data->timezone_initial) > 0)
            g_setenv("TZ", data->timezone_initial, TRUE);
        else
            g_unsetenv("TZ");
    }
}


void
update_offset(plugin_data *data)
{
    GDateTime *dt;

    dt = g_date_time_new_now_local();
    if (G_LIKELY(data->offset))
        g_free(data->offset);

    data->offset = g_date_time_format(dt, "%:z");
    g_date_time_unref(dt);
}


void
update_icon(plugin_data *data)
{
    cairo_surface_t *icon;
    xml_time *conditions;
    gchar *str;
    gint size;
    gint scale_factor;

    /* set panel icon according to current weather conditions */
    size = data->icon_size;
    conditions = get_current_conditions(data->weatherdata);
    str = get_data(conditions, data->units, SYMBOL,
                   data->round, data->night_time);
    scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(data->plugin));
    icon = get_icon(data->icon_theme, str, size, scale_factor, data->night_time);
    gtk_image_set_from_surface(GTK_IMAGE(data->iconimage), icon);
    if (G_LIKELY(icon))
        cairo_surface_destroy(icon);

    /* set tooltip icon too */
    size = get_tooltip_icon_size(data);
    if (G_LIKELY(data->tooltip_icon))
        cairo_surface_destroy(data->tooltip_icon);
    data->tooltip_icon = get_icon(data->icon_theme, str, size, scale_factor, data->night_time);
    g_free(str);
    weather_debug("Updated panel and tooltip icons.");
}


void
scrollbox_set_visible(plugin_data *data)
{
    if (data->show_scrollbox && data->labels->len > 0)
        gtk_widget_show_all(GTK_WIDGET(data->vbox_center_scrollbox));
    else
        gtk_widget_hide(GTK_WIDGET(data->vbox_center_scrollbox));
    gtk_scrollbox_set_visible(GTK_SCROLLBOX(data->scrollbox),
                              data->show_scrollbox);
}


void
update_scrollbox(plugin_data *data,
                 gboolean immediately)
{
    GString *out;
    gchar *label = NULL;
    data_types type;
    guint i = 0, j = 0;

    gtk_scrollbox_clear_new(GTK_SCROLLBOX(data->scrollbox));
    if (data->weatherdata && data->weatherdata->current_conditions) {
        while (i < data->labels->len) {
            j = 0;
            out = g_string_sized_new(128);
            while ((i + j) < data->labels->len && j < data->scrollbox_lines) {
                type = g_array_index(data->labels, data_types, i + j);
                label = make_label(data, type);
                g_string_append_printf(out, "%s%s", label,
                                       (j < (data->scrollbox_lines - 1) &&
                                        (i + j + 1) < data->labels->len
                                        ? "\n"
                                        : ""));
                g_free(label);
                j++;
            }
            gtk_scrollbox_add_label(GTK_SCROLLBOX(data->scrollbox),
                                    -1, out->str);
            g_string_free(out, TRUE);
            i = i + j;
        }
        weather_debug("Added %u labels to scrollbox.", data->labels->len);
    } else {
        gtk_scrollbox_add_label(GTK_SCROLLBOX(data->scrollbox), -1,
                                _("No Data"));
        weather_debug("No weather data available, set single label '%s'.",
                      _("No Data"));
    }
#ifdef HAVE_UPOWER_GLIB
    if (data->upower_on_battery)
        gtk_scrollbox_set_animate(GTK_SCROLLBOX(data->scrollbox), FALSE);
    else
#endif
        gtk_scrollbox_set_animate(GTK_SCROLLBOX(data->scrollbox),
                                  data->scrollbox_animate);
    /* update labels immediately (mainly used on config change) */
    if (immediately) {
        gtk_scrollbox_prev_label(GTK_SCROLLBOX(data->scrollbox));
        gtk_scrollbox_swap_labels(GTK_SCROLLBOX(data->scrollbox));
    }
    scrollbox_set_visible(data);
    weather_debug("Updated scrollbox.");
}


/* get astrodata for the current day */
static void
update_current_astrodata(plugin_data *data)
{
    time_t now_t = time(NULL);
    gdouble tdiff = -99999;

    if (G_UNLIKELY(data->astrodata == NULL)) {
        data->current_astro = NULL;
        return;
    }

    if (data->current_astro)
        tdiff = difftime(now_t, data->current_astro->day);

    if (data->current_astro == NULL || tdiff >= 24 * 3600 || tdiff < 0) {
        data->current_astro = get_astro_data_for_day(data->astrodata, 0);
        if (G_UNLIKELY(data->current_astro == NULL))
            weather_debug("No current astrodata available.");
        else {
            weather_debug("Updated current astrodata.");
            weather_debug("%s",weather_dump_astro(data->current_astro));
        }
    }
}


static void
update_current_conditions(plugin_data *data,
                          gboolean immediately)
{
    struct tm now_tm;

    if (G_UNLIKELY(data->weatherdata == NULL)) {
        update_icon(data);
        update_scrollbox(data, TRUE);
        schedule_next_wakeup(data);
        return;
    }

    if (data->weatherdata->current_conditions) {
        xml_time_free(data->weatherdata->current_conditions);
        data->weatherdata->current_conditions = NULL;
    }
    /* use exact 5 minute intervals for calculation */
    time(&data->conditions_update->last);
    now_tm = *localtime(&data->conditions_update->last);
    now_tm.tm_min -= (now_tm.tm_min % 5);
    if (now_tm.tm_min < 0)
        now_tm.tm_min = 0;
    now_tm.tm_sec = 0;
    data->conditions_update->last = mktime(&now_tm);

    data->weatherdata->current_conditions =
        make_current_conditions(data->weatherdata,
                                data->conditions_update->last);

    /* update current astrodata */
    update_current_astrodata(data);
    data->night_time = is_night_time(data->current_astro, data->offset);

    /* update widgets */
    update_icon(data);
    update_scrollbox(data, immediately);

    /* schedule next update */
    now_tm.tm_min += 5;
    data->conditions_update->next = mktime(&now_tm);
    schedule_next_wakeup(data);

    weather_debug("Updated current conditions.");
}


static time_t
calc_next_download_time(const update_info *upi,
                        time_t retry_t) {
    struct tm retry_tm;
    guint interval;

    retry_tm = *localtime(&retry_t);

    /* If the download failed, retry immediately using a small retry
     * interval for a limited number of times. If it still fails after
     * that, continue using a larger interval or the default check,
     * whatever is smaller.
     */
    if (G_LIKELY(upi->attempt == 0))
        interval = upi->check_interval;
    else if (upi->attempt <= CONN_MAX_ATTEMPTS)
        interval = CONN_RETRY_INTERVAL_SMALL;
    else {
        if (upi->check_interval > CONN_RETRY_INTERVAL_LARGE)
            interval = CONN_RETRY_INTERVAL_LARGE;
        else
            interval = upi->check_interval;
    }

    weather_debug("interval=%d", interval);

    return time_calc(retry_tm, 0, 0, 0, 0, 0, interval);
}

/*
 * Process downloaded sun astro data and schedule next astro update.
 */
static void
cb_astro_update_sun(SoupSession *session,
                    SoupMessage *msg,
                    gpointer user_data)
{
    plugin_data *data = user_data;
    json_object *json_tree;
    time_t now_t;
    guint astro_forecast_days;

    data->msg_parse->sun_msg_processed++;
    data->astro_update->http_status_code = msg->status_code;
    if ((msg->status_code == 200 || msg->status_code == 203)) {
        json_tree = get_json_tree(msg);
        if (G_LIKELY(json_tree)) {
            if (!parse_astrodata_sun(json_tree, data->astrodata))  {
                data->msg_parse->sun_msg_parse_error++;
                g_warning("Error parsing sun astronomical data!");
                weather_debug("data->astrodata:%s",
                              weather_dump_astrodata(data->astrodata));
            } else {
                weather_dump(weather_dump_astrodata, data->astrodata);
            }
            g_assert(json_object_put(json_tree) ==1);
        } else {
            g_warning("Error parsing sun astronomical data!");
            weather_debug("No json_tree");
        }
    } else {
        data->msg_parse->http_msg_fail = TRUE;
        g_warning_once("Download of sun astronomical data failed with HTTP Status Code %d, Reason phrase: %s",
                       msg->status_code, msg->reason_phrase);
    }

    astro_forecast_days = data->forecast_days + 1;
    if (data->msg_parse->sun_msg_processed == astro_forecast_days) {
        if (G_LIKELY(data->msg_parse->sun_msg_parse_error == 0 && !data->msg_parse->http_msg_fail)) {
            data->msg_parse->astro_dwnld_state = ASTRO_DWNLD_MOON;
            time(&now_t);
            /* schedule astro moon data downloads immediately */
            data->astro_update->next = now_t;
            weather_debug( "astro moon data update scheduled! \n");
            schedule_next_wakeup(data);
        } else {
            data->msg_parse->astro_dwnld_state = ASTRO_DWNLD_SUN;
            weather_debug( "astro sun data update failed! \n");
            time(&now_t);
            data->astro_update->next = calc_next_download_time(data->astro_update,now_t);
        }
    }
}


/*
 * Process downloaded moon astro data and schedule next astro update.
 */
static void
cb_astro_update_moon(SoupSession *session,
                     SoupMessage *msg,
                     gpointer user_data)
{
    plugin_data *data = user_data;
    json_object *json_tree;
    time_t now_t;
    guint astro_forecast_days;

    data->msg_parse->moon_msg_processed++;
    data->astro_update->http_status_code = msg->status_code;
    if ((msg->status_code == 200 || msg->status_code == 203)) {
        json_tree = get_json_tree(msg);
        if (G_LIKELY(json_tree)) {
            if (!parse_astrodata_moon(json_tree, data->astrodata))  {
                data->msg_parse->moon_msg_parse_error++;
                g_warning("Error parsing moon astronomical data");
                weather_debug("data->astrodata:%s",
                              weather_dump_astrodata(data->astrodata));
            } else {
                weather_dump(weather_dump_astrodata, data->astrodata);
            }
            g_assert(json_object_put(json_tree) ==1);
        } else {
            g_warning("Error parsing moon astronomical data");
            weather_debug("No json_tree");
        }
    } else {
        data->msg_parse->http_msg_fail = TRUE;
        g_warning_once("Download of moon astronomical data failed with HTTP Status Code %d, Reason phrase: %s",
                       msg->status_code, msg->reason_phrase);
    }

    astro_forecast_days = data->forecast_days + 1;
    if (data->msg_parse->sun_msg_processed == astro_forecast_days && data->msg_parse->moon_msg_processed == astro_forecast_days) {
        if (G_LIKELY(data->msg_parse->moon_msg_parse_error == 0 && !data->msg_parse->http_msg_fail)) {
            astrodata_clean(data->astrodata);
            g_array_sort(data->astrodata, (GCompareFunc) xml_astro_compare);
            data->astro_update->attempt = 0;
            weather_debug( "astro sun data update scheduled! \n");
            time(&now_t);
            data->astro_update->last = now_t;
            data->astro_update->next = calc_next_download_time(data->astro_update,now_t);
            update_current_astrodata(data);
            /* update icon */
            data->night_time = is_night_time(data->current_astro, data->offset);
            update_icon(data);
            data->astro_update->finished = TRUE;
            data->msg_parse->astro_dwnld_state = ASTRO_DWNLD_SUN;
        } else {
            data->msg_parse->astro_dwnld_state = ASTRO_DWNLD_MOON;
            weather_debug( "astro moon data update failed! \n");
            time(&now_t);
            data->astro_update->next = calc_next_download_time(data->astro_update,now_t);
            data->astro_update->attempt++;
        }
    }
}


/*
 * Process downloaded weather data and schedule next weather update.
 */
static void
cb_weather_update(SoupSession *session,
                  SoupMessage *msg,
                  gpointer user_data)
{
    plugin_data *data = user_data;
    xmlDoc *doc;
    xmlNode *root_node;
    time_t now_t;
    gboolean parsing_error = TRUE;

    weather_debug("Processing downloaded weather data.");
    time(&now_t);
    data->weather_update->attempt++;
    data->weather_update->http_status_code = msg->status_code;
    if (msg->status_code == 200 || msg->status_code == 203) {
        doc = get_xml_document(msg);
        if (G_LIKELY(doc)) {
            root_node = xmlDocGetRootElement(doc);
            if (G_LIKELY(root_node))
                if (parse_weather(root_node, data->weatherdata)) {
                    data->weather_update->attempt = 0;
                    data->weather_update->last = now_t;
                    parsing_error = FALSE;
                }
            xmlFreeDoc(doc);
        }
        if (parsing_error)
            g_warning("Error parsing weather data!");
    } else
        weather_debug
            ("Download of weather data failed with HTTP Status Code %d, "
             "Reason phrase: %s", msg->status_code, msg->reason_phrase);
    data->weather_update->next = calc_next_download_time(data->weather_update,
                                                         now_t);

    xml_weather_clean(data->weatherdata);
    g_array_sort(data->weatherdata->timeslices,
                 (GCompareFunc) xml_time_compare);
    weather_debug("Updating current conditions.");
    update_current_conditions(data, !parsing_error);
    gtk_scrollbox_reset(GTK_SCROLLBOX(data->scrollbox));

    data->weather_update->finished = TRUE;
    weather_dump(weather_dump_weatherdata, data->weatherdata);
}


static gboolean
update_handler(gpointer user_data)
{
    plugin_data *data = user_data;
    gchar *api_version = FORECAST_API;
    gchar *url;
    gboolean night_time;
    time_t now_t, day_t;
    struct tm now_tm;
    guint day;
    dwnld_state astro_dwnld_state = data->msg_parse->astro_dwnld_state;
    guint astro_forecast_days;

    g_assert(data != NULL);
    if (G_UNLIKELY(data == NULL))
        return FALSE;

    /* plugin has not been configured yet, so simply update icon and
       scrollbox and return */
    if (G_UNLIKELY(data->lat == NULL || data->lon == NULL)) {
        update_icon(data);
        update_scrollbox(data, TRUE);
        return FALSE;
    }

    now_t = time(NULL);
    now_tm = *localtime(&now_t);

    /* check if all started downloads are finished and the cache file
       can be written */
    if (data->astro_update->started && data->astro_update->finished &&
        data->weather_update->started && data->weather_update->finished) {
        data->astro_update->started = FALSE;
        data->astro_update->finished = FALSE;
        data->weather_update->started = FALSE;
        data->weather_update->finished = FALSE;
        write_cache_file(data);
    }

    /* fetch astronomical data */
    if (difftime(data->astro_update->next, now_t) <= 0) {
        /* real next update time will be calculated when update is finished,
           this is to prevent spawning multiple updates in a row */
        data->astro_update->next = time_calc_hour(now_tm, 1);
        data->msg_parse->http_msg_fail = FALSE;
        /* forecast astronomical data one day in advance */
        astro_forecast_days = data->forecast_days + 1;
        weather_debug("Fetching astronomical data. State: %s.\n", (int)astro_dwnld_state ? "ASTRO_DWNLD_MOON" : "ASTRO_DWNLD_SUN");
        switch (astro_dwnld_state) {
        case ASTRO_DWNLD_SUN:
            data->astro_update->started = TRUE;
            data->astro_update->attempt++;
            data->msg_parse->sun_msg_processed = 0;
            data->msg_parse->sun_msg_parse_error = 0;
            for (day = 0; day < astro_forecast_days; day++) {
                day_t = day_at_midnight(now_t, day);
                now_tm = *localtime(&day_t);
                /* build url */
                url = g_strdup_printf("https://aa062reffgwvo1efa.api.met.no/weatherapi"
                                      "/sunrise/3.0/sun?lat=%s&lon=%s&"
                                      "date=%04d-%02d-%02d&"
                                      "offset=%s",
                                      data->lat, data->lon,
                                      now_tm.tm_year + 1900,
                                      now_tm.tm_mon + 1,
                                      now_tm.tm_mday,
                                      data->offset
                                     );
                /* start receive thread */
                weather_debug("getting sun data:%s", url);
                weather_http_queue_request(data->session, url,
                                           cb_astro_update_sun, data);
                g_free(url);
            }
            break;

        case ASTRO_DWNLD_MOON:
            data->msg_parse->moon_msg_processed = 0;
            data->msg_parse->moon_msg_parse_error = 0;
            for (day = 0; day < astro_forecast_days; day++) {
                day_t = day_at_midnight(now_t, day);
                now_tm = *localtime(&day_t);
                url = g_strdup_printf("https://aa062reffgwvo1efa.api.met.no/weatherapi"
                                      "/sunrise/3.0/moon?lat=%s&lon=%s&"
                                      "date=%04d-%02d-%02d&"
                                      "offset=%s",
                                      data->lat, data->lon,
                                      now_tm.tm_year + 1900,
                                      now_tm.tm_mon + 1,
                                      now_tm.tm_mday,
                                      data->offset
                                      );
                /* start receive thread */
                weather_debug("getting moon data: %s", url);
                weather_http_queue_request(data->session, url,
                                           cb_astro_update_moon, data);
                g_free(url);
            }
            break;
        }
    }

    /* fetch weather data */
    if (difftime(data->weather_update->next, now_t) <= 0) {
        /* real next update time will be calculated when update is finished,
           this is to prevent spawning multiple updates in a row */
        data->weather_update->next = time_calc_hour(now_tm, 1);
        data->weather_update->started = TRUE;

        /* build url */
        url = g_strdup_printf("https://aa062reffgwvo1efa.api.met.no"
                              "/weatherapi/locationforecast/%s/"
                              "classic?lat=%s&lon=%s&altitude=%d",
                              api_version,
                              data->lat, data->lon, data->msl);

        /* start receive thread */
        weather_debug("getting %s", url);
        weather_http_queue_request(data->session, url,
                                   cb_weather_update, data);
        g_free(url);

        /* cb_weather_update will deal with everything that follows this
         * block, so let's return instead of doing things twice */
        return FALSE;
    }

    /* update current conditions, icon and labels */
    if (difftime(data->conditions_update->next, now_t) <= 0) {
        /* real next update time will be calculated when update is finished,
           this is to prevent spawning multiple updates in a row */
        data->conditions_update->next = time_calc_hour(now_tm, 1);
        weather_debug("Updating current conditions.");
        update_current_conditions(data, FALSE);
        /* update_current_conditions updates day/night time status
           too, so quit here */
        return FALSE;
    }

    /* update night time status and icon */
    update_current_astrodata(data);
    night_time = is_night_time(data->current_astro, data->offset);
    if (data->night_time != night_time) {
        weather_debug("Night time status changed, updating icon.");
        data->night_time = night_time;
        update_icon(data);
    }

    schedule_next_wakeup(data);
    return FALSE;
}


static void
schedule_next_wakeup(plugin_data *data)
{
    time_t now_t = time(NULL), next_day_t;
    gdouble diff;
    gchar *date;
    GSource *source;

    if (data->update_timer) {
        source = g_main_context_find_source_by_id(NULL, data->update_timer);
        if (source) {
            g_source_destroy(source);
            data->update_timer = 0;
        }
    }

    next_day_t = day_at_midnight(now_t, 1);
    diff = difftime(next_day_t, now_t);
    data->next_wakeup_reason = "current astro data update";
    SCHEDULE_WAKEUP_COMPARE(data->astro_update->next,
                            "astro data download");
    SCHEDULE_WAKEUP_COMPARE(data->weather_update->next,
                            "weather data download");
    SCHEDULE_WAKEUP_COMPARE(data->conditions_update->next,
                            "current conditions update");

    /* If astronomical data is unavailable, current conditions update
       will usually handle night/day. */
    if (data->current_astro) {
        if (data->night_time &&
            difftime(data->current_astro->sunrise, now_t) >= 0)
            SCHEDULE_WAKEUP_COMPARE(data->current_astro->sunrise,
                                    "sunrise icon change");
        if (!data->night_time &&
            difftime(data->current_astro->sunset, now_t) >= 0)
            SCHEDULE_WAKEUP_COMPARE(data->current_astro->sunset,
                                    "sunset icon change");
    }

#ifdef HAVE_UPOWER_GLIB
    if (data->upower_on_battery && diff > POWERSAVE_UPDATE_INTERVAL) {
        /* next wakeup time is greater than the power saving check
           interval, so call the update handler earlier to deal with
           cases like system resume events etc. */
        diff = POWERSAVE_UPDATE_INTERVAL;
        data->next_wakeup_reason = "regular check (power saving)";
    } else
#endif
    if (diff > UPDATE_INTERVAL) {
        /* next wakeup time is greater than the standard check
           interval, so call the update handler earlier to deal with
           cases like system resume events etc. */
        diff = UPDATE_INTERVAL;
        data->next_wakeup_reason = "regular check";
    } else if (diff < 0) {
        /* last wakeup time expired, force update immediately */
        diff = 0;
        data->next_wakeup_reason = "forced";
    }

    date = format_date(now_t, "%Y-%m-%d %H:%M:%S", TRUE);
    data->update_timer =
        g_timeout_add_seconds((guint) diff, update_handler, data);
    if (!strcmp(data->next_wakeup_reason, "regular check"))
        weather_debug("[%s]: Running regular check for updates, "
                      "interval %d secs.", date, UPDATE_INTERVAL);
    else {
        weather_dump(weather_dump_plugindata, data);
        weather_debug("[%s]: Next wakeup in %.0f seconds, reason: %s",
                      date, diff, data->next_wakeup_reason);
    }
    g_free(date);
}


GArray *
labels_clear(GArray *array)
{
    if (!array || array->len > 0) {
        if (array)
            g_array_free(array, TRUE);
        array = g_array_new(FALSE, TRUE, sizeof(data_types));
    }
    return array;
}


static void
constrain_to_limits(gint *i,
                    const gint min,
                    const gint max)
{
    g_assert(i != NULL);
    if (G_UNLIKELY(i == NULL))
        return;
    if (*i < min)
        *i = min;
    if (*i > max)
        *i = max;
}


static void
constrain_to_ulimits(guint *i,
                    const guint min,
                    const guint max)
{
    g_assert(i != NULL);
    if (G_UNLIKELY(i == NULL))
        return;
    if (*i < min)
        *i = min;
    if (*i > max)
        *i = max;
}


static void
xfceweather_xfconf_set_intbool (plugin_data *data, gchar* setting, gint value, gboolean is_boolean)
{
    gchar          *property;

    property = g_strconcat (data->property_base, setting, NULL);
    if (is_boolean)
        xfconf_channel_set_bool (data->channel, property, (gboolean) value);
    else
        xfconf_channel_set_int (data->channel, property, value);
    g_free (property);
}


static void
xfceweather_xfconf_set_string (plugin_data *data, gchar* setting, gchar *value)
{
    gchar          *property;

    property = g_strconcat (data->property_base, setting, NULL);
    xfconf_channel_set_string (data->channel, property, value);
    g_free (property);
}


static gboolean
xfceweather_xfconf_get_bool (plugin_data *data, gchar* setting, gboolean fallback)
{
    gchar          *property;
    gboolean            value;

    property = g_strconcat (data->property_base, setting, NULL);
    value = xfconf_channel_get_bool (data->channel, property, fallback);
    g_free (property);

    return value;
}


static gint
xfceweather_xfconf_get_int (plugin_data *data, gchar* setting, gint fallback)
{
    gchar          *property;
    gint            value;

    property = g_strconcat (data->property_base, setting, NULL);
    value = xfconf_channel_get_int (data->channel, property, fallback);
    g_free (property);

    return value;
}


static gchar *
xfceweather_xfconf_get_string (plugin_data *data, gchar* setting)
{
    gchar          *property, *value;

    property = g_strconcat (data->property_base, setting, NULL);
    value = xfconf_channel_get_string (data->channel, property, NULL);
    g_free (property);

    return value;
}


static void
xfceweather_read_config (XfcePanelPlugin *plugin,
                         plugin_data *data)
{
    const gchar *value;
    gchar *property;
    gchar label[10];
    gint label_count = 0, val;

    g_return_if_fail (XFCONF_IS_CHANNEL (data->channel));

    data->location_name = xfceweather_xfconf_get_string (data, SETTING_LOCATION_NAME);
    data->lat = xfceweather_xfconf_get_string (data, SETTING_LATITUDE);
    data->lon = xfceweather_xfconf_get_string (data, SETTING_LONGITUDE);

    data->msl = xfceweather_xfconf_get_int (data, SETTING_MSL, 0);
    constrain_to_limits(&data->msl, -420, 10000);
    data->timezone = xfceweather_xfconf_get_string (data, SETTING_TIMEZONE);
    data->offset = xfceweather_xfconf_get_string (data, SETTING_OFFSET);
    data->geonames_username = xfceweather_xfconf_get_string (data, SETTING_GEONAMES);
    data->cache_file_max_age = xfceweather_xfconf_get_int (data, SETTING_CACHE_MAX_AGE, CACHE_FILE_MAX_AGE);
    data->power_saving = xfceweather_xfconf_get_bool (data, SETTING_POWER_SAVING, TRUE);

    /* Units */
    if (data->units)
        g_slice_free(units_config, data->units);
    data->units = g_slice_new0(units_config);
    data->units->temperature = xfceweather_xfconf_get_int (data, SETTING_TEMPERATURE, CELSIUS);
    data->units->pressure = xfceweather_xfconf_get_int (data, SETTING_PRESSURE, HECTOPASCAL);
    data->units->windspeed = xfceweather_xfconf_get_int (data, SETTING_WINDSPEED, KMH);
    data->units->precipitation = xfceweather_xfconf_get_int (data, SETTING_PRECIPITATION, MILLIMETERS);
    data->units->altitude = xfceweather_xfconf_get_int (data, SETTING_ALTITUDE, METERS);
    data->units->apparent_temperature = xfceweather_xfconf_get_int (data, SETTING_APPARENT_TEMP, STEADMAN);

    data->round = xfceweather_xfconf_get_bool (data, SETTING_ROUND, TRUE);
    data->single_row = xfceweather_xfconf_get_bool (data, SETTING_SINGLE_ROW, TRUE);
    data->tooltip_style = xfceweather_xfconf_get_int (data, SETTING_TOOLTIP_STYLE, TOOLTIP_VERBOSE);

    /* Forecast */
    val = xfceweather_xfconf_get_int (data, SETTING_FC_LAYOUT, FC_LAYOUT_LIST);
    if (val == FC_LAYOUT_CALENDAR || val == FC_LAYOUT_LIST)
        data->forecast_layout = val;
    else
        data->forecast_layout = FC_LAYOUT_LIST;

    data->forecast_days = xfceweather_xfconf_get_int (data, SETTING_FC_DAYS, DEFAULT_FORECAST_DAYS);
    constrain_to_ulimits(&data->forecast_days, 1, MAX_FORECAST_DAYS);

    value = xfceweather_xfconf_get_string (data, SETTING_THEME_DIR);
    if (data->icon_theme)
        icon_theme_free(data->icon_theme);
    data->icon_theme = icon_theme_load(value);

    /* Scrollbox */
    data->show_scrollbox = xfceweather_xfconf_get_bool (data, SETTING_SB_SHOW, TRUE);
    data->scrollbox_use_color = xfceweather_xfconf_get_bool (data, SETTING_SB_USE_COLOR, FALSE);
    data->scrollbox_lines = xfceweather_xfconf_get_int (data, SETTING_SB_LINES, 1);
    constrain_to_ulimits(&data->scrollbox_lines, 1, MAX_SCROLLBOX_LINES);

    value = xfceweather_xfconf_get_string (data, SETTING_SB_FONT);
    if (value) {
        g_free (data->scrollbox_font);
        data->scrollbox_font = g_strdup (value);
    }

    value = xfceweather_xfconf_get_string (data, SETTING_SB_COLOR);
    if (value) {
        gdk_rgba_parse(&(data->scrollbox_color), value);
    }
    data->scrollbox_animate = xfceweather_xfconf_get_bool (data, SETTING_SB_ANIMATE, TRUE);
    gtk_scrollbox_set_animate (GTK_SCROLLBOX(data->scrollbox),
                               data->scrollbox_animate);

    data->labels = labels_clear(data->labels);
    val = 0;
    while (val != -1) {
        g_snprintf(label, 10, "/label%d", label_count++);
        property = g_strconcat (SETTING_LABELS, label, NULL);

        val = xfceweather_xfconf_get_int (data, property, -1);
        if (val >= 0) {
            g_array_append_val (data->labels, val);
        }
        g_free (property);
    }

    weather_debug("Config file read.");
}

static void
xfceweather_write_config (XfcePanelPlugin *plugin,
                          plugin_data *data)
{
    gchar           label[10];
    guint           i;
    gchar          *property;

    g_return_if_fail (XFCONF_IS_CHANNEL (data->channel));

    if (data->location_name)
    {
        xfceweather_xfconf_set_string (data, SETTING_LOCATION_NAME, data->location_name);
    }
    if (data->lat)
    {
        xfceweather_xfconf_set_string (data, SETTING_LATITUDE, data->lat);
    }
    if (data->lon)
    {
        xfceweather_xfconf_set_string (data, SETTING_LONGITUDE, data->lon);
    }

    xfceweather_xfconf_set_intbool (data, SETTING_MSL, data->msl, FALSE);
    xfceweather_xfconf_set_string (data, SETTING_TIMEZONE, data->timezone);
    xfceweather_xfconf_set_string (data, SETTING_OFFSET, data->offset);

    if (data->geonames_username)
    {
        xfceweather_xfconf_set_string (data, SETTING_GEONAMES, data->geonames_username);
    }

    xfceweather_xfconf_set_intbool (data, SETTING_CACHE_MAX_AGE, data->cache_file_max_age, FALSE);
    xfceweather_xfconf_set_intbool (data, SETTING_POWER_SAVING, data->power_saving, TRUE);

    xfceweather_xfconf_set_intbool (data, SETTING_TEMPERATURE, data->units->temperature, FALSE);
    xfceweather_xfconf_set_intbool (data, SETTING_PRESSURE, data->units->pressure, FALSE);
    xfceweather_xfconf_set_intbool (data, SETTING_WINDSPEED, data->units->windspeed, FALSE);
    xfceweather_xfconf_set_intbool (data, SETTING_PRECIPITATION, data->units->precipitation, FALSE);
    xfceweather_xfconf_set_intbool (data, SETTING_ALTITUDE, data->units->altitude, FALSE);
    xfceweather_xfconf_set_intbool (data, SETTING_APPARENT_TEMP, data->units->apparent_temperature, FALSE);

    xfceweather_xfconf_set_intbool (data, SETTING_ROUND, data->round, TRUE);
    xfceweather_xfconf_set_intbool (data, SETTING_SINGLE_ROW, data->single_row, TRUE);

    xfceweather_xfconf_set_intbool (data, SETTING_TOOLTIP_STYLE, data->tooltip_style, FALSE);
    xfceweather_xfconf_set_intbool (data, SETTING_FC_LAYOUT, data->forecast_layout, FALSE);
    xfceweather_xfconf_set_intbool (data, SETTING_FC_DAYS, data->forecast_days, FALSE);


    if (data->icon_theme && data->icon_theme->dir)
    {
        xfceweather_xfconf_set_string (data, SETTING_THEME_DIR, data->icon_theme->dir);
    }

    /* Scrollbox */
    xfceweather_xfconf_set_intbool (data, SETTING_SB_SHOW, data->show_scrollbox, TRUE);
    xfceweather_xfconf_set_intbool (data, SETTING_SB_ANIMATE, data->scrollbox_animate, TRUE);
    xfceweather_xfconf_set_intbool (data, SETTING_SB_LINES, data->scrollbox_lines, FALSE);
    if (data->scrollbox_font)
    {
      xfceweather_xfconf_set_string (data, SETTING_SB_FONT, data->scrollbox_font);
    }
    xfceweather_xfconf_set_string (data, SETTING_SB_COLOR, gdk_rgba_to_string(&(data->scrollbox_color)));
    xfceweather_xfconf_set_intbool (data, SETTING_SB_USE_COLOR, data->scrollbox_use_color, TRUE);

    /* Labels */
    /* Reset all labels prior to storing */
    property = g_strconcat (data->property_base, SETTING_LABELS, NULL);
    xfconf_channel_reset_property (data->channel, property, TRUE);
    g_free (property);

    for (i = 0; i < data->labels->len; i++) {
        g_snprintf(label, 10, "/label%d", i);
        property = g_strconcat (data->property_base, SETTING_LABELS, label, NULL);
        xfconf_channel_set_int (data->channel, property,
                                (gint) g_array_index(data->labels, data_types, i));
        g_free (property);
    }

    weather_debug("Config written.");
}


/*
 * Generate file name for the weather data cache file.
 */
static gchar *
make_cache_filename(plugin_data *data)
{
    gchar *cache_dir, *file;

    if (G_UNLIKELY(data->lat == NULL || data->lon == NULL))
        return NULL;

    cache_dir = get_cache_directory();
    file = g_strdup_printf("%s%sweatherdata_%s_%s_%d",
                           cache_dir, G_DIR_SEPARATOR_S,
                           data->lat, data->lon, data->msl);
    g_free(cache_dir);
    return file;
}


static void
write_cache_file(plugin_data *data)
{
    GString *out;
    xml_weather *wd = data->weatherdata;
    xml_time *timeslice;
    xml_location *loc;
    xml_astro *astro;
    gchar *file, *start, *end, *point, *now, *value;
    gchar *date_format = "%Y-%m-%dT%H:%M:%SZ";
    time_t now_t = time(NULL);
    guint i, j;

    file = make_cache_filename(data);
    if (G_UNLIKELY(file == NULL))
        return;

    out = g_string_sized_new(20480);
    g_string_assign(out, "# xfce4-weather-plugin cache file\n\n[info]\n");
    CACHE_APPEND("location_name=%s\n", data->location_name);
    CACHE_APPEND("lat=%s\n", data->lat);
    CACHE_APPEND("lon=%s\n", data->lon);
    CACHE_APPEND("offset=%s\n", data->offset);
    g_string_append_printf(out, "msl=%d\n", data->msl);
    g_string_append_printf(out, "timeslices=%d\n", wd->timeslices->len);
    if (G_LIKELY(data->weather_update)) {
        value = format_date(data->weather_update->last, date_format, FALSE);
        CACHE_APPEND("last_weather_download=%s\n", value);
        g_free(value);
    }
    if (G_LIKELY(data->astro_update)) {
        value = format_date(data->astro_update->last, date_format, FALSE);
        CACHE_APPEND("last_astro_download=%s\n", value);
        g_free(value);
    }
    now = format_date(now_t, date_format, FALSE);
    CACHE_APPEND("cache_date=%s\n\n", now);
    g_free(now);

    if (data->astrodata) {
        for (i = 0; i < data->astrodata->len; i++) {
            astro = g_array_index(data->astrodata, xml_astro *, i);
            if (G_UNLIKELY(astro == NULL))
                continue;
            value = format_date(astro->day, "%Y-%m-%d", TRUE);
            start = format_date(astro->sunrise, date_format, TRUE);
            end = format_date(astro->sunset, date_format, TRUE);
            g_string_append_printf(out, "[astrodata%d]\n", i);
            CACHE_APPEND("day=%s\n", value);
            CACHE_APPEND("sunrise=%s\n", start);
            CACHE_APPEND("sunset=%s\n", end);
            CACHE_APPEND("sun_never_rises=%s\n",
                         astro->sun_never_rises ? "true" : "false");
            CACHE_APPEND("sun_never_sets=%s\n",
                         astro->sun_never_sets ? "true" : "false");
            CACHE_APPEND("solarnoon_elevation=%e\n", astro->solarnoon_elevation);
            CACHE_APPEND("solarmidnight_elevation=%e\n", astro->solarmidnight_elevation);
            g_free(value);
            g_free(start);
            g_free(end);

            start = format_date(astro->moonrise, date_format, TRUE);
            end = format_date(astro->moonset, date_format, TRUE);
            CACHE_APPEND("moonrise=%s\n", start);
            CACHE_APPEND("moonset=%s\n", end);
            CACHE_APPEND("moon_never_rises=%s\n",
                         astro->moon_never_rises ? "true" : "false");
            CACHE_APPEND("moon_never_sets=%s\n",
                         astro->moon_never_sets ? "true" : "false");
            CACHE_APPEND("moon_phase=%s\n", astro->moon_phase);
            g_free(start);
            g_free(end);

            g_string_append(out, "\n");
        }
    } else
        g_string_append(out, "\n");

    for (i = 0; i < wd->timeslices->len; i++) {
        timeslice = g_array_index(wd->timeslices, xml_time *, i);
        if (G_UNLIKELY(timeslice == NULL || timeslice->location == NULL))
            continue;
        loc = timeslice->location;
        start = format_date(timeslice->start, date_format, FALSE);
        end = format_date(timeslice->end, date_format, FALSE);
        point = format_date(timeslice->point, date_format, FALSE);
        g_string_append_printf(out, "[timeslice%d]\n", i);
        CACHE_APPEND("start=%s\n", start);
        CACHE_APPEND("end=%s\n", end);
        CACHE_APPEND("point=%s\n", point);
        CACHE_APPEND("altitude=%s\n", loc->altitude);
        CACHE_APPEND("latitude=%s\n", loc->latitude);
        CACHE_APPEND("longitude=%s\n", loc->longitude);
        CACHE_APPEND("temperature_value=%s\n", loc->temperature_value);
        CACHE_APPEND("temperature_unit=%s\n", loc->temperature_unit);
        CACHE_APPEND("wind_dir_deg=%s\n", loc->wind_dir_deg);
        CACHE_APPEND("wind_dir_name=%s\n", loc->wind_dir_name);
        CACHE_APPEND("wind_speed_mps=%s\n", loc->wind_speed_mps);
        CACHE_APPEND("wind_speed_beaufort=%s\n", loc->wind_speed_beaufort);
        CACHE_APPEND("humidity_value=%s\n", loc->humidity_value);
        CACHE_APPEND("humidity_unit=%s\n", loc->humidity_unit);
        CACHE_APPEND("pressure_value=%s\n", loc->pressure_value);
        CACHE_APPEND("pressure_unit=%s\n", loc->pressure_unit);
        g_free(start);
        g_free(end);
        g_free(point);
        for (j = 0; j < CLOUDS_PERC_NUM; j++)
            if (loc->clouds_percent[j])
                g_string_append_printf(out, "clouds_percent_%d=%s\n", j,
                                       loc->clouds_percent[j]);
        CACHE_APPEND("fog_percent=%s\n", loc->fog_percent);
        CACHE_APPEND("precipitation_value=%s\n", loc->precipitation_value);
        CACHE_APPEND("precipitation_unit=%s\n", loc->precipitation_unit);
        if (loc->symbol)
            g_string_append_printf(out, "symbol_id=%d\nsymbol=%s\n",
                                   loc->symbol_id, loc->symbol);
        g_string_append(out, "\n");
    }

    if (!g_file_set_contents(file, out->str, -1, NULL))
        g_warning("Error writing cache file %s!", file);
    else
        weather_debug("Cache file %s has been written.", file);

    g_string_free(out, TRUE);
    g_free(file);
}


static void
read_cache_file(plugin_data *data)
{
    GKeyFile *keyfile;
    GError *err = NULL;
    xml_weather *wd;
    xml_time *timeslice = NULL;
    xml_location *loc = NULL;
    xml_astro *astro = NULL;
    time_t cache_date_t;
    gchar *file, *locname = NULL, *lat = NULL, *lon = NULL, *group = NULL, *offset = NULL;
    gchar *timestring;
    gint msl, num_timeslices = 0, i, j;

    g_assert(data != NULL);
    if (G_UNLIKELY(data == NULL))
        return;
    wd = data->weatherdata;

    if (G_UNLIKELY(data->lat == NULL || data->lon == NULL))
        return;

    file = make_cache_filename(data);
    if (G_UNLIKELY(file == NULL))
        return;

    keyfile = g_key_file_new();
    if (!g_key_file_load_from_file(keyfile, file, G_KEY_FILE_NONE, NULL)) {
        weather_debug("Could not read cache file %s.", file);
        g_free(file);
        return;
    }
    weather_debug("Reading cache file %s.", file);
    g_free(file);

    group = "info";
    if (!g_key_file_has_group(keyfile, group)) {
        CACHE_FREE_VARS();
        return;
    }

    /* check all needed values are present and match the current parameters */
    locname = g_key_file_get_string(keyfile, group, "location_name", NULL);
    lat = g_key_file_get_string(keyfile, group, "lat", NULL);
    lon = g_key_file_get_string(keyfile, group, "lon", NULL);
    offset = g_key_file_get_string(keyfile, group, "offset", NULL);
    if (locname == NULL || lat == NULL || lon == NULL || offset == NULL) {
        CACHE_FREE_VARS();
        weather_debug("Required values are missing in the cache file, "
                      "reading cache file aborted.");
        return;
    }
    msl = g_key_file_get_integer(keyfile, group, "msl", &err);
    if (!err)
        num_timeslices = g_key_file_get_integer(keyfile, group,
                                                "timeslices", &err);
    if (err || strcmp(lat, data->lat) || strcmp(lon, data->lon) ||
        strcmp(offset, data->offset) || msl != data->msl ||
        num_timeslices < 1) {
        CACHE_FREE_VARS();
        weather_debug("The required values are not present in the cache file "
                      "or do not match the current plugin data. Reading "
                      "cache file aborted.");
        return;
    }
    /* read cache creation date and check if cache file is not too old */
    CACHE_READ_STRING(timestring, "cache_date");
    cache_date_t = parse_timestring(timestring, NULL, FALSE);
    g_free(timestring);
    if (difftime(time(NULL), cache_date_t) > data->cache_file_max_age) {
        weather_debug("Cache file is too old and will not be used.");
        CACHE_FREE_VARS();
        return;
    }
    if (G_LIKELY(data->weather_update)) {
        CACHE_READ_STRING(timestring, "last_weather_download");
        data->weather_update->last = parse_timestring(timestring, NULL, FALSE);
        data->weather_update->next =
            calc_next_download_time(data->weather_update,
                                    data->weather_update->last);
        g_free(timestring);
    }
    if (G_LIKELY(data->astro_update)) {
        CACHE_READ_STRING(timestring, "last_astro_download");
        data->astro_update->last = parse_timestring(timestring, NULL, FALSE);
        data->astro_update->next =
            calc_next_download_time(data->astro_update,
                                    data->astro_update->last);
        g_free(timestring);
    }

    /* read cached astrodata if available and up-to-date */
    i = 0;
    group = g_strdup_printf("astrodata%d", i);
    while (g_key_file_has_group(keyfile, group)) {

        astro = g_slice_new0(xml_astro);
        if (G_UNLIKELY(astro == NULL))
            break;

        CACHE_READ_STRING(timestring, "day");
        astro->day = parse_timestring(timestring, "%Y-%m-%d", TRUE);
        weather_debug("cached astrodata for day=%s\n",
                      format_date(astro->day, NULL, TRUE));
        g_free(timestring);
        CACHE_READ_STRING(timestring, "sunrise");
        astro->sunrise = parse_timestring(timestring, NULL, TRUE);
        g_free(timestring);
        CACHE_READ_STRING(timestring, "sunset");
        astro->sunset = parse_timestring(timestring, NULL, TRUE);
        g_free(timestring);
        astro->sun_never_rises =
            g_key_file_get_boolean(keyfile, group, "sun_never_rises", NULL);
        astro->sun_never_sets =
            g_key_file_get_boolean(keyfile, group, "sun_never_sets", NULL);
        astro->solarnoon_elevation =
            g_key_file_get_double(keyfile, group, "solarnoon_elevation", NULL);
        astro->solarmidnight_elevation =
            g_key_file_get_double(keyfile, group, "solarmidnight_elevation", NULL);

        CACHE_READ_STRING(timestring, "moonrise");
        astro->moonrise = parse_timestring(timestring, NULL, TRUE);
        g_free(timestring);
        CACHE_READ_STRING(timestring, "moonset");
        astro->moonset = parse_timestring(timestring, NULL, TRUE);
        g_free(timestring);
        CACHE_READ_STRING(astro->moon_phase, "moon_phase");
        astro->moon_never_rises =
            g_key_file_get_boolean(keyfile, group, "moon_never_rises", NULL);
        astro->moon_never_sets =
            g_key_file_get_boolean(keyfile, group, "moon_never_sets", NULL);

        merge_astro(data->astrodata, astro);
        xml_astro_free(astro);

        g_free(group);
        group = g_strdup_printf("astrodata%d", ++i);
    }

    /* downloads the astrodata of the day if necessary */
    if (G_LIKELY(get_astro_data_for_day(data->astrodata, data->forecast_days)))
        weather_debug("Reusing cached astrodata instead of downloading it.");
    else {
        weather_debug("Astrodata of the day not in cache. Downloading scheduled in 30s.");
        data->astro_update->attempt = 0;
        data->astro_update->next += 30;
        schedule_next_wakeup(data);
    }
    g_free(group);
    group = NULL;

    /* parse available timeslices */
    for (i = 0; i < num_timeslices; i++) {
        group = g_strdup_printf("timeslice%d", i);
        if (!g_key_file_has_group(keyfile, group)) {
            weather_debug("Group %s not found, continuing with next.", group);
            g_free(group);
            continue;
        }

        timeslice = make_timeslice();
        if (G_UNLIKELY(timeslice == NULL)) {
            g_free(group);
            continue;
        }

        /* parse time strings (start, end, point) */
        CACHE_READ_STRING(timestring, "start");
        timeslice->start = parse_timestring(timestring, NULL, FALSE);
        g_free(timestring);
        CACHE_READ_STRING(timestring, "end");
        timeslice->end = parse_timestring(timestring, NULL, FALSE);
        g_free(timestring);
        CACHE_READ_STRING(timestring, "point");
        timeslice->point = parse_timestring(timestring, NULL, FALSE);
        g_free(timestring);

        /* parse location data */
        loc = timeslice->location;
        CACHE_READ_STRING(loc->altitude, "altitude");
        CACHE_READ_STRING(loc->latitude, "latitude");
        CACHE_READ_STRING(loc->longitude, "longitude");
        CACHE_READ_STRING(loc->temperature_value, "temperature_value");
        CACHE_READ_STRING(loc->temperature_unit, "temperature_unit");
        CACHE_READ_STRING(loc->wind_dir_name, "wind_dir_name");
        CACHE_READ_STRING(loc->wind_dir_deg, "wind_dir_deg");
        CACHE_READ_STRING(loc->wind_speed_mps, "wind_speed_mps");
        CACHE_READ_STRING(loc->wind_speed_beaufort, "wind_speed_beaufort");
        CACHE_READ_STRING(loc->humidity_value, "humidity_value");
        CACHE_READ_STRING(loc->humidity_unit, "humidity_unit");
        CACHE_READ_STRING(loc->pressure_value, "pressure_value");
        CACHE_READ_STRING(loc->pressure_unit, "pressure_unit");

        for (j = 0; j < CLOUDS_PERC_NUM; j++) {
            gchar *key = g_strdup_printf("clouds_percent_%d", j);
            if (g_key_file_has_key(keyfile, group, key, NULL))
                loc->clouds_percent[j] =
                    g_key_file_get_string(keyfile, group, key, NULL);
            g_free(key);
        }

        CACHE_READ_STRING(loc->fog_percent, "fog_percent");
        CACHE_READ_STRING(loc->precipitation_value, "precipitation_value");
        CACHE_READ_STRING(loc->precipitation_unit, "precipitation_unit");
        CACHE_READ_STRING(loc->symbol, "symbol");
        if (loc->symbol &&
            g_key_file_has_key(keyfile, group, "symbol_id", NULL))
            loc->symbol_id =
                g_key_file_get_integer(keyfile, group, "symbol_id", NULL);

        merge_timeslice(wd, timeslice);
        xml_time_free(timeslice);
    }
    CACHE_FREE_VARS();
    weather_debug("Reading cache file complete.");
}


void
update_weatherdata_with_reset(plugin_data *data)
{
    time_t now_t;
    GSource *source;

    weather_debug("Update weatherdata with reset.");
    g_assert(data != NULL);
    if (G_UNLIKELY(data == NULL))
        return;

    if (data->update_timer) {
        source = g_main_context_find_source_by_id(NULL, data->update_timer);
        if (source) {
            g_source_destroy(source);
            data->update_timer = 0;
        }
    }

    /* set location timezone */
    update_timezone(data);

    /* set the offset of timezone */
    update_offset(data);

    /* clear update times */
    init_update_infos(data);

    /* clear existing weather data */
    if (data->weatherdata) {
        xml_weather_free(data->weatherdata);
        data->weatherdata = make_weather_data();
    }

    /* clear existing astronomical data */
    if (data->astrodata) {
        astrodata_free(data->astrodata);
        data->astrodata = g_array_sized_new(FALSE, TRUE, sizeof(xml_astro *), 30);
    }

    /* update GUI to display NODATA */
    update_icon(data);
    update_scrollbox(data, TRUE);

    /* make use of previously saved data */
    read_cache_file(data);

    /* schedule downloads immediately */
    time(&now_t);
    data->weather_update->next = now_t;
    data->astro_update->next = now_t;
    schedule_next_wakeup(data);

    weather_debug("Updated weatherdata with reset.");
}


/* This is only a dummy handler, the clicks will be processed by
   cb_click. This is needed to synchronise the toggled state with
   the existence of the summary window. */
static gboolean
cb_toggled(GtkToggleButton *button,
           gpointer user_data)
{
    plugin_data *data = (plugin_data *) user_data;
    g_signal_handlers_block_by_func(data->button, cb_toggled, data);
    if (data->summary_window)
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->button), TRUE);
    else
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->button), FALSE);
    g_signal_handlers_unblock_by_func(data->button, cb_toggled, data);
    return FALSE;
}


static void
close_summary(GtkWidget *widget,
              gpointer *user_data)
{
    plugin_data *data = (plugin_data *) user_data;
    GSource *source;

    if (data->summary_details)
        summary_details_free(data->summary_details);
    data->summary_details = NULL;
    data->summary_window = NULL;

    /* deactivate the summary window update timer */
    if (data->summary_update_timer) {
        source = g_main_context_find_source_by_id(NULL,
                                                  data->summary_update_timer);
        if (source) {
            g_source_destroy(source);
            data->summary_update_timer = 0;
        }
    }

    /* sync toggle button state */
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->button), FALSE);
}


void
forecast_click(GtkWidget *widget,
               gpointer user_data)
{
    plugin_data *data = user_data;

    if (data->summary_window != NULL)
        gtk_widget_destroy(data->summary_window);
    else {
        /* sync toggle button state */
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(data->button), TRUE);

        data->summary_window = create_summary_window(data);

        /* start the summary window subtitle update timer */
        update_summary_subtitle(data);

        g_signal_connect(G_OBJECT(data->summary_window), "destroy",
                         G_CALLBACK(close_summary), data);
        gtk_widget_show_all(data->summary_window);
    }
}


static gboolean
cb_click(GtkWidget *widget,
         GdkEventButton *event,
         gpointer user_data)
{
    plugin_data *data = (plugin_data *) user_data;

    if (event->button == 1)
        forecast_click(widget, user_data);
    else if (event->button == 2)
        update_weatherdata_with_reset(data);
    return FALSE;
}


static gboolean
cb_scroll(GtkWidget *widget,
          GdkEventScroll *event,
          gpointer user_data)
{
    plugin_data *data = (plugin_data *) user_data;

    if (event->direction == GDK_SCROLL_UP)
        gtk_scrollbox_next_label(GTK_SCROLLBOX(data->scrollbox));
    else if (event->direction == GDK_SCROLL_DOWN)
        gtk_scrollbox_prev_label(GTK_SCROLLBOX(data->scrollbox));

    return FALSE;
}


static void
mi_click(GtkWidget *widget,
         gpointer user_data)
{
    plugin_data *data = (plugin_data *) user_data;

    update_weatherdata_with_reset(data);
}

static void
proxy_auth(SoupSession *session,
           SoupMessage *msg,
           SoupAuth *auth,
           gboolean retrying,
           gpointer user_data)
{
    SoupURI *soup_proxy_uri;
    const gchar *proxy_uri;

    if (!retrying) {
        if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
            proxy_uri = g_getenv("HTTP_PROXY");
            if (!proxy_uri)
                proxy_uri = g_getenv("http_proxy");
            if (proxy_uri) {
                soup_proxy_uri = soup_uri_new(proxy_uri);
                soup_auth_authenticate(auth,
                                       soup_uri_get_user(soup_proxy_uri),
                                       soup_uri_get_password(soup_proxy_uri));
                soup_uri_free(soup_proxy_uri);
            }
        }
    }
}


#ifdef HAVE_UPOWER_GLIB
static void
#if UP_CHECK_VERSION(0, 99, 0)
upower_changed_cb(UpClient *client,
                  GParamSpec *pspec,
                  plugin_data *data)
#else /* UP_CHECK_VERSION < 0.99 */
upower_changed_cb(UpClient *client,
                  plugin_data *data)
#endif /* UP_CHECK_VERSION */
{
    gboolean on_battery;

    if (G_UNLIKELY(data->upower == NULL) || !data->power_saving)
        return;

    on_battery = data->upower_on_battery;
    weather_debug("upower old status: on_battery=%d", on_battery);

    data->upower_on_battery = up_client_get_on_battery(client);
    weather_debug("upower new status: on_battery=%d", data->upower_on_battery);

    if (data->upower_on_battery != on_battery) {
        if (data->summary_window)
            update_summary_subtitle(data);

        update_icon(data);
        update_scrollbox(data, FALSE);
        schedule_next_wakeup(data);
    }
}
#endif /* HAVE_UPOWER_GLIB */


static void
xfceweather_dialog_response(GtkWidget *dlg,
                            gint response,
                            xfceweather_dialog *dialog)
{
    plugin_data *data = (plugin_data *) dialog->pd;
    icon_theme *theme;
    gboolean result;
    guint i;

    if (response == GTK_RESPONSE_HELP) {
        /* show help */
        result = g_spawn_command_line_async("exo-open --launch WebBrowser "
                                            PLUGIN_WEBSITE, NULL);

        if (G_UNLIKELY(result == FALSE))
            g_warning("Unable to open the following url: %s",
                      PLUGIN_WEBSITE);
    } else {
        /* free stuff used in config dialog */
        gtk_widget_destroy(dlg);
        g_object_unref(dialog->builder);
        gtk_list_store_clear(dialog->model_datatypes);
        for (i = 0; i < dialog->icon_themes->len; i++) {
            theme = g_array_index(dialog->icon_themes, icon_theme *, i);
            icon_theme_free(theme);
        }
        g_array_free(dialog->icon_themes, FALSE);
        if (dialog->timer_id != 0) {
            g_source_remove(dialog->timer_id);
        }
        g_slice_free(xfceweather_dialog, dialog);

        xfce_panel_plugin_unblock_menu(data->plugin);

        weather_debug("Write configuration");
        xfceweather_write_config(data->plugin, data);
        weather_dump(weather_dump_plugindata, data);
    }
}


static void
xfceweather_create_options(XfcePanelPlugin *plugin,
                           plugin_data *data)
{
    GtkWidget *dlg;
    GtkBuilder *builder;
    xfceweather_dialog *dialog;
    GError *error = NULL;
    gint response;
    time_t now_t;
    guint previous_forecast_days;

    xfce_panel_plugin_block_menu(plugin);

    if (xfce_titled_dialog_get_type () == 0)
        return;

    builder = gtk_builder_new ();
    if (gtk_builder_add_from_string (builder, weather_config_ui,
                                     weather_config_ui_length, &error) != 0)
    {
        dlg = GTK_WIDGET (gtk_builder_get_object (builder, "dialog"));
        gtk_window_set_transient_for (GTK_WINDOW (dlg),
                                      GTK_WINDOW (gtk_widget_get_toplevel
                                       (GTK_WIDGET(plugin))));

        dialog = create_config_dialog(data, builder);
        previous_forecast_days = data->forecast_days;

        gtk_widget_show_all (GTK_WIDGET (dlg));
        response = gtk_dialog_run(GTK_DIALOG (dlg));
        xfceweather_dialog_response(dlg, response, dialog);

        weather_debug("forecast_days configuration changes? previous %d ---> current %d\n",
                      previous_forecast_days, data->forecast_days);
        /* due to probable configuration changes schedule astro data downloads */
        if ((previous_forecast_days < data->forecast_days) && !data->astro_update->started) {
            time(&now_t);
            data->astro_update->next = now_t + 1;
            weather_debug("due to probable configuration changes: astro data update scheduled! \n");
            schedule_next_wakeup(data);
        }
    } else {
        g_warning ("Failed to load dialog: %s", error->message);
    }
}


static gchar *
weather_get_tooltip_text(const plugin_data *data)
{
    xml_time *conditions;
    gchar *text, *sym, *alt, *temp;
    gchar *windspeed, *windbeau, *winddir, *winddeg;
    gchar *pressure, *humidity, *precipitation;
    gchar *fog, *cloudiness, *sunval = NULL, *value;
    gchar *point, *interval_start, *interval_end, *sunrise, *sunset = NULL;
    const gchar *unit;

    conditions = get_current_conditions(data->weatherdata);
    if (G_UNLIKELY(conditions == NULL)) {
        text = g_strdup(_("Short-term forecast data unavailable."));
        return text;
    }

    /* times for forecast and point data */
    point = format_date(conditions->point, "%H:%M", TRUE);
    interval_start = format_date(conditions->start, "%H:%M", TRUE);
    interval_end = format_date(conditions->end, "%H:%M", TRUE);

    /* use sunrise and sunset times if available */
    if (data->current_astro)
        if (data->current_astro->sun_never_rises && data->current_astro->sun_never_sets) {
            if (data->current_astro->solarmidnight_elevation > 0)
                sunval = g_strdup(_("The sun never sets today."));
            else if (data->current_astro->solarnoon_elevation <= 0)
                sunval = g_strdup(_("The sun never rises today."));
        }
        else if (data->current_astro->sun_never_rises){
            sunset = format_date(data->current_astro->sunset,
                                 "%H:%M:%S", FALSE);
            sunval =
                g_strdup_printf(_("The sun never rises and sets at %s."),
                                 sunset);
        }
        else if (data->current_astro->sun_never_sets){
            sunrise = format_date(data->current_astro->sunrise,
                                 "%H:%M:%S", FALSE);
            sunval =
                g_strdup_printf(_("The sun rises at %s and never sets."),
                                 sunrise);
        } else {
            sunrise = format_date(data->current_astro->sunrise,
                                  "%H:%M:%S", TRUE);
            sunset = format_date(data->current_astro->sunset,
                                 "%H:%M:%S", TRUE);
            sunval =
                g_strdup_printf(_("The sun rises at %s and sets at %s."),
                                sunrise, sunset);
            g_free(sunrise);
            g_free(sunset);
        }
    else
        sunval = g_strdup("");

    sym = get_data(conditions, data->units, SYMBOL, FALSE, data->night_time);
    DATA_AND_UNIT(alt, ALTITUDE);
    DATA_AND_UNIT(temp, TEMPERATURE);
    DATA_AND_UNIT(windspeed, WIND_SPEED);
    DATA_AND_UNIT(windbeau, WIND_BEAUFORT);
    DATA_AND_UNIT(winddir, WIND_DIRECTION);
    DATA_AND_UNIT(winddeg, WIND_DIRECTION_DEG);
    DATA_AND_UNIT(pressure, PRESSURE);
    DATA_AND_UNIT(humidity, HUMIDITY);
    DATA_AND_UNIT(precipitation, PRECIPITATION);
    DATA_AND_UNIT(fog, FOG);
    DATA_AND_UNIT(cloudiness, CLOUDINESS);

    switch (data->tooltip_style) {
    case TOOLTIP_SIMPLE:
        text = g_markup_printf_escaped
            /*
             * TRANSLATORS: This is the simple tooltip. For a bigger challenge,
             * look at the verbose tooltip style further below ;-)
             */
            (_("<b><span size=\"large\">%s</span></b> "
               "<span size=\"medium\">(%s)</span>\n"
               "<b><span size=\"large\">%s</span></b>\n\n"
               "<b>Temperature:</b> %s\n"
               "<b>Wind:</b> %s from %s\n"
               "<b>Pressure:</b> %s\n"
               "<b>Humidity:</b> %s\n"),
             data->location_name, alt,
             translate_desc(sym, data->night_time),
             temp, windspeed, winddir, pressure, humidity);
        break;

    case TOOLTIP_VERBOSE:
    default:
        text = g_markup_printf_escaped
            /*
             * TRANSLATORS: Re-arrange and align at will, optionally using
             * abbreviations for labels if desired or necessary. Just take
             * into account the possible size constraints, the centered
             * vertical alignment of the icon - which unfortunately cannot
             * be changed easily - and try to make it compact and look
             * good! The missing space after "%son the ..." is intentional,
             * it is included in the %s.
             */
            (_("<b><span size=\"large\">%s</span></b> "
               "<span size=\"medium\">(%s)</span>\n"
               "<b><span size=\"large\">%s</span></b>\n"
               "<span size=\"smaller\">"
               "from %s to %s, with %s of precipitation</span>\n\n"
               "<b>Temperature:</b> %s\t\t"
               "<span size=\"smaller\">(values at %s)</span>\n"
               "<b>Wind:</b> %s (%son the Beaufort scale) from %s(%s)\n"
               "<b>Pressure:</b> %s    <b>Humidity:</b> %s\n"
               "<b>Fog:</b> %s    <b>Cloudiness:</b> %s\n\n"
               "<span size=\"smaller\">%s</span>"),
             data->location_name, alt,
             translate_desc(sym, data->night_time),
             interval_start, interval_end,
             precipitation,
             temp, point,
             windspeed, windbeau, winddir, winddeg,
             pressure, humidity,
             fog, cloudiness,
             sunval);
        break;
    }
    g_free(sunval);
    g_free(sym);
    g_free(alt);
    g_free(temp);
    g_free(interval_start);
    g_free(interval_end);
    g_free(point);
    g_free(windspeed);
    g_free(windbeau);
    g_free(winddir);
    g_free(winddeg);
    g_free(pressure);
    g_free(humidity);
    g_free(precipitation);
    g_free(fog);
    g_free(cloudiness);
    return text;
}


static gboolean
weather_get_tooltip_cb(GtkWidget *widget,
                       gint x,
                       gint y,
                       gboolean keyboard_mode,
                       GtkTooltip *tooltip,
                       plugin_data *data)
{
    GtkWidget *box, *label, *image;
    gchar *markup_text;

    if (data->weatherdata == NULL)
        label = gtk_label_new(_("Cannot update weather data"));
    else {
        markup_text = weather_get_tooltip_text(data);
        label = gtk_label_new(markup_text);
        gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
        g_free(markup_text);
    }

    image = gtk_image_new_from_surface(data->tooltip_icon);
    box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_box_pack_start(GTK_BOX(box), image, TRUE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(box), label, TRUE, FALSE, 0);
    gtk_widget_show_all(box);
    gtk_tooltip_set_custom(tooltip, box);

    return TRUE;
}


static plugin_data *
xfceweather_create_control(XfcePanelPlugin *plugin)
{
    plugin_data *data = g_slice_new0(plugin_data);
    SoupURI *soup_proxy_uri;
    const gchar *proxy_uri;
    const gchar *proxy_user;
    GtkWidget *refresh;
    cairo_surface_t *icon = NULL;
    data_types lbl;
    gint scale_factor;

    /* Initialize with sane default values */
    data->plugin = plugin;
#ifdef HAVE_UPOWER_GLIB
    data->upower = up_client_new();
    if (data->upower)
        data->upower_on_battery = up_client_get_on_battery(data->upower);
#endif
    data->units = g_slice_new0(units_config);
    data->weatherdata = make_weather_data();
    data->astrodata = g_array_sized_new(FALSE, TRUE, sizeof(xml_astro *), 30);
    data->cache_file_max_age = CACHE_FILE_MAX_AGE;
    data->show_scrollbox = TRUE;
    data->scrollbox_lines = 1;
    data->scrollbox_animate = TRUE;
    data->tooltip_style = TOOLTIP_VERBOSE;
    data->forecast_layout = FC_LAYOUT_LIST;
    data->forecast_days = DEFAULT_FORECAST_DAYS;
    data->round = TRUE;
    data->single_row = TRUE;
    data->power_saving = TRUE;

    /* Setup Message parse infos */
    if (data->msg_parse)
        g_slice_free(parse_info, data->msg_parse);
    data->msg_parse = g_slice_new0(parse_info);
 
    /* Setup update infos */
    init_update_infos(data);
    data->next_wakeup = time(NULL);

    /* Setup session for HTTP connections */
    data->session = soup_session_new();
    g_object_set(data->session, SOUP_SESSION_USER_AGENT,
                 PACKAGE_NAME "-" PACKAGE_VERSION, NULL);
    g_object_set(data->session, SOUP_SESSION_TIMEOUT,
                 CONN_TIMEOUT, NULL);

    /* Set the proxy URI from environment */
    proxy_uri = g_getenv("HTTP_PROXY");
    if (!proxy_uri)
        proxy_uri = g_getenv("http_proxy");
    if (proxy_uri) {
        soup_proxy_uri = soup_uri_new(proxy_uri);
        g_object_set(data->session, SOUP_SESSION_PROXY_URI,
                     soup_proxy_uri, NULL);

        /* check if uri contains authentication info */
        proxy_user = soup_uri_get_user(soup_proxy_uri);
        if (proxy_user && strlen(proxy_user) > 0) {
            g_signal_connect(G_OBJECT(data->session), "authenticate",
                             G_CALLBACK(proxy_auth), NULL);
        }

        soup_uri_free(soup_proxy_uri);
    }

    data->scrollbox = gtk_scrollbox_new();

    data->panel_size = xfce_panel_plugin_get_size(plugin);
    data->panel_rows = xfce_panel_plugin_get_nrows(plugin);
    data->icon_theme = icon_theme_load(NULL);
    scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(plugin));
    icon = get_icon(data->icon_theme, NULL, 16, scale_factor, FALSE);
    if (G_LIKELY(icon)) {
        data->iconimage = gtk_image_new_from_surface(icon);
        cairo_surface_destroy(icon);
    } else
        g_warning("No default icon theme? "
                  "This should not happen, plugin will crash!");

    data->labels = g_array_new(FALSE, TRUE, sizeof(data_types));

    /* create panel toggle button which will contain all other widgets */
    data->button = xfce_panel_create_toggle_button();
    gtk_container_add(GTK_CONTAINER(plugin), data->button);
    xfce_panel_plugin_add_action_widget(plugin, data->button);
    gtk_widget_show(data->button);

    /* create alignment box that can be easily adapted to the panel
       orientation */
    data->alignbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_container_add(GTK_CONTAINER(data->button), data->alignbox);

    /* add widgets to alignment box */
    data->vbox_center_scrollbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    gtk_widget_set_halign (GTK_WIDGET (data->iconimage), 1);
    gtk_widget_set_valign (GTK_WIDGET (data->iconimage), 0.5);
    gtk_box_pack_start(GTK_BOX(data->alignbox),
                       data->iconimage, TRUE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(data->vbox_center_scrollbox),
                       data->scrollbox, TRUE, TRUE, 0);
    gtk_box_pack_start(GTK_BOX(data->alignbox),
                       data->vbox_center_scrollbox, TRUE, TRUE, 0);
    gtk_widget_show_all(data->alignbox);

    /* hook up events for the button */
    g_object_set(G_OBJECT(data->button), "has-tooltip", TRUE, NULL);
    g_signal_connect(G_OBJECT(data->button), "query-tooltip",
                     G_CALLBACK(weather_get_tooltip_cb), data);
    g_signal_connect(G_OBJECT(data->button), "button-press-event",
                     G_CALLBACK(cb_click), data);
    g_signal_connect(G_OBJECT(data->button), "scroll-event",
                     G_CALLBACK(cb_scroll), data);
    g_signal_connect(G_OBJECT(data->button), "toggled",
                     G_CALLBACK(cb_toggled), data);
    gtk_widget_add_events(data->scrollbox, GDK_BUTTON_PRESS_MASK);

    /* add refresh button to right click menu, for people who missed
       the middle mouse click feature */
    refresh = gtk_menu_item_new_with_mnemonic(_("Re_fresh"));
    gtk_widget_show(refresh);
    g_signal_connect(G_OBJECT(refresh), "activate",
                     G_CALLBACK(mi_click), data);
    xfce_panel_plugin_menu_insert_item(plugin, GTK_MENU_ITEM(refresh));

    /* assign to tempval because g_array_append_val() is using & operator */
    lbl = TEMPERATURE;
    g_array_append_val(data->labels, lbl);
    lbl = WIND_DIRECTION;
    g_array_append_val(data->labels, lbl);
    lbl = WIND_SPEED;
    g_array_append_val(data->labels, lbl);

    weather_debug("Plugin widgets set up and ready.");
    return data;
}


static void
xfceweather_free(XfcePanelPlugin *plugin,
                 plugin_data *data)
{
    GSource *source;

    weather_debug("Freeing plugin data.");
    g_assert(data != NULL);

    if (data->update_timer) {
        source = g_main_context_find_source_by_id(NULL, data->update_timer);
        if (source) {
            g_source_destroy(source);
            data->update_timer = 0;
        }
    }

#ifdef HAVE_UPOWER_GLIB
    if (data->upower) {
        g_object_unref(data->upower);
        data->upower = NULL;
    }
#endif

    if (data->weatherdata)
        xml_weather_free(data->weatherdata);

    if (data->units)
        g_slice_free(units_config, data->units);

    xmlCleanupParser();

    /* free chars */
    g_free(data->lat);
    g_free(data->lon);
    g_free(data->location_name);
    g_free(data->scrollbox_font);
    g_free(data->timezone);
    g_free(data->offset);
    g_free(data->timezone_initial);
    g_free(data->geonames_username);

    /* free update infos */
    g_slice_free(update_info, data->weather_update);
    g_slice_free(update_info, data->astro_update);
    g_slice_free(update_info, data->conditions_update);

    /* free current data */
    data->current_astro = NULL;

    /* free arrays */
    g_array_free(data->labels, TRUE);
    astrodata_free(data->astrodata);

    /* free icon theme */
    icon_theme_free(data->icon_theme);

    g_slice_free(plugin_data, data);
    xfconf_shutdown ();
}

static gboolean
xfceweather_set_size(XfcePanelPlugin *panel,
                     gint size,
                     plugin_data *data)
{
    gint icon_size;

    data->panel_rows = xfce_panel_plugin_get_nrows(panel);
    if (data->single_row)
        size /= data->panel_rows;
    data->panel_size = size;

    icon_size = xfce_panel_plugin_get_icon_size (panel);
    data->icon_size = icon_size;

    update_icon(data);
    update_scrollbox(data, FALSE);

    weather_dump(weather_dump_plugindata, data);

    /* we handled the size */
    return TRUE;
}


gboolean
xfceweather_set_mode(XfcePanelPlugin *panel,
                     XfcePanelPluginMode mode,
                     plugin_data *data)
{
    data->panel_orientation = xfce_panel_plugin_get_mode(panel);

    if (data->panel_orientation == XFCE_PANEL_PLUGIN_MODE_HORIZONTAL ||
        (data->panel_orientation == XFCE_PANEL_PLUGIN_MODE_DESKBAR &&
         data->single_row)) {
        gtk_orientable_set_orientation(GTK_ORIENTABLE(data->alignbox),
                                   GTK_ORIENTATION_HORIZONTAL);
        gtk_widget_set_halign (GTK_WIDGET (data->iconimage), 1);
        gtk_widget_set_valign (GTK_WIDGET (data->iconimage), 0.5);
    } else {
        gtk_orientable_set_orientation(GTK_ORIENTABLE(data->alignbox),
                                   GTK_ORIENTATION_VERTICAL);
        gtk_widget_set_halign (GTK_WIDGET (data->iconimage), 0.5);
        gtk_widget_set_valign (GTK_WIDGET (data->iconimage), 1);
    }

    if (mode == XFCE_PANEL_PLUGIN_MODE_DESKBAR)
        xfce_panel_plugin_set_small(panel, FALSE);
    else
        xfce_panel_plugin_set_small(panel, data->single_row);

    gtk_scrollbox_set_orientation(GTK_SCROLLBOX(data->scrollbox),
                                  (mode != XFCE_PANEL_PLUGIN_MODE_VERTICAL)
                                  ? GTK_ORIENTATION_HORIZONTAL
                                  : GTK_ORIENTATION_VERTICAL);

    xfceweather_set_size(panel, xfce_panel_plugin_get_size(panel), data);

    weather_dump(weather_dump_plugindata, data);

    /* we handled the orientation */
    return TRUE;
}


static void
xfceweather_show_about(XfcePanelPlugin *plugin,
                       plugin_data *data)
{
    const gchar *auth[] = {
        "Bob Schlärmann <weatherplugin@atreidis.nl.eu.org>",
        "Benedikt Meurer <benny@xfce.org>",
        "Jasper Huijsmans <jasper@xfce.org>",
        "Masse Nicolas <masse_nicolas@yahoo.fr>",
        "Nick Schermer <nick@xfce.org>",
        "Colin Leroy <colin@colino.net>",
        "Harald Judt <h.judt@gmx.at>",
        "Simon Steinbeiß <simon@xfce.org>",
        NULL };
    gtk_show_about_dialog
        (NULL,
         "logo-icon-name", "org.xfce.panel.weather",
         "license", xfce_get_license_text(XFCE_LICENSE_TEXT_GPL),
         "version", PACKAGE_VERSION,
         "program-name", PACKAGE_NAME,
         "comments", _("Show weather conditions and forecasts"),
         "website", PLUGIN_WEBSITE,
         "copyright", "Copyright \302\251 2004-2024 The Xfce development team",
         "authors", auth,
         NULL);
}


static void
weather_construct(XfcePanelPlugin *plugin)
{
    plugin_data *data;
    const gchar *panel_debug_env;

    /* Enable debug level logging if PANEL_DEBUG contains G_LOG_DOMAIN */
    panel_debug_env = g_getenv("PANEL_DEBUG");
    if (panel_debug_env && strstr(panel_debug_env, G_LOG_DOMAIN))
        debug_mode = TRUE;
    weather_debug_init(G_LOG_DOMAIN, debug_mode);
    weather_debug("weather plugin version " VERSION " starting up");

    xfce_textdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR, "UTF-8");
    data = xfceweather_create_control(plugin);

    if (xfconf_init (NULL))
        data->channel = xfconf_channel_get ("xfce4-panel");
    else
    {
        g_warning ("Could not initialize xfconf.");
        return;
    }

    data->property_base = xfce_panel_plugin_get_property_base (plugin);

    /* save initial timezone so we can reset it later */
    data->timezone_initial = g_strdup(g_getenv("TZ"));

    data->tooltip_icon = NULL;
    xfceweather_read_config(plugin, data);
    update_timezone(data);
    update_offset(data);
    read_cache_file(data);
    update_current_conditions(data, TRUE);
    scrollbox_set_visible(data);
    gtk_scrollbox_set_fontname(GTK_SCROLLBOX(data->scrollbox),
                               data->scrollbox_font);
    if (data->scrollbox_use_color)
        gtk_scrollbox_set_color(GTK_SCROLLBOX(data->scrollbox),
                                data->scrollbox_color);

    xfceweather_set_mode(plugin, xfce_panel_plugin_get_mode(plugin), data);
    xfceweather_set_size(plugin, data->panel_size, data);

    g_signal_connect(G_OBJECT(plugin), "free-data",
                     G_CALLBACK(xfceweather_free), data);
    g_signal_connect(G_OBJECT(plugin), "save",
                     G_CALLBACK(xfceweather_write_config), data);
    g_signal_connect(G_OBJECT(plugin), "size-changed",
                     G_CALLBACK(xfceweather_set_size), data);
    g_signal_connect(G_OBJECT(plugin), "mode-changed",
                     G_CALLBACK(xfceweather_set_mode), data);
    xfce_panel_plugin_menu_show_configure(plugin);
    g_signal_connect(G_OBJECT(plugin), "configure-plugin",
                     G_CALLBACK(xfceweather_create_options), data);

    xfce_panel_plugin_menu_show_about(plugin);
    g_signal_connect(G_OBJECT(plugin), "about",
                     G_CALLBACK(xfceweather_show_about), data);

#ifdef HAVE_UPOWER_GLIB
    if (data->upower) {
#if UP_CHECK_VERSION(0, 99, 0)
        g_signal_connect (data->upower, "notify",
                          G_CALLBACK(upower_changed_cb), data);
#else /* UP_CHECK_VERSION < 0.99 */
        g_signal_connect (data->upower, "changed",
                          G_CALLBACK(upower_changed_cb), data);
#endif /* UP_CHECK_VERSION */
    }
#endif /* HAVE_UPOWER_GLIB */

    weather_dump(weather_dump_plugindata, data);
}

XFCE_PANEL_PLUGIN_REGISTER(weather_construct)
