/* SPDX-FileCopyrightText: 2026 - Sébastien Wilmet
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

#include "gfls-bytes-region-builder.h"
#include "gfls-bytes-region-private.h"

/**
 * SECTION:gfls-bytes-region-builder
 * @Title: GflsBytesRegionBuilder
 * @Short_description: Builds #GflsBytesRegion objects
 *
 * Builds #GflsBytesRegion objects.
 */

struct _GflsBytesRegionBuilder
{
	GArray *sub_regions; /* (element-type GflsBytesSubRegionBounds) */
	gsize current_position;
};

/**
 * gfls_bytes_region_builder_new: (skip)
 *
 * Returns: (transfer full): a new #GflsBytesRegionBuilder.
 * Since: 0.4
 */
GflsBytesRegionBuilder *
gfls_bytes_region_builder_new (void)
{
	GflsBytesRegionBuilder *builder;

	builder = g_new0 (GflsBytesRegionBuilder, 1);

	builder->sub_regions = g_array_new (FALSE, FALSE, sizeof (GflsBytesSubRegionBounds));

	return builder;
}

/**
 * gfls_bytes_region_builder_free: (skip)
 * @builder: (nullable): a #GflsBytesRegionBuilder.
 * @free_data: if %TRUE, the data is freed as well.
 *
 * Creates a #GflsBytesRegion and frees @builder.
 *
 * Returns: (transfer full) (nullable): a #GflsBytesRegion, or %NULL if
 *   @free_data is %TRUE.
 * Since: 0.4
 */
GflsBytesRegion *
gfls_bytes_region_builder_free (GflsBytesRegionBuilder *builder,
				gboolean                free_data)
{
	GflsBytesRegion *region = NULL;

	if (builder == NULL)
	{
		return NULL;
	}

	if (!free_data)
	{
		region = _gfls_bytes_region_new (builder->sub_regions,
						 builder->current_position);
	}

	g_array_unref (builder->sub_regions);
	g_free (builder);

	return region;
}

/**
 * gfls_bytes_region_builder_append:
 * @builder: a #GflsBytesRegionBuilder.
 * @sub_region_length: the length of the sub-region. Must not be equal to 0.
 * @is_part_of_region: whether the sub-region is part of the region.
 *
 * Appends a sub-region at the end.
 *
 * If @is_part_of_region is %FALSE it creates a hole.
 *
 * Contiguous sub-regions of the same kind are merged and will thus be treated
 * as a single sub-region. So you can conveniently call this function several
 * times in a row with the same value for @is_part_of_region.
 *
 * In order to create a #GflsBytesRegion that will traverse a whole #GBytes, it
 * is required to call this function the right amount of times, even if it ends
 * with a hole. See also gfls_bytes_region_match_bytes().
 *
 * Since: 0.4
 */
void
gfls_bytes_region_builder_append (GflsBytesRegionBuilder *builder,
				  gsize                   sub_region_length,
				  gboolean                is_part_of_region)
{
	gsize next_position = 0;

	g_return_if_fail (builder != NULL);
	g_return_if_fail (sub_region_length != 0);

	if (!g_size_checked_add (&next_position,
				 builder->current_position,
				 sub_region_length))
	{
		g_warning ("Ignoring the sub-region because of an integer overflow.");
		return;
	}

	if (is_part_of_region)
	{
		GflsBytesSubRegionBounds *last_sub_region = NULL;

		if (builder->sub_regions->len != 0)
		{
			last_sub_region = &g_array_index (builder->sub_regions,
							  GflsBytesSubRegionBounds,
							  builder->sub_regions->len - 1);
		}

		/* Merge */
		if (last_sub_region != NULL &&
		    last_sub_region->end == builder->current_position)
		{
			last_sub_region->end = next_position;
		}

		/* Insert new sub-region */
		else
		{
			GflsBytesSubRegionBounds new_sub_region;

			new_sub_region.start = builder->current_position;
			new_sub_region.end = next_position;

			g_array_append_val (builder->sub_regions, new_sub_region);
		}
	}

	builder->current_position = next_position;
}

/**
 * gfls_bytes_region_builder_get_current_size:
 * @builder: a #GflsBytesRegionBuilder.
 *
 * Returns: the current total size, in number of bytes (holes included).
 * Since: 0.4
 */
gsize
gfls_bytes_region_builder_get_current_size (GflsBytesRegionBuilder *builder)
{
	g_return_val_if_fail (builder != NULL, 0);
	return builder->current_position;
}
