/*
 * This file provides a standard interfact for access to type information.
 *
 * Copyright 1999 Silicon Graphics, Inc. All rights reserved.
 */
#include <klib.h>

/* Global type tree (binary sort tree by type name)
 */
kltype_t *kltype_tree;

/* Extern function declarations
 */
kltype_t *__first_sym(int);
kltype_t *__next_sym(kltype_t *);
kltype_t *__find_sym(char *, int);
kltype_t *__first_type(void);
kltype_t *__next_type(kltype_t *);
kltype_t *__find_type(char *);
kltype_t *__get_member(char *, char *);
int st_struct_len(char *s);
int __member_offset(char *, char *);
int __is_member(char *, char *);
int __member_size(char *, char *);

/*
 * kl_find_sym()
 */
kltype_t *
kl_find_sym(char *name, int type)
{
	return(__find_sym(name, type));
}

/*
 * kl_first_sym()
 */
kltype_t *
kl_first_sym(int type) 
{
	return(__first_sym(type));
}

/*
 * kl_next_sym()
 */
kltype_t *
kl_next_sym(kltype_t *kltp)
{
	return(__next_sym(kltp));
}

/*
 * kl_find_type()
 */
kltype_t *
kl_find_type(char *name)
{
	return(__find_type(name));
}

/*
 * kl_first_type()
 */
kltype_t *
kl_first_type() 
{
	return(__first_type());
}

/*
 * kl_next_type()
 */
kltype_t *
kl_next_type(kltype_t *kltp)
{
	return(__next_type(kltp));
}

/*
 * kl_realtype()
 */
kltype_t *
kl_realtype(kltype_t *kltp, int type)
{
	kltype_t *rkltp = kltp;

	while (rkltp) {
		if (type && (rkltp->kl_type == type)) {
			break;
		}
		if (!rkltp->kl_realtype) {
			break;
		}
		if (rkltp->kl_realtype == rkltp) {
			break;
		}
		rkltp = rkltp->kl_realtype;
		if (rkltp == kltp) {
			break;
		}
	}
	return(rkltp);
}

/*
 * kl_type_size()
 */
int
kl_type_size(kltype_t *kltp)
{
	kltype_t *kp;
	
	if (!kltp) {
		return(0);
	}
	if (!(kp = kl_realtype(kltp, 0))) {
		return(0);
	}
	return(kp->kl_size);
}

/*
 * kl_struct_len()
 */
int
kl_struct_len(char *s)
{               
	return(st_struct_len(s));
}       

/*
 * kl_find_member()
 */
kltype_t *
kl_find_member(char *s, char *f)
{
	return(__get_member(s, f));
}

/*
 * kl_get_member()
 */
kltype_t *
kl_get_member(kltype_t *kltp, char *f)
{
	kltype_t *mp;

	if ((mp = kltp->kl_member)) {
		while (mp) {
			if (!strcmp(mp->kl_name, f)) {
				break;
			}
			mp = mp->kl_member;
		}
	}
	return(mp);
}

/*      
 * kl_member_offset()
 */      
int     
kl_member_offset(char *s, char *f)
{       
	return(__member_offset(s, f));
}

/*
 * kl_is_member() 
 */
int
kl_is_member(char *s, char *f)
{
	return(__is_member(s, f));
}

/*
 * kl_member_size()
 */
int
kl_member_size(char *s, char *f)
{
	return(__member_size(s, f));
}

#define TAB_SPACES		     8
#define LEVEL_INDENT(level, flags) {\
	int i, j; \
	if (!(flags & NO_INDENT)) { \
		for (i = 0; i < level; i++) { \
			for (j = 0; j < TAB_SPACES; j++) { \
				fprintf(ofp, " "); \
			} \
		}\
	} \
}
#define PRINT_NL(flags, ofp) \
	if (!(flags & SUPPRESS_NL)) { \
		fprintf(ofp, "\n"); \
	}

/** Forward declarations
 **/
