/* gtd-provider.c
 *
 * Copyright (C) 2015-2020 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/>.
 */

#define G_LOG_DOMAIN "GtdProvider"

#include "gtd-provider.h"
#include "gtd-task.h"
#include "gtd-task-list.h"
#include "gtd-utils.h"

/**
 * SECTION:gtd-provider
 * @short_description:data sources for Endeavour
 * @title: GtdProvider
 * @stability:Unstable
 *
 * The #GtdProvider is the interface that Endeavour uses to
 * connect to data sources. It must provide ways to create, update
 * and remove tasks and tasklists.
 *
 * A provider implementation must also expose which is the default
 * tasklist among the tasklists it manages.
 */

G_DEFINE_INTERFACE (GtdProvider, gtd_provider, GTD_TYPE_OBJECT)

enum
{
  LIST_ADDED,
  LIST_CHANGED,
  LIST_REMOVED,
  NUM_SIGNALS
};

static guint signals[NUM_SIGNALS] = { 0, };


static void
gtd_provider_default_init (GtdProviderInterface *iface)
{
  /**
   * GtdProvider::enabled:
   *
   * Whether the #GtdProvider is enabled.
   */
  g_object_interface_install_property (iface,
                                       g_param_spec_boolean ("enabled",
                                                             "Identifier of the provider",
                                                             "The identifier of the provider",
                                                             FALSE,
                                                             G_PARAM_READABLE));

  /**
   * GtdProvider::icon:
   *
   * The icon of the #GtdProvider, e.g. the account icon
   * of a GNOME Online Accounts' account.
   */
  g_object_interface_install_property (iface,
                                       g_param_spec_object ("icon",
                                                            "Icon of the provider",
                                                            "The icon of the provider",
                                                            G_TYPE_ICON,
                                                            G_PARAM_READABLE));

  /**
   * GtdProvider::id:
   *
   * The unique identifier of the #GtdProvider.
   */
  g_object_interface_install_property (iface,
                                       g_param_spec_string ("id",
                                                            "Identifier of the provider",
                                                            "The identifier of the provider",
                                                            NULL,
                                                            G_PARAM_READABLE));

  /**
   * GtdProvider::name:
   *
   * The user-visible name of the #GtdProvider.
   */
  g_object_interface_install_property (iface,
                                       g_param_spec_string ("name",
                                                            "Name of the provider",
                                                            "The user-visible name of the provider",
                                                            NULL,
                                                            G_PARAM_READABLE));

  /**
   * GtdProvider::provider-type:
   *
   * The type of the #GtdProvider.
   */
  g_object_interface_install_property (iface,
                                       g_param_spec_string ("provider-type",
                                                            "Type of the provider",
                                                            "The type of the provider",
                                                            NULL,
                                                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

  /**
   * GtdProvider::description:
   *
   * The description of the #GtdProvider, e.g. the account user
   * of a GNOME Online Accounts' account.
   */
  g_object_interface_install_property (iface,
                                       g_param_spec_string ("description",
                                                            "Description of the provider",
                                                            "The description of the provider",
                                                            NULL,
                                                            G_PARAM_READABLE));

  /**
   * GtdProvider::list-added:
   * @provider: a #GtdProvider
   * @list: a #GtdTaskList
   *
   * The ::list-added signal is emmited after a #GtdTaskList
   * is connected.
   */
  signals[LIST_ADDED] = g_signal_new ("list-added",
                                      GTD_TYPE_PROVIDER,
                                      G_SIGNAL_RUN_LAST,
                                      0,
                                      NULL,
                                      NULL,
                                      NULL,
                                      G_TYPE_NONE,
                                      1,
                                      GTD_TYPE_TASK_LIST);

  /**
   * GtdProvider::list-changed:
   * @provider: a #GtdProvider
   * @list: a #GtdTaskList
   *
   * The ::list-changed signal is emmited after a #GtdTaskList
   * has any of it's properties changed.
   */
  signals[LIST_CHANGED] = g_signal_new ("list-changed",
                                        GTD_TYPE_PROVIDER,
                                        G_SIGNAL_RUN_LAST,
                                        0,
                                        NULL,
                                        NULL,
                                        NULL,
                                        G_TYPE_NONE,
                                        1,
                                        GTD_TYPE_TASK_LIST);

  /**
   * GtdProvider::list-removed:
   * @provider: a #GtdProvider
   * @list: a #GtdTaskList
   *
   * The ::list-removed signal is emmited after a #GtdTaskList
   * is disconnected.
   */
  signals[LIST_REMOVED] = g_signal_new ("list-removed",
                                        GTD_TYPE_PROVIDER,
                                        G_SIGNAL_RUN_LAST,
                                        0,
                                        NULL,
                                        NULL,
                                        NULL,
                                        G_TYPE_NONE,
                                        1,
                                        GTD_TYPE_TASK_LIST);
}

/**
 * gtd_provider_get_id:
 * @provider: a #GtdProvider
 *
 * Retrieves the identifier of @provider.
 *
 * Returns: (transfer none): the id of @provider
 */
const gchar*
gtd_provider_get_id (GtdProvider *provider)
{
  g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL);
  g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_id, NULL);

  return GTD_PROVIDER_GET_IFACE (provider)->get_id (provider);
}

