/*
 * 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-path-page.h"

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

#define METERS_TO_SECOND_TO_MPH 2.236936
#define METERS_T0_MILES 0.0006213712

struct _GeoclueStumblerPathPage
{
  AdwBin           parent_instance;
  AdwToastOverlay *toast_overlay;
  GtkWidget       *maps_page_bin;

  GtkLabel      *average_speed_metric_label;
  GtkLabel      *average_speed_imperial_label;
  GtkLabel      *speed_metric_label;
  GtkLabel      *speed_imperial_label;
  GtkLabel      *distance_travelled_metric_label;
  GtkLabel      *distance_travelled_imperial_label;
  GtkLabel      *time_label;
  AdwButtonRow  *record_path_button;
  AdwButtonRow  *reset_button;

  gboolean       gps_lock;
  gboolean       record_path;

  ShumateCoordinate *old_location;
  double             old_altitude;
  double             total_path_distance_meters;
  guint64            start_timestamp;
  GString           *gpx_file_contents;
};

G_DEFINE_TYPE (GeoclueStumblerPathPage, geoclue_stumbler_path_page, ADW_TYPE_BIN)

static void
geoclue_stumbler_path_page_display_toast (GeoclueStumblerPathPage *self,
                                          const char              *title)
{
  AdwToast *toast = adw_toast_new (title);
  adw_toast_overlay_add_toast (self->toast_overlay, toast);
  adw_toast_set_timeout (toast, 1);
}

static void
geoclue_stumbler_path_page_reset_page (GeoclueStumblerPathPage *self)
{
  g_autoptr(GDateTime) date_time = NULL;
  g_autofree char *time_ISO_8601_text = NULL;
  g_autofree char *gps_file_start_text = NULL;

  g_debug("Resetting Path Page");

  date_time = g_date_time_new_now_local ();
  self->start_timestamp = g_date_time_to_unix(date_time);
  time_ISO_8601_text = g_date_time_format_iso8601 (date_time);

  gtk_label_set_label(self->average_speed_metric_label, _("0 m/s"));
  gtk_label_set_label(self->average_speed_imperial_label, _("0 mph"));
  gtk_label_set_label(self->speed_metric_label, _("0 m/s"));
  gtk_label_set_label(self->speed_imperial_label, _("0 mph"));
  gtk_label_set_label(self->distance_travelled_metric_label, _("0 m"));
  gtk_label_set_label(self->distance_travelled_imperial_label, _("0 mi."));
  gtk_label_set_label(self->time_label, _("0 seconds"));

  adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self->record_path_button), _("Start Recording"));
  adw_button_row_set_start_icon_name (self->record_path_button, "media-playback-start");
  gtk_widget_add_css_class (GTK_WIDGET (self->record_path_button), "suggested-action");
  gtk_widget_set_sensitive (GTK_WIDGET (self->record_path_button), TRUE);

  if (self->record_path)
    geoclue_stumbler_path_page_display_toast (self, _("No Longer Recording Path"));

  self->record_path = FALSE;
  self->start_timestamp = 0;
  self->total_path_distance_meters = 0;
  self->old_altitude = 0;
  g_debug("Record Path Page %i", self->record_path);

  if (self->old_location)
      g_clear_object (&self->old_location);

  self->old_location = shumate_coordinate_new_full (0, 0);

  if (self->gpx_file_contents)
    g_string_free (self->gpx_file_contents, TRUE);

  gps_file_start_text = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n<gpx xmlns=\"http://www.topografix.com/GPX/1/1\" version=\"1.1\" creator=\"geoclue-stumbler\"\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\n <metadata>\n  <name>Geoclue Route</name>\n  <desc>A route that was created in Geoclue Stumbler</desc>\n  <time>%s</time>\n  <author>\n   <name>Geoclue Stumbler</name>\n  </author>\n </metadata>\n\n  <trk>\n    <trkseg>",
                                      time_ISO_8601_text);

  self->gpx_file_contents = g_string_new (gps_file_start_text);

}

void
geoclue_stumbler_path_page_set_toast_overlay (GeoclueStumblerPathPage *self,
                                              AdwToastOverlay         *toast_overlay)
{
  self->toast_overlay = toast_overlay;
}

void
geoclue_stumbler_path_page_set_maps_page (GeoclueStumblerPathPage *self,
                                          GtkWidget               *maps_page_bin)
{
  self->maps_page_bin = maps_page_bin;
}

gboolean
geoclue_stumbler_path_page_get_recording_path (GeoclueStumblerPathPage *self)
{
  return self->record_path;
}

void
geoclue_stumbler_path_page_set_gps_lock (GeoclueStumblerPathPage *self,
                                         gboolean                 lock)
{
  self->gps_lock = lock;
}

void
geoclue_stumbler_path_page_update_page (GeoclueStumblerPathPage *self,
                                        double lat2,
                                        double lng2,
                                        double speed_meters_per_second,
                                        double altitude)
{
  double speed_mph, abs_speed_meters_per_second;
  ShumateCoordinate *new_location;
  guint64 sec_now;
  char lat_str[G_ASCII_DTOSTR_BUF_SIZE];
  char lng_str[G_ASCII_DTOSTR_BUF_SIZE];
  char alt_str[G_ASCII_DTOSTR_BUF_SIZE];

  g_autofree char *speed_meters_per_second_text = NULL;
  g_autofree char *speed_mph_text = NULL;
  g_autofree char *gps_file_add_text = NULL;
  g_autofree char *time_ISO_8601_text = NULL;
  g_autoptr(GDateTime) date_time_now = NULL;

  if (!self->record_path) {
    g_debug ("Not recording path");
    return;
  }

  new_location = shumate_coordinate_new_full (lat2, lng2);

  date_time_now = g_date_time_new_now_local ();
  sec_now = g_date_time_to_unix(date_time_now);
  time_ISO_8601_text = g_date_time_format_iso8601 (date_time_now);

  if (self->start_timestamp != 0) {
    guint64 time_difference;
    guint64 time_difference_seconds;
    guint64 time_difference_minutes;
    guint64 time_difference_hours;
    double average_speed_meters_per_second;
    double average_speed_mph;
    double total_path_distance_miles;
    double segment_distance_flat;

    g_autofree char *total_distance_traveled_metric_text = NULL;
    g_autofree char *total_distance_traveled_imperial_text = NULL;
    g_autofree char *average_speed_meters_per_second_text = NULL;
    g_autofree char *average_speed_mph_text = NULL;
    g_autofree char *time_label_text = NULL;

    if (self->start_timestamp > sec_now)
      time_difference = 1;
    else
      time_difference = sec_now - self->start_timestamp;

    g_debug ("Time path has been on: %" G_GUINT64_FORMAT " Seconds", time_difference);

    if (time_difference < 60) {
      time_label_text = g_strdup_printf(_("%" G_GUINT64_FORMAT " Seconds"), time_difference);
    } else if (time_difference < 3600) {
      time_difference_minutes = time_difference / 60;
      time_difference_seconds = time_difference % 60;
      time_label_text = g_strdup_printf(_("%" G_GUINT64_FORMAT " Minutes %" G_GUINT64_FORMAT " Seconds"),
                                        time_difference_minutes,
                                        time_difference_seconds);
    } else {
      time_difference_hours = time_difference / (60*60);
      time_difference_minutes = (time_difference / 60) - (time_difference_hours*60);
      time_difference_seconds = time_difference % 60;
      time_label_text = g_strdup_printf(_("%" G_GUINT64_FORMAT " Hours %" G_GUINT64_FORMAT " Minutes %" G_GUINT64_FORMAT " Seconds"),
                                        time_difference_hours,
                                        time_difference_minutes,
                                        time_difference_seconds);
    }

    gtk_label_set_label(self->time_label, time_label_text);
    segment_distance_flat = shumate_location_distance (SHUMATE_LOCATION (self->old_location), SHUMATE_LOCATION (new_location));
    g_debug ("Dist flat: %f", segment_distance_flat);
    /* shumate_location_distance() seems to return NaN at very small distances (when the GPD module is still) */
    if (isnan(segment_distance_flat) || isinf(segment_distance_flat))
      g_warning ("Segment distance flat is %f", segment_distance_flat);
    else {
      double segment_distance_alt;
      double altitude_difference;

      altitude_difference = fabs (self->old_altitude - altitude);
      g_debug ("Altitude Distance absolute: %f", altitude_difference);

      /*
       * Even though shumate_location_distance() is spherical,
       * we are dealing with distances small ennough that we can assume it's flat
       */
      segment_distance_alt = (segment_distance_flat*segment_distance_flat) + (altitude_difference*altitude_difference);
      segment_distance_alt = sqrt (segment_distance_alt);

      if (isnan(segment_distance_alt) || isinf(segment_distance_alt)) {
        g_warning ("Segment distance altitude is %f", segment_distance_alt);
        self->total_path_distance_meters = self->total_path_distance_meters + segment_distance_flat;
      } else {
        self->total_path_distance_meters = self->total_path_distance_meters + segment_distance_alt;
        g_debug ("Segment Distance altitude: %f", segment_distance_alt);
      }
    }
    total_path_distance_miles = self->total_path_distance_meters * METERS_T0_MILES;

    average_speed_meters_per_second = self->total_path_distance_meters / time_difference;
    average_speed_mph = average_speed_meters_per_second * METERS_TO_SECOND_TO_MPH;

    g_debug ("Distance Traveled: %f meters", self->total_path_distance_meters);
    total_distance_traveled_metric_text = g_strdup_printf(_("%.2f m"), self->total_path_distance_meters);
    gtk_label_set_label(self->distance_travelled_metric_label, total_distance_traveled_metric_text);

    g_debug ("Distance Traveled: %f miles", total_path_distance_miles);
    total_distance_traveled_imperial_text = g_strdup_printf(_("%.4f mi."), total_path_distance_miles);
    gtk_label_set_label(self->distance_travelled_imperial_label, total_distance_traveled_imperial_text);

    g_debug ("Average Speed: %f m/s", average_speed_meters_per_second);
    average_speed_meters_per_second_text = g_strdup_printf(_("%.1f m/s"), average_speed_meters_per_second);
    gtk_label_set_label(self->average_speed_metric_label, average_speed_meters_per_second_text);

    g_debug ("Average Speed: %f mph", average_speed_mph);
    average_speed_mph_text = g_strdup_printf(_("%.2f mph"), average_speed_mph);
    gtk_label_set_label(self->average_speed_imperial_label, average_speed_mph_text);

  } else
    self->start_timestamp = sec_now;

  /* I don't know why, but GPS can give you a negative speed.....*/
  abs_speed_meters_per_second = fabs (speed_meters_per_second);
  speed_mph = abs_speed_meters_per_second * METERS_TO_SECOND_TO_MPH;

  g_debug ("Speed: %f m/s", abs_speed_meters_per_second);
  speed_meters_per_second_text = g_strdup_printf(_("%.1f m/s"), abs_speed_meters_per_second);
  gtk_label_set_label(self->speed_metric_label, speed_meters_per_second_text);

  g_debug ("Speed: %f mph", speed_mph);
  speed_mph_text = g_strdup_printf(_("%.2f mph"), speed_mph);
  gtk_label_set_label(self->speed_imperial_label, speed_mph_text);

  g_ascii_dtostr (lat_str, G_ASCII_DTOSTR_BUF_SIZE, lat2);
  g_ascii_dtostr (lng_str, G_ASCII_DTOSTR_BUF_SIZE, lng2);
  g_ascii_dtostr (alt_str, G_ASCII_DTOSTR_BUF_SIZE, altitude);
  gps_file_add_text = g_strdup_printf("\n      <trkpt lat=\"%s\" lon=\"%s\">\n        <ele>%s</ele>\n        <time>%s</time>\n      </trkpt>",
                                      lat_str,
                                      lng_str,
                                      alt_str,
                                      time_ISO_8601_text);

  self->gpx_file_contents = g_string_append (self->gpx_file_contents, gps_file_add_text);

  g_object_unref (self->old_location);
  self->old_location = new_location;
  self->old_altitude = altitude;

}

