/*
 * Copyright (C) 2023 Chris Talbot
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

#include "config.h"
#include "geoclue-stumbler-maps-page.h"
#include "geoclue-stumbler-stats-page.h"
#include "geoclue-stumbler-settings.h"

#define _GNU_SOURCE
#include <glib.h>
#include <glib/gi18n.h>
#include <glib-object.h>
#include <json-glib/json-glib.h>

struct _GeoclueStumblerStatsPage
{
  AdwBin        parent_instance;
  GtkWidget    *maps_page_bin;

  GtkLabel *submission_label;
  GtkLabel *queued_submissions_label;
  GtkLabel *total_submissions_label;
  GtkLabel *upload_time_label;
  GtkLabel *wifi_aps_label;
  GtkLabel *cell_tower_label;

  GtkWidget *upload_time_row;
  GtkWidget *submission_row;

  JsonParser *parser;

  unsigned int queued_submissions;
  unsigned int cell_towers;
  unsigned int wifi_aps;

  char *save_path;
};

G_DEFINE_TYPE (GeoclueStumblerStatsPage, geoclue_stumbler_stats_page, ADW_TYPE_BIN)

static void
geoclue_stumbler_stats_update_submission_Label (GeoclueStumblerStatsPage *self)
{
  g_autofree char *submissions_text = NULL;

  g_debug ("Updating Submission Queue number");
  submissions_text = g_strdup_printf("%d", self->queued_submissions);
  gtk_label_set_label(self->queued_submissions_label, submissions_text);
}

void
geoclue_stumbler_stats_update_total_submission_label (GeoclueStumblerStatsPage *self)
{
  int number_of_submissions;
  GeoclueStumblerSettings *settings;
  g_autofree char *submissions_text = NULL;

  settings = geoclue_stumbler_settings_get_default ();
  number_of_submissions =  geoclue_stumbler_settings_get_total_number_of_submissions (settings);

  submissions_text = g_strdup_printf("%d", number_of_submissions);
  gtk_label_set_label (self->total_submissions_label, submissions_text);
}

void
geoclue_stumbler_stats_reset_submissions (GeoclueStumblerStatsPage *self)
{
  self->queued_submissions = 0;
  geoclue_stumbler_stats_update_submission_Label (self);
}

unsigned int
geoclue_stumbler_stats_get_queued_submission_count (GeoclueStumblerStatsPage *self)
{
  return self->queued_submissions;
}

void
geoclue_stumbler_stats_page_new_submission (GeoclueStumblerStatsPage *self,
                                            const char *json_contents,
                                            gboolean    first_load)
{
  JsonReader *reader = NULL;
  JsonNode *current;
  JsonArray *array;
  JsonNode *root;
  JsonNode *lat_lng;
  double lat, lng;

  gboolean parsing_success = TRUE;

  g_autoptr(GDateTime) time = NULL;
  g_autoptr(GError) error = NULL;
  g_autofree gchar *time_str = NULL;
  g_autofree gchar *wifi_aps_text = NULL;
  g_autofree gchar *cell_towers_text = NULL;

  g_debug ("Processing Submission!");

  self->queued_submissions++;
  geoclue_stumbler_stats_update_submission_Label (self);

  /* Check that the data is a valid JSON object */
  if (!json_contents || !*json_contents)
    return;

  parsing_success = json_parser_load_from_data (self->parser, json_contents, strlen (json_contents), &error);

  if (error) {
    g_warning ("Error parsing JSON text: %s", error->message);
    g_clear_error (&error);
    return;
  }

  if (!parsing_success) {
    g_warning ("Error parsing JSON text");
    return;
  }

  g_debug ("JSON Contents: %s", json_contents);

  /* json_parser_get_root() is transfer none, don't try to free it */
  root = json_parser_get_root (self->parser);
  if (!root) {
    g_warning ("Error finding root of the JSON");
    return;
  }

  reader = json_reader_new (root);
  if (!reader) {
    g_warning ("Error reading root of the JSON");
    return;
  }

  if (!json_reader_read_member (reader, "items")) {
    const GError *reader_error = NULL;
    reader_error = json_reader_get_error (reader);
    g_warning ("Error finding items: %s", reader_error->message);
    g_clear_object (&reader);
    return;
  }

  if (!json_reader_is_array (reader)) {
    g_warning ("Malformed JSON: No Array");
    g_clear_object (&reader);
    return;
  }
  /* json_reader_get_current_node() is transfer none, don't try to free it */
  current = json_reader_get_current_node (reader);

  /*
   * AFAICT, I can't enter an array with json_reader. So I have
   * to get it's node, get the array in the node, get the first element
   * in the array, THEN I can read it.
   */
  g_clear_object (&reader);

  /* json_node_get_array() is transfer none, don't try to free it */
  array = json_node_get_array (current);
  /* json_array_get_element() is transfer none, don't try to free it */
  current = json_array_get_element (array, 0);
  reader = json_reader_new (current);
  if (!reader) {
    g_warning ("Error reading root of the JSON Array");
    return;
  }

  if (json_reader_read_member (reader, "timestamp")) {
    gint64 timestamp;
    g_autoptr(GDateTime) time_utc = NULL;

    timestamp = json_reader_get_int_value (reader);
    /* timestamp is in milliseconds */
    timestamp = timestamp/1000;

    time_utc = g_date_time_new_from_unix_utc (timestamp);
    time = g_date_time_to_local (time_utc);
  } else {
    const GError *reader_error = NULL;
    reader_error = json_reader_get_error (reader);
    g_debug ("Error finding timestamp: %s", reader_error->message);

    time = g_date_time_new_now_local ();
  }

  time_str = g_date_time_format (time, "%c");
  g_debug ("Submission Timestamp: %s", time_str);

  if (!first_load) {
    gtk_label_set_label (self->submission_label, time_str);
    gtk_widget_set_visible (self->submission_row, TRUE);
  }

  /* Reset to the array so you can look for position */
  json_reader_end_member (reader);

  if (json_reader_read_member (reader, "position")) {
    if (json_reader_read_member (reader, "latitude")) {
      lat_lng = json_reader_get_value (reader);
      lat = json_node_get_double (lat_lng);
    } else {
      const GError *reader_error = NULL;
      reader_error = json_reader_get_error (reader);
      g_debug ("Error finding latitude: %s", reader_error->message);
      lat = 0;
    }
    /* Reset to the array so you can look for longitude */
    json_reader_end_member (reader);

    if (json_reader_read_member (reader, "longitude")) {
      lat_lng = json_reader_get_value (reader);
      lng = json_node_get_double (lat_lng);
    } else{
      const GError *reader_error = NULL;
      reader_error = json_reader_get_error (reader);
      g_debug ("Error finding longitude: %s", reader_error->message);
      lng = 0;
    }
    /*
     * Reset to the array (end_member twice)so you can look for
     * next elements in the array
     */
    json_reader_end_member (reader);
    json_reader_end_member (reader);
  } else {
    const GError *reader_error = NULL;
    reader_error = json_reader_get_error (reader);
    g_debug ("Error finding Position: %s", reader_error->message);
    /* Reset to the array so you can look for cell towers */
    json_reader_end_member (reader);
  }

  if (json_reader_read_member (reader, "wifiAccessPoints"))
    self->wifi_aps = json_reader_count_elements (reader);
  else {
    const GError *reader_error = NULL;
    reader_error = json_reader_get_error (reader);
    g_debug ("Error finding Wifi APs: %s", reader_error->message);
    self->wifi_aps = 0;
  }

  /* Reset to the array so you can look for cell towers */
  json_reader_end_member (reader);
  if (json_reader_read_member (reader, "cellTowers"))
    self->cell_towers = json_reader_count_elements (reader);
  else {
    const GError *reader_error = NULL;
    reader_error = json_reader_get_error (reader);
    g_debug ("Error finding Cell Towers: %s", reader_error->message);
    self->cell_towers = 0;
  }

  g_clear_object (&reader);

  g_debug ("latitude: %f, Longitude: %f", lat, lng);
  if (lat != 0 && lng != 0)
    geoclue_stumbler_maps_page_set_subission_marker (GEOCLUE_STUMBLER_MAPS_PAGE (self->maps_page_bin),
                                                     lat,
                                                     lng,
                                                     time_str,
                                                     self->wifi_aps,
                                                     self->cell_towers);

  wifi_aps_text = g_strdup_printf ("%d", self->wifi_aps);
  gtk_label_set_label (self->wifi_aps_label, wifi_aps_text);
  cell_towers_text = g_strdup_printf ("%d", self->cell_towers);
  gtk_label_set_label (self->cell_tower_label, cell_towers_text);
}

