/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
 * Copyright (C) 2022 Canonical Ltd.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */

/* Canonical Ubuntu Pro page {{{1 */

#define PAGE_ID "UbuntuPro"
#define CHECK_ICON "/org/gnome/initial-setup/checkmark.svg"
#define FAIL_ICON  "/org/gnome/initial-setup/fail.svg"
#define LOGO       "/org/gnome/initial-setup/ubuntu-pro.svg"
#define LOGO_DARK  "/org/gnome/initial-setup/ubuntu-pro-dark.svg"

#include "config.h"
#include "gis-ubuntupro-page.h"
#include "ubuntupro-resources.h"
#include "utils.h"

#include <glib/gi18n.h>
#include <gio/gio.h>
#include <polkit/polkit.h>
#include <json-glib/json-glib.h>
#include <time.h>
#include <pthread.h>

/* For debugging faster, flip this to be able to skip to 3rd page
 * without needing to attach to Ubuntu Pro first. */
static gboolean SuccessfullyAttached = FALSE;

typedef enum {
  NONE = 0,
  SUCCESS,
  FAIL,
  EXPIRED,
} status_t;

struct _GisUbuntuProPagePrivate {
  GtkWidget *offer_page;
  GtkWidget *attach_page;
  GtkWidget *services_page;
  GtkWidget *stack;

  enum {
      OFFER_PAGE,
      ATTACH_PAGE,
      SERVICES_PAGE,
  } current_page;
};
typedef struct _GisUbuntuProPagePrivate GisUbuntuProPagePrivate;

struct _GisUbuntuProOfferPagePrivate {
  GtkWidget *ubuntu_pro_logo;
  GtkWidget *enable_pro_select;
  GtkWidget *skip_pro_select;
  GtkWidget *offline_warning;
  GtkWidget *pro_status_image;
  gint      network_cb;
};
typedef struct _GisUbuntuProOfferPagePrivate GisUbuntuProOfferPagePrivate;

struct _GisUbuntuProAttachPagePrivate {
  GtkWidget *ubuntu_pro_logo;
  GtkWidget *pin_label;
  GtkWidget *token_field;
  GtkWidget *token_status;
  GtkWidget *token_spinner;
  GtkWidget *pin_spinner;
  GtkWidget *token_status_icon;
  GtkWidget *pin_hint;
  GtkWidget *pin_status;
  GtkWidget *pin_status_icon;
  GtkWidget *token_radio;
  GtkWidget *magic_radio;
  /* This being here is a hack that makes changing the 2nd page's button
   * from Skip to Next once the machine is attached possible. */
  GisUbuntuProPage *main_page;

  gboolean complete;
  gboolean magic_token_polling;
  gboolean attaching;
  gint64 expired_by;
  pthread_t poll_id;
  enum active_radio_t {
    MAGIC,
    TOKEN,
  } active_radio;
  gchar *contract_token;
  gchar *token;
};
typedef struct _GisUbuntuProAttachPagePrivate GisUbuntuProAttachPagePrivate;

struct _GisUbuntuProServicesPagePrivate {
  GtkWidget *enabled_services;
  GtkWidget *enabled_services_header;
  GtkWidget *available_services;
  GtkWidget *available_services_header;
  GtkWidget *contract_name;
  GtkWidget *checkmark;
};
typedef struct _GisUbuntuProServicesPagePrivate GisUbuntuProServicesPagePrivate;

struct _RestJSONResponse {
    gint64 expiresIn;
    gchar *token;
    gchar *code;
    gchar *contract_token;
};

typedef struct _RestJSONResponse RestJSONResponse;
static void ua_attach(const gchar *, GisUbuntuProAttachPagePrivate *);
static gboolean magic_parser(gchar*, RestJSONResponse*);
static gboolean gis_ubuntupro_page_apply (GisPage*, GCancellable*);

