/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
 * Copyright (C) 2018-2024 Matthias Klumpp <matthias@tenstral.net>
 *
 * Licensed under the GNU Lesser General Public License Version 2.1
 *
 * This library 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, either version 2.1 of the license, or
 * (at your option) any later version.
 *
 * This library 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 library.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * SECTION:as-agreement
 * @short_description: Object representing a privacy policy
 * @include: appstream.h
 * @stability: Unstable
 *
 * Agreements can be used by components to specify GDPR, EULA or other warnings.
 *
 * See also: #AsAgreementSection
 */

#include "config.h"
#include "as-agreement-private.h"
#include "as-agreement-section-private.h"

#include "as-utils-private.h"

typedef struct {
	AsAgreementKind kind;
	gchar *version_id;
	GPtrArray *sections;

	AsContext *context;
} AsAgreementPrivate;

G_DEFINE_TYPE_WITH_PRIVATE (AsAgreement, as_agreement, G_TYPE_OBJECT)

#define GET_PRIVATE(o) (as_agreement_get_instance_private (o))

static void
as_agreement_finalize (GObject *object)
{
	AsAgreement *agreement = AS_AGREEMENT (object);
	AsAgreementPrivate *priv = GET_PRIVATE (agreement);

	g_free (priv->version_id);
	g_ptr_array_unref (priv->sections);

	if (priv->context != NULL)
		g_object_unref (priv->context);

	G_OBJECT_CLASS (as_agreement_parent_class)->finalize (object);
}

static void
as_agreement_init (AsAgreement *agreement)
{
	AsAgreementPrivate *priv = GET_PRIVATE (agreement);
	priv->sections = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
}

static void
as_agreement_class_init (AsAgreementClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	object_class->finalize = as_agreement_finalize;
}

/**
 * as_agreement_kind_to_string:
 * @value: the #AsAgreementKind.
 *
 * Converts the enumerated value to an text representation.
 *
 * Returns: string version of @value
 *
 * Since: 0.12.1
 **/
const gchar *
as_agreement_kind_to_string (AsAgreementKind value)
{
	if (value == AS_AGREEMENT_KIND_GENERIC)
		return "generic";
	if (value == AS_AGREEMENT_KIND_EULA)
		return "eula";
	if (value == AS_AGREEMENT_KIND_PRIVACY)
		return "privacy";
	return "unknown";
}

/**
 * as_agreement_kind_from_string:
 * @value: the string.
 *
 * Converts the text representation to an enumerated value.
 *
 * Returns: a #AsAgreementKind or %AS_AGREEMENT_KIND_UNKNOWN for unknown
 *
 * Since: 0.12.1
 **/
AsAgreementKind
as_agreement_kind_from_string (const gchar *value)
{
	if (value == NULL || g_strcmp0 (value, "") == 0)
		return AS_AGREEMENT_KIND_GENERIC;
	if (g_strcmp0 (value, "generic") == 0)
		return AS_AGREEMENT_KIND_GENERIC;
	if (g_strcmp0 (value, "eula") == 0)
		return AS_AGREEMENT_KIND_EULA;
	if (g_strcmp0 (value, "privacy") == 0)
		return AS_AGREEMENT_KIND_PRIVACY;
	return AS_AGREEMENT_KIND_UNKNOWN;
}

/**
 * as_agreement_get_kind:
 * @agreement: a #AsAgreement instance.
 *
 * Gets the agreement kind.
 *
 * Returns: a string, e.g. %AS_AGREEMENT_KIND_EULA
 *
 * Since: 0.12.1
 **/
AsAgreementKind
as_agreement_get_kind (AsAgreement *agreement)
{
	AsAgreementPrivate *priv = GET_PRIVATE (agreement);
	return priv->kind;
}

/**
 * as_agreement_set_kind:
 * @agreement: a #AsAgreement instance.
 * @kind: the agreement kind, e.g. %AS_AGREEMENT_KIND_EULA
 *
 * Sets the agreement kind.
 *
 * Since: 0.12.1
 **/
void
as_agreement_set_kind (AsAgreement *agreement, AsAgreementKind kind)
{
	AsAgreementPrivate *priv = GET_PRIVATE (agreement);
	priv->kind = kind;
}

/**
 * as_agreement_get_version_id:
 * @agreement: a #AsAgreement instance.
 *
 * Gets the agreement version_id.
 *
 * Returns: a string, e.g. "1.4a", or NULL
 *
 * Since: 0.12.1
 **/
