/*
 * 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-marker.h"
#include "geoclue-stumbler-path-page.h"
#include "geoclue-stumbler-settings.h"
#include "geoclue-stumbler-submission-marker.h"

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

#define degToRad(angleInDegrees) ((angleInDegrees) * M_PI / 180.0)
#define radToDeg(angleInRadians) ((angleInRadians) * 180.0 / M_PI)

struct _GeoclueStumblerMapsPage
{
  AdwBin        parent_instance;
  GtkWidget    *path_page_bin;

  ShumateMapSourceRegistry *registry;
  ShumateSimpleMap         *simple_map;
  ShumateMarkerLayer       *marker_layer;
  ShumateMarkerLayer       *submission_marker_layer;
  ShumatePathLayer         *path_layer;
  ShumateViewport          *viewport;

  GeoclueStumblerMarker    *marker;
  GtkRevealer              *compass_revealer;
  GtkButton                *zoom_button;
  GtkButton                *compass_button;
  gboolean                  first_marker;
  gboolean                  zoom_fast_click;
  gboolean                  zoom_follow;
  gboolean                  compass_follow;

  double                    zoom_level;
};

G_DEFINE_TYPE (GeoclueStumblerMapsPage, geoclue_stumbler_maps_page, ADW_TYPE_BIN)

void
geoclue_stumbler_maps_page_set_subission_marker (GeoclueStumblerMapsPage *self,
                                                 double lat,
                                                 double lng,
                                                 const char *time_str,
                                                 unsigned int wifi_aps,
                                                 unsigned int cell_towers)
{
  GeoclueStumblerSettings *settings;
  GeoclueStumblerSubmissionMarker *marker;

  settings = geoclue_stumbler_settings_get_default ();
  if (!geoclue_stumbler_settings_get_show_submission_location (settings)) {
    g_debug ("not showing submission locations");
    return;
  }
  g_debug ("Set submission marker");

  /*
   * Since this is attached to the marker layer, the layer will free the
   * memory, so we do not need to unref it.
   */
  marker = geoclue_stumbler_submission_marker_new ();
  geoclue_stumbler_submission_marker_set_wifi_aps (marker, wifi_aps);
  geoclue_stumbler_submission_marker_set_cell_towers (marker, cell_towers);
  geoclue_stumbler_submission_marker_set_time_str (marker, time_str);
  shumate_location_set_location (SHUMATE_LOCATION (marker), lat, lng);
  shumate_marker_layer_add_marker (self->submission_marker_layer, SHUMATE_MARKER (marker));

}

static void
geoclue_stumbler_maps_page_compass_button_clicked_cb (GeoclueStumblerMapsPage *self)
{
  self->compass_follow = !self->compass_follow;
  g_debug("Compass Follow %i", self->compass_follow);

  if (!self->compass_follow) {
    shumate_viewport_set_rotation (self->viewport, 0.0);
    gtk_widget_add_css_class (GTK_WIDGET (self->compass_button), "suggested-action");
  } else
    gtk_widget_remove_css_class (GTK_WIDGET (self->compass_button), "suggested-action");
}

static void
zoom_fast_click_false_cb (gpointer user_data)
{
  GeoclueStumblerMapsPage *self = (GeoclueStumblerMapsPage *)user_data;
  self->zoom_fast_click = FALSE;
  g_debug("Fast Click %i", self->zoom_fast_click);
}

void
geoclue_stumbler_maps_page_set_path_page (GeoclueStumblerMapsPage *self,
                                          GtkWidget               *path_page_bin)
{
  self->path_page_bin = path_page_bin;
}