G_DEFINE_TYPE_WITH_PRIVATE (GisUbuntuProPage, gis_ubuntupro_page, GIS_TYPE_PAGE);
G_DEFINE_TYPE_WITH_PRIVATE (GisUbuntuProOfferPage, gis_ubuntupro_offer_page, ADW_TYPE_BIN);
G_DEFINE_TYPE_WITH_PRIVATE (GisUbuntuProAttachPage, gis_ubuntupro_attach_page, ADW_TYPE_BIN);
G_DEFINE_TYPE_WITH_PRIVATE (GisUbuntuProServicesPage, gis_ubuntupro_services_page, ADW_TYPE_BIN);

static void
map_scalable_image(const gchar *path, GtkPicture *widget, int x, int y)
{
  g_autoptr(GError) error = NULL;
  GdkPixbuf *pixbuf;
  pixbuf = gdk_pixbuf_new_from_resource_at_scale (path, x, y, TRUE, &error);
  if (error) {
    g_warning("Unable to create pixbuf: %s\n", error->message);
  }

  if (pixbuf != NULL) {
    gtk_picture_set_pixbuf(widget, pixbuf);
  } else {
    g_warning("Unable to load pixbuf: %s\n", error->message);
  }
}

/* The user may go back any number of pages during attachment, so we can't
 * blindly skip to the services list page.
 * Furthermore, after attachment the user can again navigate back to any page
 * and then hit the attach page again.
 */
static void
consider_going_to_next_page(GisUbuntuProAttachPagePrivate *priv)
{
  static gboolean never_called = TRUE;
  GisUbuntuProPage *page = GIS_UBUNTUPRO_PAGE(priv->main_page);
  GisUbuntuProPagePrivate *main_priv = gis_ubuntupro_page_get_instance_private (page);

  if (never_called && main_priv->current_page == ATTACH_PAGE) {
    gis_page_set_complete(GIS_PAGE(priv->main_page), TRUE);
    gis_ubuntupro_page_apply(GIS_PAGE(priv->main_page), NULL);
  }
  never_called = FALSE;
}

static void
update_gui(GisUbuntuProAttachPagePrivate *priv, status_t status)
{
  GtkWidget *label, *icon, *spinner;
  gchar *hint = _("Enter this code on <a href=\"https://ubuntu.com/pro/attach\">ubuntu.com/pro/attach</a>");
  gboolean ready_to_attach;

  if (priv->active_radio == MAGIC) {
    icon = priv->pin_status_icon;
    label = priv->pin_status;
    spinner = priv->pin_spinner;
    ready_to_attach = SuccessfullyAttached ||
        (priv->contract_token && !priv->attaching);
  } else {
    icon = priv->token_status_icon;
    label = priv->token_status;
    spinner = priv->token_spinner;
    ready_to_attach = SuccessfullyAttached || (!priv->attaching &&
        gtk_entry_get_text_length(GTK_ENTRY(priv->token_field)) > 0);
  }
  gis_page_set_complete(GIS_PAGE(priv->main_page), ready_to_attach);

  if (priv->attaching) {
    gtk_spinner_start (GTK_SPINNER (spinner));
  } else {
    gtk_spinner_stop (GTK_SPINNER (spinner));
  }

  gtk_widget_set_sensitive (priv->token_radio, !priv->attaching);
  gtk_widget_set_sensitive (priv->magic_radio, !priv->attaching);
  gtk_widget_set_sensitive (priv->token_field, !priv->attaching &&
                            priv->active_radio == TOKEN);
  /* I don't know why this is needed, but otherwise even though the entry is
   * sensitive, the blinking cursor doesn't appear and one cannot type in it */
  if (gtk_widget_is_sensitive(priv->token_field))
    gtk_entry_grab_focus_without_selecting (GTK_ENTRY (priv->token_field));

  if (SuccessfullyAttached) {
    consider_going_to_next_page(priv);
    status = SUCCESS;
  }
  switch (status) {
  case SUCCESS:
    gtk_label_set_markup(GTK_LABEL(label), g_strconcat(
        "<span foreground=\"green\"><b>", _("Valid token"), "</b></span>", NULL
    ));
    gtk_image_set_from_resource(GTK_IMAGE(icon), CHECK_ICON);
    gtk_widget_set_sensitive(GTK_WIDGET(priv->token_field), FALSE);
    gtk_widget_set_sensitive(GTK_WIDGET(priv->magic_radio), FALSE);
    gtk_widget_set_sensitive(GTK_WIDGET(priv->token_radio), FALSE);
    break;
  case FAIL:
    gtk_label_set_markup(GTK_LABEL(label), g_strconcat(
        "<span foreground=\"red\"><b>", _("Invalid token"), "</b></span>", NULL
    ));
    gtk_image_set_from_resource(GTK_IMAGE(icon), FAIL_ICON);
    break;
  case EXPIRED:
    hint = "Click the button to generate a new code.";
    gtk_label_set_markup(GTK_LABEL(label), _("Code expired"));
    gtk_image_set_from_resource(GTK_IMAGE(icon), FAIL_ICON);
    break;
  default:
    break;
  }
  gtk_label_set_markup(GTK_LABEL(priv->pin_hint), hint);
  gtk_widget_set_visible(GTK_WIDGET(priv->token_status_icon), FALSE);
  gtk_widget_set_visible(GTK_WIDGET(priv->token_status), FALSE);
  gtk_widget_set_visible(GTK_WIDGET(priv->pin_status_icon), FALSE);
  gtk_widget_set_visible(GTK_WIDGET(priv->pin_status), FALSE);
  gtk_widget_set_visible(GTK_WIDGET(icon), status);
  gtk_widget_set_visible(GTK_WIDGET(label), status);
}

