/* gcc $(pkg-config --cflags --libs webkit2gtk-4.0 gtk+-3.0
   json-glib-1.0 libxml-2.0 libsoup-2.4 gio-2.0 glib-2.0) download-rhel.c */

#include <gtk/gtk.h>
#include <json-glib/json-glib.h>
#include <libsoup/soup.h>
#include <libxml/parser.h>
#include <webkit2/webkit2.h>

typedef struct _Data Data;

struct _Data
{
  GFile *authentication_uri;
  GInputStream *istream;
  GtkWidget *window;
  SoupSession *session;
};

static void
data_clear (Data *data)
{
  g_object_unref (data->authentication_uri);
  g_object_unref (data->istream);

  if (data->window != NULL)
    g_object_remove_weak_pointer (G_OBJECT (data->window), (gpointer *) &data->window);

  g_object_unref (data->session);
}

static void
output_stream_splice_download_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  GError *error;
  GOutputStream *ostream = G_OUTPUT_STREAM (source_object);

  error = NULL;
  g_output_stream_splice_finish (ostream, res, &error);
  if (error != NULL)
    {
      g_warning ("g_output_stream_splice failed: %s", error->message);
      g_error_free (error);
    }

  gtk_main_quit ();
}

static void
file_replace_download_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  Data *data = (Data *) user_data;
  GError *error;
  GFile *local_file = G_FILE (source_object);
  GFileOutputStream *ostream = NULL;

  error = NULL;
  ostream = g_file_replace_finish (local_file, res, &error);
  if (error != NULL)
    {
      g_warning ("g_file_replace failed: %s", error->message);
      gtk_main_quit ();
      g_error_free (error);
      goto out;
    }

  g_output_stream_splice_async (G_OUTPUT_STREAM (ostream),
                                data->istream,
                                G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
                                G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
                                G_PRIORITY_DEFAULT,
                                NULL,
                                output_stream_splice_download_cb,
                                data);

 out:
  g_clear_object (&ostream);
}

static void
session_send_download_cb (GObject *source_object, GAsyncResult *res, gpointer user_data)
{
  Data *data = (Data *) user_data;
  GError *error;
  GFile *local_file = NULL;
  GInputStream *istream = NULL;
  SoupSession *session = SOUP_SESSION (source_object);
  gchar *basename = NULL;

  error = NULL;
  istream = soup_session_send_finish (session, res, &error);
  if (error != NULL)
    {
      g_warning ("soup_session_send failed: %s", error->message);
      gtk_main_quit ();
      g_error_free (error);
      goto out;
    }

  data->istream = g_object_ref (istream);

  basename = g_file_get_basename (data->authentication_uri);
  local_file = g_file_new_for_path (basename);

  g_file_replace_async (local_file,
                        NULL,
                        FALSE,
                        G_FILE_CREATE_REPLACE_DESTINATION,
                        G_PRIORITY_DEFAULT,
                        NULL,
                        file_replace_download_cb,
                        data);

 out:
  g_clear_object (&local_file);
  g_clear_object (&istream);
  g_free (basename);
}

static gboolean
web_view_decide_policy_cb (WebKitWebView *web_view,
                           WebKitPolicyDecision *decision,
                           WebKitPolicyDecisionType decision_type,
                           gpointer user_data)
{
  Data *data = (Data *) user_data;
  SoupURI *uri = NULL;
  WebKitNavigationAction *action;
  WebKitURIRequest *request;
  gboolean ret_val = FALSE;
  const gchar *request_method;
  const gchar *request_uri;
  const gchar *query;

  if (decision_type != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION)
    goto out;

  action = webkit_navigation_policy_decision_get_navigation_action (WEBKIT_NAVIGATION_POLICY_DECISION (decision));
  request = webkit_navigation_action_get_request (action);
  request_method = webkit_uri_request_get_http_method (request);
  request_uri = webkit_uri_request_get_uri (request);
  g_debug ("%s: %s", request_method, request_uri);

  if (!g_str_has_prefix (request_uri, "https://developers.redhat.com/products/rhel"))
    goto out;

  uri = soup_uri_new (request_uri);
  query = soup_uri_get_query (uri);
  if (query != NULL)
    {
      GHashTable *key_value_pairs = NULL;
      const gchar *uri;

      key_value_pairs = soup_form_decode (query);
      uri = g_hash_table_lookup (key_value_pairs, "tcDownloadURL");
      if (uri != NULL)
        {
          SoupMessage *message = NULL;

          g_debug ("Download URI: %s", uri);

          message = soup_message_new ("GET", uri);
          soup_session_send_async (data->session, message, NULL, session_send_download_cb, data);

          gtk_widget_hide (data->window);
          webkit_policy_decision_ignore (decision);
          ret_val = TRUE;

          g_object_unref (message);
        }

      g_hash_table_unref (key_value_pairs);
    }

 out:
  g_clear_pointer (&uri, (GDestroyNotify) soup_uri_free);
  return ret_val;
}

