/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 * vi:set noexpandtab tabstop=8 shiftwidth=8:
 *
 * Copyright (C) 2017-2018 Richard Hughes <richard@hughsie.com>
 * Copyright (C) 2018 Kalev Lember <klember@redhat.com>
 *
 * SPDX-License-Identifier: GPL-2.0+
 */

#include "config.h"

#include <glib.h>

#include "gs-enums.h"
#include "gs-plugin-private.h"
#include "gs-plugin-job-private.h"

struct _GsPluginJob
{
	GObject			 parent_instance;
	GsPluginRefineFlags	 refine_flags;
	GsPluginRefineFlags	 filter_flags;
	GsAppListFilterFlags	 dedupe_flags;
	gboolean		 interactive;
	gboolean		 propagate_error;
	guint			 max_results;
	guint			 timeout;
	guint64			 age;
	GsPlugin		*plugin;
	GsPluginAction		 action;
	GsAppListSortFunc	 sort_func;
	gpointer		 sort_func_data;
	gchar			*search;
	GsApp			*app;
	GsAppList		*list;
	GFile			*file;
	GsCategory		*category;
	AsReview		*review;
	gint64			 time_created;
};

enum {
	PROP_0,
	PROP_ACTION,
	PROP_AGE,
	PROP_SEARCH,
	PROP_REFINE_FLAGS,
	PROP_FILTER_FLAGS,
	PROP_DEDUPE_FLAGS,
	PROP_INTERACTIVE,
	PROP_APP,
	PROP_LIST,
	PROP_FILE,
	PROP_CATEGORY,
	PROP_REVIEW,
	PROP_MAX_RESULTS,
	PROP_TIMEOUT,
	PROP_PROPAGATE_ERROR,
	PROP_LAST
};

G_DEFINE_TYPE (GsPluginJob, gs_plugin_job, G_TYPE_OBJECT)

gchar *
gs_plugin_job_to_string (GsPluginJob *self)
{
	GString *str = g_string_new (NULL);
	gint64 time_now = g_get_monotonic_time ();
	g_string_append_printf (str, "running %s",
				gs_plugin_action_to_string (self->action));
	if (self->plugin != NULL) {
		g_string_append_printf (str, " on plugin=%s",
					gs_plugin_get_name (self->plugin));
	}
	if (self->filter_flags > 0) {
		g_autofree gchar *tmp = gs_plugin_refine_flags_to_string (self->filter_flags);
		g_string_append_printf (str, " with filter-flags=%s", tmp);
	}
	if (self->dedupe_flags > 0)
		g_string_append_printf (str, " with dedupe-flags=%" G_GUINT64_FORMAT, self->dedupe_flags);
	if (self->refine_flags > 0) {
		g_autofree gchar *tmp = gs_plugin_refine_flags_to_string (self->refine_flags);
		g_string_append_printf (str, " with refine-flags=%s", tmp);
	}
	if (self->interactive)
		g_string_append_printf (str, " with interactive=True");
	if (self->propagate_error)
		g_string_append_printf (str, " with propagate-error=True");
	if (self->timeout > 0)
		g_string_append_printf (str, " with timeout=%u", self->timeout);
	if (self->max_results > 0)
		g_string_append_printf (str, " with max-results=%u", self->max_results);
	if (self->age != 0) {
		if (self->age == G_MAXUINT) {
			g_string_append (str, " with cache age=any");
		} else {
			g_string_append_printf (str, " with cache age=%" G_GUINT64_FORMAT,
						self->age);
		}
	}
	if (self->search != NULL) {
		g_string_append_printf (str, " with search=%s",
					self->search);
	}
	if (self->category != NULL) {
		GsCategory *parent = gs_category_get_parent (self->category);
		if (parent != NULL) {
			g_string_append_printf (str, " with category=%s/%s",
						gs_category_get_id (parent),
						gs_category_get_id (self->category));
		} else {
			g_string_append_printf (str, " with category=%s",
						gs_category_get_id (self->category));
		}
	}
	if (self->review != NULL) {
		g_string_append_printf (str, " with review=%s",
					as_review_get_id (self->review));
	}
	if (self->file != NULL) {
		g_autofree gchar *path = g_file_get_path (self->file);
		g_string_append_printf (str, " with file=%s", path);
	}
	if (self->list != NULL && gs_app_list_length (self->list) > 0) {
		g_autofree const gchar **unique_ids = NULL;
		g_autofree gchar *unique_ids_str = NULL;
		unique_ids = g_new0 (const gchar *, gs_app_list_length (self->list) + 1);
		for (guint i = 0; i < gs_app_list_length (self->list); i++) {
			GsApp *app = gs_app_list_index (self->list, i);
			unique_ids[i] = gs_app_get_unique_id (app);
		}
		unique_ids_str = g_strjoinv (",", (gchar**) unique_ids);
		g_string_append_printf (str, " on apps %s", unique_ids_str);
	}
	if (time_now - self->time_created > 1000) {
		g_string_append_printf (str, ", elapsed time since creation %" G_GINT64_FORMAT "ms",
					(time_now - self->time_created) / 1000);
	}
	return g_string_free (str, FALSE);
}

