
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <ctype.h>
#include <stdio.h>
#include <wchar.h>
#include <setjmp.h>
#include <limits.h>

#include <frg/random.hpp>
#include <mlibc/debug.hpp>
#include <bits/ensure.h>
#include <bits/sigset_t.h>

#include <mlibc/allocator.hpp>
#include <mlibc/charcode.hpp>
#include <mlibc/ansi-sysdeps.hpp>
#include <mlibc/strtofp.hpp>
#include <mlibc/strtol.hpp>
#include <mlibc/global-config.hpp>

#if __MLIBC_POSIX_OPTION
#include <pthread.h>
#endif // __MLIBC_POSIX_OPTION

extern "C" int __cxa_atexit(void (*function)(void *), void *argument, void *dso_tag);
void __mlibc_do_finalize();

namespace {
	// According to the first paragraph of [C11 7.22.7],
	// mblen(), mbtowc() and wctomb() have an internal state.
	// The string functions mbstowcs() and wcstombs() do *not* have this state.
	thread_local __mlibc_mbstate mblen_state = __MLIBC_MBSTATE_INITIALIZER;
	thread_local __mlibc_mbstate mbtowc_state = __MLIBC_MBSTATE_INITIALIZER;
}

double atof(const char *string) {
	return strtod(string, NULL);
}
int atoi(const char *string) {
	return strtol(string, nullptr, 10);
}
long atol(const char *string) {
	return strtol(string, nullptr, 10);
}
long long atoll(const char *string) {
	return strtoll(string, nullptr, 10);
}

// POSIX extensions but are here for simplicities sake. Forward declaration is here
// to avoid exporting sigprocmask when posix is disabled.
int sigprocmask(int, const sigset_t *__restrict, sigset_t *__restrict);
extern "C" {
	__attribute__ (( returns_twice )) int __sigsetjmp(sigjmp_buf buffer, int savesigs) {
		buffer[0].savesigs = savesigs;
		if (savesigs)
			sigprocmask(0, NULL, &buffer[0].sigset);
		return 0;
	}
}

__attribute__ (( noreturn )) void siglongjmp(sigjmp_buf buffer, int value) {
	if (buffer[0].savesigs)
		sigprocmask(SIG_SETMASK, &buffer[0].sigset, NULL);
	jmp_buf b;
	b[0].reg_state = buffer[0].reg_state;
	longjmp(b, value);
}

double strtod(const char *__restrict string, char **__restrict end) {
	return mlibc::strtofp<double>(string, end);
}
float strtof(const char *__restrict string, char **__restrict end) {
	return mlibc::strtofp<float>(string, end);
}
long double strtold(const char *__restrict string, char **__restrict end) {
	return mlibc::strtofp<long double>(string, end);
}

long strtol(const char *__restrict string, char **__restrict end, int base) {
	return mlibc::stringToInteger<long, char>(string, end, base);
}
long long strtoll(const char *__restrict string, char **__restrict end, int base) {
	return mlibc::stringToInteger<long long, char>(string, end, base);
}
unsigned long strtoul(const char *__restrict string, char **__restrict end, int base) {
	return mlibc::stringToInteger<unsigned long, char>(string, end, base);
}
unsigned long long strtoull(const char *__restrict string, char **__restrict end, int base) {
	return mlibc::stringToInteger<unsigned long long, char>(string, end, base);
}

frg::mt19937 __mlibc_rand_engine;

int rand() {
	// rand() is specified to return a positive number so we discard the MSB.
	return static_cast<int>(__mlibc_rand_engine() & 0x7FFFFFFF);
}

static unsigned temper(unsigned x) {
	x ^= x >> 11;
	x ^= x << 7 & 0x9D2C5680;
	x ^= x << 15 & 0xEFC60000;
	x ^= x >> 18;
	return x;
}

int rand_r(unsigned *seed) {
	return temper(*seed = *seed * 1103515245 + 12345) / 2;
}

void srand(unsigned int s) {
	__mlibc_rand_engine.seed(s);
}

