/* nautilus-search-popover.c
 *
 * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
 *
 * 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 <gtk/gtk.h>
#include <adwaita.h>

#include "nautilus-enum-types.h"
#include "nautilus-search-popover.h"
#include "nautilus-mime-actions.h"

#include <glib/gi18n.h>
#include "nautilus-file.h"
#include "nautilus-icon-info.h"
#include "nautilus-minimal-cell.h"
#include "nautilus-ui-utilities.h"
#include "nautilus-global-preferences.h"

 #define SEARCH_FILTER_MAX_YEARS 5

struct _NautilusSearchPopover
{
    GtkPopover parent;

    GtkWidget *around_revealer;
    GtkWidget *around_stack;
    GtkWidget *calendar;
    GtkWidget *clear_date_button;
    GtkWidget *dates_listbox;
    GtkWidget *date_entry;
    GtkWidget *date_stack;
    GtkWidget *select_date_button;
    GtkWidget *select_date_button_label;
    GtkWidget *type_label;
    GtkWidget *type_listbox;
    GtkWidget *type_stack;
    GtkWidget *last_used_button;
    GtkWidget *last_modified_button;
    GtkWidget *created_button;
    GtkWidget *full_text_search_button;
    GtkWidget *filename_search_button;

    AdwDialog *type_dialog;
    GtkStack *type_dialog_stack;

    NautilusQuery *query;
    GtkSingleSelection *other_types_model;

    gboolean fts_enabled;
};

static void          show_date_selection_widgets (NautilusSearchPopover *popover,
                                                  gboolean               visible);

static void          show_other_types_dialog (NautilusSearchPopover *popover);

static void          update_date_label (NautilusSearchPopover *popover,
                                        GPtrArray             *date_range);

G_DEFINE_TYPE (NautilusSearchPopover, nautilus_search_popover, GTK_TYPE_POPOVER)

enum
{
    PROP_0,
    PROP_QUERY,
    PROP_FTS_ENABLED,
    LAST_PROP
};

enum
{
    MIME_TYPE,
    TIME_TYPE,
    DATE_RANGE,
    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL];


/* Callbacks */

static void
calendar_day_selected (GtkCalendar           *calendar,
                       NautilusSearchPopover *popover)
{
    GDateTime *date;
    GPtrArray *date_range;

    date = gtk_calendar_get_date (calendar);

    date_range = g_ptr_array_new_full (2, (GDestroyNotify) g_date_time_unref);
    g_ptr_array_add (date_range, g_date_time_ref (date));
    g_ptr_array_add (date_range, g_date_time_ref (date));
    update_date_label (popover, date_range);
    g_signal_emit_by_name (popover, "date-range", date_range);

    g_ptr_array_unref (date_range);
    g_date_time_unref (date);
}

/* Range on dates are partially implemented. For now just use it for differentation
 * between a exact day or a range of a first day until now.
 */
static void
setup_date (NautilusSearchPopover *popover,
            NautilusQuery         *query)
{
    GPtrArray *date_range;
    GDateTime *date_initial;

    date_range = nautilus_query_get_date_range (query);

    if (date_range)
    {
        date_initial = g_ptr_array_index (date_range, 0);

        g_signal_handlers_block_by_func (popover->calendar, calendar_day_selected, popover);

        gtk_calendar_select_day (GTK_CALENDAR (popover->calendar), date_initial);

        update_date_label (popover, date_range);

        g_signal_handlers_unblock_by_func (popover->calendar, calendar_day_selected, popover);
    }
}

static void
query_date_changed (GObject               *object,
                    GParamSpec            *pspec,
                    NautilusSearchPopover *popover)
{
    setup_date (popover, NAUTILUS_QUERY (object));
}

static void
clear_date_button_clicked (GtkButton             *button,
                           NautilusSearchPopover *popover)
{
    nautilus_search_popover_reset_date_range (popover);
}

