#include "json_deserializer.hpp"
#include "duckdb/common/types/blob.hpp"

namespace duckdb {

void JsonDeserializer::OnPropertyBegin(const field_id_t, const char *tag) {
	current_tag = tag;
}

void JsonDeserializer::OnPropertyEnd() {
}

bool JsonDeserializer::OnOptionalPropertyBegin(const field_id_t, const char *tag) {
	auto parent = Current();
	auto present = yyjson_obj_get(parent.val, tag) != nullptr;
	if (present) {
		current_tag = tag;
	}
	return present;
}

void JsonDeserializer::OnOptionalPropertyEnd(bool) {
}

// If inside an object, return the value associated by the current tag (property name)
// If inside an array, return the next element in the sequence
yyjson_val *JsonDeserializer::GetNextValue() {
	auto &parent_val = Current();
	yyjson_val *val;
	if (yyjson_is_obj(parent_val.val)) {
		val = yyjson_obj_get(parent_val.val, current_tag);
		if (!val) {
			const char *json = yyjson_val_write(Current().val, 0, nullptr);
			auto msg =
			    StringUtil::Format("Expected but did not find property '%s' in json object: '%s'", current_tag, json);
			free((void *)json);
			throw ParserException(msg);
		}
	} else if (yyjson_is_arr(parent_val.val)) {
		val = yyjson_arr_iter_next(&parent_val.arr_iter);
		if (!val) {
			const char *json = yyjson_val_write(Current().val, 0, nullptr);
			auto msg =
			    StringUtil::Format("Expected but did not find another value after exhausting json array: '%s'", json);
			free((void *)json);
			throw ParserException(msg);
		}
	} else {
		// unreachable?
		throw InternalException("Cannot get value from non-array/object");
	}
	return val;
}

void JsonDeserializer::ThrowTypeError(yyjson_val *val, const char *expected) {
	auto actual = yyjson_get_type_desc(val);
	auto &parent = Current();
	if (yyjson_is_obj(parent.val)) {
		auto msg =
		    StringUtil::Format("property '%s' expected type '%s', but got type: '%s'", current_tag, expected, actual);
		throw ParserException(msg);
	} else if (yyjson_is_arr(parent.val)) {
		auto msg = StringUtil::Format("Sequence expect child of type '%s', but got type: %s", expected, actual);
		throw ParserException(msg);
	} else {
		// unreachable?
		throw InternalException("cannot get nested value from non object or array-type");
	}
}

void JsonDeserializer::DumpDoc() {
	const char *json = yyjson_write(doc, 0, nullptr);
	printf("json: %s\n", json);
	free((void *)json);
}

void JsonDeserializer::DumpCurrent() {
	const char *json = yyjson_val_write(Current().val, 0, nullptr);
	printf("json: %s\n", json);
	free((void *)json);
}

void JsonDeserializer::Dump(yyjson_mut_val *val) {
	const char *json = yyjson_mut_val_write(val, 0, nullptr);
	printf("json: %s\n", json);
	free((void *)json);
}

void JsonDeserializer::Dump(yyjson_val *val) {
	const char *json = yyjson_val_write(val, 0, nullptr);
	printf("json: %s\n", json);
	free((void *)json);
}

//===--------------------------------------------------------------------===//
// Nested Types Hooks
//===--------------------------------------------------------------------===//
void JsonDeserializer::OnObjectBegin() {
	auto val = GetNextValue();
	if (!yyjson_is_obj(val)) {
		ThrowTypeError(val, "object");
	}
	Push(val);
}

void JsonDeserializer::OnObjectEnd() {
	stack.pop_back();
}

idx_t JsonDeserializer::OnListBegin() {
	auto val = GetNextValue();
	if (!yyjson_is_arr(val)) {
		ThrowTypeError(val, "array");
	}
	Push(val);
	return yyjson_arr_size(val);
}

void JsonDeserializer::OnListEnd() {
	Pop();
}

bool JsonDeserializer::OnNullableBegin() {
	auto &parent_val = Current();
	yyjson_arr_iter iter;
	if (yyjson_is_arr(parent_val.val)) {
		iter = parent_val.arr_iter;
	}
	auto val = GetNextValue();

	// Recover the iterator if we are inside an array
	if (yyjson_is_arr(parent_val.val)) {
		parent_val.arr_iter = iter;
	}

	if (yyjson_is_null(val)) {
		return false;
	}

	return true;
}

void JsonDeserializer::OnNullableEnd() {
}

//===--------------------------------------------------------------------===//
// Primitive Types
//===--------------------------------------------------------------------===//
bool JsonDeserializer::ReadBool() {
	auto val = GetNextValue();
	if (!yyjson_is_bool(val)) {
		ThrowTypeError(val, "bool");
	}
	return yyjson_get_bool(val);
}

int8_t JsonDeserializer::ReadSignedInt8() {
	auto val = GetNextValue();
	if (!yyjson_is_int(val)) {
		ThrowTypeError(val, "int8_t");
	}
	return yyjson_get_sint(val);
}

uint8_t JsonDeserializer::ReadUnsignedInt8() {
	auto val = GetNextValue();
	if (!yyjson_is_uint(val)) {
		ThrowTypeError(val, "uint8_t");
	}
	return yyjson_get_uint(val);
}

int16_t JsonDeserializer::ReadSignedInt16() {
	auto val = GetNextValue();
	if (!yyjson_is_int(val)) {
		ThrowTypeError(val, "int16_t");
	}
	return yyjson_get_sint(val);
}

uint16_t JsonDeserializer::ReadUnsignedInt16() {
	auto val = GetNextValue();
	if (!yyjson_is_uint(val)) {
		ThrowTypeError(val, "uint16_t");
	}
	return yyjson_get_uint(val);
}

int32_t JsonDeserializer::ReadSignedInt32() {
	auto val = GetNextValue();
	if (!yyjson_is_int(val)) {
		ThrowTypeError(val, "int32_t");
	}
	return yyjson_get_sint(val);
}

uint32_t JsonDeserializer::ReadUnsignedInt32() {
	auto val = GetNextValue();
	if (!yyjson_is_uint(val)) {
		ThrowTypeError(val, "uint32_t");
	}
	return yyjson_get_uint(val);
}

int64_t JsonDeserializer::ReadSignedInt64() {
	auto val = GetNextValue();
	if (!yyjson_is_int(val)) {
		ThrowTypeError(val, "int64_t");
	}
	return yyjson_get_sint(val);
}

uint64_t JsonDeserializer::ReadUnsignedInt64() {
	auto val = GetNextValue();
	if (!yyjson_is_uint(val)) {
		ThrowTypeError(val, "uint64_t");
	}
	return yyjson_get_uint(val);
}

float JsonDeserializer::ReadFloat() {
	auto val = GetNextValue();
	if (!yyjson_is_real(val)) {
		ThrowTypeError(val, "float");
	}
	return yyjson_get_real(val);
}

double JsonDeserializer::ReadDouble() {
	auto val = GetNextValue();
	if (!yyjson_is_real(val)) {
		ThrowTypeError(val, "double");
	}
	return yyjson_get_real(val);
}

string JsonDeserializer::ReadString() {
	auto val = GetNextValue();
	if (!yyjson_is_str(val)) {
		ThrowTypeError(val, "string");
	}
	return yyjson_get_str(val);
}

hugeint_t JsonDeserializer::ReadHugeInt() {
	auto val = GetNextValue();
	if (!yyjson_is_obj(val)) {
		ThrowTypeError(val, "object");
	}
	Push(val);
	hugeint_t result;
	ReadProperty(100, "upper", result.upper);
	ReadProperty(101, "lower", result.lower);
	Pop();
	return result;
}

uhugeint_t JsonDeserializer::ReadUhugeInt() {
	auto val = GetNextValue();
	if (!yyjson_is_obj(val)) {
		ThrowTypeError(val, "object");
	}
	Push(val);
	uhugeint_t result;
	ReadProperty(100, "upper", result.upper);
	ReadProperty(101, "lower", result.lower);
	Pop();
	return result;
}

void JsonDeserializer::ReadDataPtr(data_ptr_t &ptr, idx_t count) {
	auto val = GetNextValue();
	if (!yyjson_is_str(val)) {
		ThrowTypeError(val, "string");
	}
	auto str = yyjson_get_str(val);
	auto len = yyjson_get_len(val);
	D_ASSERT(len == count);
	auto blob = string_t(str, len);
	Blob::ToString(blob, char_ptr_cast(ptr));
}

} // namespace duckdb
