/*
 * declsec.h:  Support for the new declarative security attribute
 *	metadata format (2.0)
 *
 * Author:
 *	Sebastien Pouliot  <sebastien@ximian.com>
 *
 * Copyright (C) 2005 Novell, Inc (http://www.novell.com)
 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
 */

#include <glib.h>
#include <math.h>

#include "mono/metadata/blob.h"
#include "mono/metadata/metadata.h"
#include "mono/metadata/mono-endian.h"
#include "mono/utils/mono-compiler.h"
#include "mono/utils/mono-math.h"

#include "declsec.h"
#include "util.h"

static char*
declsec_20_get_classname (const char* p, const char **rptr)
{
	int apos, cpos = 0;
	char *c;
	char *a;
	char *result;
	GString *res = g_string_new ("");
	int len = mono_metadata_decode_value (p, &p);

	c = (char *) p;
	while ((*c++ != ',') && (cpos++ < len));
	c++;

	apos = cpos;
	a = c;
	while ((*a++ != ',') && (apos++ < len));

	if (apos - cpos > 1) {
		g_string_append_printf (res, "[%.*s]%.*s", apos - cpos, c, cpos, p);
	} else {
		/* in-assembly type aren't fully qualified (no comma) */
		g_string_append_printf (res, "%.*s", cpos - 1, p);
	}

	p += len;
	if (rptr)
		*rptr = p;

	result = res->str;
	g_string_free (res, FALSE);
	return result;
}

static gboolean
declsec_20_write_type (GString *str, char type)
{
	switch (type) {
	case MONO_TYPE_BOOLEAN:
		g_string_append (str, "bool");
		break;
	case MONO_TYPE_SZARRAY:
		g_string_append (str, "[]");
		break;
	case MONO_TYPE_SYSTEM_TYPE:
		g_string_append (str, "type");
		break;
	case MONO_TYPE_STRING:
		g_string_append (str, "string");
		break;
	default:
		g_warning ("TODO type %d - please fill a bug report on this!", type);
		return FALSE;
	}
	return TRUE;
}

static const char*
declsec_20_write_value (GString *str, char type, const char *value)
{
	switch (type) {
	case MONO_TYPE_U1:
		g_string_append_printf (str, "%d", (unsigned char)*value);
		return value + 1;
	case MONO_TYPE_I1:
		g_string_append_printf (str, "%d", *value);
		return value + 1;
	case MONO_TYPE_BOOLEAN:
		g_string_append_printf (str, "%s", *value ? "true" : "false");
		return value + 1;
	case MONO_TYPE_CHAR:
		g_string_append_printf (str, "0x%04X", read16 (value));
		return value + 2;
	case MONO_TYPE_U2:
		g_string_append_printf (str, "%d", read16 (value));
		return value + 2;
	case MONO_TYPE_I2:
		g_string_append_printf (str, "%d", (gint16)read16 (value));
		return value + 2;
	case MONO_TYPE_U4:
		g_string_append_printf (str, "%d", read32 (value));
		return value + 4;
	case MONO_TYPE_I4:
		g_string_append_printf (str, "%d", (gint32)read32 (value));
		return value + 4;
	case MONO_TYPE_U8:
	case MONO_TYPE_I8:
		g_string_append_printf (str, "%" PRId64, (gint64)read64 (value));
		return value + 8;
	case MONO_TYPE_R4: {
		float val;
		readr4 (value, &val);
		const int inf = mono_isinf (val);
		if (inf == -1) 
			g_string_append_printf (str, "0xFF800000"); /* negative infinity */
		else if (inf == 1)
			g_string_append_printf (str, "0x7F800000"); /* positive infinity */
		else if (mono_isnan (val))
			g_string_append_printf (str, "0xFFC00000"); /* NaN */
		else
			g_string_append_printf (str, "%.8g", val);
		return value + 4;
	}
	case MONO_TYPE_R8: {
		double val;
		readr8 (value, &val);
		const int inf = mono_isinf (val);
		if (inf == -1) 
			g_string_append_printf (str, "0xFFF00000000000000"); /* negative infinity */
		else if (inf == 1)
			g_string_append_printf (str, "0x7FFF0000000000000"); /* positive infinity */
		else if (mono_isnan (val))
			g_string_append_printf (str, "0xFFF80000000000000"); /* NaN */
		else
			g_string_append_printf (str, "%.17g", val);
		return value + 8;
	}
	case MONO_TYPE_STRING:
		if (*value == (char)0xff) {
			g_string_append (str, "nullref");
			return value + 1;
		} else {
			int len = mono_metadata_decode_value (value, &value);
			g_string_append_printf (str, "'%.*s'", len, value);
			return value + len;
		}
	case MONO_TYPE_SYSTEM_TYPE: {
		char *cname = declsec_20_get_classname (value, NULL);
		int len = mono_metadata_decode_value (value, &value);
		g_string_append (str, cname);
		g_free (cname);
		return value + len;
	}
	}
	return 0;
}

