/*
 * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation.
 *
 * 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 Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: Milan Crha <mcrha@redhat.com>
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <glib/gi18n-lib.h>

#include "e-cal-base-shell-sidebar.h"
#include "e-cal-base-shell-view.h"
#include "e-cal-base-shell-content.h"

struct _ECalBaseShellContentPrivate {
	ECalDataModel *data_model;
	ECalModel *model;
	gulong object_created_id;
	gulong view_state_changed_id;
};

enum {
	PROP_0,
	PROP_DATA_MODEL,
	PROP_MODEL
};

G_DEFINE_ABSTRACT_TYPE (ECalBaseShellContent, e_cal_base_shell_content, E_TYPE_SHELL_CONTENT)

static void
cal_base_shell_content_client_opened_cb (ECalBaseShellSidebar *cal_base_shell_sidebar,
					 ECalClient *client,
					 ECalBaseShellContent *shell_content)
{
	g_return_if_fail (E_IS_CAL_CLIENT (client));
	g_return_if_fail (E_IS_CAL_BASE_SHELL_CONTENT (shell_content));

	e_cal_data_model_add_client (shell_content->priv->data_model, client);
}

static void
cal_base_shell_content_client_closed_cb (ECalBaseShellSidebar *cal_base_shell_sidebar,
					 ESource *source,
					 ECalBaseShellContent *shell_content)
{
	g_return_if_fail (E_IS_SOURCE (source));
	g_return_if_fail (E_IS_CAL_BASE_SHELL_CONTENT (shell_content));

	e_cal_data_model_remove_client (shell_content->priv->data_model, e_source_get_uid (source));
}

static void
cal_base_shell_content_primary_selection_changed_cb (ESourceSelector *selector,
						     GParamSpec *param,
						     ECalBaseShellContent *shell_content)
{
	ESource *source;

	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
	g_return_if_fail (E_IS_CAL_BASE_SHELL_CONTENT (shell_content));

	source = e_source_selector_ref_primary_selection (selector);
	if (source)
		e_cal_model_set_default_source_uid (shell_content->priv->model, e_source_get_uid (source));

	g_clear_object (&source);
}

static void
cal_base_shell_content_object_created_cb (ECalBaseShellContent *cal_base_shell_content,
					  ECalClient *client,
					  ECalModel *model)
{
	EShellView *shell_view;
	EShellSidebar *shell_sidebar;
	ESourceSelector *selector;

	g_return_if_fail (E_IS_CAL_BASE_SHELL_CONTENT (cal_base_shell_content));
	g_return_if_fail (E_IS_CAL_CLIENT (client));

	shell_view = e_shell_content_get_shell_view (E_SHELL_CONTENT (cal_base_shell_content));
	g_return_if_fail (E_IS_SHELL_VIEW (shell_view));

	shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
	g_return_if_fail (E_IS_SHELL_SIDEBAR (shell_sidebar));

	selector = e_cal_base_shell_sidebar_get_selector (E_CAL_BASE_SHELL_SIDEBAR (shell_sidebar));
	e_source_selector_select_source (selector, e_client_get_source (E_CLIENT (client)));
}

static void
cal_base_shell_content_view_state_changed_cb (ECalDataModel *data_model,
					      ECalClientView *view,
					      ECalDataModelViewState state,
					      guint percent,
					      const gchar *message,
					      const GError *error,
					      ECalBaseShellContent *cal_base_shell_content)
{
	EShellView *shell_view;
	EShellSidebar *shell_sidebar;
	ESourceSelector *selector;
	ESource *source;
	ECalClient *client;

	shell_view = e_shell_content_get_shell_view (E_SHELL_CONTENT (cal_base_shell_content));
	g_return_if_fail (E_IS_SHELL_VIEW (shell_view));

	shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
	g_return_if_fail (E_IS_SHELL_SIDEBAR (shell_sidebar));

	selector = e_cal_base_shell_sidebar_get_selector (E_CAL_BASE_SHELL_SIDEBAR (shell_sidebar));
	client = e_cal_client_view_ref_client (view);
	/* Can be NULL when the corresponding source had been removed or disabled */
	if (!client)
		return;

	source = e_client_get_source (E_CLIENT (client));
	g_clear_object (&client);

	if (state == E_CAL_DATA_MODEL_VIEW_STATE_START ||
	    state == E_CAL_DATA_MODEL_VIEW_STATE_PROGRESS) {
		e_source_selector_set_source_is_busy (selector, source, TRUE);

		if (message) {
			gchar *tooltip;

			/* Translators: This is a running activity whose percent complete is known. */
			tooltip = g_strdup_printf (_("%s (%d%% complete)"), message, percent);
			e_source_selector_set_source_tooltip (selector, source, tooltip);
			g_free (tooltip);
		}
	} else {
		e_source_selector_set_source_is_busy (selector, source, FALSE);
		e_source_selector_set_source_tooltip (selector, source, NULL);
	}
}