void kl_print_pointer_type(void *, kltype_t *, int, int, FILE *);
void kl_print_function_type(void *, kltype_t *, int, int, FILE *);
void kl_print_array_type(void *, kltype_t *, int, int, FILE *);
void kl_print_enumeration_type(void *, kltype_t *, int, int, FILE *);
void kl_print_base_type(void *, kltype_t *, int, int, FILE *);
void kl_print_type(void *, kltype_t *, int, int, FILE *);
void kl_print_struct_type(void *, kltype_t *, int, int, FILE *);
void kl_print_base_value(void *, kltype_t *, int, FILE *);

/* 
 * kl_print_typedef_type()
 */
void
kl_print_typedef_type(
	void *ptr, 
	kltype_t *kltp, 
	int level, 
	int flags, 
	FILE *ofp)
{
	kltype_t *rkltp;

	if (ptr) {
		rkltp = kltp->kl_realtype;
		while (rkltp->kl_type == KLT_TYPEDEF) {
			if (rkltp->kl_realtype) {
				rkltp = rkltp->kl_realtype;
			}
		}
		if (rkltp->kl_type == KLT_POINTER) {
			kl_print_pointer_type(ptr, kltp, level, flags, ofp);
			return;
		} 
		switch (rkltp->kl_type) {
			case KLT_BASE:
				kl_print_base_type(ptr, kltp, 
					level, flags, ofp);
				break;

			case KLT_UNION:
			case KLT_STRUCT:
				kl_print_struct_type(ptr, kltp, 
					level, flags, ofp);
				break;

			case KLT_ARRAY:
				kl_print_array_type(ptr, kltp, 
					level, flags, ofp);
				break;

			case KLT_ENUMERATION:
				kl_print_enumeration_type(ptr, 
					kltp, level, flags, ofp);
				break;

			default:
				kl_print_base_type(ptr, kltp, 
					level, flags, ofp);
				break;
		}
	} else {
		LEVEL_INDENT(level, flags);
		rkltp = kltp->kl_realtype;
		while (rkltp && rkltp->kl_type == KLT_POINTER) {
			rkltp = rkltp->kl_realtype;
		}
		if (rkltp && rkltp->kl_type == KLT_FUNCTION) {
			if (kltp->kl_realtype->kl_type == KLT_POINTER) {
				fprintf(ofp, "typedef %s(*%s)();", 
					kltp->kl_typestr, kltp->kl_name);
			} else {
				fprintf(ofp, "typedef %s(%s)();", 
					kltp->kl_typestr, kltp->kl_name);
			}
		} else {
			if (SUPPRESS_NAME) {
				fprintf(ofp, "%s", kltp->kl_typestr);
			} else {
				fprintf(ofp, "typedef %s%s;", 
					kltp->kl_typestr, kltp->kl_name);
			}
		} 
		PRINT_NL(flags, ofp);
	}
}

/*
 * kl_print_pointer_type()
 */
void
kl_print_pointer_type(
	void *ptr, 
	kltype_t *kltp, 
	int level, 
	int flags, 
	FILE *ofp)
{
	kltype_t *itp;

	if (kltp->kl_type == KLT_MEMBER) {
		itp = kltp->kl_realtype;
	} else {
		itp = kltp;
	}

	/* See if this is a pointer to a function. If it is, then it
	 * has to be handled differently...
	 */ 
	while (itp->kl_type == KLT_POINTER) {
		if ((itp = itp->kl_realtype)) {
			if (itp->kl_type == KLT_FUNCTION) {
				kl_print_function_type(ptr, 
					kltp, level, flags, ofp);
				return;
			}
		} else {
			LEVEL_INDENT(level, flags);
			fprintf(ofp, "%s%s;\n", 
				kltp->kl_typestr, kltp->kl_name);
			return;
		}
	}

	LEVEL_INDENT(level, flags);
	if (ptr) {
		if (*(kaddr_t *)ptr) {
			fprintf(ofp, "%s = 0x%"FMTPTR"x", 
				kltp->kl_name, *(kaddr_t *)ptr); 
		} else {
			fprintf(ofp, "%s = (nil)", kltp->kl_name); 
		}
	} else {
		if (kltp->kl_typestr) {
			if (kltp->kl_name) {
				fprintf(ofp, "%s%s;", 
					kltp->kl_typestr, kltp->kl_name);
			} else {
				fprintf(ofp, "%s;", kltp->kl_typestr);
			}
		} else {
			fprintf(ofp, "<UNKNOWN>;");
		}
	}
	PRINT_NL(flags, ofp);
}

