/*
 * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017
 *   Jonathan Schleifer <js@heap.zone>
 *
 * All rights reserved.
 *
 * This file is part of ObjFW. It may be distributed under the terms of the
 * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
 * the packaging of this file.
 *
 * Alternatively, it may be distributed under the terms of the GNU General
 * Public License, either version 2 or 3, which can be found in the file
 * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
 * file.
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>

#import "ObjFW-RT.h"
#import "private.h"

static struct objc_dtable_level2 *empty_level2 = NULL;
#ifdef OF_SELUID24
static struct objc_dtable_level3 *empty_level3 = NULL;
#endif

static void
init(void)
{
	empty_level2 = malloc(sizeof(struct objc_dtable_level2));
	if (empty_level2 == NULL)
		OBJC_ERROR("Not enough memory to allocate dtable!");

#ifdef OF_SELUID24
	empty_level3 = malloc(sizeof(struct objc_dtable_level3));
	if (empty_level3 == NULL)
		OBJC_ERROR("Not enough memory to allocate dtable!");
#endif

#ifdef OF_SELUID24
	for (uint_fast16_t i = 0; i < 256; i++) {
		empty_level2->buckets[i] = empty_level3;
		empty_level3->buckets[i] = (IMP)0;
	}
#else
	for (uint_fast16_t i = 0; i < 256; i++)
		empty_level2->buckets[i] = (IMP)0;
#endif
}

struct objc_dtable *
objc_dtable_new(void)
{
	struct objc_dtable *dtable;

#ifdef OF_SELUID24
	if (empty_level2 == NULL || empty_level3 == NULL)
		init();
#else
	if (empty_level2 == NULL)
		init();
#endif

	if ((dtable = malloc(sizeof(struct objc_dtable))) == NULL)
		OBJC_ERROR("Not enough memory to allocate dtable!");

	for (uint_fast16_t i = 0; i < 256; i++)
		dtable->buckets[i] = empty_level2;

	return dtable;
}

void
objc_dtable_copy(struct objc_dtable *dst, struct objc_dtable *src)
{
	for (uint_fast16_t i = 0; i < 256; i++) {
		if (src->buckets[i] == empty_level2)
			continue;

#ifdef OF_SELUID24
		for (uint_fast16_t j = 0; j < 256; j++) {
			if (src->buckets[i]->buckets[j] == empty_level3)
				continue;

			for (uint_fast16_t k = 0; k < 256; k++) {
				IMP obj;
				uint32_t idx;

				obj = src->buckets[i]->buckets[j]->buckets[k];

				if (obj == (IMP)0)
					continue;

				idx = (uint32_t)
				    (((uint32_t)i << 16) | (j << 8) | k);
				objc_dtable_set(dst, idx, obj);
			}
		}
#else
		for (uint_fast16_t j = 0; j < 256; j++) {
			IMP obj = src->buckets[i]->buckets[j];
			uint32_t idx;

			if (obj == (IMP)0)
				continue;

			idx = (uint32_t)((i << 8) | j);
			objc_dtable_set(dst, idx, obj);
		}
#endif
	}
}

void
objc_dtable_set(struct objc_dtable *dtable, uint32_t idx, IMP obj)
{
#ifdef OF_SELUID24
	uint8_t i = idx >> 16;
	uint8_t j = idx >> 8;
	uint8_t k = idx;
#else
	uint8_t i = idx >> 8;
	uint8_t j = idx;
#endif

	if (dtable->buckets[i] == empty_level2) {
		struct objc_dtable_level2 *level2 =
		    malloc(sizeof(struct objc_dtable_level2));

		if (level2 == NULL)
			OBJC_ERROR("Not enough memory to insert into dtable!");

		for (uint_fast16_t l = 0; l < 256; l++)
#ifdef OF_SELUID24
			level2->buckets[l] = empty_level3;
#else
			level2->buckets[l] = (IMP)0;
#endif

		dtable->buckets[i] = level2;
	}

#ifdef OF_SELUID24
	if (dtable->buckets[i]->buckets[j] == empty_level3) {
		struct objc_dtable_level3 *level3 =
		    malloc(sizeof(struct objc_dtable_level3));

		if (level3 == NULL)
			OBJC_ERROR("Not enough memory to insert into dtable!");

		for (uint_fast16_t l = 0; l < 256; l++)
			level3->buckets[l] = (IMP)0;

		dtable->buckets[i]->buckets[j] = level3;
	}

	dtable->buckets[i]->buckets[j]->buckets[k] = obj;
#else
	dtable->buckets[i]->buckets[j] = obj;
#endif
}

void
objc_dtable_free(struct objc_dtable *dtable)
{
	for (uint_fast16_t i = 0; i < 256; i++) {
		if (dtable->buckets[i] == empty_level2)
			continue;

#ifdef OF_SELUID24
		for (uint_fast16_t j = 0; j < 256; j++)
			if (dtable->buckets[i]->buckets[j] != empty_level3)
				free(dtable->buckets[i]->buckets[j]);
#endif

		free(dtable->buckets[i]);
	}

	free(dtable);
}

void
objc_dtable_cleanup(void)
{
	if (empty_level2 != NULL)
		free(empty_level2);
#ifdef OF_SELUID24
	if (empty_level3 != NULL)
		free(empty_level3);
#endif

	empty_level2 = NULL;
#ifdef OF_SELUID24
	empty_level3 = NULL;
#endif
}