static void
replace_contents_finshed (GObject         *file,
                          GAsyncResult    *response,
                          gpointer        user_data)
{
//  GeoclueStumblerPathPage *self = GEOCLUE_STUMBLER_PATH_PAGE (user_data);
  g_autoptr(GError) error = NULL;
  gboolean success;

  success = g_file_replace_contents_finish (G_FILE (file), response, NULL, &error);
  g_clear_object (&file);
  if (!success) {
    g_warning ("Error saving: %s", error->message);
    return;
  }
  if (error != NULL) {
    g_warning ("Error saving: UNKNOWN error");
    return;
  }
  g_debug ("Sucessfully saved gpx file");
}

static void
save_dialog_finished (GObject         *dialog,
                      GAsyncResult    *response,
                      gpointer        user_data)
{
  GeoclueStumblerPathPage *self = GEOCLUE_STUMBLER_PATH_PAGE (user_data);
  g_autoptr(GError) error = NULL;
  GFile *save_filename;

  save_filename = gtk_file_dialog_save_finish (GTK_FILE_DIALOG (dialog), response, &error);
  g_clear_object (&dialog);

  if (error != NULL) {
    if (!g_error_matches (error, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_DISMISSED) &&
        !g_error_matches (error, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_CANCELLED))
      g_warning ("Error selecting save file: %s", error->message);
    return;
  }
  g_debug("Saving GPX file");

  g_file_replace_contents_async (save_filename,
                                 self->gpx_file_contents->str,
                                 strlen(self->gpx_file_contents->str),
                                 NULL,
                                 FALSE,
                                 G_FILE_CREATE_PRIVATE | G_FILE_CREATE_REPLACE_DESTINATION,
                                 NULL,
                                 replace_contents_finshed,
                                 self);
}

