/*
 * Copyright (c) 2019 Foundries.io
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <kernel.h>
#include "lwm2m_util.h"

#define SHIFT_LEFT(v, o, m) (((v) << (o)) & (m))
#define SHIFT_RIGHT(v, o, m) (((v) >> (o)) & (m))

/* convert from float32 to binary32 */
int lwm2m_f32_to_b32(float32_value_t *f32, u8_t *b32, size_t len)
{
	s32_t e = -1, v, f = 0;
	int i;

	if (len != 4) {
		return -EINVAL;
	}

	/* handle zero value special case */
	if (f32->val1 == 0 && f32->val2 == 0) {
		memset(b32, 0, len);
		return 0;
	}

	v = f32->val1;
	/* sign handled later */
	if (v < 0) {
		v = -v;
	}

	/* add whole value to fraction */
	while (v > 0) {
		f >>= 1;

		if (v & 1) {
			f |= (1 << 23);
		}

		v >>= 1;
		e++;
	}

	v = f32->val2;
	/* sign handled later */
	if (v < 0) {
		v = -v;
	}

	/* add decimal to fraction */
	i = e;
	while (v > 0 && i < 23) {
		v *= 2;
		if (e < 0 && v < LWM2M_FLOAT32_DEC_MAX) {
			/* handle -e */
			e--;
			continue;
		} else if (v >= LWM2M_FLOAT32_DEC_MAX) {
			v -= LWM2M_FLOAT32_DEC_MAX;
			f |= 1 << (22 - i);
		}

		if (v == 0) {
			break;
		}

		i++;
	}

	/* adjust exponent for bias */
	e += 127;

	memset(b32, 0, len);

	/* sign: bit 31 */
	b32[0] = f32->val1 < 0 ? 0x80 : 0;

	/* exponent: bits 30-23 */
	b32[0] |= e >> 1;
	b32[1] = (e & 1) << 7;

	/* fraction: bits 22-0 */
	/* NOTE: ignore the "hidden" bit 23 in fraction */
	b32[1] |= (f >> 16) & 0x7F;
	b32[2] = (f >> 8) & 0xFF;
	b32[3] = f & 0xFF;

	return 0;
}

/* convert from float64 to binary64 */
int lwm2m_f64_to_b64(float64_value_t *f64, u8_t *b64, size_t len)
{
	s64_t v, f = 0;
	s32_t e = -1;
	int i;

	if (len != 8) {
		return -EINVAL;
	}

	/* handle zero value special case */
	if (f64->val1 == 0LL && f64->val2 == 0LL) {
		memset(b64, 0, len);
		return 0;
	}

	v = f64->val1;
	/* sign handled later */
	if (v < 0) {
		v = -v;
	}

	/* add whole value to fraction */
	while (v > 0) {
		f >>= 1;

		if (v & 1) {
			f |= ((s64_t)1 << 52);
		}

		v >>= 1;
		e++;
	}

	v = f64->val2;
	/* sign handled later */
	if (v < 0) {
		v = -v;
	}

	/* add decimal to fraction */
	i = e;
	while (v > 0 && i < 52) {
		v *= 2;
		if (e < 0 && v < LWM2M_FLOAT64_DEC_MAX) {
			/* handle -e */
			e--;
			continue;
		} else if (v >= LWM2M_FLOAT64_DEC_MAX) {
			v -= LWM2M_FLOAT64_DEC_MAX;
			f |= (s64_t)1 << (51 - i);
		}

		if (v == 0) {
			break;
		}

		i++;
	}

	/* adjust exponent for bias */
	e += 1023;

	memset(b64, 0, len);

	/* sign: bit 63 */
	b64[0] = f64->val1 < 0 ? 0x80 : 0;

	/* exponent: bits 62-52 */
	b64[0] |= (e >> 4);
	b64[1] = ((e & 0xF) << 4);

	/* fraction: bits 51-0 */
	/* NOTE: ignore the "hidden" bit 52 in fraction */
	b64[1] |= ((f >> 48) & 0xF);
	b64[2] = (f >> 40) & 0xFF;
	b64[3] = (f >> 32) & 0xFF;
	b64[4] = (f >> 24) & 0xFF;
	b64[5] = (f >> 16) & 0xFF;
	b64[6] = (f >> 8) & 0xFF;
	b64[7] = f & 0xFF;

	return 0;
}

/* convert from binary32 to float32 */
int lwm2m_b32_to_f32(u8_t *b32, size_t len, float32_value_t *f32)
{
	s32_t f, k, i, e;
	bool sign = false;

	if (len != 4) {
		return -EINVAL;
	}

	f32->val1 = 0;
	f32->val2 = 0;

	/* calc sign: bit 31 */
	sign = SHIFT_RIGHT(b32[0], 7, 0x1);

	/* calc exponent: bits 30-23 */
	e  = SHIFT_LEFT(b32[0], 1, 0xFF);
	e += SHIFT_RIGHT(b32[1], 7, 0x1);
	/* remove bias */
	e -= 127;

	/* enable "hidden" fraction bit 23 which is always 1 */
	f  = ((s32_t)1 << 22);
	/* calc fraction: bits 22-0 */
	f += ((s32_t)(b32[1] & 0x7F) << 16);
	f += ((s32_t)b32[2] << 8);
	f += b32[3];

	/* handle whole number */
	if (e > -1) {
		/* precision overflow */
		if (e > 23) {
			e = 23;
		}

		f32->val1 = (f >> (23 - e)) * (sign ? -1 : 1);
	}

	/* calculate the rest of the decimal */
	k = LWM2M_FLOAT32_DEC_MAX;

	/* account for -e */
	while (e < -1) {
		k /= 2;
		e++;
	}

	for (i = 22 - e; i >= 0; i--) {
		k /= 2;
		if (f & (1 << i)) {
			f32->val2 += k;

		}
	}

	return 0;
}

/* convert from binary64 to float64 */
int lwm2m_b64_to_f64(u8_t *b64, size_t len, float64_value_t *f64)
{
	s64_t f, k;
	int i, e;
	bool sign = false;

	if (len != 8) {
		return -EINVAL;
	}

	f64->val1 = 0LL;
	f64->val2 = 0LL;

	/* calc sign: bit 63 */
	sign = SHIFT_RIGHT(b64[0], 7, 0x1);

	/* get exponent: bits 62-52 */
	e  = SHIFT_LEFT((u16_t)b64[0], 4, 0x7F0);
	e += SHIFT_RIGHT(b64[1], 4, 0xF);
	/* remove bias */
	e -= 1023;

	/* enable "hidden" fraction bit 53 which is always 1 */
	f  = ((s64_t)1 << 52);
	/* get fraction: bits 51-0 */
	f += ((s64_t)(b64[1] & 0xF) << 48);
	f += ((s64_t)b64[2] << 40);
	f += ((s64_t)b64[3] << 32);
	f += ((s64_t)b64[4] << 24);
	f += ((s64_t)b64[5] << 16);
	f += ((s64_t)b64[6] << 8);
	f += b64[7];

	/* handle whole number */
	if (e > -1) {
		/* precision overflow */
		if (e > 52) {
			e = 52;
		}

		f64->val1 = (f >> (52 - e)) * (sign ? -1 : 1);
	}

	/* calculate the rest of the decimal */
	k = LWM2M_FLOAT64_DEC_MAX;

	/* account for -e */
	while (e < -1) {
		k /= 2;
		e++;
	}

	for (i = 51 - e; i >= 0; i--) {
		k /= 2;
		if (f & ((s64_t)1 << i)) {
			f64->val2 += k;

		}
	}

	return 0;
}
