/*
 *	Copyright (c) 1994, by Sun Microsytems, Inc.
 */

#pragma	ident	"@(#)info.c	1.13	94/10/04 SMI"

#include "libtnf.h"

#define	TAG_INDEX(x)	(TNF_TAG16_ABS16(x) / sizeof (tnf_ref32_t))

/*
 *
 */

static struct taginfo *	add_info(TNF *, tnf_ref32_t *);

static struct taginfo *
init_abstract_info(TNF *, tnf_ref32_t *, struct taginfo *);

static struct taginfo *
init_derived_info(TNF *, tnf_ref32_t *, struct taginfo *);

static struct taginfo *
init_scalar_info(TNF *, tnf_ref32_t *, struct taginfo *);

static struct taginfo *
init_struct_info(TNF *, tnf_ref32_t *, struct taginfo *);

static struct taginfo *
init_array_info(TNF *, tnf_ref32_t *, struct taginfo *);

static void init_slots(TNF *, tnf_ref32_t *, struct taginfo *);

/*
 * Allocate tag table and directory
 */

tnf_errcode_t
_tnf_init_tags(TNF *tnf)
{
	if ((tnf->tag_table = calloc(TAGTABCNT, sizeof (struct taginfo *)))
	    == NULL)
		return (TNF_ERR_ALLOCFAIL);
	if ((tnf->tag_directory = calloc(TAGDIRCNT(tnf->directory_size),
						sizeof (struct taginfo *)))
	    == NULL)
		return (TNF_ERR_ALLOCFAIL);
	return (TNF_ERR_NONE);
}

/*
 * Deallocate all taginfos and tables associated with TNF handle
 */

tnf_errcode_t
_tnf_fini_tags(TNF *tnf)
{
	int		i;
	struct taginfo	*info, *link;

	/*
	 * free taginfos
	 */
	for (i = 0; i < TAGTABCNT; i++) {
		info = tnf->tag_table[i];
		while (info) {
			/* remember link */
			link = info->link;
			/* free slot information */
			if (info->slotinfo)
				free(info->slotinfo);
			/* free taginfo */
			free(info);
			/* next in hash chain */
			info = link;
		}
	}
	/*
	 * free the tables
	 */
	free(tnf->tag_table);
	tnf->tag_table = NULL;
	free(tnf->tag_directory);
	tnf->tag_directory = NULL;

	return (TNF_ERR_NONE);
}

/*
 * Get info for supplied tag
 */

struct taginfo *
_tnf_get_info(TNF *tnf, tnf_ref32_t *tag)
{
	struct taginfo	*bucket, *info;

	bucket = tnf->tag_table[TAGHASH(tnf, tag)];
	for (info = bucket; info; info = info->link)
		if (info->tag == tag)
			return (info); /* found it */

	/* default: not there, create */
	return (add_info(tnf, tag));
}

/*
 * Get info for supplied record
 * Use fast lookup, if possible
 */

struct taginfo *
_tnf_record_info(TNF *tnf, tnf_ref32_t *record)
{
	tnf_ref32_t	ref32;
	tnf_ref16_t	tag16;
	tnf_abs16_t	index;
	struct taginfo	*info;

	ref32 = _GET_INT32(tnf, record);

	index = 0;
	if (TNF_REF32_IS_PAIR(ref32)) {
		tag16 = TNF_REF32_TAG16(ref32);
		if (TNF_TAG16_IS_ABS(tag16))
			index = TAG_INDEX(tag16);
	}

	if (index) {
		if ((info = tnf->tag_directory[index]) != NULL)
			return (info);
		else {		/* not in directory yet */
			info = _tnf_get_info(tnf, _tnf_get_tag(tnf, record));
			/* enter into tag directory */
			return ((tnf->tag_directory[index] = info));
		}
	}

	/* default: not referenced via index */
	return (_tnf_get_info(tnf, _tnf_get_tag(tnf, record)));
}

/*
 * Add a new taginfo for tag
 */