/*
 * kl_print_function_type()
 */
void
kl_print_function_type(
	void *ptr, 
	kltype_t *kltp, 
	int level, 
	int flags, 
	FILE *ofp)
{
	LEVEL_INDENT(level, flags);
	if (ptr) {
		fprintf(ofp, "%s = 0x%"FMTPTR"x", 
			kltp->kl_name, *(kaddr_t *)ptr); 
	} else { 
		if (flags & SUPPRESS_NAME) {
			fprintf(ofp, "%s(*)()", kltp->kl_typestr);
		} else {
			fprintf(ofp, "%s(*%s)();", 
				kltp->kl_typestr, kltp->kl_name);
		}
	}
	PRINT_NL(flags, ofp);
}

/*
 * kl_print_array_type()
 */
void
kl_print_array_type(void *ptr, kltype_t *kltp, int level, int flags, FILE *ofp)
{
	int i, count = 0, size, low, high;
	char *typestr, *name, *p;
	kltype_t *rkltp, *etp;

	if (kltp->kl_type != KLT_ARRAY) {
		rkltp = kltp->kl_realtype;
		while (rkltp->kl_type != KLT_ARRAY) {
			if (!(rkltp = rkltp->kl_realtype)) {
				break;
			}
		}
		if (!rkltp) {
			LEVEL_INDENT(level, flags);
			fprintf(ofp, "<ARRAY_TYPE>;");
			PRINT_NL(flags, ofp);
			return;
		}
	} else {
		rkltp = kltp;
	}

	etp = rkltp->kl_elementtype;
	while (etp && (etp->kl_type == KLT_TYPEDEF)) {
		etp = etp->kl_realtype;
	}
	if (!etp) {
		LEVEL_INDENT(level, flags);
		fprintf(ofp, "<BAD_ELEMENT_TYPE> %s;\n", rkltp->kl_name);
		PRINT_NL(flags, ofp);
		return;
	}
	low = rkltp->kl_low_bounds;
	high = rkltp->kl_high_bounds;

	if (ptr) {

		p = ptr;

		if ((etp->kl_size == 1) && (etp->kl_encoding == ENC_CHAR)) {
			LEVEL_INDENT(level, flags);
			if (flags & SUPPRESS_NAME) {
				fprintf(ofp, "\"");
				flags &= ~SUPPRESS_NAME;
			} else {
				fprintf(ofp, "%s = \"", kltp->kl_name);
			}
			for (i = 0; i < high; i++) {
				if (*(char*)p == 0) {
					break;
				}
				fprintf(ofp, "%c", *(char *)p);
				p++;
			}
			fprintf(ofp, "\"");
			PRINT_NL(flags, ofp);
		} else {
			LEVEL_INDENT(level, flags);
			if (flags & SUPPRESS_NAME) {
				fprintf(ofp, "{\n");
				flags &= ~SUPPRESS_NAME;
			} else {
				fprintf(ofp, "%s = {\n", kltp->kl_name);
			}

			if (etp->kl_type == KLT_POINTER) {
				size = sizeof(void *);
			} else {
				size = etp->kl_size;
			}
			for (i = low; i <= high; i++) {

				LEVEL_INDENT(level + 1, flags);
				fprintf(ofp, "[%d] ", i);

				switch (etp->kl_type) {
					case KLT_POINTER :
						kl_print_pointer_type(p, etp, 
							level, flags, ofp);
						break;

					case KLT_TYPEDEF:
						kl_print_typedef_type(p, etp, 
							level, flags, ofp);
						break;

					case KLT_BASE:
						kl_print_base_value(p, 
							etp, flags, ofp);
						fprintf(ofp, "\n");
						break;

					case KLT_STRUCT:
					case KLT_UNION:
						kl_print_struct_type(p, 
							etp, level + 1, 
							flags|NO_INDENT, ofp);
						break;

					default:
						kl_print_base_value(p, 
							etp, flags, ofp);
						fprintf(ofp, "\n");
						break;
				}
				p = (void *)((uaddr_t)p + size);
			}
			LEVEL_INDENT(level, flags);
			fprintf(ofp, "}");
			PRINT_NL(flags, ofp);
		}
	} else {
		if (rkltp) {
			count = (rkltp->kl_high_bounds - 
					rkltp->kl_low_bounds) + 1;
		} else {
			count = 1;
		}
		LEVEL_INDENT(level, flags);
		if (flags & SUPPRESS_NAME) {
			typestr = etp->kl_typestr;
			fprintf(ofp, "%s[%d]", typestr, count);
		} else {
			typestr = kltp->kl_typestr;
			name = kltp->kl_name;
			fprintf(ofp, "%s%s[%d];", typestr, name, count);
		}
		PRINT_NL(flags, ofp);
	}
}

