#include <limits.h>
#include <mlibc/debug.hpp>

#define FMT(obj) format_object((obj), opts, formatter)

#define LOG_NAME_LOC(name, loc) "ubsan: " name " at " << loc << "\n  "
#define LOG_LHS_RHS(lhs, rhs) "LHS = " << (lhs) << ", RHS = " << (rhs)

struct SourceLocation {
	const char *filename;
	uint32_t line;
	uint32_t column;
};

template<class F>
void format_object(const SourceLocation &loc, frg::format_options opts, F &formatter) {
	FMT(loc.filename);
	FMT(":");
	FMT(loc.line);
	FMT(":");
	FMT(loc.column);
}

using ValueHandle = uintptr_t;

struct TypeDescriptor {
	enum class Kind : uint16_t {
		Integer = 0x0000,
		Float = 0x0001,
		Unknown = 0xffff
	} kind;

	uint16_t info;
	char name[];

	unsigned bitWidthInt() const {
		return 1 << (info >> 1);
	}

	bool isInlineInt() const {
		if (kind != Kind::Integer)
			return false;

		auto inlineBits = sizeof(ValueHandle) * CHAR_BIT;
		auto valueBits = bitWidthInt();
		return inlineBits <= valueBits;
	}

	bool isSigned() const {
		return info & 1;
	}
};

template<class F>
void format_object(const TypeDescriptor &type, frg::format_options opts, F &formatter) {
	FMT(type.name);
}

struct Value {
	const TypeDescriptor &type;
	ValueHandle val;

	Value(const TypeDescriptor &type, ValueHandle val) : type(type), val(val) {}
};

template<class F>
void format_object(const Value &val, frg::format_options opts, F &formatter) {
	if (val.type.isInlineInt() && val.type.isSigned()) {
		auto signedValue = static_cast<int64_t>(val.val);
		FMT(signedValue);
	} else if (val.type.isInlineInt() && !val.type.isSigned()) {
		auto unsignedValue = static_cast<uint64_t>(val.val);
		FMT(unsignedValue);
	}

	FMT(" (");
	FMT(val.type);
	FMT(")");
}


// --- Hook implementations ---

struct TypeMismatch {
	SourceLocation loc;
	const TypeDescriptor &type;
	unsigned char logAlignment;
	unsigned char kind;
};

extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_type_mismatch_v1(TypeMismatch *tm, ValueHandle pointer) {
	// TODO: Make this print more information.
	mlibc::panicLogger()
		<< LOG_NAME_LOC("type mismatch", tm->loc)
		<< "accessed address " << (void *)pointer << " but type "
		<< tm->type << " requires alignment " << (1 << tm->logAlignment)
		<< frg::endlog;
}

struct PointerOverflowData {
	SourceLocation loc;
};

extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_pointer_overflow(PointerOverflowData *pod, ValueHandle base, ValueHandle result) {
	(void)base;
	(void)result;
	mlibc::panicLogger()
		<< LOG_NAME_LOC("pointer overflow", pod->loc)
		<< frg::endlog;
}

struct InvalidValueData {
	SourceLocation loc;
	const TypeDescriptor &type;
};

extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_load_invalid_value(InvalidValueData *ivd, ValueHandle value) {
	(void)value;
	mlibc::panicLogger()
		<< LOG_NAME_LOC("load of invalid value", ivd->loc)
		<< frg::endlog;
}

struct OverflowData {
	SourceLocation loc;
	const TypeDescriptor &type;
};

extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_add_overflow(OverflowData *od, ValueHandle lhs, ValueHandle rhs) {
	mlibc::panicLogger()
		<< LOG_NAME_LOC("add overflowed ", od->loc)
		<< LOG_LHS_RHS(Value(od->type, lhs), Value(od->type, rhs))
		<< frg::endlog;
}

extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_sub_overflow(OverflowData *od, ValueHandle lhs, ValueHandle rhs) {
	mlibc::panicLogger()
		<< LOG_NAME_LOC("sub overflowed", od->loc)
		<< LOG_LHS_RHS(Value(od->type, lhs), Value(od->type, rhs))
		<< frg::endlog;
}

extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_mul_overflow(OverflowData *od, ValueHandle lhs, ValueHandle rhs) {
	mlibc::panicLogger()
		<< LOG_NAME_LOC("mul overflowed", od->loc)
		<< LOG_LHS_RHS(Value(od->type, lhs), Value(od->type, rhs))
		<< frg::endlog;
}

extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_divrem_overflow(OverflowData *od, ValueHandle lhs, ValueHandle rhs) {
	mlibc::panicLogger()
		<< LOG_NAME_LOC("divrem overflowed", od->loc)
		<< LOG_LHS_RHS(Value(od->type, lhs), Value(od->type, rhs))
		<< frg::endlog;
}

extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_negate_overflow(OverflowData *od, ValueHandle lhs, ValueHandle rhs) {
	mlibc::panicLogger()
		<< LOG_NAME_LOC("negate overflowed", od->loc)
		<< LOG_LHS_RHS(Value(od->type, lhs), Value(od->type, rhs))
		<< frg::endlog;
}

struct ShiftOutOfBoundsData {
	SourceLocation loc;
	const TypeDescriptor &lhsType;
	const TypeDescriptor &rhsType;
};

extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_shift_out_of_bounds(ShiftOutOfBoundsData *soob, ValueHandle lhs, ValueHandle rhs) {
	mlibc::panicLogger()
		<< LOG_NAME_LOC("shift out of bounds", soob->loc)
		<< LOG_LHS_RHS(Value(soob->lhsType, lhs), Value(soob->rhsType, rhs))
		<< frg::endlog;
}

struct OutOfBoundsData {
	SourceLocation loc;
	const TypeDescriptor &arrayType;
	const TypeDescriptor &indexType;
};

extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_out_of_bounds(OutOfBoundsData *oobd, ValueHandle data) {
	(void)data;
	mlibc::panicLogger()
		<< LOG_NAME_LOC("out of bounds access", oobd->loc)
		<< frg::endlog;
}

struct UnreachableData {
	SourceLocation loc;
};

extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_builtin_unreachable(UnreachableData *ubd) {
	mlibc::panicLogger()
		<< LOG_NAME_LOC("reached __builtin_unreachable()", ubd->loc)
		<< frg::endlog;
}

struct InvalidBuiltinData {
	SourceLocation loc;
	unsigned char kind;
};

extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_invalid_builtin(InvalidBuiltinData *ibd) {
	mlibc::panicLogger()
		<< LOG_NAME_LOC("reached invalid builtin", ibd->loc)
		<< frg::endlog;
}

struct VLABoundData {
	SourceLocation loc;
	const TypeDescriptor &type;
};

extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_vla_bound_not_positive(VLABoundData *vlabd) {
	mlibc::panicLogger()
		<< LOG_NAME_LOC("VLA bound not positive", vlabd->loc)
		<< frg::endlog;
}

extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_missing_return(UnreachableData *data) {
	mlibc::panicLogger()
		<< LOG_NAME_LOC("reached end of a value-returning function without returning a value", data->loc)
		<< frg::endlog;
}

struct NonNullArgData {
	SourceLocation loc;
	SourceLocation attr_loc;
	int arg_index;
};

extern "C" [[gnu::visibility("hidden")]]
void __ubsan_handle_nonnull_arg(NonNullArgData *data) {
	mlibc::panicLogger()
		<< LOG_NAME_LOC("null pointer passed to non-null argument", data->loc)
		<< "argument " << data->arg_index << " is required to be non-null in "
		<< data->attr_loc << frg::endlog;
}