static void
date_entry_activate (GtkEntry              *entry,
                     NautilusSearchPopover *popover)
{
    if (gtk_entry_get_text_length (entry) > 0)
    {
        GDateTime *now;
        GDateTime *date_time;
        GDate *date;

        date = g_date_new ();
        g_date_set_parse (date, gtk_editable_get_text (GTK_EDITABLE (entry)));

        /* Invalid date silently does nothing */
        if (!g_date_valid (date))
        {
            g_date_free (date);
            return;
        }

        now = g_date_time_new_now_local ();
        date_time = g_date_time_new_local (g_date_get_year (date),
                                           g_date_get_month (date),
                                           g_date_get_day (date),
                                           0,
                                           0,
                                           0);

        /* Future dates also silently fails */
        if (g_date_time_compare (date_time, now) != 1)
        {
            GPtrArray *date_range;

            date_range = g_ptr_array_new_full (2, (GDestroyNotify) g_date_time_unref);
            g_ptr_array_add (date_range, g_date_time_ref (date_time));
            g_ptr_array_add (date_range, g_date_time_ref (date_time));
            update_date_label (popover, date_range);
            show_date_selection_widgets (popover, FALSE);
            g_signal_emit_by_name (popover, "date-range", date_range);

            g_ptr_array_unref (date_range);
        }

        g_date_time_unref (now);
        g_date_time_unref (date_time);
        g_date_free (date);
    }
}

static void
dates_listbox_row_activated (GtkListBox            *listbox,
                             GtkListBoxRow         *row,
                             NautilusSearchPopover *popover)
{
    GDateTime *date;
    GDateTime *now;
    GPtrArray *date_range = NULL;

    now = g_date_time_new_now_local ();
    date = g_object_get_data (G_OBJECT (row), "date");
    if (date)
    {
        date_range = g_ptr_array_new_full (2, (GDestroyNotify) g_date_time_unref);
        g_ptr_array_add (date_range, g_date_time_ref (date));
        g_ptr_array_add (date_range, g_date_time_ref (now));
    }
    update_date_label (popover, date_range);
    show_date_selection_widgets (popover, FALSE);
    g_signal_emit_by_name (popover, "date-range", date_range);

    if (date_range)
    {
        g_ptr_array_unref (date_range);
    }
    g_date_time_unref (now);
}

static void
listbox_header_func (GtkListBoxRow         *row,
                     GtkListBoxRow         *before,
                     NautilusSearchPopover *popover)
{
    gboolean show_separator;

    show_separator = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "show-separator"));

    if (show_separator)
    {
        GtkWidget *separator;

        separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);

        gtk_list_box_row_set_header (row, separator);
    }
}

static void
select_date_button_clicked (GtkButton             *button,
                            NautilusSearchPopover *popover)
{
    /* Hide the type selection widgets when date selection
     * widgets are shown.
     */
    gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-button");

    show_date_selection_widgets (popover, TRUE);
}

static void
select_type_button_clicked (GtkButton             *button,
                            NautilusSearchPopover *popover)
{
    GtkListBoxRow *selected_row;

    selected_row = gtk_list_box_get_selected_row (GTK_LIST_BOX (popover->type_listbox));

    gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-list");
    if (selected_row != NULL)
    {
        gtk_widget_grab_focus (GTK_WIDGET (selected_row));
    }

    /* Hide the date selection widgets when the type selection
     * listbox is shown.
     */
    show_date_selection_widgets (popover, FALSE);
}

static void
toggle_calendar_icon_clicked (GtkEntry              *entry,
                              GtkEntryIconPosition   position,
                              NautilusSearchPopover *popover)
{
    const gchar *current_visible_child;
    const gchar *child;
    const gchar *icon_name;
    const gchar *tooltip;

    current_visible_child = gtk_stack_get_visible_child_name (GTK_STACK (popover->around_stack));

    if (g_strcmp0 (current_visible_child, "date-list") == 0)
    {
        child = "date-calendar";
        icon_name = "view-list-symbolic";
        tooltip = _("Show Time Ranges");
    }
    else
    {
        child = "date-list";
        icon_name = "x-office-calendar-symbolic";
        tooltip = _("Use Calendar");
    }

    gtk_stack_set_visible_child_name (GTK_STACK (popover->around_stack), child);
    gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY, icon_name);
    gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_SECONDARY, tooltip);
}