static void
network_status_changed (GNetworkMonitor *monitor,
                        gboolean         available,
                        gpointer         user_data)
{
  GisUbuntuProOfferPagePrivate *priv = user_data;

  if (!available) {
    gtk_widget_set_sensitive (priv->enable_pro_select, FALSE);
    gtk_widget_show (GTK_WIDGET (priv->offline_warning));
    gtk_widget_show (GTK_WIDGET (priv->pro_status_image));
    gtk_check_button_set_active (GTK_CHECK_BUTTON (priv->skip_pro_select), TRUE);
    gtk_image_set_from_icon_name (GTK_IMAGE(priv->pro_status_image), "dialog-warning");
  }
  else {
    gtk_widget_set_sensitive (priv->enable_pro_select, TRUE);
    gtk_widget_hide (GTK_WIDGET (priv->offline_warning));
    gtk_widget_hide (GTK_WIDGET (priv->pro_status_image));
  }
}

static void
gis_ubuntupro_offer_page_dispose (GObject *object)
{
  GisUbuntuProOfferPage *offer_page = GIS_UBUNTUPRO_OFFER_PAGE (object);
  GisUbuntuProOfferPagePrivate *offer_priv = gis_ubuntupro_offer_page_get_instance_private (offer_page);

  GNetworkMonitor *network_monitor = g_network_monitor_get_default ();
  g_signal_handler_disconnect(network_monitor, offer_priv->network_cb);

  G_OBJECT_CLASS (gis_ubuntupro_offer_page_parent_class)->dispose (object);
}

static void
gis_ubuntupro_page_dispose (GObject *object)
{
  G_OBJECT_CLASS (gis_ubuntupro_page_parent_class)->dispose (object);
}

