/*
 * picplanner-window.c
 * Copyright (C) 2021 Zwarf <zwarf@mail.de>
 *
 * 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 3 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/>.
 */

#include "window/picplanner-window.h"
#include "window/overview-page/overview-view.h"
#include "window/sun-page/sun-view.h"
#include "window/moon-page/moon-view.h"
#include "window/milky-way-page/milky-way-view.h"
#include "calculations/calculations_sun.h"
#include "calculations/calculations_moon.h"
#include "calculations/calculations_milky_way.h"


/*
 * The time of no input of map movement that has to pass until a calculation of the positions
 * of sun, moon or milky way starts.
 */
#define INPUT_CHANGED_TIMEOUT_LENGTH 100


struct _PicplannerWindow
{
  GtkApplicationWindow  parent_instance;

  /* Template widgets */
  GtkHeaderBar      *header_bar;
  GtkWidget         *box_main;
  GtkWidget         *stack;           /* Stack containing overview, sun, moon and milky way page */
  GtkWidget         *search_bar;      /* Search bar for the location */
  GtkWidget         *search_entry;    /* The search entry inside the search bar */
  GtkWidget         *search_button;   /* The search button in the header bar so show the search bar */
  GtkWidget         *map_button;      /* The map button in the header bar to show the map fullscreen */
  GtkWidget         *location_button; /* Button in the header bar to start the GPS search of the device location */
  GtkWidget         *overview_box;    /* The overview page */
  GtkWidget         *sun_box;         /* The sun page */
  GtkWidget         *moon_box;        /* The moon page */
  GtkWidget         *milky_way_box;   /* The milky way page */
  GtkWidget         *north_entry;     /* SpinButton for North coordinates */
  GtkWidget         *east_entry;      /* SpinButton for East coordinates */
  GtkWidget         *clamp_time_selector;
  GtkWidget         *overview_page;

  /* Search functionality */
  GtkWidget         *search_result_box; /* ListBox containing search results */
  GtkWidget         *search_results_scroll; /* ScrollView containing search_result_box */
  GtkWidget         *search_result_row[NUM_SEARCH_RESULTS]; /* Row widget for every search result */
  GtkWidget         *search_result_label[NUM_SEARCH_RESULTS]; /* The label showing the coordinates */
  GWeatherLocation  *search_result_location[NUM_SEARCH_RESULTS]; /* The location information for every search result */

  /* Time and date selector */
  GtkWidget           *label_time_zone;

  GtkWidget           *calendar;
  GtkWidget           *calendar_popover;
  GtkWidget           *calendar_button;

  GtkWidget           *spin_button_hour;
  GtkWidget           *spin_button_minute;

  /* Regulating amount of inputs per time */
  gint               input_count;
  guint              input_timeout_id;
  gboolean           input_new;

  /* All settings of PicPlanner */
  GSettings         *settings;

  GDateTime         *date_time;
};

G_DEFINE_TYPE (PicplannerWindow, picplanner_window, ADW_TYPE_APPLICATION_WINDOW)


/*
 * Function used after the user selects a search result
 */
static void
search_location_chosen (GtkWidget *self,
                        GtkWidget *row,
                        gpointer  user_data)
{
  (void) self;
  PicplannerWindow *window = user_data;
  double latitude;
  double longitude;
  double *p_latitude = &latitude;
  double *p_longitude = &longitude;

  int i;
  for (i=0; i<NUM_SEARCH_RESULTS; i++)
    {
      if (row == window->search_result_row[i])
        break;
    }
  gweather_location_get_coords (GWEATHER_LOCATION (window->search_result_location[i]),
                                p_latitude,
                                p_longitude);
  gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (window->search_bar),
                                  FALSE);

  gtk_spin_button_set_value (GTK_SPIN_BUTTON (window->north_entry),
                             latitude);
  gtk_spin_button_set_value (GTK_SPIN_BUTTON (window->east_entry),
                             longitude);
}