static void
types_listbox_row_activated (GtkListBox            *listbox,
                             GtkListBoxRow         *row,
                             NautilusSearchPopover *popover)
{
    gint group;

    group = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "mimetype-group"));

    /* The -1 group stands for the "Other Types" group, for which
     * we should show the mimetype dialog.
     */
    if (group == -1)
    {
        show_other_types_dialog (popover);
    }
    else
    {
        gtk_label_set_label (GTK_LABEL (popover->type_label),
                             nautilus_mime_types_group_get_name (group));

        g_signal_emit_by_name (popover, "mime-type", group, NULL);
    }

    gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-button");
}

static void
search_time_type_changed (GtkCheckButton        *button,
                          NautilusSearchPopover *popover)
{
    NautilusQuerySearchType type = -1;

    if (gtk_check_button_get_active (GTK_CHECK_BUTTON (popover->last_modified_button)))
    {
        type = NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED;
    }
    else if (gtk_check_button_get_active (GTK_CHECK_BUTTON (popover->last_used_button)))
    {
        type = NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS;
    }
    else
    {
        type = NAUTILUS_QUERY_SEARCH_TYPE_CREATED;
    }

    g_settings_set_enum (nautilus_preferences, "search-filter-time-type", type);

    g_signal_emit_by_name (popover, "time-type", type, NULL);
}

static void
search_fts_mode_changed (GtkToggleButton       *button,
                         NautilusSearchPopover *popover)
{
    if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (popover->full_text_search_button)) &&
        popover->fts_enabled == FALSE)
    {
        popover->fts_enabled = TRUE;
        g_settings_set_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_FTS_ENABLED, TRUE);
        g_object_notify (G_OBJECT (popover), "fts-enabled");
    }
    else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (popover->filename_search_button)) &&
             popover->fts_enabled == TRUE)
    {
        popover->fts_enabled = FALSE;
        g_settings_set_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_FTS_ENABLED, FALSE);
        g_object_notify (G_OBJECT (popover), "fts-enabled");
    }
}

/* Auxiliary methods */

static GtkWidget *
create_row_for_label (const gchar *text,
                      gboolean     show_separator)
{
    GtkWidget *row;
    GtkWidget *label;

    row = gtk_list_box_row_new ();

    g_object_set_data (G_OBJECT (row), "show-separator", GINT_TO_POINTER (show_separator));

    label = g_object_new (GTK_TYPE_LABEL,
                          "label", text,
                          "hexpand", TRUE,
                          "xalign", 0.0,
                          "margin-start", 6,
                          NULL);

    gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), label);

    return row;
}

static void
fill_fuzzy_dates_listbox (NautilusSearchPopover *popover)
{
    GDateTime *maximum_dt, *now;
    GtkWidget *row;
    GDateTime *current_date;
    GPtrArray *date_range;
    gint days, max_days;

    days = 1;
    maximum_dt = g_date_time_new_from_unix_local (0);
    now = g_date_time_new_now_local ();
    max_days = SEARCH_FILTER_MAX_YEARS * 365;

    /* Add the no date filter element first */
    row = create_row_for_label (_("Any time"), TRUE);
    gtk_list_box_insert (GTK_LIST_BOX (popover->dates_listbox), row, -1);

    /* This is a tricky loop. The main intention here is that each
     * timeslice (day, week, month) have 2 or 3 entries.
     *
     * For the first appearance of each timeslice, there is made a
     * check in order to be sure that there is no offset added to days.
     */
    while (days <= max_days)
    {
        gchar *label;
        gint normalized;
        gint step;

        if (days < 7)
        {
            /* days */
            normalized = days;
            step = 2;
        }
        else if (days < 30)
        {
            /* weeks */
            normalized = days / 7;
            if (normalized == 1)
            {
                days = 7;
            }
            step = 7;
        }
        else if (days < 365)
        {
            /* months */
            normalized = days / 30;
            if (normalized == 1)
            {
                days = 30;
            }
            step = 90;
        }
        else
        {
            /* years */
            normalized = days / 365;
            if (normalized == 1)
            {
                days = 365;
            }
            step = 365;
        }

        current_date = g_date_time_add_days (now, -days);
        date_range = g_ptr_array_new_full (2, (GDestroyNotify) g_date_time_unref);
        g_ptr_array_add (date_range, g_date_time_ref (current_date));
        g_ptr_array_add (date_range, g_date_time_ref (now));
        label = get_text_for_date_range (date_range, FALSE);
        row = create_row_for_label (label, normalized == 1);
        g_object_set_data_full (G_OBJECT (row),
                                "date",
                                g_date_time_ref (current_date),
                                (GDestroyNotify) g_date_time_unref);

        gtk_list_box_insert (GTK_LIST_BOX (popover->dates_listbox), row, -1);

        g_free (label);
        g_date_time_unref (current_date);
        g_ptr_array_unref (date_range);

        days += step;
    }

    g_date_time_unref (maximum_dt);
    g_date_time_unref (now);
}