const gchar *
as_agreement_get_version_id (AsAgreement *agreement)
{
	AsAgreementPrivate *priv = GET_PRIVATE (agreement);
	return priv->version_id;
}

/**
 * as_agreement_set_version_id:
 * @agreement: a #AsAgreement instance.
 * @version_id: the agreement version ID, e.g. "1.4a"
 *
 * Sets the agreement version identifier.
 *
 * Since: 0.12.1
 **/
void
as_agreement_set_version_id (AsAgreement *agreement, const gchar *version_id)
{
	AsAgreementPrivate *priv = GET_PRIVATE (agreement);
	as_assign_string_safe (priv->version_id, version_id);
}

/**
 * as_agreement_get_sections:
 * @agreement: a #AsAgreement instance.
 *
 * Gets all the sections in the agreement.
 *
 * Returns: (transfer none) (element-type AsAgreementSection): array
 *
 * Since: 0.12.1
 **/
GPtrArray *
as_agreement_get_sections (AsAgreement *agreement)
{
	AsAgreementPrivate *priv = GET_PRIVATE (agreement);
	return priv->sections;
}

/**
 * as_agreement_get_section_default:
 * @agreement: a #AsAgreement instance.
 *
 * Gets the first section in the agreement.
 *
 * Returns: (transfer none) (nullable): agreement section, or %NULL
 *
 * Since: 0.12.1
 **/
AsAgreementSection *
as_agreement_get_section_default (AsAgreement *agreement)
{
	AsAgreementPrivate *priv = GET_PRIVATE (agreement);
	if (priv->sections->len == 0)
		return NULL;
	return AS_AGREEMENT_SECTION (g_ptr_array_index (priv->sections, 0));
}

/**
 * as_agreement_add_detail:
 * @agreement: a #AsAgreement instance.
 * @agreement_section: a #AsAgreementSection instance.
 *
 * Adds a section to the agreement.
 *
 * Since: 0.12.1
 **/
void
as_agreement_add_section (AsAgreement *agreement, AsAgreementSection *agreement_section)
{
	AsAgreementPrivate *priv = GET_PRIVATE (agreement);
	g_ptr_array_add (priv->sections, g_object_ref (agreement_section));
}

/**
 * as_agreement_get_context:
 * @agreement: An instance of #AsAgreement.
 *
 * Returns: the #AsContext associated with this agreement.
 * This function may return %NULL if no context is set.
 *
 * Since: 0.12.1
 */
AsContext *
as_agreement_get_context (AsAgreement *agreement)
{
	AsAgreementPrivate *priv = GET_PRIVATE (agreement);
	return priv->context;
}

/**
 * as_agreement_set_context:
 * @agreement: An instance of #AsAgreement.
 * @context: the #AsContext.
 *
 * Sets the document context this agreement is associated
 * with.
 *
 * Since: 0.12.1
 */
void
as_agreement_set_context (AsAgreement *agreement, AsContext *context)
{
	AsAgreementPrivate *priv = GET_PRIVATE (agreement);
	if (priv->context != NULL)
		g_object_unref (priv->context);
	priv->context = g_object_ref (context);
}

/**
 * as_agreement_load_from_xml:
 * @agreement: an #AsAgreement
 * @ctx: the AppStream document context.
 * @node: the XML node.
 * @error: a #GError.
 *
 * Loads data from an XML node.
 **/
gboolean
as_agreement_load_from_xml (AsAgreement *agreement, AsContext *ctx, xmlNode *node, GError **error)
{
	AsAgreementPrivate *priv = GET_PRIVATE (agreement);
	xmlNode *iter;
	gchar *prop;

	/* propagate context */
	as_agreement_set_context (agreement, ctx);

	prop = as_xml_get_prop_value (node, "type");
	if (prop != NULL) {
		priv->kind = as_agreement_kind_from_string (prop);
		g_free (prop);
	}

	prop = as_xml_get_prop_value (node, "version_id");
	if (prop != NULL) {
		as_agreement_set_version_id (agreement, prop);
		g_free (prop);
	}

	/* read agreement sections */
	for (iter = node->children; iter != NULL; iter = iter->next) {
		if (iter->type != XML_ELEMENT_NODE)
			continue;

		if (g_strcmp0 ((gchar *) iter->name, "agreement_section") == 0) {
			g_autoptr(AsAgreementSection) asection = as_agreement_section_new ();

			if (!as_agreement_section_load_from_xml (asection,
								 priv->context,
								 iter,
								 error))
				return FALSE;
			as_agreement_add_section (agreement, asection);
		}
	}

	return TRUE;
}