static void
gis_ubuntupro_page_constructed (GObject *object)
{
  GisUbuntuProPage *page = GIS_UBUNTUPRO_PAGE (object);
  GisUbuntuProPagePrivate *priv = gis_ubuntupro_page_get_instance_private (page);
  GisUbuntuProOfferPage *offer_page = GIS_UBUNTUPRO_OFFER_PAGE (priv->offer_page);
  GisUbuntuProOfferPagePrivate *offer_priv = gis_ubuntupro_offer_page_get_instance_private (offer_page);
  GisUbuntuProAttachPage *attach_page = GIS_UBUNTUPRO_ATTACH_PAGE (priv->attach_page);
  GisUbuntuProAttachPagePrivate *attach_priv = gis_ubuntupro_attach_page_get_instance_private (attach_page);
  GisUbuntuProServicesPage *services_page = GIS_UBUNTUPRO_SERVICES_PAGE(priv->services_page);
  GisUbuntuProServicesPagePrivate *services_priv = gis_ubuntupro_services_page_get_instance_private (services_page);

  gchar *logo = is_dark_theme(GTK_WIDGET(page)) ? LOGO_DARK: LOGO;
  map_scalable_image(logo, GTK_PICTURE(attach_priv->ubuntu_pro_logo), -1, 66);
  map_scalable_image(logo, GTK_PICTURE(offer_priv->ubuntu_pro_logo), -1, 66);
  map_scalable_image(CHECK_ICON, GTK_PICTURE(services_priv->checkmark), 30, -1);
  // Needed so the picture is shown inside the GtkFixed (god knows why!)
  gtk_widget_set_size_request(services_priv->checkmark, 30, 30);

  G_OBJECT_CLASS (gis_ubuntupro_page_parent_class)->constructed (object);

  GNetworkMonitor *network_monitor = g_network_monitor_get_default ();
  offer_priv->network_cb = g_signal_connect (network_monitor, "network-changed",
                    G_CALLBACK (network_status_changed), offer_priv);

  if (!g_network_monitor_get_network_available (network_monitor)) {
    gtk_widget_set_sensitive (offer_priv->enable_pro_select, FALSE);
    gtk_widget_show (GTK_WIDGET (offer_priv->offline_warning));
    gtk_widget_show (GTK_WIDGET (offer_priv->pro_status_image));
  }

  /* Initializate priv values */
  attach_priv->magic_token_polling = FALSE;
  attach_priv->active_radio = MAGIC;
  attach_priv->expired_by = 0;
  attach_priv->contract_token = NULL;
  attach_priv->main_page = page;
  priv->current_page = OFFER_PAGE;

  gis_page_set_skippable (GIS_PAGE (page), FALSE);
  gis_page_set_complete (GIS_PAGE (page), TRUE);
  gtk_widget_show (GTK_WIDGET (page));
}

static void
gis_ubuntupro_page_locale_changed (GisPage *page)
{
  gis_page_set_title (GIS_PAGE (page), _("Ubuntu Pro"));
}

static gboolean
poll_token_attach (GisUbuntuProAttachPagePrivate *priv)
{
  gchar *std_out, *std_err, *cmd;
  RestJSONResponse resp; /* relevant response fields */
  g_autoptr(GError) error = NULL;

  cmd = g_strconcat("/bin/ua api u.pro.attach.magic.wait.v1 --args magic_token=", priv->token, NULL);
  if (!g_spawn_command_line_sync(cmd, &std_out, &std_err, NULL, &error)) {
    g_warning ("Failed to request magic token: %s", error->message);
  }
  else if (!magic_parser(std_out, &resp)) {
    g_warning("Couldn't parse response.");
  }
  else if (resp.contract_token != NULL) {
    priv->contract_token = g_strdup(resp.contract_token);
    return TRUE;
  }
  return FALSE;
}

static void*
poll_magic_token (void *data)
{
  GisUbuntuProAttachPagePrivate *priv = gis_ubuntupro_attach_page_get_instance_private (data);
  status_t status;

  if (priv->active_radio != MAGIC) {
    pthread_exit(NULL);
  }
  priv->magic_token_polling = TRUE;
  
  if (time(0) > priv->expired_by) {
    status = EXPIRED;
    update_gui(priv, status);
    priv->magic_token_polling = FALSE;
  }
  gboolean token_received = poll_token_attach(priv);
  if (token_received) {
    priv->magic_token_polling = FALSE;
    status = SUCCESS;
    update_gui(priv, status);
  }
  pthread_exit(NULL);
}

