/*
 * Range list value
 *
 * Copyright (C) 2006-2020, Joachim Metz <joachim.metz@gmail.com>
 *
 * Refer to AUTHORS for acknowledgements.
 *
 * This program 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 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 Lesser General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <common.h>
#include <memory.h>
#include <types.h>

#include "libcdata_libcerror.h"
#include "libcdata_range_list_value.h"

/* Creates a range list value
 * Make sure the value range_list_value is referencing, is set to NULL
 * Returns 1 if successful or -1 on error
 */
int libcdata_range_list_value_initialize(
     libcdata_range_list_value_t **range_list_value,
     libcerror_error_t **error )
{
	static char *function = "libcdata_range_list_value_initialize";

	if( range_list_value == NULL )
	{
		libcerror_error_set(
		 error,
		 LIBCERROR_ERROR_DOMAIN_ARGUMENTS,
		 LIBCERROR_ARGUMENT_ERROR_INVALID_VALUE,
		 "%s: invalid range list value.",
		 function );

		return( -1 );
	}
	if( *range_list_value != NULL )
	{
		libcerror_error_set(
		 error,
		 LIBCERROR_ERROR_DOMAIN_RUNTIME,
		 LIBCERROR_RUNTIME_ERROR_VALUE_ALREADY_SET,
		 "%s: invalid range list value value already set.",
		 function );

		return( -1 );
	}
	*range_list_value = memory_allocate_structure(
	                     libcdata_range_list_value_t );

	if( *range_list_value == NULL )
	{
		libcerror_error_set(
		 error,
		 LIBCERROR_ERROR_DOMAIN_MEMORY,
		 LIBCERROR_MEMORY_ERROR_INSUFFICIENT,
		 "%s: unable to create range list value.",
		 function );

		goto on_error;
	}
	if( memory_set(
	     *range_list_value,
	     0,
	     sizeof( libcdata_range_list_value_t ) ) == NULL )
	{
		libcerror_error_set(
		 error,
		 LIBCERROR_ERROR_DOMAIN_MEMORY,
		 LIBCERROR_MEMORY_ERROR_SET_FAILED,
		 "%s: unable to clear range list value.",
		 function );

		goto on_error;
	}
	return( 1 );

on_error:
	if( *range_list_value != NULL )
	{
		memory_free(
		 *range_list_value );

		*range_list_value = NULL;
	}
	return( -1 );
}

/* Frees a range list value
 * Uses the value_free_function to free the element value
 * Returns 1 if successful or -1 on error
 */
int libcdata_range_list_value_free(
     libcdata_range_list_value_t **range_list_value,
     int (*value_free_function)(
            intptr_t **value,
            libcerror_error_t **error ),
     libcerror_error_t **error )
{
	static char *function = "libcdata_range_list_value_free";
	int result            = 1;

	if( range_list_value == NULL )
	{
		libcerror_error_set(
		 error,
		 LIBCERROR_ERROR_DOMAIN_ARGUMENTS,
		 LIBCERROR_ARGUMENT_ERROR_INVALID_VALUE,
		 "%s: invalid range list value.",
		 function );

		return( -1 );
	}
	if( *range_list_value != NULL )
	{
		if( value_free_function != NULL )
		{
			if( value_free_function(
			     &( ( *range_list_value )->value ),
			     error ) != 1 )
			{
				libcerror_error_set(
				 error,
				 LIBCERROR_ERROR_DOMAIN_RUNTIME,
				 LIBCERROR_RUNTIME_ERROR_FINALIZE_FAILED,
				 "%s: unable to free value.",
				 function );

				result = -1;
			}
		}
		memory_free(
		 *range_list_value );

		*range_list_value = NULL;
	}
	return( result );
}

/* Clones the range list value
 *
 * The values are cloned using the value_clone_function
 * On error the values are freed using the value_free_function
 *
 * Returns 1 if successful or -1 on error
 */