/*
 * kl_print_enumeration_type()
 */
void
kl_print_enumeration_type(
	void *ptr, 
	kltype_t *kltp, 
	int level, 
	int flags, 
	FILE *ofp)
{
	uint64_t val;
	kltype_t *mp, *rkltp;

	rkltp = kl_realtype(kltp, KLT_ENUMERATION);
	if (ptr) {
		switch (kltp->kl_size) {
			case 1:
				val = *(char*)ptr;
				break;

			case 2:
				val = *(short*)ptr;
				break;

			case 4:
				val = *(int32_t*)ptr;
				break;

			case 8:
				val = *(int64_t*)ptr;
				break;
		}
		mp = rkltp->kl_member;
		while (mp) {
			if (mp->kl_value == val) {
				break;
			}
			mp = mp->kl_member;
		}
		LEVEL_INDENT(level, flags);
		if (mp) {
			fprintf(ofp, "%s = (%s=%"FMT64"d)", 
				kltp->kl_name, mp->kl_name, val);
		} else {
			fprintf(ofp, "%s = %"FMT64"d", kltp->kl_name, val);
		}
		PRINT_NL(flags, ofp);
	} else {
		LEVEL_INDENT(level, flags);
		fprintf (ofp, "%s {", kltp->kl_typestr);
		mp = rkltp->kl_member;
		while (mp) {
			fprintf(ofp, "%s = %d", mp->kl_name, mp->kl_value);
			if ((mp = mp->kl_member)) {
				fprintf(ofp, ", ");
			}
		}
		mp = kltp;
		if (level) {
			fprintf(ofp, "} %s;", mp->kl_name);
		} else {
			fprintf(ofp, "};");
		}
		PRINT_NL(flags, ofp);
	}
}

/*
 * kl_print_base_value()
 */
void
kl_print_base_value(void *ptr, kltype_t *kltp, int flags, FILE *ofp)
{
	kltype_t *rkltp;

	if (kltp->kl_type != KLT_BASE) {
		if (!(rkltp = kltp->kl_realtype)) {
			return;
		}
		if (rkltp->kl_type != KLT_BASE) {
			return;
		}
	} else {
		rkltp = kltp;
	}
	kl_print_base(ptr, rkltp->kl_size, rkltp->kl_encoding, flags, ofp); 
}

/*
 * kl_print_base_type()
 */