static void
cal_base_shell_content_view_created_cb (EShellWindow *shell_window,
					EShellView *shell_view,
					ECalBaseShellContent *cal_base_shell_content)
{
	EShellSidebar *shell_sidebar;
	ECalBaseShellContentClass *klass;
	ESourceSelector *selector;

	g_signal_handlers_disconnect_by_func (
		shell_window,
		cal_base_shell_content_view_created_cb, cal_base_shell_content);

	g_return_if_fail (E_IS_CAL_BASE_SHELL_CONTENT (cal_base_shell_content));

	shell_view = e_shell_content_get_shell_view (E_SHELL_CONTENT (cal_base_shell_content));
	g_return_if_fail (E_IS_SHELL_VIEW (shell_view));

	shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
	g_return_if_fail (E_IS_SHELL_SIDEBAR (shell_sidebar));

	g_signal_connect (shell_sidebar, "client-opened",
		G_CALLBACK (cal_base_shell_content_client_opened_cb), cal_base_shell_content);
	g_signal_connect (shell_sidebar, "client-closed",
		G_CALLBACK (cal_base_shell_content_client_closed_cb), cal_base_shell_content);

	cal_base_shell_content->priv->object_created_id = g_signal_connect_swapped (
		cal_base_shell_content->priv->model, "object-created",
		G_CALLBACK (cal_base_shell_content_object_created_cb), cal_base_shell_content);

	selector = e_cal_base_shell_sidebar_get_selector (E_CAL_BASE_SHELL_SIDEBAR (shell_sidebar));
	g_signal_connect (selector, "notify::primary-selection",
		G_CALLBACK (cal_base_shell_content_primary_selection_changed_cb), cal_base_shell_content);

	cal_base_shell_content->priv->view_state_changed_id = g_signal_connect (
		cal_base_shell_content->priv->data_model, "view-state-changed",
		G_CALLBACK (cal_base_shell_content_view_state_changed_cb), cal_base_shell_content);

	klass = E_CAL_BASE_SHELL_CONTENT_GET_CLASS (cal_base_shell_content);
	g_return_if_fail (klass != NULL);

	if (klass->view_created)
		klass->view_created (cal_base_shell_content);
}

static GCancellable *
cal_base_shell_content_submit_data_model_thread_job (GObject *responder,
						     const gchar *description,
						     const gchar *alert_ident,
						     const gchar *alert_arg_0,
						     EAlertSinkThreadJobFunc func,
						     gpointer user_data,
						     GDestroyNotify free_user_data)
{
	EShellView *shell_view;
	EActivity *activity;
	GCancellable *cancellable = NULL;

	g_return_val_if_fail (E_IS_CAL_BASE_SHELL_CONTENT (responder), NULL);

	shell_view = e_shell_content_get_shell_view (E_SHELL_CONTENT (responder));
	activity = e_shell_view_submit_thread_job (shell_view, description,
		alert_ident, alert_arg_0, func, user_data, free_user_data);

	if (activity) {
		cancellable = e_activity_get_cancellable (activity);
		if (cancellable)
			g_object_ref (cancellable);
		g_object_unref (activity);
	}

	return cancellable;
}