static void
geoclue_stumbler_maps_page_zoom_button_clicked_cb (GeoclueStumblerMapsPage *self)
{
  double lat;
  double lng;
  ShumateMap *map;

  map = shumate_simple_map_get_map(self->simple_map);
  shumate_map_set_go_to_duration (map, 100);

  lat = shumate_location_get_latitude (SHUMATE_LOCATION (self->marker));
  lng = shumate_location_get_longitude (SHUMATE_LOCATION (self->marker));
  if (lat == 0 && lng == 0) {
    g_debug ("No location, not zooming");
    return;
  }

  if (self->zoom_fast_click || self->zoom_follow) {
    self->zoom_follow = !self->zoom_follow;
    g_debug("Zoom Follow %i", self->zoom_follow);
    gtk_revealer_set_reveal_child (self->compass_revealer, self->zoom_follow);
    if (!self->zoom_follow) {
      self->compass_follow = FALSE;
      g_debug("Compass Follow %i", self->compass_follow);
      gtk_widget_add_css_class (GTK_WIDGET (self->zoom_button), "suggested-action");
      gtk_widget_add_css_class (GTK_WIDGET (self->compass_button), "suggested-action");
    } else
      gtk_widget_remove_css_class (GTK_WIDGET (self->zoom_button), "suggested-action");

  }

  if (!self->zoom_fast_click)
    g_timeout_add_once (2000, zoom_fast_click_false_cb, self);

  self->zoom_fast_click = TRUE;
  g_debug("Fast Click %i", self->zoom_fast_click);

  shumate_map_go_to_full_with_duration (map, lat, lng, self->zoom_level, 500);

  if (!self->compass_follow)
    shumate_viewport_set_rotation (self->viewport, 0.0);
}

/*
 * shumate_viewport_set_rotation() rotates counterclockwise and wants radians.
 * atan2() also rotate counterclockwise and outputs radians.
 */
double
maps_page_adjust_heading (ShumateLocation *prev_location,
                          double lat2,
                          double lng2)
{
  double lat1;
  double lng1;
  double delta_y;
  double delta_x;
  double brng;

  lat1 = shumate_location_get_latitude (SHUMATE_LOCATION (prev_location));
  lng1 = shumate_location_get_longitude (SHUMATE_LOCATION (prev_location));
  /*
   * We shouldn't ever get this due to the distance filter, but it's good
   * practice to check anyways
   */
  if (lat2 == lat1 && lng2 == lng1)
    return 0;

  delta_y = lat2 - lat1;
  delta_x = lng2 - lng1;

  /*
   * While the Earth is spherical, our distances are so small
   * that we can assume the Earth is flat here
   */
  brng = atan2(delta_y, delta_x);
  /*
   * 0 is north, but in atan2(), 0 is east. We need to adjust
   * by -M_pi/2 to account for this.
   */
  brng = brng - M_PI/2;

  /*
   * atan2()-M_PI/2 does from -3*M_PI/2 to M_PI/2.
   * Make sure bearing is in between 0 and 2*M_PI
   */
  if (brng < 0)
    brng = brng + 2*M_PI;

  return brng;
}

void
geoclue_stumbler_maps_page_reset_path (GeoclueStumblerMapsPage *self)
{
  g_debug ("Deleting Path from Map");
  shumate_path_layer_remove_all (self->path_layer);
}

void
geoclue_stumbler_maps_page_reset_submission_markers (GeoclueStumblerMapsPage *self)
{
  g_debug ("Deleting submission markers from Map");

  shumate_marker_layer_remove_all (self->submission_marker_layer);
}

void
geoclue_stumbler_maps_page_update_marker_no_fix (GeoclueStumblerMapsPage *self,
                                                 double lat,
                                                 double lng,
                                                 double accuracy_in_meters)
{
  shumate_location_set_location (SHUMATE_LOCATION (self->marker), lat, lng);

  if (!self->first_marker) {
    ShumateMap *map;
    shumate_marker_layer_add_marker (self->marker_layer, SHUMATE_MARKER (self->marker));

    self->first_marker = TRUE;
    map = shumate_simple_map_get_map(self->simple_map);
    shumate_map_go_to_full_with_duration (map, lat, lng, self->zoom_level, 500);
  }
  geoclue_stumbler_marker_set_accuracy (self->marker, accuracy_in_meters);
}