void
gs_plugin_job_set_refine_flags (GsPluginJob *self, GsPluginRefineFlags refine_flags)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	self->refine_flags = refine_flags;
}

void
gs_plugin_job_set_filter_flags (GsPluginJob *self, GsPluginRefineFlags filter_flags)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	self->filter_flags = filter_flags;
}

void
gs_plugin_job_set_dedupe_flags (GsPluginJob *self, GsAppListFilterFlags dedupe_flags)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	self->dedupe_flags = dedupe_flags;
}

GsPluginRefineFlags
gs_plugin_job_get_refine_flags (GsPluginJob *self)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), GS_PLUGIN_REFINE_FLAGS_DEFAULT);
	return self->refine_flags;
}

GsPluginRefineFlags
gs_plugin_job_get_filter_flags (GsPluginJob *self)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), GS_PLUGIN_REFINE_FLAGS_DEFAULT);
	return self->filter_flags;
}

GsAppListFilterFlags
gs_plugin_job_get_dedupe_flags (GsPluginJob *self)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), GS_APP_LIST_FILTER_FLAG_NONE);
	return self->dedupe_flags;
}

gboolean
gs_plugin_job_has_refine_flags (GsPluginJob *self, GsPluginRefineFlags refine_flags)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), FALSE);
	return (self->refine_flags & refine_flags) > 0;
}

void
gs_plugin_job_add_refine_flags (GsPluginJob *self, GsPluginRefineFlags refine_flags)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	self->refine_flags |= refine_flags;
}

void
gs_plugin_job_remove_refine_flags (GsPluginJob *self, GsPluginRefineFlags refine_flags)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	self->refine_flags &= ~refine_flags;
}

void
gs_plugin_job_set_interactive (GsPluginJob *self, gboolean interactive)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	self->interactive = interactive;
}

gboolean
gs_plugin_job_get_interactive (GsPluginJob *self)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), FALSE);
	return self->interactive;
}

void
gs_plugin_job_set_propagate_error (GsPluginJob *self, gboolean propagate_error)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	self->propagate_error = propagate_error;
}

gboolean
gs_plugin_job_get_propagate_error (GsPluginJob *self)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), FALSE);
	return self->propagate_error;
}

void
gs_plugin_job_set_max_results (GsPluginJob *self, guint max_results)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	self->max_results = max_results;
}

guint
gs_plugin_job_get_max_results (GsPluginJob *self)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), 0);
	return self->max_results;
}

void
gs_plugin_job_set_timeout (GsPluginJob *self, guint timeout)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	self->timeout = timeout;
}

guint
gs_plugin_job_get_timeout (GsPluginJob *self)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), 0);
	return self->timeout;
}