/**
 * gtd_provider_get_name:
 * @provider: a #GtdProvider
 *
 * Retrieves the user-visible name of @provider.
 *
 * Returns: (transfer none): the name of @provider
 */
const gchar*
gtd_provider_get_name (GtdProvider *provider)
{
  g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL);
  g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_name, NULL);

  return GTD_PROVIDER_GET_IFACE (provider)->get_name (provider);
}

/**
 * gtd_provider_get_provider_type:
 * @provider: a #GtdProvider
 *
 * Retrieves the type of the @provider. This should return the
 * same value, regardless of the account name.
 *
 * For example: "todoist", "todo-txt" or "google"
 *
 * Returns: (transfer none): the type of the @provider
 */
const gchar*
gtd_provider_get_provider_type (GtdProvider *provider)
{
  g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL);
  g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_name, NULL);

  return GTD_PROVIDER_GET_IFACE (provider)->get_provider_type (provider);
}

/**
 * gtd_provider_get_description:
 * @provider: a #GtdProvider
 *
 * Retrieves the description of @provider.
 *
 * Returns: (transfer none): the description of @provider
 */
const gchar*
gtd_provider_get_description (GtdProvider *provider)
{
  g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL);
  g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_description, NULL);

  return GTD_PROVIDER_GET_IFACE (provider)->get_description (provider);
}

/**
 * gtd_provider_get_enabled:
 * @provider: a #GtdProvider
 *
 * Retrieves whether @provider is enabled or not. A disabled
 * provider cannot be selected to be default nor be selected
 * to add tasks to it.
 *
 * Returns: %TRUE if provider is enabled, %FALSE otherwise.
 */
gboolean
gtd_provider_get_enabled (GtdProvider *provider)
{
  g_return_val_if_fail (GTD_IS_PROVIDER (provider), FALSE);
  g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_enabled, FALSE);

  return GTD_PROVIDER_GET_IFACE (provider)->get_enabled (provider);
}

/**
 * gtd_provider_refresh:
 * @provider: a #GtdProvider
 *
 * Asks the provider to refresh. Online providers may want to
 * synchronize tasks and tasklists, credentials, etc, when this
 * is called.
 *
 * This is an optional feature. Providers that do not implement
 * the "refresh" vfunc will be ignored.
 */
void
gtd_provider_refresh (GtdProvider *provider)
{
  g_return_if_fail (GTD_IS_PROVIDER (provider));

  if (GTD_PROVIDER_GET_IFACE (provider)->refresh)
    GTD_PROVIDER_GET_IFACE (provider)->refresh (provider);
}