static void
fill_types_listbox (NautilusSearchPopover *popover)
{
    GtkWidget *row;
    guint n_groups = nautilus_mime_types_get_number_of_groups ();

    /* Mimetypes */
    for (guint i = 0; i < n_groups; i++)
    {
        /* On the third row, which is right below "Folders", there should be an
         * separator to logically group the types.
         */
        row = create_row_for_label (nautilus_mime_types_group_get_name (i), i == 3);
        g_object_set_data (G_OBJECT (row), "mimetype-group", GINT_TO_POINTER (i));

        gtk_list_box_insert (GTK_LIST_BOX (popover->type_listbox), row, -1);
    }

    /* Other types */
    row = create_row_for_label (_("Other Type…"), TRUE);
    g_object_set_data (G_OBJECT (row), "mimetype-group", GINT_TO_POINTER (-1));
    gtk_list_box_insert (GTK_LIST_BOX (popover->type_listbox), row, -1);
}

static void
show_date_selection_widgets (NautilusSearchPopover *popover,
                             gboolean               visible)
{
    gtk_stack_set_visible_child_name (GTK_STACK (popover->date_stack),
                                      visible ? "date-entry" : "date-button");
    gtk_stack_set_visible_child_name (GTK_STACK (popover->around_stack), "date-list");
    gtk_entry_set_icon_from_icon_name (GTK_ENTRY (popover->date_entry),
                                       GTK_ENTRY_ICON_SECONDARY,
                                       "x-office-calendar-symbolic");

    gtk_widget_set_visible (popover->around_revealer, visible);

    gtk_revealer_set_reveal_child (GTK_REVEALER (popover->around_revealer), visible);
}

static void
on_other_types_dialog_response (NautilusSearchPopover *popover)
{
    NautilusMinimalCell *item = gtk_single_selection_get_selected_item (popover->other_types_model);
    const gchar *description = nautilus_minimal_cell_get_title (item);
    const gchar *mimetype = nautilus_minimal_cell_get_subtitle (item);

    gtk_label_set_label (GTK_LABEL (popover->type_label), description);

    g_signal_emit_by_name (popover, "mime-type", -1, mimetype);

    gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-button");

    g_clear_object (&popover->other_types_model);

    adw_dialog_close (popover->type_dialog);
}

static gchar *
join_type_and_description (NautilusMinimalCell *cell)
{
    const gchar *description = nautilus_minimal_cell_get_title (cell);
    const gchar *content_type = nautilus_minimal_cell_get_subtitle (cell);

    return g_strdup_printf ("%s %s", content_type, description);
}

static void
file_type_search_changed (GtkEditable           *search_entry,
                          GParamSpec            *pspec,
                          NautilusSearchPopover *self)
{
    const gchar *string = gtk_editable_get_text (search_entry);

    if (string == NULL || *string == '\0')
    {
        gtk_stack_set_visible_child_name (self->type_dialog_stack, "start");
        gtk_widget_set_sensitive (GTK_WIDGET (adw_dialog_get_default_widget (self->type_dialog)),
                                  FALSE);

        return;
    }

    guint result_count = g_list_model_get_n_items (G_LIST_MODEL (self->other_types_model));

    if (result_count == 0)
    {
        gtk_stack_set_visible_child_name (self->type_dialog_stack, "empty");
        gtk_widget_set_sensitive (GTK_WIDGET (adw_dialog_get_default_widget (self->type_dialog)),
                                  FALSE);
    }
    else
    {
        gtk_stack_set_visible_child_name (self->type_dialog_stack, "results");
        gtk_widget_set_sensitive (GTK_WIDGET (adw_dialog_get_default_widget (self->type_dialog)),
                                  TRUE);
    }
}