static gchar *
parse_json (SoupMessageBody *response_body)
{
  GError *error;
  JsonArray *root_array;
  JsonNode *root_array_node_0;
  JsonNode *root_node;
  JsonObject *artifact_object;
  JsonObject *root_array_object_0;
  JsonParser *parser = NULL;
  const gchar *product_code;
  gchar *ret_val = NULL;

  parser = json_parser_new ();

  error = NULL;
  if (!json_parser_load_from_data (parser, response_body->data, response_body->length, &error))
    {
      g_warning ("json_parser_load_from_data failed: %s", error->message);
      g_error_free (error);
      goto out;
    }

  root_node = json_parser_get_root (parser);
  root_array = json_node_get_array (root_node);
  root_array_node_0 = json_array_get_element (root_array, 0);
  root_array_object_0 = json_node_get_object (root_array_node_0);
  if (!json_object_has_member (root_array_object_0, "productCode"))
    {
      g_warning ("Failed to parse JSON: couldn't find productCode");
      goto out;
    }
  if (!json_object_has_member (root_array_object_0, "featuredArtifact"))
    {
      g_warning ("Failed to parse JSON: couldn't find featuredArtifact");
      goto out;
    }

  product_code = json_object_get_string_member (root_array_object_0, "productCode");
  if (g_strcmp0 (product_code, "rhel") != 0)
    {
      g_warning ("Failed to parse JSON: productCode != rhel: %s", product_code);
      goto out;
    }

  artifact_object = json_object_get_object_member (root_array_object_0, "featuredArtifact");
  if (!json_object_has_member (artifact_object, "url"))
    {
      g_warning ("Failed to parse JSON: couldn't find featuredArtifact.url");
      goto out;
    }

  ret_val = g_strdup (json_object_get_string_member (artifact_object, "url"));

 out:
  g_clear_object (&parser);
  return ret_val;
}

static gchar *
parse_xml (SoupMessageBody *response_body)
{
  xmlDoc *doc = NULL;
  xmlNode *artifact_node;
  xmlNode *node;
  xmlNode *product_node;
  xmlNode *product_code_node;
  xmlNode *root_node;
  xmlNode *uri_node;
  gchar *product_code = NULL;
  gchar *ret_val = NULL;

  doc = xmlReadMemory (response_body->data, response_body->length, "rhel", NULL, 0);
  if (doc == NULL)
    {
      g_warning ("xmlReadMemory failed");
      goto out;
    }

  root_node = xmlDocGetRootElement (doc);
  if (g_strcmp0 (root_node->name, "products") != 0)
    {
      g_warning ("Failed to parse XML: couldn't find products");
      goto out;
    }

  product_node = root_node->children;
  if (g_strcmp0 (product_node->name, "product") != 0)
    {
      g_warning ("Failed to parse XML: couldn't find products.product");
      goto out;
    }

  for (node = product_node->children; node != NULL; node = node->next)
    {
      if (node->type == XML_ELEMENT_NODE && g_strcmp0 ((const gchar *) node->name, "productCode") == 0)
        break;
    }
  if (node == NULL)
    {
      g_warning ("Failed to parse XML: couldn't find products.product.productCode");
      goto out;
    }

  product_code_node = node;
  product_code = xmlNodeGetContent (product_code_node);
  if (g_strcmp0 (product_code, "rhel") != 0)
    {
      g_warning ("Failed to parse XML: products.product.productCode != rhel: %s", product_code);
      goto out;
    }

  for (node = product_node->children; node != NULL; node = node->next)
    {
      if (node->type == XML_ELEMENT_NODE && g_strcmp0 ((const gchar *) node->name, "featuredArtifact") == 0)
        break;
    }
  if (node == NULL)
    {
      g_warning ("Failed to parse XML: couldn't find products.product.featuredArtifact");
      goto out;
    }

  artifact_node = node;
  for (node = artifact_node->children; node != NULL; node = node->next)
    {
      if (node->type == XML_ELEMENT_NODE && g_strcmp0 ((const gchar *) node->name, "url") == 0)
        break;
    }
  if (node == NULL)
    {
      g_warning ("Failed to parse XML: couldn't find products.product.featuredArtifact.url");
      goto out;
    }

  uri_node = node;
  ret_val = xmlNodeGetContent (uri_node);

 out:
  g_clear_pointer (&doc, (GDestroyNotify) xmlFreeDoc);
  g_free (product_code);
  return ret_val;
}