static void
record_path_button_clicked_cb (GeoclueStumblerPathPage *self)
{
  if (!adw_button_row_get_start_icon_name (self->record_path_button)) {
    GtkFileDialog *dialog;
    g_autoptr(GFile) home_folder = NULL;
    g_autoptr(GError) error = NULL;
    GtkWindow *window;

    window = gtk_application_get_active_window (GTK_APPLICATION (g_application_get_default ()));

    dialog = gtk_file_dialog_new ();

    home_folder = g_file_new_build_filename (g_get_home_dir (), NULL);
    gtk_file_dialog_set_initial_folder (dialog, home_folder);

    gtk_file_dialog_set_initial_name (dialog,  "route.gpx");

    gtk_file_dialog_set_modal (dialog, TRUE);
    gtk_file_dialog_set_title (dialog, "Save GPX file");

    gtk_file_dialog_save (dialog, window, NULL, save_dialog_finished, self);

    return;
  }

  self->record_path = !self->record_path;
  g_debug("Record Path Page %i", self->record_path);

  if (self->record_path && self->gps_lock)
    geoclue_stumbler_path_page_display_toast (self, _("Recording Path"));
  else if (self->record_path)
    geoclue_stumbler_path_page_display_toast (self, _("Recording Path once GPS Locks"));
  else
    geoclue_stumbler_path_page_display_toast (self, _("No Longer Recording Path"));

  if (self->record_path) {
    adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self->record_path_button), _("Stop Recording"));
    adw_button_row_set_start_icon_name (self->record_path_button, "media-playback-stop");
    gtk_widget_remove_css_class (GTK_WIDGET (self->record_path_button), "suggested-action");
  } else {
    adw_preferences_row_set_title (ADW_PREFERENCES_ROW (self->record_path_button), _("Save Path as GPX"));
    adw_button_row_set_start_icon_name (self->record_path_button, NULL);
    self->gpx_file_contents = g_string_append (self->gpx_file_contents, "\n    </trkseg>\n  </trk>\n\n</gpx>");
  }
}