int libcdata_range_list_value_clone(
     libcdata_range_list_value_t **destination_range_list_value,
     libcdata_range_list_value_t *source_range_list_value,
     int (*value_free_function)(
            intptr_t **value,
            libcerror_error_t **error ),
     int (*value_clone_function)(
            intptr_t **destination_value,
            intptr_t *source_value,
            libcerror_error_t **error ),
     libcerror_error_t **error )
{
	static char *function = "libcdata_range_list_value_clone";

	if( destination_range_list_value == NULL )
	{
		libcerror_error_set(
		 error,
		 LIBCERROR_ERROR_DOMAIN_ARGUMENTS,
		 LIBCERROR_ARGUMENT_ERROR_INVALID_VALUE,
		 "%s: invalid destination range list value.",
		 function );

		return( -1 );
	}
	if( *destination_range_list_value != NULL )
	{
		libcerror_error_set(
		 error,
		 LIBCERROR_ERROR_DOMAIN_RUNTIME,
		 LIBCERROR_RUNTIME_ERROR_VALUE_ALREADY_SET,
		 "%s: invalid destination range list value value already set.",
		 function );

		return( -1 );
	}
	if( value_free_function == NULL )
	{
		libcerror_error_set(
		 error,
		 LIBCERROR_ERROR_DOMAIN_ARGUMENTS,
		 LIBCERROR_ARGUMENT_ERROR_INVALID_VALUE,
		 "%s: invalid value free function.",
		 function );

		return( -1 );
	}
	if( value_clone_function == NULL )
	{
		libcerror_error_set(
		 error,
		 LIBCERROR_ERROR_DOMAIN_ARGUMENTS,
		 LIBCERROR_ARGUMENT_ERROR_INVALID_VALUE,
		 "%s: invalid value clone function.",
		 function );

		return( -1 );
	}
	if( source_range_list_value == NULL )
	{
		*destination_range_list_value = NULL;

		return( 1 );
	}
	*destination_range_list_value = memory_allocate_structure(
	                                 libcdata_range_list_value_t );

	if( *destination_range_list_value == NULL )
	{
		libcerror_error_set(
		 error,
		 LIBCERROR_ERROR_DOMAIN_MEMORY,
		 LIBCERROR_MEMORY_ERROR_INSUFFICIENT,
		 "%s: unable to create destination range list value.",
		 function );

		goto on_error;
	}
	if( memory_copy(
	     *destination_range_list_value,
	     source_range_list_value,
	     sizeof( libcdata_range_list_value_t ) ) == NULL )
	{
		libcerror_error_set(
		 error,
		 LIBCERROR_ERROR_DOMAIN_MEMORY,
		 LIBCERROR_MEMORY_ERROR_COPY_FAILED,
		 "%s: unable to copy source to destination range list value.",
		 function );

		memory_free(
		 *destination_range_list_value );

		*destination_range_list_value = NULL;

		return( -1 );
	}
	( *destination_range_list_value )->value = NULL;

	if( value_clone_function(
	     &( ( *destination_range_list_value )->value ),
	     source_range_list_value->value,
	     error ) != 1 )
	{
		libcerror_error_set(
		 error,
		 LIBCERROR_ERROR_DOMAIN_RUNTIME,
		 LIBCERROR_RUNTIME_ERROR_INITIALIZE_FAILED,
		 "%s: unable to create destination value.",
		 function );

		goto on_error;
	}
	return( 1 );

on_error:
	if( *destination_range_list_value != NULL )
	{
		if( ( *destination_range_list_value )->value != NULL )
		{
			value_free_function(
			 &( ( *destination_range_list_value )->value ),
			 NULL );
		}
		memory_free(
		 *destination_range_list_value );

		*destination_range_list_value = NULL;
	}
	return( -1 );
}

/* Merges the range list values
 *
 * The values are merged using the value_merge_function.
 * If the source value is NULL the merge function is not called.
 *
 * Returns 1 if successful or -1 on error
 */