/**
 * as_agreement_to_xml_node:
 * @agreement: an #AsAgreement
 * @ctx: the AppStream document context.
 * @root: XML node to attach the new nodes to.
 *
 * Serializes the data to an XML node.
 **/
void
as_agreement_to_xml_node (AsAgreement *agreement, AsContext *ctx, xmlNode *root)
{
	AsAgreementPrivate *priv = GET_PRIVATE (agreement);
	xmlNode *agnode;
	guint i;

	agnode = as_xml_add_node (root, "agreement");
	as_xml_add_text_prop (agnode, "type", as_agreement_kind_to_string (priv->kind));
	as_xml_add_text_prop (agnode, "version_id", priv->version_id);

	for (i = 0; i < priv->sections->len; i++) {
		AsAgreementSection *agsec = AS_AGREEMENT_SECTION (
		    g_ptr_array_index (priv->sections, i));
		as_agreement_section_to_xml_node (agsec, ctx, agnode);
	}
}

/**
 * as_agreement_load_from_yaml:
 * @agreement: an #AsAgreement
 * @ctx: the AppStream document context.
 * @node: the YAML node.
 * @error: a #GError.
 *
 * Loads data from a YAML field.
 **/
gboolean
as_agreement_load_from_yaml (AsAgreement *agreement, AsContext *ctx, GNode *node, GError **error)
{
	AsAgreementPrivate *priv = GET_PRIVATE (agreement);
	GNode *n;

	/* propagate context */
	as_agreement_set_context (agreement, ctx);

	for (n = node->children; n != NULL; n = n->next) {
		const gchar *key = as_yaml_node_get_key (n);
		const gchar *value = as_yaml_node_get_value (n);

		if (g_strcmp0 (key, "type") == 0) {
			priv->kind = as_agreement_kind_from_string (value);
		} else if (g_strcmp0 (key, "version-id") == 0) {
			as_agreement_set_version_id (agreement, value);
		} else if (g_strcmp0 (key, "sections") == 0) {
			GNode *sn;

			for (sn = n->children; sn != NULL; sn = sn->next) {
				g_autoptr(AsAgreementSection) asec = as_agreement_section_new ();

				if (!as_agreement_section_load_from_yaml (asec, ctx, sn, error))
					return FALSE;
				as_agreement_add_section (agreement, asec);
			}
		} else {
			as_yaml_print_unknown ("agreement", key);
		}
	}

	return TRUE;
}

/**
 * as_agreement_emit_yaml:
 * @agreement: an #AsAgreement
 * @ctx: the AppStream document context.
 * @emitter: The YAML emitter to emit data on.
 *
 * Emit YAML data for this object.
 **/
void
as_agreement_emit_yaml (AsAgreement *agreement, AsContext *ctx, yaml_emitter_t *emitter)
{
	AsAgreementPrivate *priv = GET_PRIVATE (agreement);

	/* start mapping for this agreement */
	as_yaml_mapping_start (emitter);

	/* type */
	as_yaml_emit_entry (emitter, "type", as_agreement_kind_to_string (priv->kind));

	/* version */
	as_yaml_emit_entry (emitter, "version-id", priv->version_id);

	as_yaml_emit_scalar (emitter, "sections");
	as_yaml_sequence_start (emitter);
	for (guint i = 0; i < priv->sections->len; i++) {
		AsAgreementSection *asec = AS_AGREEMENT_SECTION (
		    g_ptr_array_index (priv->sections, i));
		as_agreement_section_emit_yaml (asec, ctx, emitter);
	}
	as_yaml_sequence_end (emitter);

	/* end mapping for the agreement */
	as_yaml_mapping_end (emitter);
}

/**
 * as_agreement_new:
 *
 * Creates a new #AsAgreement.
 *
 * Returns: (transfer full): a #AsAgreement
 *
 * Since: 0.12.1
 **/
AsAgreement *
as_agreement_new (void)
{
	AsAgreement *agreement;
	agreement = g_object_new (AS_TYPE_AGREEMENT, NULL);
	return AS_AGREEMENT (agreement);
}