static gboolean
magic_parser(gchar *ptr,              /* pointer to actual response */
              RestJSONResponse *resp) /* relevant response fields */
{
    g_autoptr(JsonParser) parser = json_parser_new();
    JsonNode *root;
    g_autoptr(GError) error = NULL;

    json_parser_load_from_data(parser, ptr, -1, &error);
    if (error) {
        g_warning("Couldn't parse magic token JSON; %s\n", error->message);
        g_print("%s\n", ptr);
        return FALSE;
    }
    root = json_parser_get_root(parser);
    if (!JSON_NODE_HOLDS_OBJECT(root)) {
        g_warning("Invalid magic token JSON\n");
        return FALSE;
    }

    JsonObject  *data, *attributes;
    JsonObject *response = json_node_get_object(root);
    data = json_object_get_object_member(response, "data");
    attributes = json_object_get_object_member(data, "attributes");

    resp->expiresIn = json_object_has_member(attributes, "expires_in") ?
        json_object_get_int_member(attributes, "expires_in") :
        0;
    resp->token = json_object_has_member(attributes, "token") ?
        g_strdup(json_object_get_string_member(attributes, "token")) :
        NULL;
    resp->code = json_object_has_member(attributes, "user_code") ?
        g_strdup(json_object_get_string_member(attributes, "user_code")) :
        NULL;
    resp->contract_token = json_object_has_member(attributes, "contract_token") ?
        g_strdup(json_object_get_string_member(attributes, "contract_token")) :
        NULL;

    return TRUE;
}

static void
display_ua_services(GisUbuntuProServicesPagePrivate *priv)
{
    g_autoptr(JsonParser) parser = json_parser_new();
    JsonNode    *root_node;
    JsonObject  *root, *services, *contract;
    JsonArray   *services_array;
    g_autoptr(GError) error = NULL;
    guint       i, n_services;
    const char  *status, *description, *available, *contract_name;
    gsize       len;
    gchar *str;

    if (!parse_ua_status(&str, &len)) {
        return;
    }
    g_autofree gchar *available_str = (gchar *)g_malloc0(len);
    g_autofree gchar *enabled_str = (gchar *)g_malloc0(len);

    json_parser_load_from_data(parser, str, strlen(str), &error);
    g_free(str);
    if (error) {
        g_warning("Couldn't parse magic token JSON; %s\n", error->message);
    }
    root_node = json_parser_get_root(parser);
    if (!JSON_NODE_HOLDS_OBJECT(root_node)) {
        g_warning("Invalid magic token JSON\n");
    }

    root = json_node_get_object(root_node);

    contract = json_object_get_object_member(root, "contract");
    contract_name = json_object_get_string_member(contract, "name");
    gtk_label_set_text(GTK_LABEL(priv->contract_name), contract_name);

    services_array = json_object_get_array_member(root, "services");
    n_services = json_array_get_length(services_array);

    /* Get services description, status and availability */
    for (i = 0; i < n_services; i++) {
        services = json_array_get_object_element(services_array, i);
        if (
            json_object_has_member(services, "description") &&
            json_object_has_member(services, "status") &&
            json_object_has_member(services, "available")
        ) {
            description = json_object_get_string_member(services, "description");
            status = json_object_get_string_member(services, "status");
            available = json_object_get_string_member(services, "available");
            if (strcmp(status, "enabled") == 0) {
              itemize(enabled_str, description, len);
            } else if (strcmp(available, "yes") == 0) {
              itemize(available_str, description, len);
            }
        }
    }

    /* Display enabled and disabled but available services */
    if (enabled_str == NULL) {
      g_object_unref(GTK_WIDGET(priv->enabled_services_header));
    } else {
      gtk_label_set_text(GTK_LABEL(priv->enabled_services), enabled_str);
    }
    if (available_str == NULL) {
      g_object_unref(GTK_WIDGET(priv->available_services_header));
    } else {
      gtk_label_set_text(GTK_LABEL(priv->available_services), available_str);
    }
}