/*
 * Function activated after user input into the search entry.
 * TODO: Rewrite with dynamic memory? to search.c
 * This function is more like a work around and will definitely be changed.
 */
static void
search_location (GtkWidget  *self,
                 gpointer   user_data)
{
  (void) self;
  PicplannerWindow *window = user_data;

  const char *search_text = gtk_editable_get_text (GTK_EDITABLE (window->search_entry));

  char *search_norm = g_utf8_normalize (search_text, strlen (search_text), G_NORMALIZE_ALL_COMPOSE);
  char *search_string = g_utf8_casefold (search_text, strlen (search_norm));

  g_autoptr (GWeatherLocation) world = NULL;

  world = gweather_location_get_world ();

  for (int i=0; i<NUM_SEARCH_RESULTS; i++)
    {
      if (GWEATHER_IS_LOCATION (window->search_result_location[i]))
        g_object_unref (window->search_result_location[i]);
      window->search_result_location[i] = NULL;
    }

  if (!strstr ("", search_string))
    {
      int iter = 0;
      int *iteration = &iter;

      double longitude;
      double latitude;
      double *p_longitude = &longitude;
      double *p_latitude = &latitude;

      char *char_coordinates;
      char *char_subtitle;

      gtk_widget_set_visible (window->stack, FALSE);
      gtk_widget_set_visible (window->clamp_time_selector, FALSE);
      gtk_widget_set_visible (window->search_results_scroll, TRUE);

      find_loc_children (world, search_string, window->search_result_location, iteration);

      for (int i=0; i<NUM_SEARCH_RESULTS; i++)
        {
          if (GTK_IS_WIDGET (window->search_result_row[i]))
            {
              gtk_list_box_remove (GTK_LIST_BOX (window->search_result_box),
                                            window->search_result_row[i]);
            }

          window->search_result_row[i] = NULL;

          if (window->search_result_location[i])
            {
              window->search_result_row[i] = adw_action_row_new ();
              gweather_location_get_coords (window->search_result_location[i],
                                            p_latitude, p_longitude);
              char_coordinates = g_strdup_printf ("N: %.04f\nE: %.04f", latitude, longitude);

              adw_preferences_row_set_title (ADW_PREFERENCES_ROW (window->search_result_row[i]),
                                             gweather_location_get_name (window->search_result_location[i]));
              if (gweather_location_get_level (
                  gweather_location_get_parent (window->search_result_location[i])) == GWEATHER_LOCATION_COUNTRY ||
                  gweather_location_get_level (window->search_result_location[i]) == GWEATHER_LOCATION_COUNTRY)
                char_subtitle = g_strdup_printf ("%s",
                                                 gweather_location_get_country_name (window->search_result_location[i]));
              else
                char_subtitle = g_strdup_printf ("%s, %s",
                                                 gweather_location_get_name (gweather_location_get_parent (window->search_result_location[i])),
                                                 gweather_location_get_country_name (window->search_result_location[i]));


              adw_action_row_set_subtitle (ADW_ACTION_ROW (window->search_result_row[i]),
                                           char_subtitle);

              window->search_result_label[i] = gtk_label_new (char_coordinates);

              adw_action_row_add_suffix (ADW_ACTION_ROW (window->search_result_row[i]),
                                         window->search_result_label[i]);

              gtk_widget_set_can_focus (window->search_result_row[i], TRUE);
              gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (window->search_result_row[i]), TRUE);
              gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (window->search_result_row[i]), TRUE);

              gtk_list_box_append (GTK_LIST_BOX (window->search_result_box),
                                         window->search_result_row[i]);

              g_free (char_coordinates);
            }
        }

    }

  else
    {
      gtk_widget_set_visible (window->stack, TRUE);
      gtk_widget_set_visible (window->clamp_time_selector, TRUE);
      gtk_widget_set_visible (window->search_results_scroll, FALSE);
    }

  g_free (search_norm);
  g_free (search_string);
}