static void
session_queue_message_rhel_cb (SoupSession *session, SoupMessage *message, gpointer user_data)
{
  Data *data = (Data *) user_data;
  GFile *authentication_uri = NULL;
  GtkWidget *header;
  GtkWidget *web_view;
  GtkWidget *window;
  const gchar *content_type;
  gchar *uri = NULL;

  if (message->status_code != SOUP_STATUS_OK)
    {
      g_warning ("soup_session_queue_message failed: %u — %s", message->status_code, message->reason_phrase);
      gtk_main_quit ();
      goto out;
    }

  content_type = soup_message_headers_get_one (message->response_headers, "Content-Type");
  if (content_type == NULL)
    {
      g_warning ("Missing Content-Type");
      gtk_main_quit ();
      goto out;
    }

  if (g_str_has_prefix (content_type, "application/json"))
    {
      uri = parse_json (message->response_body);
    }
  else if (g_str_has_prefix (content_type, "application/xml"))
    {
      uri = parse_xml (message->response_body);
    }
  else
    {
      g_warning ("Unknown Content-Type: %s", content_type);
      gtk_main_quit ();
      goto out;
    }

  if (uri == NULL)
    {
      gtk_main_quit ();
      goto out;
    }

  g_debug ("Authentication URI: %s", uri);

  authentication_uri = g_file_new_for_uri (uri);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_default_size (GTK_WINDOW (window), 600, 400);
  g_signal_connect (window, "delete-event", gtk_main_quit, NULL);

  header = gtk_header_bar_new ();
  gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (header), TRUE);
  gtk_window_set_titlebar (GTK_WINDOW (window), header);

  web_view = webkit_web_view_new ();
  webkit_web_view_load_uri (WEBKIT_WEB_VIEW (web_view), uri);
  gtk_container_add (GTK_CONTAINER (window), web_view);

  data->authentication_uri = g_object_ref (authentication_uri);
  data->window = window;
  g_object_add_weak_pointer (G_OBJECT (data->window), (gpointer *) &data->window);

  g_signal_connect (web_view, "decide-policy", G_CALLBACK (web_view_decide_policy_cb), data);

  gtk_widget_show_all (window);

 out:
  g_clear_object (&authentication_uri);
  g_free (uri);
}

gint
main (void)
{
  Data data;
  SoupLogger *logger = NULL;
  SoupMessage *message = NULL;
  SoupSession *session = NULL;

  gtk_init (NULL, NULL);

  session = soup_session_new ();
  data.session = g_object_ref (session);

  logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
  soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));

  message = soup_message_new ("GET", "https://developers.redhat.com/download-manager/rest/available/rhel");
  soup_session_queue_message (session, message, session_queue_message_rhel_cb, &data);

  gtk_main ();

  data_clear (&data);
  g_clear_object (&logger);
  g_clear_object (&session);
  return 0;
}