static void
request_magic_attach (GisUbuntuProAttachPage *page)
{
  GisUbuntuProAttachPagePrivate *priv = gis_ubuntupro_attach_page_get_instance_private (page);
  gchar *std_out, *std_err;
  g_autoptr(GError) error = NULL;
  int rc;

  if (priv->magic_token_polling) {
    return;
  }

  if (!g_spawn_command_line_sync("/bin/ua api u.pro.attach.magic.initiate.v1",
                                 &std_out, &std_err, NULL, &error)) {
    g_warning ("Failed to request magic token: %s", error->message);
    return;
  }

  RestJSONResponse resp;
  if (!magic_parser(std_out, &resp)) {
    g_print("%s\n",std_out);
    g_warning("Couldn't parse response.\n");
    return;
  }
  gtk_label_set_text (GTK_LABEL (priv->pin_label), resp.code);
  priv->expired_by = resp.expiresIn + time(0);
  priv->token = g_strdup(resp.token);
  priv->magic_token_polling = TRUE;
  rc = pthread_create(&priv->poll_id, NULL, poll_magic_token, (void *)page);
  if (rc) {
    g_warning ("Couldn't create PIN polling thread");
    return;
  }
  update_gui(priv, 0);
  g_free(resp.token);
  g_free(resp.code);
}

static void
on_ua_attach_requested (GObject *source,
                        GAsyncResult *result,
                        gpointer user_data)
{
  GisUbuntuProAttachPagePrivate *priv = user_data;
  g_autoptr(GError) error = NULL;
  g_autoptr(GVariant) retval = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), result, &error);
  status_t status;

  if (retval == NULL) {
    g_warning ("Failed to attach token: %s", error->message);
    status = FAIL;
  } else {
    SuccessfullyAttached = TRUE;
    status = SUCCESS;
    pthread_cancel(priv->poll_id);
    gis_page_set_complete (GIS_PAGE(priv->main_page), TRUE);
    g_free(priv->contract_token);
    g_free(priv->token);
  }
  priv->attaching = FALSE;
  update_gui(priv, status);
}

static void
ua_attach(const gchar *token, GisUbuntuProAttachPagePrivate *priv)
{
  GDBusConnection *bus;
  g_autoptr(GError) error = NULL;
  status_t        status = NONE;

  if (priv->attaching) {
    return;
  }

  bus = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
  if (bus == NULL) {
      g_warning("Failed to get system bus: %s", error->message);
  } else {
    priv->attaching = TRUE;
    update_gui(priv, status);

    g_dbus_connection_call(bus,
      "com.canonical.UbuntuAdvantage",
      "/com/canonical/UbuntuAdvantage/Manager",
      "com.canonical.UbuntuAdvantage.Manager",
      "Attach",
      g_variant_new("(s)", token),
      G_VARIANT_TYPE("()"),
      G_DBUS_CALL_FLAGS_NONE,
      543210, /* I have observed that -1, the default timeout, is not enough. */
      NULL,
      on_ua_attach_requested,
      priv);
  }
}

void
on_token_typing (GtkButton *button, GisUbuntuProAttachPage *page)
{
  GisUbuntuProAttachPagePrivate *priv = gis_ubuntupro_attach_page_get_instance_private (page);

  gis_page_set_complete (
      GIS_PAGE(priv->main_page),
      gtk_entry_get_text_length(GTK_ENTRY(priv->token_field)) > 0
  );
}

void
request_token_attach (GtkButton *button, GisUbuntuProAttachPage *page)
{
  GisUbuntuProAttachPagePrivate *priv = gis_ubuntupro_attach_page_get_instance_private (page);

  const gchar *token = gtk_editable_get_text(GTK_EDITABLE(priv->token_field));
  ua_attach(token, priv);
}

void
on_magic_clicked (GtkButton *button, GisUbuntuProAttachPage *page)
{
  GisUbuntuProAttachPagePrivate *priv = gis_ubuntupro_attach_page_get_instance_private (page);
  priv->active_radio = MAGIC;

  request_magic_attach(page);
}

