#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "mochilo.h"
#include "mochilo_api.h"

static inline int unpack_array(mo_value *_array, size_t elements, mochilo_src *buf)
{
	size_t i;
	int error;

	mo_value array = moapi_array_new(elements);

	for (i = 0; i < elements; ++i) {
		mo_value element;

		if ((error = mochilo_unpack_one(&element, buf)) < 0)
			return error;

		moapi_array_append(array, element);
	}

	*_array = array;
	return 0;
}

static inline int unpack_hash(mo_value *_hash, size_t elements, mochilo_src *buf)
{
	size_t i;
	int error;

	mo_value hash = moapi_hash_new();

	for (i = 0; i < elements; ++i) {
		mo_value key, value;

		if ((error = mochilo_unpack_one(&key, buf)) < 0)
			return error;

		if ((error = mochilo_unpack_one(&value, buf)) < 0)
			return error;

		moapi_hash_set(hash, key, value);
	}

	*_hash = hash;
	return 0;
}

#define UNPACK_INT(sign, bits) { \
	sign##bits##_t integer; \
	SRC_ENSURE_AVAIL(src, (bits / 8)); \
	mochilo_src_get##bits##be(src, &integer); \
	*_value = moapi_##sign##bits##_new(integer); \
	return 0; \
}

int mochilo_unpack_one(mo_value *_value, mochilo_src *src)
{
	uint8_t leader;

	if (!SRC_CHECK_AVAIL(src, 1))
		return MSGPACK_ENOTHING;

	mochilo_src_get8be(src, &leader);

	switch (leader) {
		case MSGPACK_T_UINT8:
			UNPACK_INT(uint, 8);

		case MSGPACK_T_INT8:
			UNPACK_INT(int, 8);

		case MSGPACK_T_UINT16:
			UNPACK_INT(uint, 16);

		case MSGPACK_T_INT16:
			UNPACK_INT(int, 16);

		case MSGPACK_T_UINT32:
			UNPACK_INT(uint, 32);

		case MSGPACK_T_INT32:
			UNPACK_INT(int, 32);

		case MSGPACK_T_UINT64:
			UNPACK_INT(uint, 64);

		case MSGPACK_T_INT64:
			UNPACK_INT(int, 64);

		case MSGPACK_T_NIL:
		case MSGPACK_T_TRUE:
		case MSGPACK_T_FALSE:
			*_value = moapi_atom_new(leader);
			return 0;

		case MSGPACK_T_FLOAT:
		{
			float flt;

			SRC_ENSURE_AVAIL(src, 4);
			mochilo_src_get32be(src, &flt);

			*_value = moapi_float_new(flt);
			return 0;
		}

		case MSGPACK_T_DOUBLE:
		{
			double flt;

			SRC_ENSURE_AVAIL(src, 8);
			mochilo_src_get64be(src, &flt);

			*_value = moapi_double_new(flt);
			return 0;
		}

		case MSGPACK_T_ARRAY16:
		{
			uint16_t length;

			SRC_ENSURE_AVAIL(src, 2);
			mochilo_src_get16be(src, &length);

			return unpack_array(_value, (size_t)length, src);
		}

		case MSGPACK_T_ARRAY32:
		{
			uint32_t length;

			SRC_ENSURE_AVAIL(src, 4);
			mochilo_src_get32be(src, &length);

			return unpack_array(_value, (size_t)length, src);
		}

		case MSGPACK_T_MAP16:
		{
			uint16_t length;

			SRC_ENSURE_AVAIL(src, 2);
			mochilo_src_get16be(src, &length);

			return unpack_hash(_value, (size_t)length, src);
		}

		case MSGPACK_T_MAP32:
		{
			uint32_t length;

			SRC_ENSURE_AVAIL(src, 4);
			mochilo_src_get32be(src, &length);

			return unpack_hash(_value, (size_t)length, src);
		}

		case MSGPACK_T_SYM:
		{
			uint8_t length;
			const char *ptr;
			
			if (!src->trusted) {
				return MSGPACK_EUNSAFE;
			}

			SRC_ENSURE_AVAIL(src, 1);
			mochilo_src_get8be(src, &length);

			if (!(ptr = mochilo_src_peek(src, length)))
				return -1;

			*_value = moapi_sym_new(ptr, length);
			return 0;
		}

#ifdef HAVE_RUBY_ENCODING_H
		case MSGPACK_T_STR16:
		{
			uint16_t length;
			uint8_t encoding;
			const char *ptr;

			SRC_ENSURE_AVAIL(src, 2 + 1);
			mochilo_src_get16be(src, &length);
			mochilo_src_get8be(src, &encoding);

			if (!(ptr = mochilo_src_peek(src, length)))
				return -1;

			*_value = moapi_str_new(ptr, length, encoding);
			return 0;
		}

		case MSGPACK_T_STR32:
		{
			uint32_t length;
			uint8_t encoding;
			const char *ptr;

			SRC_ENSURE_AVAIL(src, 4 + 1);
			mochilo_src_get32be(src, &length);
			mochilo_src_get8be(src, &encoding);

			if (!(ptr = mochilo_src_peek(src, length)))
				return -1;

			*_value = moapi_str_new(ptr, length, encoding);
			return 0;
		}
#endif

		case MSGPACK_T_RAW16:
		{
			uint16_t length;
			const char *ptr;

			SRC_ENSURE_AVAIL(src, 2);
			mochilo_src_get16be(src, &length);

			if (!(ptr = mochilo_src_peek(src, length)))
				return -1;

			*_value = moapi_bytes_new(ptr, length);
			return 0;
		}

		case MSGPACK_T_RAW32:
		{
			uint32_t length;
			const char *ptr;

			SRC_ENSURE_AVAIL(src, 4);
			mochilo_src_get32be(src, &length);

			if (!(ptr = mochilo_src_peek(src, length)))
				return -1;

			*_value = moapi_bytes_new(ptr, length);
			return 0;
		}

		default:
		{
			if (leader < 0x80 || leader >= 0xe0) {
				*_value = moapi_int8_new((int8_t)leader);
				return 0;
			}

			else if (leader < 0x90) {
				uint8_t length = leader & (~0x80);
				return unpack_hash(_value, length, src);
			}

			else if (leader < 0xa0) {
				uint8_t length = leader & (~0x90);
				return unpack_array(_value, length, src);
			}

			else if (leader < 0xc0) {
				uint8_t length = leader & (~0xa0);
				const char *ptr;

				if (!(ptr = mochilo_src_peek(src, length)))
					return -1;

				*_value = moapi_bytes_new(ptr, length);
				return 0;
			}

			return MSGPACK_EINVALID;
		}
	}
}