/*
 * Show the map in fullscreen
 */
static void
map_fullscreen (GtkToggleButton   *self,
                PicplannerWindow  *window)
{
  (void) self;
  picplanner_overview_map_fullscreen (PICPLANNER_OVERVIEW (window->overview_box),!gtk_toggle_button_get_active (self));
}


/*
 * Hide the map fullscreen button if the active view is not "page1" (overview)
 */
static void
stack_changed (AdwViewStack *self,
               gpointer pspec,
               PicplannerWindow *window)
{
  (void) pspec;
  if (!strcmp(adw_view_stack_get_visible_child_name (self),"page1"))
    gtk_widget_set_visible (window->map_button, TRUE);
  else
    gtk_widget_set_visible (window->map_button, FALSE);
}

void
picplanner_set_location (double latitude, double longitude, PicplannerWindow *window)
{
  gtk_spin_button_set_value (GTK_SPIN_BUTTON (window->north_entry), latitude);
  gtk_spin_button_set_value (GTK_SPIN_BUTTON (window->east_entry), longitude);
}


/*
 * Changing the date_time variable after a user input was recognized.
 * Afterwards, emit a signal to start the calculations.
 */
void
picplanner_update_date_time (PicplannerWindow *window)
{
  int year, month, day, hour, minute;
  char *char_time_zone;
  GDateTime *date_selected;

  double latitude, longitude;
  GWeatherLocation *loc;

  longitude = gtk_spin_button_get_value (GTK_SPIN_BUTTON (window->east_entry));
  latitude = gtk_spin_button_get_value (GTK_SPIN_BUTTON (window->north_entry));
  loc = gweather_location_new_detached ("Custom Location", NULL, latitude, longitude);

  /*
   * Get year, month and day information from the calendar widget.
   */
  date_selected = gtk_calendar_get_date (GTK_CALENDAR (window->calendar));
  year = g_date_time_get_year (date_selected);
  month = g_date_time_get_month (date_selected);
  day = g_date_time_get_day_of_month (date_selected);

  /*
   * Get hour and minute from the spin buttons.
   */
  hour = gtk_spin_button_get_value (GTK_SPIN_BUTTON (window->spin_button_hour));
  minute = gtk_spin_button_get_value (GTK_SPIN_BUTTON (window->spin_button_minute));

  /*
   * Save the information into the date_time variable.
   * The time zone is either selected automatically or taken from the input spin button in the preference dialog.
   */
  if (!g_settings_get_boolean (window->settings, "time-zone-manual"))
    {
      window->date_time = g_date_time_new (gweather_location_get_timezone (loc),
                                             year, month, day,
                                             hour, minute,
                                             0);
      g_settings_set_double (window->settings,
                             "time-zone",
                             (double) g_date_time_get_utc_offset (window->date_time)/3600/1000/1000);
    }
  else
    {
      window->date_time = g_date_time_new (g_time_zone_new_offset ((int) (g_settings_get_double (window->settings, "time-zone")*3600)),
                                             year, month, day,
                                             hour, minute,
                                             0);
    }

  double time_zone = (double) g_date_time_get_utc_offset (window->date_time)/3600/1000/1000;
  char_time_zone = g_strdup_printf ("UTC %s%.1f", time_zone>=0 ? "+" : "", time_zone);
  gtk_label_set_text (GTK_LABEL (window->label_time_zone), char_time_zone);

  g_free (char_time_zone);
  g_date_time_unref (date_selected);
}


/*
 * Starts the main calculations.
 * 1. The necessary information is collected from the inputs.
 * 2. The position sun, moon and milky way are calculated.
 * 3. The calculated data is handed over to the views which then provide the information to the users.
 */