void
geoclue_stumbler_stats_page_new_upload (GeoclueStumblerStatsPage *self)
{
  g_autoptr(GDateTime) time = NULL;
  g_autofree gchar *str = NULL;
  g_warning ("Submission Updated!");

  time = g_date_time_new_now_local ();
  str = g_date_time_format (time, "%c");

  g_debug ("Upload Timestamp: %s", str);
  gtk_label_set_label (self->upload_time_label, str);
  gtk_widget_set_visible (self->upload_time_row, TRUE);
}

static void
geoclue_stumbler_stats_count_submissions (GeoclueStumblerStatsPage *self)
{
  GDir *dir;
  const char *file_path;

  self->queued_submissions = 0;

  dir = g_dir_open (self->save_path, 0, NULL);
  if (dir == NULL) {
    g_warning ("Cannot open directory: %s", self->save_path);
  } else {
    while ((file_path = g_dir_read_name (dir)) != NULL) {
      GFile *file;
      gsize file_contents_length;

      g_autofree char *file_contents = NULL;
      g_autoptr(GError) error = NULL;
      g_autofree char *absolute_file_path = NULL;

      g_debug ("Processing file: %s", file_path);

      absolute_file_path = g_build_filename (self->save_path , file_path, NULL);
      file = g_file_new_for_path (absolute_file_path);
      g_file_load_contents (file, NULL, &file_contents, &file_contents_length, NULL, &error);
      g_clear_object (&file);

      if (error != NULL) {
        g_warning ("Error in loading contents: %s\n", error->message);
        g_clear_error (&error);
        continue;
      }

      geoclue_stumbler_stats_page_new_submission (self,
                                                  file_contents,
                                                  TRUE);

    }
  }

  g_debug ("Queued Submissions: %i", self->queued_submissions);
  geoclue_stumbler_stats_update_submission_Label (self);
  g_dir_close (dir);
}