static void
reset_button_clicked_cb (GeoclueStumblerPathPage *self)
{
  geoclue_stumbler_path_page_reset_page (self);

  geoclue_stumbler_maps_page_reset_path (GEOCLUE_STUMBLER_MAPS_PAGE (self->maps_page_bin));
}

static void
geoclue_stumbler_path_page_finalize (GObject *object)
{
  GeoclueStumblerPathPage *self = (GeoclueStumblerPathPage *)object;
  if (self->old_location)
      g_clear_object (&self->old_location);

  if (self->gpx_file_contents)
    g_string_free (self->gpx_file_contents, TRUE);

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

static void
geoclue_stumbler_path_page_class_init (GeoclueStumblerPathPageClass *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-path-page.ui");

  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerPathPage, average_speed_metric_label);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerPathPage, average_speed_imperial_label);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerPathPage, speed_metric_label);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerPathPage, speed_imperial_label);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerPathPage, distance_travelled_metric_label);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerPathPage, distance_travelled_imperial_label);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerPathPage, record_path_button);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerPathPage, reset_button);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerPathPage, time_label);

  gtk_widget_class_bind_template_callback (widget_class, record_path_button_clicked_cb);
  gtk_widget_class_bind_template_callback (widget_class, reset_button_clicked_cb);

  object_class->finalize = geoclue_stumbler_path_page_finalize;
}

static void
geoclue_stumbler_path_page_init (GeoclueStumblerPathPage *self)
{
  gtk_widget_init_template (GTK_WIDGET (self));
  self->old_location = NULL;
  self->gpx_file_contents = NULL;

  geoclue_stumbler_path_page_reset_page (self);

  self->gps_lock = FALSE;
}
