/*
 * Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
 *
 * SPDX-License-Identifier: LGPL-2.1+
 */

#define G_LOG_DOMAIN				"XbSilo"

#include "config.h"

#include <gio/gio.h>

#include "xb-builder-fixup-private.h"

typedef struct {
	GObject			 parent_instance;
	gchar			*id;
	XbBuilderFixupFunc	 func;
	gpointer		 user_data;
	GDestroyNotify		 user_data_free;
	gint			 max_depth;
} XbBuilderFixupPrivate;

G_DEFINE_TYPE_WITH_PRIVATE (XbBuilderFixup, xb_builder_fixup, G_TYPE_OBJECT)
#define GET_PRIVATE(o) (xb_builder_fixup_get_instance_private (o))

typedef struct {
	XbBuilderFixup	*self;
	gboolean	 ret;
	GError		*error;
} XbBuilderFixupHelper;

static gboolean
xb_builder_fixup_cb (XbBuilderNode *bn, gpointer data)
{
	XbBuilderFixupHelper *helper = (XbBuilderFixupHelper *) data;
	XbBuilderFixup *self = XB_BUILDER_FIXUP (helper->self);
	XbBuilderFixupPrivate *priv = GET_PRIVATE (self);

	/* run all node funcs on the source */
	if (!priv->func (self, bn, priv->user_data, &helper->error)) {
		helper->ret = FALSE;
		return TRUE;
	}

	/* keep going */
	return FALSE;
}

/* private */
gboolean
xb_builder_fixup_node (XbBuilderFixup *self, XbBuilderNode *bn, GError **error)
{
	XbBuilderFixupPrivate *priv = GET_PRIVATE (self);
	XbBuilderFixupHelper helper = {
		.self = self,
		.ret = TRUE,
		.error = NULL,
	};

	/* visit each node */
	xb_builder_node_traverse (bn, G_PRE_ORDER, G_TRAVERSE_ALL, priv->max_depth,
				  xb_builder_fixup_cb, &helper);
	if (!helper.ret) {
		g_propagate_error (error, helper.error);
		return FALSE;
	}
	return TRUE;
}

/**
 * xb_builder_fixup_get_id:
 * @self: a #XbBuilderFixup
 *
 * Gets the fixup ID.
 *
 * Returns: string, e.g. `AppStreamUpgrade`
 *
 * Since: 0.1.3
 **/
const gchar *
xb_builder_fixup_get_id (XbBuilderFixup *self)
{
	XbBuilderFixupPrivate *priv = GET_PRIVATE (self);
	g_return_val_if_fail (XB_IS_BUILDER_FIXUP (self), NULL);
	return priv->id;
}

/* private */
gchar *
xb_builder_fixup_get_guid (XbBuilderFixup *self)
{
	GString *str = g_string_new ("func-id=");
	XbBuilderFixupPrivate *priv = GET_PRIVATE (self);

	g_return_val_if_fail (XB_IS_BUILDER_FIXUP (self), 0);

	/* build GUID using ID and max-depth, if set */
	g_string_append (str, priv->id);
	if (priv->max_depth != -1)
		g_string_append_printf (str, "@%i", priv->max_depth);
	return g_string_free (str, FALSE);
}

/**
 * xb_builder_fixup_get_max_depth:
 * @self: a #XbBuilderFixup
 *
 * Gets the maximum depth used for this fixup, if each node is being visited.
 *
 * Returns: integer, or -1 if unset
 *
 * Since: 0.1.3
 **/
gint
xb_builder_fixup_get_max_depth (XbBuilderFixup *self)
{
	XbBuilderFixupPrivate *priv = GET_PRIVATE (self);
	g_return_val_if_fail (XB_IS_BUILDER_FIXUP (self), 0);
	return priv->max_depth;
}

/**
 * xb_builder_fixup_set_max_depth:
 * @self: a #XbBuilderFixup
 * @max_depth: integer, -1 for "all"
 *
 * Sets the maximum depth used for this fixup. Use a @max_depth of 0 to only
 * visit the root node.
 *
 * Setting a maximum depth may increase performance considerably if using
 * fixup functions on large and deeply nested XML files.
 *
 * Since: 0.1.3
 **/
void
xb_builder_fixup_set_max_depth (XbBuilderFixup *self, gint max_depth)
{
	XbBuilderFixupPrivate *priv = GET_PRIVATE (self);
	g_return_if_fail (XB_IS_BUILDER_FIXUP (self));
	priv->max_depth = max_depth;
}

static void
xb_builder_fixup_finalize (GObject *obj)
{
	XbBuilderFixup *self = XB_BUILDER_FIXUP (obj);
	XbBuilderFixupPrivate *priv = GET_PRIVATE (self);

	if (priv->user_data_free != NULL)
		priv->user_data_free (priv->user_data);
	g_free (priv->id);

	G_OBJECT_CLASS (xb_builder_fixup_parent_class)->finalize (obj);
}

static void
xb_builder_fixup_class_init (XbBuilderFixupClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	object_class->finalize = xb_builder_fixup_finalize;
}

static void
xb_builder_fixup_init (XbBuilderFixup *self)
{
	XbBuilderFixupPrivate *priv = GET_PRIVATE (self);
	priv->max_depth = -1;
}

/**
 * xb_builder_fixup_new:
 * @id: a text ID value, e.g. `AppStreamUpgrade`
 * @func: a callback
 * @user_data: user pointer to pass to @func, or %NULL
 * @user_data_free: a function which gets called to free @user_data, or %NULL
 *
 * Creates a function that will get run on every #XbBuilderNode compile creates.
 *
 * Returns: a new #XbBuilderFixup
 *
 * Since: 0.1.3
 **/
XbBuilderFixup *
xb_builder_fixup_new (const gchar *id,
		      XbBuilderFixupFunc func,
		      gpointer user_data,
		      GDestroyNotify user_data_free)
{
	XbBuilderFixup *self = g_object_new (XB_TYPE_BUILDER_FIXUP, NULL);
	XbBuilderFixupPrivate *priv = GET_PRIVATE (self);

	g_return_val_if_fail (XB_IS_BUILDER_FIXUP (self), NULL);
	g_return_val_if_fail (id != NULL, NULL);
	g_return_val_if_fail (func != NULL, NULL);

	priv->id = g_strdup (id);
	priv->func = func;
	priv->user_data = user_data;
	priv->user_data_free = user_data_free;
	return self;
}