static void
calculate_positions (PicplannerWindow *window)
{
  int       current_index;
  int       phase_moon_index;

  double    longitude, latitude;
  double    illumination_moon, illumination_moon_later;

  int       *rise_upper_set_lower_index_sun;
  int       *rise_upper_set_index_moon;
  int       *rise_upper_set_index_milky_way;
  int       *dark_blue_golden_index;

  double    *array_coordinates_sun;
  double    *array_coordinates_moon;
  double    *array_coordinates_milky_way;

  char      *phase_moon;

  GDateTime *date_time_noon;
  GDateTime *date_time_later;


  longitude = gtk_spin_button_get_value (GTK_SPIN_BUTTON (window->east_entry));
  latitude = gtk_spin_button_get_value (GTK_SPIN_BUTTON (window->north_entry));

  picplanner_update_date_time (window);

  date_time_noon = g_date_time_new (g_date_time_get_timezone (window->date_time),
                                    g_date_time_get_year (window->date_time),
                                    g_date_time_get_month (window->date_time),
                                    g_date_time_get_day_of_month (window->date_time),
                                    12, 0, 0);

  current_index = g_date_time_difference (window->date_time, date_time_noon)/1000/1000/60 * NUM_DATA_POINTS/24/60 + NUM_DATA_POINTS/2;

  /*
   * Sun
   */

  array_coordinates_sun = picplanner_get_array_coordinates_sun (date_time_noon,
                                                                longitude,
                                                                latitude);

  rise_upper_set_lower_index_sun = picplanner_get_index_rise_upper_set_lower (array_coordinates_sun);
  dark_blue_golden_index = picplanner_get_index_dark_blue_golden (array_coordinates_sun);

  picplanner_overview_set_current_coordinates_sun (PICPLANNER_OVERVIEW (window->overview_box),
                                                   array_coordinates_sun,
                                                   rise_upper_set_lower_index_sun,
                                                   current_index,
                                                   window->date_time);

  picplanner_sun_set_rise_upper_set (PICPLANNER_SUN (window->sun_box),
                                     date_time_noon,
                                     array_coordinates_sun,
                                     rise_upper_set_lower_index_sun);

  picplanner_sun_set_dark_blue_golden (PICPLANNER_SUN (window->sun_box),
                                       date_time_noon,
                                       dark_blue_golden_index);


  /*
   * Moon
   */
  array_coordinates_moon = picplanner_get_array_coordinates_moon (date_time_noon,
                                                                  longitude,
                                                                  latitude);

  rise_upper_set_index_moon = picplanner_get_index_rise_upper_set_lower (array_coordinates_moon);


  picplanner_overview_set_current_coordinates_moon (PICPLANNER_OVERVIEW (window->overview_box),
                                                    array_coordinates_moon,
                                                    rise_upper_set_index_moon,
                                                    current_index,
                                                    window->date_time);

  picplanner_moon_set_rise_upper_set (PICPLANNER_MOON (window->moon_box),
                                      date_time_noon,
                                      array_coordinates_moon,
                                      rise_upper_set_index_moon);


  date_time_later = g_date_time_add_hours (window->date_time, 1);
  illumination_moon = picplanner_get_illumination (window->date_time);
  illumination_moon_later = picplanner_get_illumination (date_time_later);
  if (illumination_moon > 99.5)
    {
      phase_moon_index = 0;
      phase_moon = g_strdup_printf ("Full Moon");
    }
  else if (illumination_moon_later >= illumination_moon && illumination_moon > 0.5)
    {
      phase_moon_index = 1;
      phase_moon = g_strdup_printf ("Waxing Moon");
    }
  else if (illumination_moon_later < illumination_moon && illumination_moon > 0.5)
    {
      phase_moon_index = 2;
      phase_moon = g_strdup_printf ("Waning Moon");
    }
  else
    {
      phase_moon_index = 3;
      phase_moon = g_strdup_printf ("New Moon");
    }
  picplanner_moon_set_illumination_intensity (PICPLANNER_MOON (window->moon_box),
                                              illumination_moon,
                                              phase_moon);


  /*
   * Milky Way
   */
  array_coordinates_milky_way = picplanner_get_array_coordinates_milky_way (date_time_noon,
                                                                            longitude,
                                                                            latitude);

  rise_upper_set_index_milky_way = picplanner_get_index_rise_upper_set_lower (array_coordinates_milky_way);

  picplanner_overview_set_current_coordinates_milky_way (PICPLANNER_OVERVIEW (window->overview_box),
                                                         array_coordinates_milky_way,
                                                         rise_upper_set_index_milky_way,
                                                         current_index,
                                                         window->date_time);

  picplanner_milky_way_set_rise_upper_set (PICPLANNER_MILKYWAY (window->milky_way_box),
                                           date_time_noon,
                                           array_coordinates_milky_way,
                                           rise_upper_set_index_milky_way);


  picplanner_milky_way_set_disturbance (PICPLANNER_MILKYWAY (window->milky_way_box),
                                        date_time_noon,
																				array_coordinates_sun,
																				array_coordinates_moon,
																				array_coordinates_milky_way);


  /*
   * Update Shumate Map
   */
  picplanner_overview_update_map_center (PICPLANNER_OVERVIEW (window->overview_box));


  if (g_settings_get_boolean (window->settings, "sun-visible"))
    {
      picplanner_overview_update_map_sun (PICPLANNER_OVERVIEW (window->overview_box),
                                      array_coordinates_sun,
                                      current_index,
                                      rise_upper_set_lower_index_sun);

    }

  if (g_settings_get_boolean (window->settings, "moon-visible"))
    {
      picplanner_overview_update_map_moon (PICPLANNER_OVERVIEW (window->overview_box),
                                       array_coordinates_moon,
                                       current_index,
                                       rise_upper_set_index_moon,
                                       phase_moon_index);
    }

  if (g_settings_get_boolean (window->settings, "milky-way-visible"))
    {
      picplanner_overview_update_map_milky_way (PICPLANNER_OVERVIEW (window->overview_box),
                                                array_coordinates_milky_way,
                                                current_index,
                                                rise_upper_set_index_milky_way);
    }

  /*
   * Free allocated memory
   */
  g_free (rise_upper_set_lower_index_sun);
  g_free (rise_upper_set_index_moon);
  g_free (rise_upper_set_index_milky_way);
  g_free (dark_blue_golden_index);
  g_free (array_coordinates_sun);
  g_free (array_coordinates_moon);
  g_free (array_coordinates_milky_way);
  g_free (phase_moon);
  g_date_time_unref (date_time_noon);

  /*
   * TODO:
   * Free loc?
   */
}