void
geoclue_stumbler_stats_page_set_maps_page (GeoclueStumblerStatsPage *self,
                                           GtkWidget               *maps_page_bin)
{
  self->maps_page_bin = maps_page_bin;
  /*
   * geoclue_stumbler_stats_count_submissions () need maps_page_bin to put markers
   * on the map
   */
  geoclue_stumbler_stats_count_submissions (self);
}

static void
reset_submission_number_response_cb (AdwMessageDialog *dialog,
                               const char       *response,
                               gpointer          user_data)

{
  GeoclueStumblerStatsPage *self = GEOCLUE_STUMBLER_STATS_PAGE (user_data);

  if (g_strcmp0 (response, "continue") == 0) {
    GeoclueStumblerSettings *settings;
    g_debug ("Continue Response");

    g_debug ("Resetting Total Number of Submissions");
    settings = geoclue_stumbler_settings_get_default ();
    geoclue_stumbler_settings_set_total_number_of_submissions (settings, 0);
    geoclue_stumbler_stats_update_total_submission_label (self);
    return;
  } else
    g_debug ("Cancel Response");
}

static void
reset_button_clicked_cb (GeoclueStumblerStatsPage *self)
{
  AdwDialog *dialog;
  GtkWindow *window;

  window = gtk_application_get_active_window (GTK_APPLICATION (g_application_get_default ()));
  dialog = adw_alert_dialog_new (_("Alert"),
                                 _("You will reset your submission number to zero, continue?"));

  adw_alert_dialog_add_responses (ADW_ALERT_DIALOG (dialog),
                                    "cancel",  _("_Cancel"),
                                    "continue", _("Continue"),
                                    NULL);

  adw_alert_dialog_set_response_appearance (ADW_ALERT_DIALOG (dialog), "continue", ADW_RESPONSE_DESTRUCTIVE);

  adw_alert_dialog_set_default_response (ADW_ALERT_DIALOG (dialog), "cancel");
  adw_alert_dialog_set_close_response (ADW_ALERT_DIALOG (dialog), "cancel");

  g_signal_connect (dialog, "response", G_CALLBACK (reset_submission_number_response_cb), self);

  adw_dialog_present (dialog, GTK_WIDGET (window));
}