char*
dump_declsec_entry20 (MonoImage *m, const char* p, const char *indent)
{
	int i, num;
	char *result;
	GString *res = g_string_new ("");

	if (*p++ != MONO_DECLSEC_FORMAT_20)
		return NULL;

	g_string_append (res, "{");

	/* number of encoded permission attributes */
	num = mono_metadata_decode_value (p, &p);

	for (i = 0; i < num; i++) {
		int len, j, pos = 0, param_len;
		char *param_start;
		char *s = declsec_20_get_classname (p, &p);
		g_string_append_printf (res, "%s = {", s);
		g_free (s);

		/* optional parameters length */
		param_len = mono_metadata_decode_value (p, &p);
		param_start = (char *) p;

		/* number of parameters */
		pos = mono_metadata_decode_value (p, &p);
		for (j = 0; j < pos; j++) {
			int k, elem;
			int type = *p++;

			switch (type) {
			case MONO_DECLSEC_FIELD:
				/* not sure if/how we can get this in a declarative security attribute... */
				g_string_append (res, "field ");
				break;
			case MONO_DECLSEC_PROPERTY:
				g_string_append (res, "property ");
				break;
			default:
				g_warning ("TODO %d - please fill a bug report on this!", type);
				break;
			}

			type = *p++;

			if (type == MONO_DECLSEC_ENUM) {
				s = declsec_20_get_classname (p, &p);
				len = mono_metadata_decode_value (p, &p);
				g_string_append_printf (res, "enum %s '%.*s' = ", s, len, p);
				g_free (s);
				p += len;
				/* TODO: we must detect the size of the enum element (from the type ? length ?)
				 * note: ildasm v2 has some problem decoding them too and doesn't
				 * seems to rely on the type (as the other assembly isn't loaded) */
				g_string_append_printf (res, "int32(%d)", read32 (p));
				p += 4;
			} else {
				int arraytype = 0;
				if (type == MONO_TYPE_SZARRAY) {
					arraytype = *p++;
					declsec_20_write_type (res, arraytype);
				}
				declsec_20_write_type (res, type);

				len = mono_metadata_decode_value (p, &p);
				g_string_append_printf (res, " '%.*s' = ", len, p);
				p += len;

				if (type == MONO_TYPE_SZARRAY) {
					type = arraytype;
					declsec_20_write_type (res, type);
					elem = read32 (p);
					p += 4;
					g_string_append_printf (res, "[%d]", elem);
				} else {
					declsec_20_write_type (res, type);
					elem = 1;
				}
				g_string_append (res, "(");

				/* write value - or each element in the array */
				for (k = 0; k < elem; k++) {
					p = declsec_20_write_value (res, type, p);
					/* separate array elements */
					if (k < elem - 1)
						g_string_append (res, " ");
				}
				
				if (j < pos - 1)
					g_string_append_printf (res, ")\n%s", indent);
				else
					g_string_append (res, ")");
			}

		}

		if (i < num - 1)
			g_string_append_printf (res, "},\n%s", indent);
		else
			g_string_append (res, "}");

		if (param_len > 0)
			p = param_start + param_len;
	}
	g_string_append (res, "}");

	result = res->str;
	g_string_free (res, FALSE);
	return result;
}