static gboolean
click_policy_mapping_get (GValue   *gvalue,
                          GVariant *variant,
                          gpointer  listview)
{
    int click_policy = g_settings_get_enum (nautilus_preferences,
                                            NAUTILUS_PREFERENCES_CLICK_POLICY);

    g_value_set_boolean (gvalue, click_policy == NAUTILUS_CLICK_POLICY_SINGLE);

    return TRUE;
}

static void
show_other_types_dialog (NautilusSearchPopover *popover)
{
    GtkStringFilter *filter;
    GtkFilterListModel *filter_model;
    g_autoptr (GList) mime_infos = NULL;
    GListStore *file_type_list = g_list_store_new (NAUTILUS_TYPE_MINIMAL_CELL);
    g_autoptr (GtkBuilder) builder = NULL;
    AdwToolbarView *toolbar_view;
    GtkWidget *content_area;
    GtkWidget *search_entry;
    GtkListView *listview;
    GtkRoot *toplevel = gtk_widget_get_root (GTK_WIDGET (popover));

    gtk_popover_popdown (GTK_POPOVER (popover));

    mime_infos = g_content_types_get_registered ();
    mime_infos = g_list_sort (mime_infos, (GCompareFunc) g_strcmp0);
    gint scale = gtk_widget_get_scale_factor (GTK_WIDGET (toplevel));
    for (GList *l = mime_infos; l != NULL; l = l->next)
    {
        g_autofree gchar *content_type = l->data;
        g_autofree gchar *description = g_content_type_get_description (content_type);
        g_autoptr (GIcon) icon = g_content_type_get_icon (content_type);
        g_autoptr (NautilusIconInfo) icon_info = nautilus_icon_info_lookup (icon, 32, scale);
        GdkPaintable *paintable = nautilus_icon_info_get_paintable (icon_info);

        g_list_store_append (file_type_list, nautilus_minimal_cell_new (description,
                                                                        content_type,
                                                                        GDK_PAINTABLE (paintable)));
    }

    filter = gtk_string_filter_new (gtk_cclosure_expression_new (G_TYPE_STRING,
                                                                 NULL, 0, NULL,
                                                                 G_CALLBACK (join_type_and_description),
                                                                 NULL, NULL));
    filter_model = gtk_filter_list_model_new (G_LIST_MODEL (file_type_list), GTK_FILTER (filter));
    popover->other_types_model = gtk_single_selection_new (G_LIST_MODEL (filter_model));

    builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-search-types-dialog.ui");

    popover->type_dialog = ADW_DIALOG (gtk_builder_get_object (builder, "file_types_dialog"));
    search_entry = GTK_WIDGET (gtk_builder_get_object (builder, "search_entry"));
    toolbar_view = ADW_TOOLBAR_VIEW (gtk_builder_get_object (builder, "toolbar_view"));
    popover->type_dialog_stack = GTK_STACK (gtk_builder_get_object (builder, "search_stack"));
    listview = GTK_LIST_VIEW (gtk_builder_get_object (builder, "listview"));

    content_area = adw_toolbar_view_get_content (toolbar_view);
    gtk_search_entry_set_key_capture_widget (GTK_SEARCH_ENTRY (search_entry), content_area);
    g_object_bind_property (search_entry, "text", filter, "search", G_BINDING_SYNC_CREATE);
    g_signal_connect_after (search_entry, "notify::text",
                            G_CALLBACK (file_type_search_changed), popover);

    gtk_list_view_set_model (listview,
                             GTK_SELECTION_MODEL (g_object_ref (popover->other_types_model)));
    g_settings_bind_with_mapping (nautilus_preferences, NAUTILUS_PREFERENCES_CLICK_POLICY,
                                  listview, "single-click-activate", G_SETTINGS_BIND_GET,
                                  click_policy_mapping_get, NULL, listview, NULL);

    g_signal_connect_swapped (adw_dialog_get_default_widget (popover->type_dialog), "clicked",
                              G_CALLBACK (on_other_types_dialog_response), popover);
    g_signal_connect_swapped (popover->type_dialog, "close-attempt",
                              G_CALLBACK (on_other_types_dialog_response), popover);
    g_signal_connect_swapped (listview, "activate",
                              G_CALLBACK (on_other_types_dialog_response), popover);

    adw_dialog_present (popover->type_dialog, GTK_WIDGET (toplevel));
}