void
gs_plugin_job_set_age (GsPluginJob *self, guint64 age)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	self->age = age;
}

guint64
gs_plugin_job_get_age (GsPluginJob *self)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), 0);
	return self->age;
}

void
gs_plugin_job_set_action (GsPluginJob *self, GsPluginAction action)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	self->action = action;
}

GsPluginAction
gs_plugin_job_get_action (GsPluginJob *self)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), GS_PLUGIN_ACTION_UNKNOWN);
	return self->action;
}

void
gs_plugin_job_set_sort_func (GsPluginJob *self, GsAppListSortFunc sort_func, gpointer user_data)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	self->sort_func = sort_func;
	self->sort_func_data = user_data;
}

GsAppListSortFunc
gs_plugin_job_get_sort_func (GsPluginJob *self, gpointer *user_data_out)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL);
	if (user_data_out != NULL)
		*user_data_out = self->sort_func_data;
	return self->sort_func;
}

void
gs_plugin_job_set_search (GsPluginJob *self, const gchar *search)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	g_free (self->search);
	self->search = g_strdup (search);
}

const gchar *
gs_plugin_job_get_search (GsPluginJob *self)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL);
	return self->search;
}

void
gs_plugin_job_set_app (GsPluginJob *self, GsApp *app)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	g_set_object (&self->app, app);

	/* ensure we can always operate on a list object */
	if (self->list != NULL && app != NULL && gs_app_list_length (self->list) == 0)
		gs_app_list_add (self->list, self->app);
}

GsApp *
gs_plugin_job_get_app (GsPluginJob *self)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL);
	return self->app;
}

void
gs_plugin_job_set_list (GsPluginJob *self, GsAppList *list)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	if (list == NULL)
		g_warning ("trying to set list to NULL, not a good idea");
	g_set_object (&self->list, list);
}

GsAppList *
gs_plugin_job_get_list (GsPluginJob *self)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL);
	return self->list;
}

void
gs_plugin_job_set_file (GsPluginJob *self, GFile *file)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	g_set_object (&self->file, file);
}

GFile *
gs_plugin_job_get_file (GsPluginJob *self)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL);
	return self->file;
}

void
gs_plugin_job_set_plugin (GsPluginJob *self, GsPlugin *plugin)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	g_set_object (&self->plugin, plugin);
}

GsPlugin *
gs_plugin_job_get_plugin (GsPluginJob *self)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL);
	return self->plugin;
}

void
gs_plugin_job_set_category (GsPluginJob *self, GsCategory *category)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	g_set_object (&self->category, category);
}

GsCategory *
gs_plugin_job_get_category (GsPluginJob *self)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL);
	return self->category;
}

void
gs_plugin_job_set_review (GsPluginJob *self, AsReview *review)
{
	g_return_if_fail (GS_IS_PLUGIN_JOB (self));
	g_set_object (&self->review, review);
}

AsReview *
gs_plugin_job_get_review (GsPluginJob *self)
{
	g_return_val_if_fail (GS_IS_PLUGIN_JOB (self), NULL);
	return self->review;
}

static void
gs_plugin_job_get_property (GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec)
{
	GsPluginJob *self = GS_PLUGIN_JOB (obj);

	switch (prop_id) {
	case PROP_ACTION:
		g_value_set_enum (value, self->action);
		break;
	case PROP_AGE:
		g_value_set_uint64 (value, self->age);
		break;
	case PROP_REFINE_FLAGS:
		g_value_set_flags (value, self->refine_flags);
		break;
	case PROP_FILTER_FLAGS:
		g_value_set_flags (value, self->filter_flags);
		break;
	case PROP_DEDUPE_FLAGS:
		g_value_set_flags (value, self->dedupe_flags);
		break;
	case PROP_INTERACTIVE:
		g_value_set_boolean (value, self->interactive);
		break;
	case PROP_SEARCH:
		g_value_set_string (value, self->search);
		break;
	case PROP_APP:
		g_value_set_object (value, self->app);
		break;
	case PROP_LIST:
		g_value_set_object (value, self->list);
		break;
	case PROP_FILE:
		g_value_set_object (value, self->file);
		break;
	case PROP_CATEGORY:
		g_value_set_object (value, self->category);
		break;
	case PROP_REVIEW:
		g_value_set_object (value, self->review);
		break;
	case PROP_MAX_RESULTS:
		g_value_set_uint (value, self->max_results);
		break;
	case PROP_TIMEOUT:
		g_value_set_uint (value, self->timeout);
		break;
	case PROP_PROPAGATE_ERROR:
		g_value_set_boolean (value, self->propagate_error);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
		break;
	}
}