void
on_radio_toggled (GtkButton *button, GisUbuntuProAttachPage *page)
{
  GisUbuntuProAttachPagePrivate *priv = gis_ubuntupro_attach_page_get_instance_private (page);
  status_t status = NONE;

  priv->active_radio = gtk_check_button_get_active(GTK_CHECK_BUTTON(priv->token_radio)) ? TOKEN : MAGIC;
  update_gui(priv, status);
}

/* Callback for the Previous button */
static gboolean
gis_ubuntupro_page_go_back (GisPage *gis_page)
{
  GisUbuntuProPage *page = GIS_UBUNTUPRO_PAGE (gis_page);
  GisUbuntuProPagePrivate *priv = gis_ubuntupro_page_get_instance_private (page);
  GisUbuntuProOfferPage *offer_page = GIS_UBUNTUPRO_OFFER_PAGE (priv->offer_page);
  GisUbuntuProAttachPage *attach_page = GIS_UBUNTUPRO_ATTACH_PAGE (priv->attach_page);

  switch (priv->current_page) {
  case OFFER_PAGE:
    return FALSE;
  case ATTACH_PAGE:
    gis_page_set_complete (gis_page, TRUE);
    gtk_stack_set_visible_child (GTK_STACK (priv->stack), GTK_WIDGET(offer_page));
    priv->current_page = OFFER_PAGE;
    break;
  case SERVICES_PAGE:
    gtk_stack_set_visible_child (GTK_STACK (priv->stack), GTK_WIDGET(attach_page));
    priv->current_page = ATTACH_PAGE;
    break;
  }
  return TRUE;
}

/* Callback for the Next button */
static gboolean
gis_ubuntupro_page_apply (GisPage      *gis_page,
                          GCancellable *cancellable)
{
  GisUbuntuProPage *page = GIS_UBUNTUPRO_PAGE (gis_page);
  GisUbuntuProPagePrivate *priv = gis_ubuntupro_page_get_instance_private (page);
  GisUbuntuProOfferPage *offer_page = GIS_UBUNTUPRO_OFFER_PAGE (priv->offer_page);
  GisUbuntuProOfferPagePrivate *offer_priv = gis_ubuntupro_offer_page_get_instance_private (offer_page);
  GisUbuntuProAttachPage *attach_page = GIS_UBUNTUPRO_ATTACH_PAGE (priv->attach_page);
  GisUbuntuProAttachPagePrivate *attach_priv = gis_ubuntupro_attach_page_get_instance_private (attach_page);
  switch (priv->current_page) {
  case OFFER_PAGE:
    if (gtk_check_button_get_active(GTK_CHECK_BUTTON (offer_priv->skip_pro_select))) {
      return FALSE;
    }
    /* Request magic token already if didn't yet and advance to next local page */
    if (!SuccessfullyAttached && !attach_priv->attaching) {
      request_magic_attach(attach_page);
    }
    gis_page_apply_complete (GIS_PAGE (page), FALSE);
    update_gui(attach_priv, 0);
    gtk_stack_set_visible_child (GTK_STACK (priv->stack), GTK_WIDGET(attach_page));
    priv->current_page = ATTACH_PAGE;
    break;
  case ATTACH_PAGE:
    if (SuccessfullyAttached) {
      GisUbuntuProServicesPage *services_page = GIS_UBUNTUPRO_SERVICES_PAGE(priv->services_page);
      display_ua_services(gis_ubuntupro_services_page_get_instance_private (services_page));
      gtk_stack_set_visible_child (GTK_STACK (priv->stack), priv->services_page);
      gis_page_apply_complete (GIS_PAGE (page), FALSE);
    } else {
      if (attach_priv->active_radio == MAGIC) {
        ua_attach(attach_priv->contract_token, attach_priv);
      } else {
        request_token_attach(NULL, attach_page);
      }
      gis_page_apply_complete (GIS_PAGE (page), FALSE);
      return FALSE;
    }
    priv->current_page = SERVICES_PAGE;
    break;
  case SERVICES_PAGE:
    return FALSE;
  }
  return TRUE;
}