static void
update_date_label (NautilusSearchPopover *popover,
                   GPtrArray             *date_range)
{
    if (date_range)
    {
        gint days;
        GDateTime *initial_date;
        GDateTime *end_date;
        GDateTime *now;
        gchar *label;

        now = g_date_time_new_now_local ();
        initial_date = g_ptr_array_index (date_range, 0);
        end_date = g_ptr_array_index (date_range, 0);
        days = g_date_time_difference (end_date, initial_date) / G_TIME_SPAN_DAY;

        label = get_text_for_date_range (date_range, TRUE);

        gtk_editable_set_text (GTK_EDITABLE (popover->date_entry), days < 1 ? label : "");

        gtk_widget_set_visible (popover->clear_date_button, TRUE);
        gtk_label_set_label (GTK_LABEL (popover->select_date_button_label), label);

        g_date_time_unref (now);
        g_free (label);
    }
    else
    {
        gtk_label_set_label (GTK_LABEL (popover->select_date_button_label),
                             _("Select Dates…"));
        gtk_editable_set_text (GTK_EDITABLE (popover->date_entry), "");
        gtk_widget_set_visible (popover->clear_date_button, FALSE);
    }
}

void
nautilus_search_popover_set_fts_sensitive (NautilusSearchPopover *popover,
                                           gboolean               sensitive)
{
    gtk_widget_set_sensitive (popover->full_text_search_button, sensitive);
    gtk_widget_set_sensitive (popover->filename_search_button, sensitive);
}

static void
nautilus_search_popover_closed (GtkPopover *popover)
{
    NautilusSearchPopover *self = NAUTILUS_SEARCH_POPOVER (popover);
    GDateTime *now;

    /* Always switch back to the initial states */
    gtk_stack_set_visible_child_name (GTK_STACK (self->type_stack), "type-button");
    show_date_selection_widgets (self, FALSE);

    /* If we're closing an ongoing query, the popover must not
     * clear the current settings.
     */
    if (self->query)
    {
        return;
    }

    now = g_date_time_new_now_local ();

    /* Reselect today at the calendar */
    g_signal_handlers_block_by_func (self->calendar, calendar_day_selected, self);

    gtk_calendar_select_day (GTK_CALENDAR (self->calendar), now);

    g_signal_handlers_unblock_by_func (self->calendar, calendar_day_selected, self);
}

static void
nautilus_search_popover_get_property (GObject    *object,
                                      guint       prop_id,
                                      GValue     *value,
                                      GParamSpec *pspec)
{
    NautilusSearchPopover *self;

    self = NAUTILUS_SEARCH_POPOVER (object);

    switch (prop_id)
    {
        case PROP_QUERY:
        {
            g_value_set_object (value, self->query);
        }
        break;

        case PROP_FTS_ENABLED:
        {
            g_value_set_boolean (value, self->fts_enabled);
        }
        break;

        default:
        {
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        }
    }
}

static void
nautilus_search_popover_set_property (GObject      *object,
                                      guint         prop_id,
                                      const GValue *value,
                                      GParamSpec   *pspec)
{
    NautilusSearchPopover *self;

    self = NAUTILUS_SEARCH_POPOVER (object);

    switch (prop_id)
    {
        case PROP_QUERY:
        {
            nautilus_search_popover_set_query (self, g_value_get_object (value));
        }
        break;

        case PROP_FTS_ENABLED:
        {
            self->fts_enabled = g_value_get_boolean (value);
        }
        break;

        default:
        {
            G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        }
    }
}

static void
nautilus_search_popover_dispose (GObject *obj)
{
    NautilusSearchPopover *self = NAUTILUS_SEARCH_POPOVER (obj);

    gtk_popover_set_child (GTK_POPOVER (obj), NULL);
    gtk_widget_dispose_template (GTK_WIDGET (self), NAUTILUS_TYPE_SEARCH_POPOVER);

    G_OBJECT_CLASS (nautilus_search_popover_parent_class)->dispose (obj);
}

