/* Pi-hole: A black hole for Internet advertisements
*  (c) 2020 Pi-hole, LLC (https://pi-hole.net)
*  Network-wide ad blocking via your own hardware.
*
*  FTL Engine
*  Dynamic vector routines
*
*  This file is copyright under the latest version of the EUPL.
*  Please see LICENSE file for your rights under this license. */

#include "vector.h"
// struct config
#include "config.h"
// logg()
#include "log.h"

sqlite3_stmt_vec *new_sqlite3_stmt_vec(unsigned int initial_size)
{
	if(config.debug & DEBUG_VECTORS)
		logg("Initializing new sqlite3_stmt* vector with size %u", initial_size);

	sqlite3_stmt_vec *v = calloc(1, sizeof(sqlite3_stmt_vec));
	v->capacity = initial_size;
	// Calloc ensures they are all set to zero which is the default state
	v->items = calloc(initial_size, sizeof(sqlite3_stmt *) * initial_size);
	// Set correct subroutine pointers
	v->set = set_sqlite3_stmt_vec;
	v->get = get_sqlite3_stmt_vec;
	v->free = free_sqlite3_stmt_vec;
	return v;
}

static void resize_sqlite3_stmt_vec(sqlite3_stmt_vec *v, unsigned int capacity)
{
	if(config.debug & DEBUG_VECTORS)
		logg("Resizing sqlite3_stmt* vector %p from %u to %u", v, v->capacity, capacity);

	// If ptr is NULL, the call to realloc(ptr, size) is equivalent to
	// malloc(size) so we can use it also for initializing a vector for the
	// first time.
	sqlite3_stmt **items = realloc(v->items, sizeof(sqlite3_stmt *) * capacity);
	if(!items)
	{
		logg("ERROR: Memory allocation failed in resize_sqlite3_stmt_vec(%p, %u)",
		       v, capacity);
		return;
	}

	// Update items pointer
	v->items = items;

	// NULL-initialize newly allocated memory slots
	for(unsigned int i = v->capacity; i < capacity; i++)
		v->items[i] = NULL;

	// Update capacity
	v->capacity = capacity;
}

void set_sqlite3_stmt_vec(sqlite3_stmt_vec *v, unsigned int index, sqlite3_stmt *item)
{
	if(config.debug & DEBUG_VECTORS)
		logg("Setting sqlite3_stmt** %p[%u] <-- %p", v, index, item);

	if(v == NULL)
	{
		logg("ERROR: Passed NULL vector to set_sqlite3_stmt_vec(%p, %u, %p)",
		       v, index, item);
		return;
	}

	if(index >= v->capacity)
	{
		// Allocate more memory when trying to set a statement vector entry with
		// an index larger than the current array size (this makes set an
		// equivalent alternative to append)
		resize_sqlite3_stmt_vec(v, index + VEC_ALLOC_STEP);
	}

	// Set item
	v->items[index] = item;
}

// This function has no effects except to return a value. It can be subject to
// data flow analysis and might be eliminated. Hence, we add the "pure"
// attribute to this function.
sqlite3_stmt * __attribute__((pure)) get_sqlite3_stmt_vec(sqlite3_stmt_vec *v, unsigned int index)
{
	if(v == NULL)
	{
		logg("ERROR: Passed NULL vector to get_sqlite3_stmt_vec(%p, %u)",
		       v, index);
		return 0;
	}

	if(index >= v->capacity)
	{
		// Silently increase size of vector if trying to read out-of-bounds
		resize_sqlite3_stmt_vec(v, index + VEC_ALLOC_STEP);
	}

	sqlite3_stmt* item = v->items[index];
	if(config.debug & DEBUG_VECTORS)
		logg("Getting sqlite3_stmt** %p[%u] --> %p", v, index, item);

	return item;
}

void free_sqlite3_stmt_vec(sqlite3_stmt_vec *v)
{
	if(config.debug & DEBUG_VECTORS)
		logg("Freeing sqlite3_stmt* vector %p", v);

	// This vector was never allocated, invoking free_sqlite3_stmt_vec() on a
	// NULL pointer should be a harmless no-op.
	if(v == NULL || v->items == NULL)
		return;

	// Free elements of the vector...
	free(v->items);
	// ...and then the vector itself
	free(v);
}