static struct taginfo *
add_info(TNF *tnf, tnf_ref32_t *tag)
{
	struct taginfo 	*info, *bucket;
	unsigned	hash;
	tnf_ref32_t	*meta;

	info = (struct taginfo *)calloc(1, sizeof (struct taginfo));

	/* Initialize members */
	info->tnf 	= tnf;
	info->tag 	= tag;
	info->name	= _tnf_get_name(tnf, tag);
	info->props	= _tnf_get_props(tnf, tag);
	info->kind	= _tnf_get_kind(tnf, tag);
	info->size	= _tnf_get_storage_size(tnf, tag);
	info->align	= _tnf_get_align(tnf, tag);

	/* Add it to table */
	hash 		= TAGHASH(tnf, tag);
	bucket 		= tnf->tag_table[hash];
	info->link 	= bucket;
	tnf->tag_table[hash] = info;

	/* Ensure meta info is available */
	meta		= _tnf_get_tag(tnf, tag);
	info->meta	= _tnf_get_info(tnf, meta);

	/*
	 * Initialize info
	 * Derived must be first clause due to property inheritance
	 */

	if (INFO_DERIVED(info))
		return (init_derived_info(tnf, tag, info));
	else if (INFO_STRUCT(info))
		return (init_struct_info(tnf, tag, info));
	else if (INFO_ARRAY(info))
		return (init_array_info(tnf, tag, info));
	else if (INFO_SCALAR(info))
		return (init_scalar_info(tnf, tag, info));
	else			/* XXX assume abstract type */
		return (init_abstract_info(tnf, tag, info));
}


/*
 * Initialize info for an abstract tag
 */

static struct taginfo *
/* ARGSUSED */
init_abstract_info(TNF *tnf, tnf_ref32_t *tag, struct taginfo *info)
{
	if (INFO_SCALAR(info) || INFO_DERIVED(info) ||
	    INFO_STRUCT(info) || INFO_ARRAY(info))
		_tnf_error(tnf, TNF_ERR_INTERNAL);
	if (info->size == (size_t)-1)
		_tnf_error(tnf, TNF_ERR_BADTNF);
	return (info);
}

/*
 * Initialize info for a derived tag
 */

static struct taginfo *
init_derived_info(TNF *tnf, tnf_ref32_t *tag, struct taginfo *info)
{
	tnf_ref32_t	*base_tag;

	if (!INFO_DERIVED(info))
		_tnf_error(tnf, TNF_ERR_INTERNAL);

	/* Ensure ultimate base information is available */
	base_tag 	= _tnf_get_base_tag(tnf, tag);
	info->base 	= _tnf_get_info(tnf, base_tag);

	return (info);
}

/*
 * Initialize info for a scalar tag
 */

static struct taginfo *
/* ARGSUSED */
init_scalar_info(TNF *tnf, tnf_ref32_t *tag, struct taginfo *info)
{
	if ((!INFO_SCALAR(info)) ||
	    (INFO_DERIVED(info) || INFO_ARRAY(info) || INFO_STRUCT(info)))
		_tnf_error(tnf, TNF_ERR_INTERNAL);
	if (info->size == (size_t)-1)
		_tnf_error(tnf, TNF_ERR_BADTNF);

	/* XXX alignment already done */

	return (info);
}

/*
 * Initialize info for a struct tag
 */

static struct taginfo *
init_struct_info(TNF *tnf, tnf_ref32_t *tag, struct taginfo *info)
{
	if ((!INFO_STRUCT(info)) ||
	    (INFO_DERIVED(info) || INFO_ARRAY(info) || INFO_SCALAR(info)))
		_tnf_error(tnf, TNF_ERR_INTERNAL);
	if (info->size == (size_t)-1)
		_tnf_error(tnf, TNF_ERR_BADTNF);

	/* Get slot information */
	init_slots(tnf, tag, info);

	return (info);
}

/*
 * Initialize info for an array tag
 */