/* input_timeout_signal:
 * If the timer has been running out it is checked if the counter was increased during the time
 * or not. If it has been increased it is reseted to 0 and the timer continues.
 * If no input was detected in the meantime (input_counter==0) a new detection series is
 * initialized by setting input_new = TRUE. Furthermore the userinput is saved with
 * change_date_time and the timer is deactivated.
 */
static gboolean
input_timeout_signal (gpointer user_data)
{
  PicplannerWindow *window = PICPLANNER_WINDOW (user_data);
  if (window->input_count == 0)
    {
      window->input_new = TRUE;
      calculate_positions (window);
      g_source_remove (window->input_timeout_id);
    }
  window->input_count = 0;

  return TRUE;
}


/*
 * input_changed:
 * Function to call if the user changes the location or date/time.
 * At the beginning input_new is TRUE because a new input starts.
 * The input_new variable is set to FALSE if a user input is detected, and a timer is started.
 * The input_count is increased from now on by one, every time a user input is detected.
 */
static void
input_changed (GtkWidget  *self,
               gpointer   data_map_move,
               gpointer   data_window_change)
{
  (void) self;
  PicplannerWindow *window;

  if (PICPLANNER_IS_WINDOW (data_window_change))
    window = PICPLANNER_WINDOW (data_window_change);
  else
    window = PICPLANNER_WINDOW (data_map_move);

  if (window->input_new)
  {
    window->input_new = FALSE;
    window->input_timeout_id = g_timeout_add (INPUT_CHANGED_TIMEOUT_LENGTH,
                                              input_timeout_signal,
                                              window);
  }

  picplanner_overview_update_map_center (PICPLANNER_OVERVIEW (window->overview_box));

  window->input_count++;
}