/**
 * gtd_provider_get_icon:
 * @provider: a #GtdProvider
 *
 * The icon of @provider.
 *
 * Returns: (transfer none): a #GIcon
 */
GIcon*
gtd_provider_get_icon (GtdProvider *provider)
{
  g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL);
  g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_icon, NULL);

  return GTD_PROVIDER_GET_IFACE (provider)->get_icon (provider);
}

/**
 * gtd_provider_create_task:
 * @provider: a #GtdProvider
 * @list: a #GtdTaskLast
 * @title: The task title
 * @due_date: (nullable): a #GDateTime
 * @cancellable: (nullable): a #GCancellable
 * @callback: (scope async): a callback
 * @user_data: (closure): user data for @callback
 *
 * Creates the given task in @provider.
 */
void
gtd_provider_create_task (GtdProvider         *provider,
                          GtdTaskList         *list,
                          const gchar         *title,
                          GDateTime           *due_date,
                          GCancellable        *cancellable,
                          GAsyncReadyCallback  callback,
                          gpointer             user_data)
{
  g_return_if_fail (GTD_IS_PROVIDER (provider));
  g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->create_task);

  GTD_PROVIDER_GET_IFACE (provider)->create_task (provider,
                                                  list,
                                                  title,
                                                  due_date,
                                                  cancellable,
                                                  callback,
                                                  user_data);
}

/**
 * gtd_provider_create_task_finish:
 * @self: a #GtdProvider
 * @result: a #GAsyncResult
 * @error: (out)(nullable): return location for a #GError
 *
 * Finishes creating the task.
 *
 * Returns: (transfer none)(nullable): a #GtdTask
 */
GtdTask*
gtd_provider_create_task_finish (GtdProvider   *self,
                                 GAsyncResult  *result,
                                 GError       **error)
{
  g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE);
  g_return_val_if_fail (!error || !*error, FALSE);
  g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->create_task_finish, FALSE);

  return GTD_PROVIDER_GET_IFACE (self)->create_task_finish (self, result, error);
}

/**
 * gtd_provider_update_task:
 * @provider: a #GtdProvider
 * @task: a #GtdTask
 * @cancellable: (nullable): a #GCancellable
 * @callback: (scope async): a callback
 * @user_data: (closure): user data for @callback
 *
 * Updates the given task in @provider.
 */
void
gtd_provider_update_task (GtdProvider         *provider,
                          GtdTask             *task,
                          GCancellable        *cancellable,
                          GAsyncReadyCallback  callback,
                          gpointer             user_data)
{
  g_return_if_fail (GTD_IS_PROVIDER (provider));
  g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->update_task);

  GTD_PROVIDER_GET_IFACE (provider)->update_task (provider,
                                                  task,
                                                  cancellable,
                                                  callback,
                                                  user_data);
}

/**
 * gtd_provider_update_task_finish:
 * @self: a #GtdProvider
 * @result: a #GAsyncResult
 * @error: (out)(nullable): return location for a #GError
 *
 * Finishes updating the task list.
 *
 * Returns: %TRUE if task list was successfully updated, %FALSE otherwise
 */
gboolean
gtd_provider_update_task_finish (GtdProvider   *self,
                                 GAsyncResult  *result,
                                 GError       **error)
{
  g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE);
  g_return_val_if_fail (!error || !*error, FALSE);
  g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->update_task_finish, FALSE);

  return GTD_PROVIDER_GET_IFACE (self)->update_task_finish (self, result, error);
}

/**
 * gtd_provider_remove_task:
 * @provider: a #GtdProvider
 * @task: a #GtdTask
 * @cancellable: (nullable): a #GCancellable
 * @callback: (scope async): a callback
 * @user_data: (closure): user data for @callback
 *
 * Removes the given task from @provider.
 */