void *aligned_alloc(size_t alignment, size_t size) {
	void *ptr;

	// alignment must be a power of two, and size % alignment must be 0
	if (alignment & (alignment - 1) || size & (alignment - 1)) {
		errno = EINVAL;
		return nullptr;
	}

	// posix_memalign requires that the alignment is a multiple of sizeof(void *)
	if (alignment < sizeof(void *))
		alignment = sizeof(void *);

	int ret = posix_memalign(&ptr, alignment, size);
	if (ret) {
		errno = ret;
		return nullptr;
	}
	return ptr;

}
void *calloc(size_t count, size_t size) {
	// TODO: this could be done more efficient if the OS gives us already zero'd pages
	void *ptr = malloc(count * size);
	if(!ptr)
		return nullptr;
	memset(ptr, 0, count * size);
	return ptr;
}
// free() is provided by the platform
// malloc() is provided by the platform
// realloc() is provided by the platform

void abort(void) {
	sigset_t set;
	sigemptyset(&set);
	sigaddset(&set, SIGABRT);
	if (mlibc::sys_sigprocmask) {
		mlibc::sys_sigprocmask(SIG_UNBLOCK, &set, nullptr);
	}

	raise(SIGABRT);

	sigfillset(&set);
	sigdelset(&set, SIGABRT);
	if (mlibc::sys_sigprocmask) {
		mlibc::sys_sigprocmask(SIG_SETMASK, &set, nullptr);
	}

	struct sigaction sa;
	sa.sa_handler = SIG_DFL;
	sa.sa_flags = 0;
	sigemptyset(&sa.sa_mask);

	if (mlibc::sys_sigaction(SIGABRT, &sa, nullptr))
		mlibc::panicLogger() << "mlibc: sigaction failed in abort" << frg::endlog;

	if (raise(SIGABRT))
		mlibc::panicLogger() << "mlibc: raise failed in abort" << frg::endlog;

	__builtin_trap();
}

int atexit(void (*func)(void)) {
	// TODO: the function pointer types are not compatible;
	// the conversion here is undefined behavior. its fine to do
	// this on the x86_64 abi though.
	__cxa_atexit((void (*) (void *))func, nullptr, nullptr);
	return 0;
}
int at_quick_exit(void (*func)(void)) {
	(void)func;
	__ensure(!"Not implemented");
	__builtin_unreachable();
}

void exit(int status) {
	__mlibc_do_finalize();
	mlibc::sys_exit(status);
}

void _Exit(int status) {
	mlibc::sys_exit(status);
}

// getenv() is provided by POSIX
void quick_exit(int) {
	__ensure(!"Not implemented");
	__builtin_unreachable();
}

extern char **environ;

int system(const char *command) {
	int status = -1;
	pid_t child;

	MLIBC_CHECK_OR_ENOSYS(mlibc::sys_fork && mlibc::sys_waitpid &&
			mlibc::sys_execve && mlibc::sys_sigprocmask && mlibc::sys_sigaction, -1);

#if __MLIBC_POSIX_OPTION
	pthread_testcancel();
#endif // __MLIBC_POSIX_OPTION

	if (!command) {
		return 1;
	}

	struct sigaction new_sa, old_int, old_quit;
	sigset_t new_mask, old_mask;

	new_sa.sa_handler = SIG_IGN;
	new_sa.sa_flags = 0;
	sigemptyset(&new_sa.sa_mask);
	mlibc::sys_sigaction(SIGINT, &new_sa, &old_int);
	mlibc::sys_sigaction(SIGQUIT, &new_sa, &old_quit);

	sigemptyset(&new_mask);
	sigaddset(&new_mask, SIGCHLD);
	mlibc::sys_sigprocmask(SIG_BLOCK, &new_mask, &old_mask);

	if (int e = mlibc::sys_fork(&child)) {
		errno = e;
	} else if (!child) {
		mlibc::sys_sigaction(SIGINT, &old_int, nullptr);
		mlibc::sys_sigaction(SIGQUIT, &old_quit, nullptr);
		mlibc::sys_sigprocmask(SIG_SETMASK, &old_mask, nullptr);

		const char *args[] = {
			"sh", "-c", command, nullptr
		};

		mlibc::sys_execve("/bin/sh", const_cast<char **>(args), environ);
		_Exit(127);
	} else {
		int err;
		pid_t unused;

		while ((err = mlibc::sys_waitpid(child, &status, 0, NULL, &unused)) < 0) {
			if (err == EINTR)
				continue;

			errno = err;
			status = -1;
		}
	}

	mlibc::sys_sigaction(SIGINT, &old_int, nullptr);
	mlibc::sys_sigaction(SIGQUIT, &old_quit, nullptr);
	mlibc::sys_sigprocmask(SIG_SETMASK, &old_mask, nullptr);

	return status;
}