static void
delete_queued_submission_response_cb (AdwMessageDialog *dialog,
                               const char       *response,
                               gpointer          user_data)

{
  GeoclueStumblerStatsPage *self = GEOCLUE_STUMBLER_STATS_PAGE (user_data);

  if (g_strcmp0 (response, "continue") == 0) {
    GDir *dir;
    const char *file_path;
    g_debug ("Continue Response");
    g_debug ("Deleting all Queued Submissions");

    dir = g_dir_open (self->save_path, 0, NULL);
    if (dir == NULL) {
      g_warning ("Cannot open directory: %s", self->save_path);
    } else {
      while ((file_path = g_dir_read_name (dir)) != NULL) {
        g_autofree char *absolute_file_path = NULL;

        absolute_file_path = g_build_filename (self->save_path, file_path, NULL);
        g_debug("Deleting: %s", absolute_file_path);
        unlink (absolute_file_path);
      }
    }

    g_dir_close (dir);
    geoclue_stumbler_maps_page_reset_submission_markers (GEOCLUE_STUMBLER_MAPS_PAGE (self->maps_page_bin));
    geoclue_stumbler_stats_count_submissions (self);

    return;
  } else
    g_debug ("Cancel Response");
}

static void
delete_submission_button_clicked_cb (GeoclueStumblerStatsPage *self)
{
  AdwDialog *dialog;
  GtkWindow *window;

  window = gtk_application_get_active_window (GTK_APPLICATION (g_application_get_default ()));
  dialog = adw_alert_dialog_new (_("Alert"),
                                 _("You will delete all queued submissions, continue?"));

  adw_alert_dialog_add_responses (ADW_ALERT_DIALOG (dialog),
                                    "cancel",  _("_Cancel"),
                                    "continue", _("Continue"),
                                    NULL);

  adw_alert_dialog_set_response_appearance (ADW_ALERT_DIALOG (dialog), "continue", ADW_RESPONSE_DESTRUCTIVE);

  adw_alert_dialog_set_default_response (ADW_ALERT_DIALOG (dialog), "cancel");
  adw_alert_dialog_set_close_response (ADW_ALERT_DIALOG (dialog), "cancel");

  g_signal_connect (dialog, "response", G_CALLBACK (delete_queued_submission_response_cb), self);

  adw_dialog_present (dialog, GTK_WIDGET (window));
}

static void
geoclue_stumbler_stats_page_finalize (GObject *object)
{
  GeoclueStumblerStatsPage *self = (GeoclueStumblerStatsPage *)object;
  g_clear_object (&self->parser);

  g_free (self->save_path);

  G_OBJECT_CLASS (geoclue_stumbler_stats_page_parent_class)->finalize (object);
}


static void
geoclue_stumbler_stats_page_class_init (GeoclueStumblerStatsPageClass *klass)
{
  GObjectClass   *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  gtk_widget_class_set_template_from_resource (widget_class,
                                               "/org/kop316/stumbler/geoclue-stumbler-stats-page.ui");

  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerStatsPage, submission_label);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerStatsPage, submission_row);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerStatsPage, queued_submissions_label);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerStatsPage, upload_time_label);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerStatsPage, upload_time_row);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerStatsPage, total_submissions_label);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerStatsPage, cell_tower_label);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerStatsPage, wifi_aps_label);

  gtk_widget_class_bind_template_callback (widget_class, reset_button_clicked_cb);
  gtk_widget_class_bind_template_callback (widget_class, delete_submission_button_clicked_cb);

  object_class->finalize = geoclue_stumbler_stats_page_finalize;
}

static void
geoclue_stumbler_stats_page_init (GeoclueStumblerStatsPage *self)
{
  gtk_widget_init_template (GTK_WIDGET (self));

  self->save_path = g_build_filename (g_get_user_data_dir () , SUBMISSION_DIRECTORY, NULL);
  self->parser = json_parser_new ();
  self->cell_towers = 0;
  self->wifi_aps = 0;

  geoclue_stumbler_stats_update_total_submission_label (self);
}