void
gtd_provider_remove_task (GtdProvider         *provider,
                          GtdTask             *task,
                          GCancellable        *cancellable,
                          GAsyncReadyCallback  callback,
                          gpointer             user_data)
{
  g_return_if_fail (GTD_IS_PROVIDER (provider));
  g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->remove_task);

  GTD_PROVIDER_GET_IFACE (provider)->remove_task (provider,
                                                  task,
                                                  cancellable,
                                                  callback,
                                                  user_data);
}

/**
 * gtd_provider_remove_task_finish:
 * @self: a #GtdProvider
 * @result: a #GAsyncResult
 * @error: (out)(nullable): return location for a #GError
 *
 * Finishes removing the task.
 *
 * Returns: %TRUE if task was successfully removed, %FALSE otherwise
 */
gboolean
gtd_provider_remove_task_finish (GtdProvider   *self,
                                 GAsyncResult  *result,
                                 GError       **error)
{
  g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE);
  g_return_val_if_fail (!error || !*error, FALSE);
  g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->remove_task_finish, FALSE);

  return GTD_PROVIDER_GET_IFACE (self)->remove_task_finish (self, result, error);
}

/**
 * gtd_provider_create_task_list:
 * @provider: a #GtdProvider
 * @name: (nullable): the name of the new task list
 * @cancellable: (nullable): a #GCancellable
 * @callback: (scope async): a callback
 * @user_data: (closure): user data for @callback
 *
 * Creates the given list in @provider.
 */
void
gtd_provider_create_task_list (GtdProvider         *provider,
                               const gchar         *name,
                               GCancellable        *cancellable,
                               GAsyncReadyCallback  callback,
                               gpointer             user_data)
{
  g_return_if_fail (GTD_IS_PROVIDER (provider));
  g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->create_task_list);

  GTD_PROVIDER_GET_IFACE (provider)->create_task_list (provider,
                                                       name,
                                                       cancellable,
                                                       callback,
                                                       user_data);
}

/**
 * gtd_provider_create_task_list_finish:
 * @self: a #GtdProvider
 * @result: a #GAsyncResult
 * @error: (out)(nullable): return location for a #GError
 *
 * Finishes creating the task list. The provider will emit the
 * GtdProvider:list-added signal after creating the task list.
 *
 * Returns: %TRUE if task list was successfully created, %FALSE otherwise
 */
gboolean
gtd_provider_create_task_list_finish (GtdProvider   *self,
                                      GAsyncResult  *result,
                                      GError       **error)
{
  g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE);
  g_return_val_if_fail (!error || !*error, FALSE);
  g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->create_task_list_finish, FALSE);

  return GTD_PROVIDER_GET_IFACE (self)->create_task_list_finish (self, result, error);
}

/**
 * gtd_provider_update_task_list:
 * @provider: a #GtdProvider
 * @list: a #GtdTaskList
 * @cancellable: (nullable): a #GCancellable
 * @callback: (scope async): a callback
 * @user_data: (closure): user data for @callback
 *
 * Updates the given list in @provider.
 */
void
gtd_provider_update_task_list (GtdProvider         *provider,
                               GtdTaskList         *list,
                               GCancellable        *cancellable,
                               GAsyncReadyCallback  callback,
                               gpointer             user_data)
{
  g_return_if_fail (GTD_IS_PROVIDER (provider));
  g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->update_task_list);

  GTD_PROVIDER_GET_IFACE (provider)->update_task_list (provider,
                                                       list,
                                                       cancellable,
                                                       callback,
                                                       user_data);
}

/**
 * gtd_provider_update_task_list_finish:
 * @self: a #GtdProvider
 * @result: a #GAsyncResult
 * @error: (out)(nullable): return location for a #GError
 *
 * Finishes updating the task list. The provider will emit the
 * GtdProvider:list-updated signal after updating the task list.
 *
 * Returns: %TRUE if task list was successfully updated, %FALSE otherwise
 */