void
kl_print_base_type(void *ptr, kltype_t *kltp, int level, int flags, FILE *ofp)
{
	LEVEL_INDENT(level, flags);
	if (ptr) {
		if (!(flags & SUPPRESS_NAME))  {
			fprintf (ofp, "%s = ", kltp->kl_name);
		}
	}
	if (kltp->kl_type == KLT_MEMBER) {
		if (kltp->kl_bit_size < (kltp->kl_size * 8)) {
			if (ptr) {
				kl_print_bit_value(ptr, kltp->kl_size, 
					kltp->kl_bit_size, 
					kltp->kl_bit_offset, flags, ofp);
			} else {
				if (kltp->kl_name) {
					fprintf (ofp, "%s%s :%d;",
						kltp->kl_typestr, 
						kltp->kl_name, 
						kltp->kl_bit_size);
				} else {
					fprintf (ofp, "%s :%d;", 
						kltp->kl_typestr, 
						kltp->kl_bit_size);
				}
			}
			PRINT_NL(flags, ofp);
			return;
		}
	} 
	if (ptr) {
		kltype_t *rkltp;

		rkltp = kl_realtype(kltp, 0);
		if (rkltp->kl_encoding == ENC_UNDEFINED) {
			/* This is a void value
			 */
			fprintf(ofp, "<VOID>");
		} else {
			kl_print_base(ptr, kltp->kl_size, 
				rkltp->kl_encoding, flags, ofp); 
		}
	} else {
		if (kltp->kl_type == KLT_MEMBER) {
			if (flags & SUPPRESS_NAME) {
				fprintf (ofp, "%s", kltp->kl_typestr);
			} else {
				if (kltp->kl_name) {
					fprintf(ofp, "%s%s;", kltp->kl_typestr, 
						kltp->kl_name);
				} else {
					fprintf (ofp, "%s :%d;", 
						kltp->kl_typestr, 
						kltp->kl_bit_size);
				}
			}
		} else {
			if (SUPPRESS_NAME) {
				fprintf(ofp, "%s", kltp->kl_name);
			} else { 
				fprintf(ofp, "%s;", kltp->kl_name);
			}
		}
	}
	PRINT_NL(flags, ofp);
}

/*
 * kl_print_member()
 */
void
kl_print_member(void *ptr, kltype_t *mp, int level, int flags, FILE *ofp)
{
	int kl_type = 0;
	kltype_t *rkltp;

	if ((rkltp = mp->kl_realtype)) {
		kl_type = rkltp->kl_type;
	}
	switch (kl_type) {
		case KLT_STRUCT:
		case KLT_UNION:
			kl_print_struct_type(ptr, mp, level, flags, ofp);
			break;
		case KLT_ARRAY:
			kl_print_array_type(ptr, mp, level, flags, ofp);
			break;
		case KLT_POINTER:
			kl_print_pointer_type(ptr, mp, level, flags, ofp);
			break;
		case KLT_FUNCTION:
			kl_print_function_type(ptr, mp, level, flags, ofp);
			break;
		case KLT_BASE:
			kl_print_base_type(ptr, mp, level, flags, ofp);
			break;
	        case KLT_ENUMERATION:
			kl_print_enumeration_type(ptr, mp, level, flags, ofp);
			break;
		case KLT_TYPEDEF: 
			while (rkltp && rkltp->kl_realtype) {
				if (rkltp->kl_realtype == rkltp) {
					break;
				}
				rkltp = rkltp->kl_realtype;
			}
			if (ptr) {
				kl_print_typedef_type(ptr, mp, 
					level, flags, ofp);
				break;
			} 
			LEVEL_INDENT(level, flags);
			if (flags & SUPPRESS_NAME) {
				if (rkltp && (mp->kl_bit_size < 
						(rkltp->kl_size * 8))) {
					fprintf (ofp, "%s :%d", 
						mp->kl_typestr, 
						mp->kl_bit_size);
				} else {
					fprintf(ofp, "%s", 
						mp->kl_realtype->kl_name);
				}
			} else {
				if (rkltp && (mp->kl_bit_size < 
						(rkltp->kl_size * 8))) {
					if (mp->kl_name) {
						fprintf (ofp, "%s%s :%d;",
							mp->kl_typestr, 
							mp->kl_name, 
							mp->kl_bit_size);
					} else {
						fprintf (ofp, "%s :%d;", 
							mp->kl_typestr, 
							mp->kl_bit_size);
					}
				} else {
					fprintf(ofp, "%s %s;", 
						mp->kl_realtype->kl_name, 
						mp->kl_name);
				}
			}
			PRINT_NL(flags, ofp);
			break;

		default:
			LEVEL_INDENT(level, flags);
			if (mp->kl_typestr) {
				fprintf(ofp, "%s%s;", 
					mp->kl_typestr, mp->kl_name);
			} else {
				fprintf(ofp, "<\?\?\?> %s;", mp->kl_name);
			}
			PRINT_NL(flags, ofp);
			break;
	}
}

/*
 * kl_print_struct_type()
 */