static void
nautilus_search_popover_class_init (NautilusSearchPopoverClass *klass)
{
    GtkPopoverClass *popover_class = GTK_POPOVER_CLASS (klass);
    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
    GObjectClass *object_class = G_OBJECT_CLASS (klass);

    object_class->get_property = nautilus_search_popover_get_property;
    object_class->set_property = nautilus_search_popover_set_property;
    object_class->dispose = nautilus_search_popover_dispose;

    popover_class->closed = nautilus_search_popover_closed;

    signals[DATE_RANGE] = g_signal_new ("date-range",
                                        NAUTILUS_TYPE_SEARCH_POPOVER,
                                        G_SIGNAL_RUN_LAST,
                                        0,
                                        NULL,
                                        NULL,
                                        g_cclosure_marshal_generic,
                                        G_TYPE_NONE,
                                        1,
                                        G_TYPE_PTR_ARRAY);

    signals[MIME_TYPE] = g_signal_new ("mime-type",
                                       NAUTILUS_TYPE_SEARCH_POPOVER,
                                       G_SIGNAL_RUN_LAST,
                                       0,
                                       NULL,
                                       NULL,
                                       g_cclosure_marshal_generic,
                                       G_TYPE_NONE,
                                       2,
                                       G_TYPE_INT,
                                       G_TYPE_STRING);

    signals[TIME_TYPE] = g_signal_new ("time-type",
                                       NAUTILUS_TYPE_SEARCH_POPOVER,
                                       G_SIGNAL_RUN_LAST,
                                       0,
                                       NULL,
                                       NULL,
                                       g_cclosure_marshal_generic,
                                       G_TYPE_NONE,
                                       1,
                                       G_TYPE_INT);

    /**
     * NautilusSearchPopover::query:
     *
     * The current #NautilusQuery being edited.
     */
    g_object_class_install_property (object_class,
                                     PROP_QUERY,
                                     g_param_spec_object ("query",
                                                          "Query of the popover",
                                                          "The current query being edited",
                                                          NAUTILUS_TYPE_QUERY,
                                                          G_PARAM_READWRITE));

    g_object_class_install_property (object_class,
                                     PROP_FTS_ENABLED,
                                     g_param_spec_boolean ("fts-enabled",
                                                           "fts enabled",
                                                           "fts enabled",
                                                           FALSE,
                                                           G_PARAM_READWRITE));

    gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-search-popover.ui");

    gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, around_revealer);
    gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, around_stack);
    gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, clear_date_button);
    gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, calendar);
    gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, dates_listbox);
    gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, date_entry);
    gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, date_stack);
    gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, select_date_button);
    gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, select_date_button_label);
    gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, type_label);
    gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, type_listbox);
    gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, type_stack);
    gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, last_used_button);
    gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, last_modified_button);
    gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, created_button);
    gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, full_text_search_button);
    gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, filename_search_button);

    gtk_widget_class_bind_template_callback (widget_class, calendar_day_selected);
    gtk_widget_class_bind_template_callback (widget_class, clear_date_button_clicked);
    gtk_widget_class_bind_template_callback (widget_class, date_entry_activate);
    gtk_widget_class_bind_template_callback (widget_class, dates_listbox_row_activated);
    gtk_widget_class_bind_template_callback (widget_class, select_date_button_clicked);
    gtk_widget_class_bind_template_callback (widget_class, select_type_button_clicked);
    gtk_widget_class_bind_template_callback (widget_class, toggle_calendar_icon_clicked);
    gtk_widget_class_bind_template_callback (widget_class, types_listbox_row_activated);
    gtk_widget_class_bind_template_callback (widget_class, search_time_type_changed);
    gtk_widget_class_bind_template_callback (widget_class, search_fts_mode_changed);
}