gboolean
gtd_provider_update_task_list_finish (GtdProvider   *self,
                                      GAsyncResult  *result,
                                      GError       **error)
{
  g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE);
  g_return_val_if_fail (!error || !*error, FALSE);
  g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->update_task_list_finish, FALSE);

  return GTD_PROVIDER_GET_IFACE (self)->update_task_list_finish (self, result, error);
}

/**
 * gtd_provider_remove_task_list:
 * @provider: a #GtdProvider
 * @list: a #GtdTaskList
 * @cancellable: (nullable): a #GCancellable
 * @callback: (scope async): a callback
 * @user_data: (closure): user data for @callback
 *
 * Removes the given list from @provider.
 */
void
gtd_provider_remove_task_list (GtdProvider         *provider,
                               GtdTaskList         *list,
                               GCancellable        *cancellable,
                               GAsyncReadyCallback  callback,
                               gpointer             user_data)
{
  g_return_if_fail (GTD_IS_PROVIDER (provider));
  g_return_if_fail (GTD_PROVIDER_GET_IFACE (provider)->remove_task_list);

  GTD_PROVIDER_GET_IFACE (provider)->remove_task_list (provider,
                                                       list,
                                                       cancellable,
                                                       callback,
                                                       user_data);
}

/**
 * gtd_provider_remove_task_list_finish:
 * @self: a #GtdProvider
 * @result: a #GAsyncResult
 * @error: (out)(nullable): return location for a #GError
 *
 * Finishes removing the task list. The provider will emit the
 * GtdProvider:list-removed signal after removing the task list.
 *
 * Returns: %TRUE if task list was successfully removed, %FALSE otherwise
 */
gboolean
gtd_provider_remove_task_list_finish (GtdProvider   *self,
                                      GAsyncResult  *result,
                                      GError       **error)
{
  g_return_val_if_fail (GTD_IS_PROVIDER (self), FALSE);
  g_return_val_if_fail (!error || !*error, FALSE);
  g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (self)->remove_task_list_finish, FALSE);

  return GTD_PROVIDER_GET_IFACE (self)->remove_task_list_finish (self, result, error);
}

/**
 * gtd_provider_get_task_lists:
 * @provider: a #GtdProvider
 *
 * Retrieves the tasklists that this provider contains.
 *
 * Returns: (transfer container) (element-type Gtd.TaskList): the list of tasks, or %NULL
 */
GList*
gtd_provider_get_task_lists (GtdProvider *provider)
{
  g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL);
  g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_task_lists, NULL);

  return GTD_PROVIDER_GET_IFACE (provider)->get_task_lists (provider);
}

/**
 * gtd_provider_get_inbox:
 * @provider: a #GtdProvider
 *
 * Retrieves the inbox of @provider.
 *
 * Returns: (transfer none)(nullable): a #GtdTaskList
 */
GtdTaskList*
gtd_provider_get_inbox (GtdProvider *provider)
{
  g_return_val_if_fail (GTD_IS_PROVIDER (provider), NULL);
  g_return_val_if_fail (GTD_PROVIDER_GET_IFACE (provider)->get_inbox, NULL);

  return GTD_PROVIDER_GET_IFACE (provider)->get_inbox (provider);
}

/**
 * gtd_provider_compare:
 * @a: a #GtdProvider
 * @b: a #GtdProvider
 *
 * Compares @a and @b. The sorting criteria is internal and
 * may change.
 *
 * Returns: -1 if @a comes before @b, 1 for the oposite, and
 * 0 if they're equal
 */
gint
gtd_provider_compare (GtdProvider *a,
                      GtdProvider *b)
{
  gint result;

  g_return_val_if_fail (GTD_IS_PROVIDER (a), 0);
  g_return_val_if_fail (GTD_IS_PROVIDER (b), 0);

  if (a == b)
    return 0;

  result = gtd_collate_compare_strings (gtd_provider_get_name (a), gtd_provider_get_name (b));

  if (result != 0)
    return result;

  return gtd_collate_compare_strings (gtd_provider_get_description (a), gtd_provider_get_description (b));
}