static struct taginfo *
init_array_info(TNF *tnf, tnf_ref32_t *tag, struct taginfo *info)
{
	tnf_ref32_t	*elt_tag;
	int		defeat;

	if ((!INFO_ARRAY(info)) ||
	    (INFO_DERIVED(info) || INFO_STRUCT(info) || INFO_SCALAR(info)))
		_tnf_error(tnf, TNF_ERR_INTERNAL);

	/* XXX special-case abstract array tag */
	defeat = (strcmp(info->name, TNF_N_ARRAY) == 0);

	/* Require all arrays to be self-sized records */
	if (!(INFO_TAGGED(info) && (info->size == (size_t)-1)))
		if (!defeat)
			_tnf_error(tnf, TNF_ERR_BADTNF);

	/* Store array header size */
	info->hdrsize = _tnf_get_header_size(tnf, tag);
	/* XXX Temporary sanity check */
	if (info->hdrsize != sizeof (struct tnf_array_hdr))
		if (!defeat)
			_tnf_error(tnf, TNF_ERR_BADTNF);

	/* Get slot information */
	init_slots(tnf, tag, info);

	/* Get info for element type */
	elt_tag = (tnf_ref32_t *)_tnf_get_slot_typed(tnf, tag,
		/* LINTED pointer cast may result in improper alignment */
						    TNF_N_ELEMENT_TYPE);
	/* XXX tnf_array has element_type == NULL */
	info->base = elt_tag ? _tnf_get_info(tnf, elt_tag): NULL;

	return (info);
}

/*
 * Initialize slot information for aggregate tag
 */

static void
init_slots(TNF *tnf, tnf_ref32_t *tag, struct taginfo *info)
{
	tnf_ref32_t	*slot_types, *slot_names;
	tnf_ref32_t	*types, *names;
	unsigned	count, i, offset;
	struct slotinfo	*slotinfo;

	slot_types = (tnf_ref32_t *)
		/* LINTED pointer cast may result in improper alignment */
		_tnf_get_slot_typed(tnf, tag, TNF_N_SLOT_TYPES);
	slot_names = (tnf_ref32_t *)
		/* LINTED pointer cast may result in improper alignment */
		_tnf_get_slot_typed(tnf, tag, TNF_N_SLOT_NAMES);

	/* abstract tags have no slots */
	if (slot_types == TNF_NULL)
		return;

	count = _tnf_get_element_count(tnf, slot_types, sizeof (tnf_ref32_t));
	/* LINTED pointer cast may result in improper alignment */
	types = (tnf_ref32_t *)_tnf_get_elements(tnf, slot_types);
	names = ((slot_names == TNF_NULL) ? TNF_NULL :
		/* LINTED pointer cast may result in improper alignment */
			(tnf_ref32_t *)_tnf_get_elements(tnf, slot_names));

	slotinfo = (struct slotinfo *)
		calloc(1, sizeof (unsigned) + (count * sizeof (struct slot)));
	if (slotinfo == (struct slotinfo *)NULL)
		_tnf_error(tnf, TNF_ERR_ALLOCFAIL);

	slotinfo->slot_count = count;
	offset 	= 0;

	for (i = 0; i < count; i++) {
		tnf_ref32_t	*type_elt, *name_elt;
		struct taginfo	*elt_info;
		size_t		ref_size, align;

		/* XXX No checks here for missing tags */
		type_elt = _GET_REF32(tnf, &types[i]);
		name_elt = names ? _GET_REF32(tnf, &names[i]) : TNF_NULL;

		/* Resolve slot tag into taginfo */
		elt_info = _tnf_get_info(tnf, type_elt);
		slotinfo->slots[i].slot_type = elt_info;
		slotinfo->slots[i].slot_name =
			((name_elt != TNF_NULL) ?
				_tnf_get_chars(tnf, name_elt) :
				_tnf_get_name(tnf, type_elt));

		/* Get cached reference size */
		ref_size = INFO_REF_SIZE(elt_info);

		/* Get cached alignment */
		align = INFO_ALIGN(elt_info); /* XXX */

		/* Adjust offset to account for alignment, if needed */
		offset = ALIGN(offset, align);

		slotinfo->slots[i].slot_offset = offset;

		/* Bump offset by reference size */
		offset += ref_size;
	}

	info->slotinfo = slotinfo;
}