static void
gs_plugin_job_set_property (GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec)
{
	GsPluginJob *self = GS_PLUGIN_JOB (obj);

	switch (prop_id) {
	case PROP_ACTION:
		gs_plugin_job_set_action (self, g_value_get_enum (value));
		break;
	case PROP_AGE:
		gs_plugin_job_set_age (self, g_value_get_uint64 (value));
		break;
	case PROP_REFINE_FLAGS:
		gs_plugin_job_set_refine_flags (self, g_value_get_flags (value));
		break;
	case PROP_FILTER_FLAGS:
		gs_plugin_job_set_filter_flags (self, g_value_get_flags (value));
		break;
	case PROP_DEDUPE_FLAGS:
		gs_plugin_job_set_dedupe_flags (self, g_value_get_flags (value));
		break;
	case PROP_INTERACTIVE:
		gs_plugin_job_set_interactive (self, g_value_get_boolean (value));
		break;
	case PROP_SEARCH:
		gs_plugin_job_set_search (self, g_value_get_string (value));
		break;
	case PROP_APP:
		gs_plugin_job_set_app (self, g_value_get_object (value));
		break;
	case PROP_LIST:
		gs_plugin_job_set_list (self, g_value_get_object (value));
		break;
	case PROP_FILE:
		gs_plugin_job_set_file (self, g_value_get_object (value));
		break;
	case PROP_CATEGORY:
		gs_plugin_job_set_category (self, g_value_get_object (value));
		break;
	case PROP_REVIEW:
		gs_plugin_job_set_review (self, g_value_get_object (value));
		break;
	case PROP_MAX_RESULTS:
		gs_plugin_job_set_max_results (self, g_value_get_uint (value));
		break;
	case PROP_TIMEOUT:
		gs_plugin_job_set_timeout (self, g_value_get_uint (value));
		break;
	case PROP_PROPAGATE_ERROR:
		gs_plugin_job_set_propagate_error (self, g_value_get_boolean (value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
		break;
	}
}

static void
gs_plugin_job_finalize (GObject *obj)
{
	GsPluginJob *self = GS_PLUGIN_JOB (obj);
	g_free (self->search);
	g_clear_object (&self->app);
	g_clear_object (&self->list);
	g_clear_object (&self->file);
	g_clear_object (&self->plugin);
	g_clear_object (&self->category);
	g_clear_object (&self->review);
	G_OBJECT_CLASS (gs_plugin_job_parent_class)->finalize (obj);
}

static void
gs_plugin_job_class_init (GsPluginJobClass *klass)
{
	GParamSpec *pspec;
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	object_class->finalize = gs_plugin_job_finalize;
	object_class->get_property = gs_plugin_job_get_property;
	object_class->set_property = gs_plugin_job_set_property;

	pspec = g_param_spec_enum ("action", NULL, NULL,
				   GS_TYPE_PLUGIN_ACTION, GS_PLUGIN_ACTION_UNKNOWN,
				   G_PARAM_READWRITE);
	g_object_class_install_property (object_class, PROP_ACTION, pspec);

	pspec = g_param_spec_uint64 ("age", NULL, NULL,
				     0, G_MAXUINT64, 0,
				     G_PARAM_READWRITE);
	g_object_class_install_property (object_class, PROP_AGE, pspec);

	pspec = g_param_spec_flags ("refine-flags", NULL, NULL,
				    GS_TYPE_PLUGIN_REFINE_FLAGS, GS_PLUGIN_REFINE_FLAGS_DEFAULT,
				    G_PARAM_READWRITE);
	g_object_class_install_property (object_class, PROP_REFINE_FLAGS, pspec);

	pspec = g_param_spec_flags ("filter-flags", NULL, NULL,
				    GS_TYPE_PLUGIN_REFINE_FLAGS, GS_PLUGIN_REFINE_FLAGS_DEFAULT,
				    G_PARAM_READWRITE);
	g_object_class_install_property (object_class, PROP_FILTER_FLAGS, pspec);

	pspec = g_param_spec_flags ("dedupe-flags", NULL, NULL,
				    GS_TYPE_APP_LIST_FILTER_FLAGS, GS_APP_LIST_FILTER_FLAG_NONE,
				    G_PARAM_READWRITE);
	g_object_class_install_property (object_class, PROP_DEDUPE_FLAGS, pspec);

	pspec = g_param_spec_boolean ("interactive", NULL, NULL,
				      FALSE,
				      G_PARAM_READWRITE);

	g_object_class_install_property (object_class, PROP_INTERACTIVE, pspec);

	pspec = g_param_spec_string ("search", NULL, NULL,
				     NULL,
				     G_PARAM_READWRITE);
	g_object_class_install_property (object_class, PROP_SEARCH, pspec);

	pspec = g_param_spec_object ("app", NULL, NULL,
				     GS_TYPE_APP,
				     G_PARAM_READWRITE);
	g_object_class_install_property (object_class, PROP_APP, pspec);

	pspec = g_param_spec_object ("list", NULL, NULL,
				     GS_TYPE_APP_LIST,
				     G_PARAM_READWRITE);
	g_object_class_install_property (object_class, PROP_LIST, pspec);

	pspec = g_param_spec_object ("file", NULL, NULL,
				     G_TYPE_FILE,
				     G_PARAM_READWRITE);
	g_object_class_install_property (object_class, PROP_FILE, pspec);

	pspec = g_param_spec_object ("category", NULL, NULL,
				     GS_TYPE_CATEGORY,
				     G_PARAM_READWRITE);
	g_object_class_install_property (object_class, PROP_CATEGORY, pspec);

	pspec = g_param_spec_object ("review", NULL, NULL,
				     AS_TYPE_REVIEW,
				     G_PARAM_READWRITE);
	g_object_class_install_property (object_class, PROP_REVIEW, pspec);

	pspec = g_param_spec_uint ("max-results", NULL, NULL,
				   0, G_MAXUINT, 0,
				   G_PARAM_READWRITE);
	g_object_class_install_property (object_class, PROP_MAX_RESULTS, pspec);

	pspec = g_param_spec_uint ("timeout", NULL, NULL,
				   0, G_MAXUINT, 60,
				   G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
	g_object_class_install_property (object_class, PROP_TIMEOUT, pspec);

	pspec = g_param_spec_boolean ("propagate-error", NULL, NULL,
				      FALSE,
				      G_PARAM_READWRITE);
	g_object_class_install_property (object_class, PROP_PROPAGATE_ERROR, pspec);
}

static void
gs_plugin_job_init (GsPluginJob *self)
{
	self->refine_flags = GS_PLUGIN_REFINE_FLAGS_DEFAULT;
	self->filter_flags = GS_PLUGIN_REFINE_FLAGS_DEFAULT;
	self->dedupe_flags = GS_APP_LIST_FILTER_FLAG_KEY_ID |
			     GS_APP_LIST_FILTER_FLAG_KEY_SOURCE |
			     GS_APP_LIST_FILTER_FLAG_KEY_VERSION;
	self->list = gs_app_list_new ();
	self->time_created = g_get_monotonic_time ();
}