void
kl_print_struct_type(void *buf, kltype_t *kltp, int level, int flags, FILE *ofp)
{
	void *ptr = NULL;
	kltype_t *mp, *rkltp;

	LEVEL_INDENT(level, flags);
	if ((level == 0) || (flags & NO_INDENT)) {
		fprintf(ofp, "%s{\n", kltp->kl_typestr);
	} else {
		if (buf) {
			if (level) {
				fprintf(ofp, "%s = %s{\n", 
					kltp->kl_name, kltp->kl_typestr);
			} else {
				fprintf(ofp, "%s{\n", kltp->kl_typestr);
			}
			flags &= (~SUPPRESS_NL);
		} else {
			if (kltp->kl_typestr) {
				fprintf(ofp, "%s{\n", kltp->kl_typestr);
			} else {
				fprintf(ofp, "<UNKNOWN> {\n");
			}
		}
	}

	/* If the NO_INDENT is set, we need to turn it off at this
	 * point -- just in case we come across a member of this struct
	 * that is also a struct.
	 */
	if (flags & NO_INDENT) {
		flags &= ~(NO_INDENT);
	}

	if (kltp->kl_type == KLT_MEMBER) {
		rkltp = kl_realtype(kltp, 0);
	} else {
		rkltp = kltp;
	}
	level++;
	if ((mp = rkltp->kl_member)) {
		while (mp) {
			if (buf) {
				ptr = buf + mp->kl_offset;
			}
			kl_print_member(ptr, mp, level, flags, ofp);
			mp = mp->kl_member;
		}
	} else {
		if (kltp->kl_flags & TYP_INCOMPLETE_FLG) {
			LEVEL_INDENT(level, flags);
			fprintf(ofp, "<INCOMPLETE TYPE>\n");
		}
	}
	level--;
	LEVEL_INDENT(level, flags);
	if (ptr || ((kltp->kl_size == 0) && buf)) { /* kl_size = 0 for empty structs */
		fprintf(ofp, "}");
	} else if (kltp->kl_type == KLT_MEMBER) {
		fprintf(ofp, "} %s;", kltp->kl_name);
	} else {
		fprintf(ofp, "};");
	}
	PRINT_NL(flags, ofp);
}

/*
 * kl_print_type()
 */
void
kl_print_type(void *buf, kltype_t *kltp, int level, int flags, FILE *ofp)
{
	void *ptr;

	if (buf) {
		if (kltp->kl_offset) {
			ptr = (void *)((uaddr_t)buf + kltp->kl_offset);
		} else {
			ptr = buf;
		}
	} else {
		ptr = 0;
	}

	/* Only allow binary printing for base types
	 */
	if (kltp->kl_type != KLT_BASE) {
		flags &= (~K_BINARY);
	}
	switch (kltp->kl_type) {

		case KLT_TYPEDEF:
			kl_print_typedef_type(ptr, kltp, level, flags, ofp);
			break;

		case KLT_STRUCT:
		case KLT_UNION:
			kl_print_struct_type(ptr, kltp, level, flags, ofp);
			break;

		case KLT_MEMBER:
			kl_print_member(ptr, kltp, level, flags, ofp);
			break;

		case KLT_POINTER:
			kl_print_pointer_type(ptr, kltp, level, flags, ofp);
			break;

		case KLT_FUNCTION:
			LEVEL_INDENT(level, flags);
			kl_print_function_type(ptr, kltp, level, flags, ofp);
			break;

		case KLT_ARRAY:
			kl_print_array_type(ptr, kltp, level, flags, ofp);
			break;

		case KLT_ENUMERATION:
			kl_print_enumeration_type(ptr, 
				kltp, level, flags, ofp);
			break;

		case KLT_BASE:
			kl_print_base_type(ptr, kltp, level, flags, ofp);
			break;

		default:
			LEVEL_INDENT(level, flags);
			if (flags & SUPPRESS_NAME) {
				fprintf (ofp, "%s", kltp->kl_name);
			} else {
				fprintf (ofp, "%s %s;", 
					kltp->kl_name, kltp->kl_name);
			}
			PRINT_NL(flags, ofp);
	}
}