static void
cal_base_shell_content_get_property (GObject *object,
				     guint property_id,
				     GValue *value,
				     GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_DATA_MODEL:
			g_value_set_object (
				value, e_cal_base_shell_content_get_data_model (
				E_CAL_BASE_SHELL_CONTENT (object)));
			return;

		case PROP_MODEL:
			g_value_set_object (
				value, e_cal_base_shell_content_get_model (
				E_CAL_BASE_SHELL_CONTENT (object)));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
cal_base_shell_content_dispose (GObject *object)
{
	ECalBaseShellContent *cal_base_shell_content;

	cal_base_shell_content = E_CAL_BASE_SHELL_CONTENT (object);

	e_cal_data_model_set_disposing (cal_base_shell_content->priv->data_model, TRUE);

	if (cal_base_shell_content->priv->view_state_changed_id != 0) {
		g_signal_handler_disconnect (cal_base_shell_content->priv->data_model,
			cal_base_shell_content->priv->view_state_changed_id);
		cal_base_shell_content->priv->view_state_changed_id = 0;
	}

	if (cal_base_shell_content->priv->object_created_id != 0) {
		g_signal_handler_disconnect (cal_base_shell_content->priv->model,
			cal_base_shell_content->priv->object_created_id);
		cal_base_shell_content->priv->object_created_id = 0;
	}

	/* Chain up to parent's method. */
	G_OBJECT_CLASS (e_cal_base_shell_content_parent_class)->dispose (object);
}

static void
cal_base_shell_content_finalize (GObject *object)
{
	ECalBaseShellContent *cal_base_shell_content;

	cal_base_shell_content = E_CAL_BASE_SHELL_CONTENT (object);

	if (cal_base_shell_content->priv->model &&
	    cal_base_shell_content->priv->data_model)
		e_cal_data_model_unsubscribe (cal_base_shell_content->priv->data_model,
			E_CAL_DATA_MODEL_SUBSCRIBER (cal_base_shell_content->priv->model));

	g_clear_object (&cal_base_shell_content->priv->model);
	g_clear_object (&cal_base_shell_content->priv->data_model);

	/* Chain up to parent's method. */
	G_OBJECT_CLASS (e_cal_base_shell_content_parent_class)->finalize (object);
}

static void
cal_base_shell_content_constructed (GObject *object)
{
	EShell *shell;
	EShellView *shell_view;
	EShellWindow *shell_window;
	ECalBaseShellContent *cal_base_shell_content;
	ECalBaseShellContentClass *klass;
	ESourceRegistry *registry;
	ESource *default_source = NULL;
	const gchar *created_signal_name = NULL;

	/* Chain up to parent's method. */
	G_OBJECT_CLASS (e_cal_base_shell_content_parent_class)->constructed (object);

	cal_base_shell_content = E_CAL_BASE_SHELL_CONTENT (object);
	cal_base_shell_content->priv->data_model = e_cal_base_shell_content_create_new_data_model (cal_base_shell_content);

	klass = E_CAL_BASE_SHELL_CONTENT_GET_CLASS (cal_base_shell_content);
	g_return_if_fail (klass != NULL);
	g_return_if_fail (klass->new_cal_model != NULL);

	shell_view = e_shell_content_get_shell_view (E_SHELL_CONTENT (cal_base_shell_content));
	shell_window = e_shell_view_get_shell_window (shell_view);
	shell = e_shell_window_get_shell (shell_window);
	registry = e_shell_get_registry (shell);

	cal_base_shell_content->priv->model = klass->new_cal_model (
		cal_base_shell_content->priv->data_model, registry, shell);

	e_binding_bind_property (
		cal_base_shell_content->priv->model, "timezone",
		cal_base_shell_content->priv->data_model, "timezone",
		G_BINDING_SYNC_CREATE);

	switch (e_cal_base_shell_view_get_source_type (shell_view)) {
		case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
			e_cal_data_model_set_expand_recurrences (cal_base_shell_content->priv->data_model, TRUE);
			default_source = e_source_registry_ref_default_calendar (registry);
			created_signal_name = "shell-view-created::calendar";
			break;
		case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
			default_source = e_source_registry_ref_default_memo_list (registry);
			created_signal_name = "shell-view-created::memos";
			break;
		case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
			default_source = e_source_registry_ref_default_task_list (registry);
			created_signal_name = "shell-view-created::tasks";
			break;
		case E_CAL_CLIENT_SOURCE_TYPE_LAST:
			g_warn_if_reached ();
			return;
	}

	e_cal_model_set_default_source_uid (cal_base_shell_content->priv->model, e_source_get_uid (default_source));

	g_clear_object (&default_source);

	g_signal_connect (
		shell_window, created_signal_name,
		G_CALLBACK (cal_base_shell_content_view_created_cb),
		cal_base_shell_content);
}

static void
e_cal_base_shell_content_class_init (ECalBaseShellContentClass *class)
{
	GObjectClass *object_class;

	g_type_class_add_private (class, sizeof (ECalBaseShellContentPrivate));

	object_class = G_OBJECT_CLASS (class);
	object_class->get_property = cal_base_shell_content_get_property;
	object_class->dispose = cal_base_shell_content_dispose;
	object_class->finalize = cal_base_shell_content_finalize;
	object_class->constructed = cal_base_shell_content_constructed;

	g_object_class_install_property (
		object_class,
		PROP_DATA_MODEL,
		g_param_spec_object (
			"data-model",
			NULL,
			NULL,
			E_TYPE_CAL_DATA_MODEL,
			G_PARAM_READABLE));

	g_object_class_install_property (
		object_class,
		PROP_MODEL,
		g_param_spec_object (
			"model",
			NULL,
			NULL,
			E_TYPE_CAL_MODEL,
			G_PARAM_READABLE));
}

static void
e_cal_base_shell_content_init (ECalBaseShellContent *cal_base_shell_content)
{
	cal_base_shell_content->priv = G_TYPE_INSTANCE_GET_PRIVATE (
		cal_base_shell_content, E_TYPE_CAL_BASE_SHELL_CONTENT, ECalBaseShellContentPrivate);
}

ECalDataModel *
e_cal_base_shell_content_get_data_model (ECalBaseShellContent *cal_base_shell_content)
{
	g_return_val_if_fail (E_IS_CAL_BASE_SHELL_CONTENT (cal_base_shell_content), NULL);

	return cal_base_shell_content->priv->data_model;
}

ECalModel *
e_cal_base_shell_content_get_model (ECalBaseShellContent *cal_base_shell_content)
{
	g_return_val_if_fail (E_IS_CAL_BASE_SHELL_CONTENT (cal_base_shell_content), NULL);

	return cal_base_shell_content->priv->model;
}

void
e_cal_base_shell_content_prepare_for_quit (ECalBaseShellContent *cal_base_shell_content,
					   EActivity *activity)
{
	ECalBaseShellContentClass *klass;

	g_return_if_fail (E_IS_CAL_BASE_SHELL_CONTENT (cal_base_shell_content));

	klass = E_CAL_BASE_SHELL_CONTENT_GET_CLASS (cal_base_shell_content);
	g_return_if_fail (klass != NULL);

	if (klass->prepare_for_quit)
		klass->prepare_for_quit (cal_base_shell_content, activity);
}

ECalDataModel *
e_cal_base_shell_content_create_new_data_model (ECalBaseShellContent *cal_base_shell_content)
{
	g_return_val_if_fail (E_IS_CAL_BASE_SHELL_CONTENT (cal_base_shell_content), NULL);

	return e_cal_data_model_new (cal_base_shell_content_submit_data_model_thread_job, G_OBJECT (cal_base_shell_content));
}
