#include "compiler_internal.h"

#include <math.h>
#include <errno.h>

UNUSED static int precision_bits(TypeKind kind)
{
	switch (kind)
	{
		case TYPE_F16:
			return 11;
			/*case TYPE_BF16:
				return 8;*/
		case TYPE_F32:
			return 24;
		case TYPE_F64:
			return 53;
		case TYPE_F128:
			return 113;
		default:
			UNREACHABLE
	}
}

UNUSED static int max_exponent(TypeKind kind)
{
	switch (kind)
	{
		case TYPE_F16:
			return 15;
			/*case TYPE_BF16:
					return 127;*/
		case TYPE_F32:
			return 127;
		case TYPE_F64:
			return 1023;
		case TYPE_F128:
			return 16383;
		default:
			UNREACHABLE
	}
}

UNUSED static int min_exponent(TypeKind kind)
{
	switch (kind)
	{
		case TYPE_F16:
			return -14;
			/*case TYPE_BF16:
					return -126;*/
		case TYPE_F32:
			return -126;
		case TYPE_F64:
			return -1022;
		case TYPE_F128:
			return -16382;
		default:
			UNREACHABLE
	}
}


Float float_add(Float op1, Float op2)
{
	assert(op1.type == op2.type);
	return (Float){ op1.f + op2.f, op1.type };
}

Float float_sub(Float op1, Float op2)
{
	assert(op1.type == op2.type);
	return (Float){ op1.f - op2.f, op1.type };
}

Float float_mul(Float op1, Float op2)
{
	assert(op1.type == op2.type);
	return (Float){ op1.f * op2.f, op1.type };
}

Float float_div(Float op1, Float op2)
{
	assert(op1.type == op2.type);
	return (Float){ op1.f / op2.f, op1.type };
}

Float float_neg(Float op)
{
	op.f = -op.f;
	return op;
}

static char *err_invalid_float_width = "The float width is not valid, it must be one of 16, 32, 64 and 128.";
static char *err_float_out_of_range = "The float value is out of range.";
static char *err_float_format_invalid = "The float format is invalid.";

/**
 * This parses a float from a string. Unfortunately it is limited to parsing doubles as of now.
 *
 * @param string
 * @param error
 * @return the resulting Float, with type = TYPE_POISONED on error.
 */
Float float_from_string(const char *string, char **error)
{
	const char *index = string;
	char c;
	scratch_buffer_clear();
	while ((c = *(index++)) && (c == '_' || (c >= '0' && c <= '9')))
	{
		if (c == '_') continue;
		scratch_buffer_append_char(c);
	}
	if (c == '.')
	{
		scratch_buffer_append_char(c);
		while ((c = *(index++)) && (c == '_' || (c >= '0' && c <= '9')))
		{
			if (c == '_') continue;
			scratch_buffer_append_char(c);
		}
	}
	if (c == 'e' || c == 'E')
	{
		scratch_buffer_append_char(c);
		if (*index == '-')
		{
			scratch_buffer_append_char('-');
			index++;
		}
		else if (*index == '+') index++;
		while ((c = *(index++)) && (c >= '0' && c <= '9'))
		{
			scratch_buffer_append_char(c);
		}
	}
	TypeKind kind = TYPE_F64;
	if (c == 'f')
	{
		int i = 0;
		while ((c = *(index++)) && (c >= '0' && c <= '9'))
		{
			if (i > 100)
			{
				if (error) *error = err_invalid_float_width;
				return (Float){ .type = TYPE_POISONED };
			}
			i = i * 10 + c - '0';
		}
		switch (i)
		{
			case 0:
			case 32:
				kind = TYPE_F32;
				break;
			case 16:
				kind = TYPE_F16;
				break;
			case 64:
				kind = TYPE_F64;
				break;
			case 128:
				kind = TYPE_F128;
				break;
			default:
				if (error) *error = err_invalid_float_width;
				return (Float){ .type = TYPE_POISONED };
		}
	}

	const char *str = scratch_buffer_to_string();
	char *end = NULL;
	errno = 0;
	double d = strtod(str, &end);
	if (d == HUGE_VAL && errno == ERANGE)
	{
		if (error) *error = err_float_out_of_range;
		return (Float){ .type = TYPE_POISONED };
	}
	char *expected_end = scratch_buffer.str + scratch_buffer.len;
	if (d == 0 && end != expected_end)
	{
		if (error) *error = err_float_format_invalid;
		return (Float){ .type = TYPE_POISONED };
	}
	return (Float){ d, kind };
}

/**
 * This parses a float from hex. Unfortunately it is limited to parsing doubles as of now.
 *
 * @param string
 * @param error
 * @return the resulting Float, with type = TYPE_POISONED on error.
 */
Float float_from_hex(const char *string, char **error)
{
	const char *index = string + 2;
	char c;
	scratch_buffer_clear();
	scratch_buffer_append("0x");
	while ((c = *(index++)) && (c == '_' || char_is_hex(c)))
	{
		if (c == '_') continue;
		scratch_buffer_append_char(c);
	}
	if (c == '.')
	{
		scratch_buffer_append_char(c);
		while ((c = *(index++)) && (c == '_' || char_is_hex(c)))
		{
			if (c == '_') continue;
			scratch_buffer_append_char(c);
		}
	}
	if (c == 'p' || c == 'P')
	{
		scratch_buffer_append_char(c);
		if (*index == '-')
		{
			scratch_buffer_append_char('-');
			index++;
		}
		else if (*index == '+') index++;
		while ((c = *(index++)) && (c >= '0' && c <= '9'))
		{
			scratch_buffer_append_char(c);
		}
	}
	TypeKind kind = TYPE_F64;
	if (c == 'f')
	{
		int i = 0;
		while ((c = *(index++)) && (c >= '0' && c <= '9'))
		{
			if (i > 100)
			{
				if (error) *error = err_invalid_float_width;
				return (Float){ .type = TYPE_POISONED };
			}
			i = i * 10 + c - '0';
		}
		switch (i)
		{
			case 0:
			case 32:
				kind = TYPE_F32;
				break;
			case 16:
				kind = TYPE_F16;
				break;
			case 64:
				kind = TYPE_F64;
				break;
			case 128:
				kind = TYPE_F128;
				break;
			default:
				if (error) *error = err_invalid_float_width;
				return (Float){ .type = TYPE_POISONED };
		}
	}

	const char *str = scratch_buffer_to_string();
	char *end = NULL;
	errno = 0;
	double d = strtod(str, &end);
	if (d == HUGE_VAL && errno == ERANGE)
	{
		if (error) *error = err_float_out_of_range;
		return (Float){ .type = TYPE_POISONED };
	}
	if (d == 0 && end != scratch_buffer.str + scratch_buffer.len)
	{
		if (error) *error = err_float_format_invalid;
		return (Float){ .type = TYPE_POISONED };
	}
	return (Float){ d, kind };
}