/*
 * Remove all children on the map and cause a new input_changed event.
 */
static void
input_changed_remove_content (GtkWidget *self,
                              gpointer  data_map_move,
                              gpointer  data_window_change)
{
  (void) self;
  PicplannerWindow *window;

  if (PICPLANNER_IS_WINDOW (data_window_change))
    window = PICPLANNER_WINDOW (data_window_change);
  else
    window = PICPLANNER_WINDOW (data_map_move);

  if (window->input_new)
    picplanner_overview_remove_map_content (PICPLANNER_OVERVIEW (window->overview_box));

  input_changed (self, data_map_move, window);
}

/*
 * Change the text of the GtkSpinButton to always be a two digit number.
 * E.g. don't show one o'clock AM as 1:0 but show 01:00
 */
static gboolean
time_spin_button_text (GtkSpinButton *self)
{
  int value;
  char *button_text;
  GtkAdjustment *adjustment;

  adjustment = gtk_spin_button_get_adjustment (self);
  value = (int) gtk_adjustment_get_value (adjustment);
  button_text = g_strdup_printf ("%02d", value);
  gtk_editable_set_text (GTK_EDITABLE (self),
                         button_text);
  g_free (button_text);

  return TRUE;
}


/*
 * Change the text of the button which presents the calender after a date was selected.
 * Furthermore the behavior of the popup is controlled.
 */
static void
day_selected (GtkCalendar         *self,
              PicplannerWindow    *window)
{
  (void) self;
  GDateTime *date_selected;
  GtkPopover *popover;
  char *button_text;

  date_selected = gtk_calendar_get_date (GTK_CALENDAR (window->calendar));

  /*
   * Set text of the button to the selected date
   */
  button_text = g_strdup_printf ("%d.%d.%d",
                                 g_date_time_get_day_of_month (date_selected),
                                 g_date_time_get_month (date_selected),
                                 g_date_time_get_year (date_selected));
  gtk_menu_button_set_label (GTK_MENU_BUTTON (window->calendar_button),
                             button_text);

  /*
   * Close Popover after the day was selected
   * TODO: Popdown happens automatically if a month is chosen which has less days then
   * the day number previously selected.
   */
  popover = GTK_POPOVER(window->calendar_popover);
  gtk_popover_popdown (popover);

  g_free (button_text);
  g_date_time_unref (date_selected);

  input_changed (GTK_WIDGET (self), window, NULL);
}


static void
picplanner_window_class_init (PicplannerWindowClass *klass)
{
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  g_type_ensure (PICPLANNER_OVERVIEW_TYPE);
  g_type_ensure (PICPLANNER_SUN_TYPE);
  g_type_ensure (PICPLANNER_MOON_TYPE);
  g_type_ensure (PICPLANNER_MILKYWAY_TYPE);

  gtk_widget_class_set_template_from_resource (widget_class, "/de/zwarf/picplanner/window/picplanner-window.ui");
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, box_main);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, stack);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, header_bar);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, search_bar);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, search_entry);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, search_button);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, map_button);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, location_button);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, overview_box);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, sun_box);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, moon_box);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, milky_way_box);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, north_entry);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, east_entry);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, search_result_box);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, search_results_scroll);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, clamp_time_selector);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, label_time_zone);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, calendar);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, calendar_popover);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, calendar_button);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, spin_button_hour);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, spin_button_minute);
  gtk_widget_class_bind_template_child (widget_class, PicplannerWindow, overview_page);


  gtk_widget_class_bind_template_callback (widget_class, day_selected);
  gtk_widget_class_bind_template_callback (widget_class, time_spin_button_text);
  gtk_widget_class_bind_template_callback (widget_class, search_location);
}