static void
nautilus_search_popover_init (NautilusSearchPopover *self)
{
    NautilusQuerySearchType filter_time_type;

    gtk_widget_init_template (GTK_WIDGET (self));

    /* Fuzzy dates listbox */
    gtk_list_box_set_header_func (GTK_LIST_BOX (self->dates_listbox),
                                  (GtkListBoxUpdateHeaderFunc) listbox_header_func,
                                  self,
                                  NULL);

    fill_fuzzy_dates_listbox (self);

    /* Types listbox */
    gtk_list_box_set_header_func (GTK_LIST_BOX (self->type_listbox),
                                  (GtkListBoxUpdateHeaderFunc) listbox_header_func,
                                  self,
                                  NULL);

    fill_types_listbox (self);

    gtk_list_box_select_row (GTK_LIST_BOX (self->type_listbox),
                             gtk_list_box_get_row_at_index (GTK_LIST_BOX (self->type_listbox), 0));

    filter_time_type = g_settings_get_enum (nautilus_preferences, "search-filter-time-type");
    if (filter_time_type == NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED)
    {
        gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_modified_button), TRUE);
        gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_used_button), FALSE);
        gtk_check_button_set_active (GTK_CHECK_BUTTON (self->created_button), FALSE);
    }
    else if (filter_time_type == NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS)
    {
        gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_modified_button), FALSE);
        gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_used_button), TRUE);
        gtk_check_button_set_active (GTK_CHECK_BUTTON (self->created_button), FALSE);
    }
    else
    {
        gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_modified_button), FALSE);
        gtk_check_button_set_active (GTK_CHECK_BUTTON (self->last_used_button), FALSE);
        gtk_check_button_set_active (GTK_CHECK_BUTTON (self->created_button), TRUE);
    }

    self->fts_enabled = g_settings_get_boolean (nautilus_preferences,
                                                NAUTILUS_PREFERENCES_FTS_ENABLED);
    if (self->fts_enabled)
    {
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->full_text_search_button), TRUE);
    }
    else
    {
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->filename_search_button), TRUE);
    }
}

GtkWidget *
nautilus_search_popover_new (void)
{
    return g_object_new (NAUTILUS_TYPE_SEARCH_POPOVER, NULL);
}

/**
 * nautilus_search_popover_get_query:
 * @popover: a #NautilusSearchPopover
 *
 * Gets the current query for @popover.
 *
 * Returns: (transfer none): the current #NautilusQuery from @popover.
 */
NautilusQuery *
nautilus_search_popover_get_query (NautilusSearchPopover *popover)
{
    g_return_val_if_fail (NAUTILUS_IS_SEARCH_POPOVER (popover), NULL);

    return popover->query;
}

/**
 * nautilus_search_popover_set_query:
 * @popover: a #NautilusSearchPopover
 * @query (nullable): a #NautilusQuery
 *
 * Sets the current query for @popover.
 *
 * Returns:
 */
void
nautilus_search_popover_set_query (NautilusSearchPopover *popover,
                                   NautilusQuery         *query)
{
    NautilusQuery *previous_query;

    g_return_if_fail (NAUTILUS_IS_SEARCH_POPOVER (popover));

    previous_query = popover->query;

    if (popover->query != query)
    {
        /* Disconnect signals and bindings from the old query */
        if (previous_query)
        {
            g_signal_handlers_disconnect_by_func (previous_query, query_date_changed, popover);
        }

        g_set_object (&popover->query, query);

        if (query)
        {
            /* Date */
            setup_date (popover, query);

            g_signal_connect (query,
                              "notify::date",
                              G_CALLBACK (query_date_changed),
                              popover);
        }
        else
        {
            nautilus_search_popover_reset_mime_types (popover);
            nautilus_search_popover_reset_date_range (popover);
        }
    }
}

void
nautilus_search_popover_reset_mime_types (NautilusSearchPopover *popover)
{
    g_return_if_fail (NAUTILUS_IS_SEARCH_POPOVER (popover));

    gtk_list_box_select_row (GTK_LIST_BOX (popover->type_listbox),
                             gtk_list_box_get_row_at_index (GTK_LIST_BOX (popover->type_listbox), 0));

    gtk_label_set_label (GTK_LABEL (popover->type_label),
                         nautilus_mime_types_group_get_name (0));
    g_signal_emit_by_name (popover, "mime-type", 0, NULL);
    gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-button");
}

void
nautilus_search_popover_reset_date_range (NautilusSearchPopover *popover)
{
    g_return_if_fail (NAUTILUS_IS_SEARCH_POPOVER (popover));

    gtk_list_box_select_row (GTK_LIST_BOX (popover->dates_listbox),
                             gtk_list_box_get_row_at_index (GTK_LIST_BOX (popover->dates_listbox), 0));

    update_date_label (popover, NULL);
    show_date_selection_widgets (popover, FALSE);
    g_signal_emit_by_name (popover, "date-range", NULL);
}

gboolean
nautilus_search_popover_get_fts_enabled (NautilusSearchPopover *popover)
{
    return popover->fts_enabled;
}