void
geoclue_stumbler_maps_page_update_marker (GeoclueStumblerMapsPage *self,
                                          double lat,
                                          double lng,
                                          double heading)
{
  ShumateCoordinate *path_marker = shumate_coordinate_new_full (lat, lng);
  double distance_from_patch = 0.0;
  double adjusted_heading = 0.0;
  double current_heading = 0.0;
  ShumateMap *map;

  map = shumate_simple_map_get_map(self->simple_map);

  if (!self->first_marker) {
    shumate_map_go_to_full_with_duration (map, lat, lng, self->zoom_level, 500);
    shumate_marker_layer_add_marker (self->marker_layer, SHUMATE_MARKER (self->marker));

    shumate_location_set_location (SHUMATE_LOCATION (self->marker), lat, lng);
    self->first_marker = TRUE;
  }

  geoclue_stumbler_marker_set_accuracy (self->marker, 0);

  /*
   * https://www.healthline.com/health/exercise-fitness/average-walking-speed#average-speed-by-age
   * Says 0.89 Meters/second looks to be a lower bound
   */
  distance_from_patch = shumate_location_distance (SHUMATE_LOCATION (self->marker), SHUMATE_LOCATION (path_marker));
  g_debug ("Distance from Path: %f", distance_from_patch);
  if (distance_from_patch < 0.7 && !isnan(distance_from_patch) && !isinf(distance_from_patch)) {
     g_debug("Too small of a distance to mark");
     g_object_unref (path_marker);
     return;
  }

  if (self->compass_follow)
    adjusted_heading = maps_page_adjust_heading (SHUMATE_LOCATION (self->marker), lat, lng);
  else
    adjusted_heading = 0.0;

  shumate_location_set_location (SHUMATE_LOCATION (self->marker), lat, lng);

  if (geoclue_stumbler_path_page_get_recording_path (GEOCLUE_STUMBLER_PATH_PAGE (self->path_page_bin)))
    shumate_path_layer_add_node (self->path_layer, SHUMATE_LOCATION (path_marker));

  current_heading = shumate_viewport_get_rotation (self->viewport);
  if (current_heading != adjusted_heading)
    shumate_viewport_set_rotation (self->viewport, adjusted_heading);

  if (self->zoom_follow)
    shumate_map_go_to_full_with_duration (map, lat, lng, self->zoom_level, 500);
}

static void
geoclue_stumbler_maps_page_class_init (GeoclueStumblerMapsPageClass *klass)
{
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

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

  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerMapsPage, simple_map);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerMapsPage, compass_revealer);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerMapsPage, zoom_button);
  gtk_widget_class_bind_template_child (widget_class, GeoclueStumblerMapsPage, compass_button);

  gtk_widget_class_bind_template_callback (widget_class, geoclue_stumbler_maps_page_zoom_button_clicked_cb);
  gtk_widget_class_bind_template_callback (widget_class, geoclue_stumbler_maps_page_compass_button_clicked_cb);
}

static void
geoclue_stumbler_maps_page_init (GeoclueStumblerMapsPage *self)
{
  ShumateMapSource *map_source;

  gtk_widget_init_template (GTK_WIDGET (self));

  self->registry = shumate_map_source_registry_new_with_defaults ();
  map_source = shumate_map_source_registry_get_by_id (self->registry, SHUMATE_MAP_SOURCE_OSM_MAPNIK);
  shumate_simple_map_set_map_source (self->simple_map, map_source);

  self->viewport = shumate_simple_map_get_viewport (self->simple_map);
  self->submission_marker_layer = shumate_marker_layer_new (self->viewport);
  /*
   * order matters for shumate_simple_map_add_overlay_layer()! The last
   * shumate_simple_map_add_overlay_layer () goes on top.
   */
  shumate_simple_map_add_overlay_layer (self->simple_map, SHUMATE_LAYER (self->submission_marker_layer));
  self->path_layer = shumate_path_layer_new (self->viewport);
  shumate_simple_map_add_overlay_layer (self->simple_map, SHUMATE_LAYER (self->path_layer));
  self->marker_layer = shumate_marker_layer_new (self->viewport);
  shumate_simple_map_add_overlay_layer (self->simple_map, SHUMATE_LAYER (self->marker_layer));

  self->first_marker = FALSE;
  self->zoom_fast_click = FALSE;
  self->zoom_follow = FALSE;
  self->compass_follow = FALSE;

  self->marker = geoclue_stumbler_marker_new ();
  geoclue_stumbler_marker_set_map (self->marker,
                                   self->simple_map);
  self->zoom_level = 17;

}