static void
picplanner_window_init (PicplannerWindow *window)
{
  GTimeZone *timezone;
  gtk_widget_init_template (GTK_WIDGET (window));

  window->settings = g_settings_new ("de.zwarf.picplanner");

  /*
   * Setting the date and time of the date and time selector to the current date and time
   * with the timezone set in preferences
   */
  timezone = g_time_zone_new_offset (g_settings_get_double (window->settings, "time-zone") * 3600);
  window->date_time = g_date_time_new_now (timezone);
  gtk_calendar_select_day (GTK_CALENDAR (window->calendar), window->date_time);
  gtk_spin_button_set_value (GTK_SPIN_BUTTON (window->spin_button_hour),
                             g_date_time_get_hour (window->date_time));
  gtk_spin_button_set_value (GTK_SPIN_BUTTON (window->spin_button_minute),
                             g_date_time_get_minute (window->date_time));

  /*
   * Callbacks
   */

  g_settings_bind (window->settings, "longitude",
                   window->east_entry, "value",
                   G_SETTINGS_BIND_DEFAULT);

  g_settings_bind (window->settings, "latitude",
                   window->north_entry, "value",
                   G_SETTINGS_BIND_DEFAULT);

  g_object_bind_property (window->search_button, "active",
                          window->search_bar, "search-mode-enabled",
                          G_BINDING_BIDIRECTIONAL);


  g_signal_connect (G_OBJECT (window->north_entry),
                    "value-changed",
                    G_CALLBACK (input_changed_remove_content),
                    window);

  g_signal_connect (G_OBJECT (window->east_entry),
                    "value-changed",
                    G_CALLBACK (input_changed_remove_content),
                    window);

  g_signal_connect (G_OBJECT (window->overview_box),
                    "input-changed",
                    G_CALLBACK (input_changed),
                    window);

  g_signal_connect (G_OBJECT (window->settings),
                    "changed::sun-visible",
                    G_CALLBACK (input_changed_remove_content),
                    window);

  g_signal_connect (G_OBJECT (window->settings),
                    "changed::moon-visible",
                    G_CALLBACK (input_changed_remove_content),
                    window);

  g_signal_connect (G_OBJECT (window->settings),
                    "changed::milky-way-visible",
                    G_CALLBACK (input_changed_remove_content),
                    window);

  g_signal_connect (G_OBJECT (window->search_result_box),
                    "row-activated",
                    G_CALLBACK (search_location_chosen),
                    window);

  g_signal_connect (G_OBJECT (window->spin_button_hour),
                    "value-changed",
                    G_CALLBACK (input_changed),
                    window);

  g_signal_connect (G_OBJECT (window->spin_button_minute),
                    "value-changed",
                    G_CALLBACK (input_changed),
                    window);

  g_signal_connect (G_OBJECT (window->calendar),
                    "day-selected",
                    G_CALLBACK (day_selected),
                    window);

  g_signal_connect (G_OBJECT (window->map_button),
                    "toggled",
                    G_CALLBACK (map_fullscreen),
                    window);

  g_signal_connect (G_OBJECT (window->stack),
                    "notify::visible-child",
                    G_CALLBACK (stack_changed),
                    window);

  g_signal_connect (G_OBJECT (window->location_button),
                    "clicked",
                    G_CALLBACK (get_user_location),
                    window);

  /*
   * Initialisation of values needed to detect when a user input ends
   */
  window->input_new = true;
  window->input_count = 0;
  window->input_timeout_id = 0;


  /*
   * Setup the location searchbar to register keyboard input
   */
  gtk_search_bar_connect_entry (GTK_SEARCH_BAR (window->search_bar), GTK_EDITABLE (window->search_entry));
  gtk_search_bar_set_key_capture_widget (GTK_SEARCH_BAR (window->search_bar), GTK_WIDGET (window));

  /*
   * Initial calculation after startup
   */
  calculate_positions (window);
}