static void
gis_ubuntupro_page_class_init (GisUbuntuProPageClass *klass)
{
  GisPageClass *page_class = GIS_PAGE_CLASS (klass);
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-ubuntupro-page.ui");

  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProPage, offer_page);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProPage, attach_page);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProPage, services_page);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProPage, stack);
  gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (klass), request_token_attach);
  gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (klass), request_magic_attach);
  gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (klass), on_radio_toggled);
  gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (klass), on_magic_clicked);

  page_class->page_id = PAGE_ID;
  page_class->locale_changed = gis_ubuntupro_page_locale_changed;
  page_class->apply = gis_ubuntupro_page_apply;
  page_class->go_back = gis_ubuntupro_page_go_back;
  object_class->constructed = gis_ubuntupro_page_constructed;
  object_class->dispose = gis_ubuntupro_page_dispose;
}
static void
gis_ubuntupro_offer_page_class_init (GisUbuntuProOfferPageClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-ubuntupro-offer-page.ui");

  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProOfferPage, ubuntu_pro_logo);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProOfferPage, enable_pro_select);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProOfferPage, skip_pro_select);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProOfferPage, offline_warning);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProOfferPage, pro_status_image);

  object_class->dispose = gis_ubuntupro_offer_page_dispose;
}

static void
gis_ubuntupro_attach_page_class_init (GisUbuntuProAttachPageClass *klass)
{
  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-ubuntupro-attach-page.ui");

  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProAttachPage, ubuntu_pro_logo);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProAttachPage, pin_label);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProAttachPage, token_field);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProAttachPage, token_status);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProAttachPage, token_spinner);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProAttachPage, token_status_icon);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProAttachPage, pin_status);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProAttachPage, pin_spinner);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProAttachPage, pin_status_icon);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProAttachPage, pin_hint);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProAttachPage, token_radio);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProAttachPage, magic_radio);
}
static void
gis_ubuntupro_services_page_class_init (GisUbuntuProServicesPageClass *klass)
{
  gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/initial-setup/gis-ubuntupro-services-page.ui");

  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProServicesPage, enabled_services);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProServicesPage, enabled_services_header);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProServicesPage, available_services);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProServicesPage, available_services_header);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProServicesPage, contract_name);
  gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), GisUbuntuProServicesPage, checkmark);
}

static void
gis_ubuntupro_page_init (GisUbuntuProPage *page)
{
  g_resources_register (ubuntupro_get_resource ());

  /* Magic that makes stuff compile */
  gis_ubuntupro_offer_page_get_type ();
  gis_ubuntupro_attach_page_get_type ();
  gis_ubuntupro_services_page_get_type ();

  gtk_widget_init_template (GTK_WIDGET (page));
}
static void
gis_ubuntupro_offer_page_init (GisUbuntuProOfferPage *page)
{
  g_resources_register (ubuntupro_get_resource ());

  gtk_widget_init_template (GTK_WIDGET (page));
}
static void
gis_ubuntupro_attach_page_init (GisUbuntuProAttachPage *page)
{
  g_resources_register (ubuntupro_get_resource ());

  gtk_widget_init_template (GTK_WIDGET (page));
}
static void
gis_ubuntupro_services_page_init (GisUbuntuProServicesPage *page)
{
  g_resources_register (ubuntupro_get_resource ());

  gtk_widget_init_template (GTK_WIDGET (page));
}

GisPage *
gis_prepare_ubuntu_pro_page (GisDriver *driver)
{
  gboolean attached;
  if (!get_ubuntu_advantage_attached (&attached)) {
      g_warning("Couldn't fetch the Ubuntu Pro status.\n");
      return NULL;
  }
  if (attached) {
      g_warning("Ubuntu Pro is already attached. Skipping Ubuntu Pro pages.\n");
      return NULL;
  }

  if (!is_ubuntupro_supported ())
    return NULL;

  return g_object_new (GIS_TYPE_UBUNTUPRO_PAGE,
                       "driver", driver,
                       NULL);
}