char *mktemp(char *) {
	__ensure(!"Not implemented");
	__builtin_unreachable();
}

void *bsearch(const void *key, const void *base, size_t count, size_t size,
		int (*compare)(const void *, const void *)) {
	// Invariant: Element is in the interval [i, j).
	size_t i = 0;
	size_t j = count;

	while(i < j) {
		size_t k = (j - i) / 2;
		auto element = reinterpret_cast<const char *>(base) + (i + k) * size;
		auto res = compare(key, element);
		if(res < 0) {
			j = i + k;
		}else if(res > 0) {
			i = i + k + 1;
		}else{
			return const_cast<char *>(element);
		}
	}
	__ensure(i == j);

	return nullptr;
}

static int qsort_callback(const void *a, const void *b, void *arg) {
	auto compare = reinterpret_cast<int (*)(const void *, const void *)>(arg);

	return compare(a, b);
}

void qsort(void *base, size_t count, size_t size,
		int (*compare)(const void *, const void *)) {
	return qsort_r(base, count, size, qsort_callback, (void *) compare);
}

void qsort_r(void *base, size_t count, size_t size,
		int (*compare)(const void *, const void *, void *),
		void *arg) {
	// TODO: implement a faster sort
	for(size_t i = 0; i < count; i++) {
		void *u = (void *)((uintptr_t)base + i * size);
		for(size_t j = i + 1; j < count; j++) {
			void *v = (void *)((uintptr_t)base + j * size);
			if(compare(u, v, arg) <= 0)
				continue;

			// swap u and v
			char *u_bytes = (char *)u;
			char *v_bytes = (char *)v;
			for(size_t k = 0; k < size; k++) {
				char temp = u_bytes[k];
				u_bytes[k] = v_bytes[k];
				v_bytes[k] = temp;
			}
		}
	}
}

int abs(int num) {
	return num < 0 ? -num : num;
}

long labs(long num) {
	return num < 0 ? -num : num;
}

long long llabs(long long num) {
	return num < 0 ? -num : num;
}

div_t div(int number, int denom) {
	div_t r;
	r.quot = number / denom;
	r.rem = number % denom;
	return r;
}

ldiv_t ldiv(long number, long denom) {
	ldiv_t r;
	r.quot = number / denom;
	r.rem = number % denom;
	return r;
}

lldiv_t lldiv(long long number, long long denom) {
	lldiv_t r;
	r.quot = number / denom;
	r.rem = number % denom;
	return r;
}

int mblen(const char *mbs, size_t mb_limit) {
	auto cc = mlibc::current_charcode();
	wchar_t wc;
	mlibc::code_seq<const char> nseq{mbs, mbs + mb_limit};
	mlibc::code_seq<wchar_t> wseq{&wc, &wc + 1};

	if(!mbs) {
		mblen_state = __MLIBC_MBSTATE_INITIALIZER;
		return cc->has_shift_states;
	}

	if(auto e = cc->decode_wtranscode(nseq, wseq, mblen_state); e != mlibc::charcode_error::null)
		__ensure(!"decode_wtranscode() errors are not handled");
	return nseq.it - mbs;
}

