/*
 * Copyright (C) 2006 John Ellis
 * Copyright (C) 2008 - 2016 The Geeqie Team
 *
 * Author: John Ellis
 *
 * 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 2 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "pan-view-search.h"

#include <algorithm>
#include <cstdlib>
#include <ctime>

#include <glib-object.h>

#include "compat.h"
#include "filedata.h"
#include "image.h"
#include "intl.h"
#include "main-defines.h"
#include "misc.h"
#include "pan-calendar.h"
#include "pan-item.h"
#include "pan-types.h"
#include "pan-util.h"
#include "pan-view.h"
#include "ui-misc.h"
#include "ui-tabcomp.h"

static void pan_search_activate_cb(PanWindow *pw, const gchar *text);
static void pan_search_toggle_cb(GtkWidget *button, gpointer data);

PanViewSearchUi *pan_search_ui_new(PanWindow *pw)
{
	auto ui = g_new0(PanViewSearchUi, 1);
	GtkWidget *hbox;

	// Build the actual search UI.
	ui->search_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
	pref_spacer(ui->search_box, 0);
	pref_label_new(ui->search_box, _("Find:"));

	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
	gq_gtk_box_pack_start(GTK_BOX(ui->search_box), hbox, TRUE, TRUE, 0);
	gtk_widget_show(hbox);

	ui->search_entry = tab_completion_new_with_history(hbox, "", "pan_view_search", -1);
	tab_completion_set_enter_func(ui->search_entry,
	                              [pw](const gchar *text){ pan_search_activate_cb(pw, text); });

	ui->search_label = gtk_label_new("");
	gq_gtk_box_pack_start(GTK_BOX(hbox), ui->search_label, TRUE, TRUE, 0);
	gtk_widget_show(ui->search_label);

	// Build the spin-button to show/hide the search UI.
	ui->search_button = gtk_toggle_button_new();
	gtk_button_set_relief(GTK_BUTTON(ui->search_button), GTK_RELIEF_NONE);
	gtk_widget_set_focus_on_click(ui->search_button, FALSE);
	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, PREF_PAD_GAP);
	gq_gtk_container_add(ui->search_button, hbox);
	gtk_widget_show(hbox);
	ui->search_button_arrow = gq_gtk_image_new_from_icon_name(GQ_ICON_PAN_UP, GTK_ICON_SIZE_BUTTON);
	gq_gtk_box_pack_start(GTK_BOX(hbox), ui->search_button_arrow, FALSE, FALSE, 0);
	gtk_widget_show(ui->search_button_arrow);
	pref_label_new(hbox, _("Find"));

	g_signal_connect(G_OBJECT(ui->search_button), "clicked",
			 G_CALLBACK(pan_search_toggle_cb), pw);

	return ui;
}

void pan_search_ui_destroy(PanViewSearchUi *ui)
{
	g_free(ui);
}

static void pan_search_status(PanWindow *pw, const gchar *text)
{
	gtk_label_set_text(GTK_LABEL(pw->search_ui->search_label), (text) ? text : "");
}

static bool pan_search_list(PanWindow *pw, const PanItemList &list, const gchar *desc)
{
	if (list.empty()) return false;

	auto found = std::find(list.cbegin(), list.cend(), pw->click_pi);
	if (found != list.cend() && std::next(found) != list.cend())
		{
		found = std::next(found);
		}
	else
		{
		found = list.cbegin();
		}

	PanItem *pi = *found;

	pan_info_update(pw, pi);
	image_scroll_to_point(pw->imd, pi->x + (pi->width / 2), pi->y + (pi->height / 2), 0.5, 0.5);

	g_autofree gchar *buf = g_strdup_printf("%s ( %td / %zu )", desc,
	                                        std::distance(list.cbegin(), found) + 1,
	                                        list.size());
	pan_search_status(pw, buf);

	return true;
}

static bool pan_search_by_path(PanWindow *pw, const gchar *path)
{
	const PanItemType type = get_pan_item_type(pw->size);

	return pan_search_list(pw, pan_item_find_by_path(pw, type, path, FALSE, FALSE),
	                       (path[0] == G_DIR_SEPARATOR) ? _("path found") : _("filename found"));
}

static bool pan_search_by_partial(PanWindow *pw, const gchar *text)
{
	const PanItemType type = get_pan_item_type(pw->size);

	PanItemList list = pan_item_find_by_path(pw, type, text, TRUE, FALSE);
	if (list.empty()) list = pan_item_find_by_path(pw, type, text, FALSE, TRUE);
	if (list.empty())
		{
		g_autofree gchar *needle = g_utf8_strdown(text, -1);
		list = pan_item_find_by_path(pw, type, needle, TRUE, TRUE);
		}

	return pan_search_list(pw, list, _("partial match"));
}

static gboolean valid_date_separator(gchar c)
{
	return (c == '/' || c == '-' || c == ' ' || c == '.' || c == ',');
}

static PanItemList pan_search_by_date_val(const PanWindow *pw,
                                          PanItemType type, PanKey key,
                                          gint year, gint month, gint day)
{
	PanItemList list;

	for (PanItem *pi : pw->list_static)
		{
		if (pi->fd && pi->is_type(type) && pi->key == key)
			{
			struct tm *tl;

			tl = localtime(&pi->fd->date);
			if (tl)
				{
				bool match = (tl->tm_year == year - 1900);
				if (match && month >= 0) match = (tl->tm_mon == month - 1);
				if (match && day > 0) match = (tl->tm_mday == day);

				if (match) list.push_front(pi);
				}
			}
		}

	return list;
}

static bool pan_parse_date(const gchar *text, gint &year, gint &month, gint &day)
{
	if (!text) return false;

	const gchar *ptr = text;
	while (*ptr != '\0')
		{
		if (!g_unichar_isdigit(*ptr) && !valid_date_separator(*ptr)) return false;
		ptr++;
		}

	const time_t t = time(nullptr);
	if (t == -1) return false;

	struct tm *lt = localtime(&t);
	if (!lt) return false;

	gchar *mptr;
	if (valid_date_separator(*text))
		{
		year = -1;
		mptr = const_cast<gchar *>(text);
		}
	else
		{
		year = static_cast<gint>(strtol(text, &mptr, 10));
		if (mptr == text) return false;
		}

	if (*mptr != '\0' && valid_date_separator(*mptr))
		{
		gchar *dptr;

		mptr++;
		month = strtol(mptr, &dptr, 10);
		if (dptr == mptr)
			{
			if (valid_date_separator(*dptr))
				{
				month = lt->tm_mon + 1;
				dptr++;
				}
			else
				{
				month = -1;
				}
			}
		if (dptr != mptr && *dptr != '\0' && valid_date_separator(*dptr))
			{
			gchar *eptr;
			dptr++;
			day = strtol(dptr, &eptr, 10);
			if (dptr == eptr)
				{
				day = lt->tm_mday;
				}
			}
		}

	if (year == -1)
		{
		year = lt->tm_year + 1900;
		}
	else if (year < 100)
		{
		if (year > 70)
			year+= 1900;
		else
			year+= 2000;
		}

	return year >= 1970
	    && month >= -1 && month != 0 && month <= 12
	    && day >= -1 && day != 0 && day <= 31;
}

static gboolean pan_search_by_date(PanWindow *pw, const gchar *text,
                                   PanItemType type, PanKey key)
{
	gint year;
	gint month = -1;
	gint day = -1;

	if (!pan_parse_date(text, year, month, day)) return FALSE;

	const time_t t = pan_date_to_time(year, month, day);
	if (t < 0) return FALSE;

	g_autofree gchar *buf = nullptr;
	if (month > 0)
		{
		buf = pan_date_value_string(t, PAN_DATE_LENGTH_MONTH);
		if (day > 0)
			{
			g_autofree gchar *tmp = g_strdup_printf("%d %s", day, buf);
			std::swap(buf, tmp);
			}
		}
	else
		{
		buf = pan_date_value_string(t, PAN_DATE_LENGTH_YEAR);
		}

	g_autofree gchar *buf_count = nullptr;

	PanItemList list = pan_search_by_date_val(pw, type, key, year, month, day);
	if (!list.empty())
		{
		auto found = std::find(list.cbegin(), list.cend(), pw->search_pi);
		if (found != list.cend() && std::next(found) != list.cend())
			{
			found = std::next(found);
			}
		else
			{
			found = list.cbegin();
			}

		PanItem *pi = *found;

		pw->search_pi = pi;

		if (pw->layout == PAN_LAYOUT_CALENDAR)
			{
			pan_info_update(pw, nullptr);
			pan_calendar_update(pw, pi);
			image_scroll_to_point(pw->imd,
			                      pi->x + (pi->width / 2),
			                      pi->y + (pi->height / 2), 0.5, 0.5);
			}
		else
			{
			pan_info_update(pw, pi);
			image_scroll_to_point(pw->imd,
			                      pi->x - (PAN_BOX_BORDER * 5 / 2),
			                      pi->y, 0.0, 0.5);
			}

		buf_count = g_strdup_printf("( %td / %zu )",
		                            std::distance(list.cbegin(), found) + 1,
		                            list.size());
		}
	else
		{
		pw->search_pi = nullptr;

		buf_count = g_strdup_printf("(%s)", _("no match"));
		}

	g_autofree gchar *message = g_strdup_printf("%s %s %s", _("Date:"), buf, buf_count);
	pan_search_status(pw, message);

	return TRUE;
}

static void pan_search_activate_cb(PanWindow *pw, const gchar *text)
{
	if (!text) return;

	tab_completion_append_to_history(pw->search_ui->search_entry, text);

	if (pan_search_by_path(pw, text)) return;

	if (pw->layout == PAN_LAYOUT_TIMELINE &&
	    pan_search_by_date(pw, text, get_pan_item_type(pw->size), PanKey::None))
		{
		return;
		}

	if (pw->layout == PAN_LAYOUT_CALENDAR &&
	    pan_search_by_date(pw, text, PAN_ITEM_BOX, PanKey::Day))
		{
		return;
		}

	if (pan_search_by_partial(pw, text)) return;

	pan_search_status(pw, _("no match"));
}

void pan_search_activate(PanWindow *pw)
{
	const gchar *text = gq_gtk_entry_get_text(GTK_ENTRY(pw->search_ui->search_entry));

	pan_search_activate_cb(pw, text);
}

static void pan_search_toggle_cb(GtkWidget *button, gpointer data)
{
	auto pw = static_cast<PanWindow *>(data);
	PanViewSearchUi *ui = pw->search_ui;
	gboolean visible;
	GtkWidget *parent;

	visible = gtk_widget_get_visible(ui->search_box);
	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) == visible) return;

	gtk_widget_set_visible(ui->search_box, !visible);

	if (visible)
		{
		parent = gtk_widget_get_parent(ui->search_button_arrow);

		gq_gtk_container_remove(parent, ui->search_button_arrow);
		ui->search_button_arrow = gq_gtk_image_new_from_icon_name(GQ_ICON_PAN_UP, GTK_ICON_SIZE_BUTTON);

		gq_gtk_box_pack_start(GTK_BOX(parent), ui->search_button_arrow, FALSE, FALSE, 0);
		gq_gtk_box_reorder_child(GTK_BOX(parent), ui->search_button_arrow, 0);

		gtk_widget_show(ui->search_button_arrow);
		}
	else
		{
		parent = gtk_widget_get_parent(ui->search_button_arrow);

		gq_gtk_container_remove(parent, ui->search_button_arrow);
		ui->search_button_arrow = gq_gtk_image_new_from_icon_name(GQ_ICON_PAN_DOWN, GTK_ICON_SIZE_BUTTON);

		gq_gtk_box_pack_start(GTK_BOX(parent), ui->search_button_arrow, FALSE, FALSE, 0);
		gq_gtk_box_reorder_child(GTK_BOX(parent), ui->search_button_arrow, 0);

		gtk_widget_show(ui->search_button_arrow);
		gtk_widget_grab_focus(ui->search_entry);
		}
}

void pan_search_toggle_visible(PanWindow *pw, gboolean enable)
{
	PanViewSearchUi *ui = pw->search_ui;
	if (pw->fs) return;

	if (enable)
		{
		if (gtk_widget_get_visible(ui->search_box))
			{
			gtk_widget_grab_focus(ui->search_entry);
			}
		else
			{
			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui->search_button), TRUE);
			}
		}
	else
		{
		if (gtk_widget_get_visible(ui->search_entry))
			{
			if (gtk_widget_has_focus(ui->search_entry))
				{
				gtk_widget_grab_focus(pw->imd->widget);
				}
			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui->search_button), FALSE);
			}
		}
}