int libcdata_range_list_value_merge(
     libcdata_range_list_value_t *destination_range_list_value,
     libcdata_range_list_value_t *source_range_list_value,
     int (*value_merge_function)(
            intptr_t *destination_value,
            intptr_t *source_value,
            libcerror_error_t **error ),
     libcerror_error_t **error )
{
	static char *function = "libcdata_range_list_value_merge";

	if( destination_range_list_value == NULL )
	{
		libcerror_error_set(
		 error,
		 LIBCERROR_ERROR_DOMAIN_ARGUMENTS,
		 LIBCERROR_ARGUMENT_ERROR_INVALID_VALUE,
		 "%s: invalid destination range list value.",
		 function );

		return( -1 );
	}
	if( source_range_list_value == NULL )
	{
		libcerror_error_set(
		 error,
		 LIBCERROR_ERROR_DOMAIN_ARGUMENTS,
		 LIBCERROR_ARGUMENT_ERROR_INVALID_VALUE,
		 "%s: invalid source range list value.",
		 function );

		return( -1 );
	}
	if( source_range_list_value->value != NULL )
	{
		if( destination_range_list_value->value == NULL )
		{
			destination_range_list_value->value = source_range_list_value->value;
		}
		else
		{
			if( value_merge_function == NULL )
			{
				libcerror_error_set(
				 error,
				 LIBCERROR_ERROR_DOMAIN_ARGUMENTS,
				 LIBCERROR_ARGUMENT_ERROR_INVALID_VALUE,
				 "%s: invalid value merge function.",
				 function );

				return( -1 );
			}
			if( value_merge_function(
			     destination_range_list_value->value,
			     source_range_list_value->value,
			     error ) != 1 )
			{
				libcerror_error_set(
				 error,
				 LIBCERROR_ERROR_DOMAIN_RUNTIME,
				 LIBCERROR_RUNTIME_ERROR_INITIALIZE_FAILED,
				 "%s: unable to merge source with destination value.",
				 function );

				return( -1 );
			}
		}
	}
	if( destination_range_list_value->start > source_range_list_value->start )
	{
		/* Merge a preceding range
		 */
		destination_range_list_value->size += destination_range_list_value->start - source_range_list_value->start;
		destination_range_list_value->start = source_range_list_value->start;
	}
	if( destination_range_list_value->end < source_range_list_value->end )
	{
		/* Merge a successive range
		 */
		destination_range_list_value->size += source_range_list_value->end - destination_range_list_value->end;
		destination_range_list_value->end   = source_range_list_value->end;
	}
	return( 1 );
}

/* Checks if the range overlaps with the range list value
 * Returns 1 if the range overlaps, 0 if not or -1 on error
 */
int libcdata_range_list_value_check_range_overlap(
     libcdata_range_list_value_t *range_list_value,
     uint64_t range_start,
     uint64_t range_end,
     libcerror_error_t **error )
{
	static char *function = "libcdata_range_list_value_check_range_overlap";

	if( range_list_value == NULL )
	{
		libcerror_error_set(
		 error,
		 LIBCERROR_ERROR_DOMAIN_ARGUMENTS,
		 LIBCERROR_ARGUMENT_ERROR_INVALID_VALUE,
		 "%s: invalid range list value.",
		 function );

		return( -1 );
	}
	/* Check if the range overlaps the existing range entirely
	 */
	if( ( range_start < range_list_value->start )
	 && ( range_end > range_list_value->end ) )
	{
		return( 1 );
	}
	/* Check if the range overlaps at the end of the existing range
	 */
	if( ( range_start >= range_list_value->start )
	 && ( range_start <= range_list_value->end ) )
	{
		return( 1 );
	}
	/* Check if the range overlaps at the beginning of the existing range
	 */
	if( ( range_end >= range_list_value->start )
	 && ( range_end <= range_list_value->end ) )
	{
		return( 1 );
	}
	return( 0 );
}