int mbtowc(wchar_t *__restrict wc, const char *__restrict mb, size_t max_size) {
	auto cc = mlibc::current_charcode();
	__ensure(max_size);

	// If wc is NULL, decode into a single local character which we discard
	// to obtain the length.
	wchar_t tmp_wc;
	if (!wc)
		wc = &tmp_wc;

	if (mb) {
		if (*mb) {
			mlibc::code_seq<wchar_t> wseq{wc, wc + 1};
			mlibc::code_seq<const char> nseq{mb, mb + max_size};
			auto e = cc->decode_wtranscode(nseq, wseq, mbtowc_state);
			if (e != mlibc::charcode_error::null)
				__ensure(!"decode_wtranscode() errors are not handled");

			return nseq.it - mb;
		} else {
			*wc = L'\0';
			return 0; // When mbs is a null byte, return 0
		}
	} else {
		mblen_state = __MLIBC_MBSTATE_INITIALIZER;
		return cc->has_shift_states;
	}
}

int wctomb(char *, wchar_t) {
	__ensure(!"Not implemented");
	__builtin_unreachable();
}

size_t mbstowcs(wchar_t *wcs, const char *mbs, size_t wc_limit) {
	auto cc = mlibc::current_charcode();
	__mlibc_mbstate st = __MLIBC_MBSTATE_INITIALIZER;
	mlibc::code_seq<const char> nseq{mbs, nullptr};
	mlibc::code_seq<wchar_t> wseq{wcs, wcs + wc_limit};

	if(!wcs) {
		size_t size;
		if(auto e = cc->decode_wtranscode_length(nseq, &size, st); e != mlibc::charcode_error::null)
			__ensure(!"decode_wtranscode() errors are not handled");
		return size;
	}

	if(auto e = cc->decode_wtranscode(nseq, wseq, st); e != mlibc::charcode_error::null) {
		__ensure(!"decode_wtranscode() errors are not handled");
		__builtin_unreachable();
	}else{
		size_t n = wseq.it - wcs;
		if(n < wc_limit) // Null-terminate resulting wide string.
			wcs[n] = 0;
		return n;
	}
}

size_t wcstombs(char *mb_string, const wchar_t *wc_string, size_t max_size) {
	return wcsrtombs(mb_string, &wc_string, max_size, 0);
}

void free(void *ptr) {
	// TODO: Print PID only if POSIX option is enabled.
	if (mlibc::globalConfig().debugMalloc) {
		mlibc::infoLogger() << "mlibc (PID ?): free() on "
				<< ptr << frg::endlog;
		if((uintptr_t)ptr & 1)
			mlibc::infoLogger() << __builtin_return_address(0) << frg::endlog;
	}
	getAllocator().free(ptr);
}

void *malloc(size_t size) {
	auto nptr = getAllocator().allocate(size);
	// TODO: Print PID only if POSIX option is enabled.
	if (mlibc::globalConfig().debugMalloc)
		mlibc::infoLogger() << "mlibc (PID ?): malloc() returns "
				<< nptr << frg::endlog;
	return nptr;
}

void *realloc(void *ptr, size_t size) {
	auto nptr = getAllocator().reallocate(ptr, size);
	// TODO: Print PID only if POSIX option is enabled.
	if (mlibc::globalConfig().debugMalloc)
		mlibc::infoLogger() << "mlibc (PID ?): realloc() on "
				<< ptr << " returns " << nptr << frg::endlog;
	return nptr;
}

int posix_memalign(void **out, size_t align, size_t size) {
	if(align < sizeof(void *))
		return EINVAL;
	if(align & (align - 1)) // Make sure that align is a power of two.
		return EINVAL;
	auto p = getAllocator().allocate(frg::max(align, size));
	if(!p)
		return ENOMEM;
	// Hope that the alignment was respected. This works on the current allocator.
	// TODO: Make the allocator alignment-aware.
	__ensure(!(reinterpret_cast<uintptr_t>(p) & (align - 1)));
	*out = p;
	return 0;
}
