#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "xwrap.h"

#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#ifdef OS_IS_FREEBSD
# include <sys/sysctl.h>
#endif

#define RETRY_SEC     0
#define RETRY_NSEC    250000000

static const char fail_msg[] = "Fail to allocate memory. Retry ...\n";
static const size_t fail_len = STATIC_STRLEN (fail_msg);

int xatol (const char* str, long* result) {
	int errno_save, rval;
	long lres;
	char* endptr;

	errno_save = errno;
	errno = 0, rval = 0;

	lres = strtol (str, &endptr, 10);
	if (str == endptr || errno != 0) {
		rval = -1;
	} else {
		*result = lres;
	}

	errno = errno_save;

	return rval;
}

void* xmalloc (size_t size) {
	void* memptr;

	while ((memptr = malloc (size)) == NULL) {
		nanosleep (&(struct timespec) { RETRY_SEC, RETRY_NSEC }, NULL);
		write (STDERR_FILENO, fail_msg, fail_len);
	}

	return memptr;
}

void* xrealloc (void* ptr, size_t size) {
	void* newptr;

	while ((newptr = realloc (ptr, size)) == NULL) {
		nanosleep (&(struct timespec) { RETRY_SEC, RETRY_NSEC }, NULL);
		write (STDERR_FILENO, fail_msg, fail_len);
	}

	return newptr;
}

bool xstrend (const char* str, const char* suffix) {
	size_t len = strlen (str);
	size_t suflen = strlen (suffix);
	int i, j;
	for (i = len - 1, j = suflen - 1; i >= 0 && j >= 0; i--, j--) {
		if (str[i] != suffix[j]) {
			return false;
		}
	}
	if (i < 0 && j >= 0) {
		return false;
	}
	return true;
}

char* xstrcat (const char* str, ...) {
	va_list ap;
	char* strp;
	char* strnow;
	char* newstr;
	int len = strlen (str);

	va_start (ap, str);
	while ((strp = va_arg (ap, char*)) != NULL) {
		len += strlen (strp);
	}
	va_end (ap);

	newstr = xmalloc (len + 1);

	va_start (ap, str);
	strnow = stpcpy (newstr, str);
	while ((strp = va_arg (ap, char*)) != NULL) {
		strnow = stpcpy (strnow, strp);
	}
	newstr[len] = '\0';
	va_end (ap);

    return newstr;
}

char* xstrdup (const char* str) {
	char* newstr;

	while ((newstr = strdup (str)) == NULL) {
		nanosleep (&(struct timespec) { RETRY_SEC, RETRY_NSEC }, NULL);
		write (STDERR_FILENO, fail_msg, fail_len);
	}

	return newstr;
}

char* xsprintf (const char* format, ...) {
	va_list ap;
	char* newstr;
	int len;

	va_start (ap, format);
	len = vsnprintf (NULL, 0, format, ap) + 1;
	va_end (ap);

	newstr = xmalloc (len);

	va_start (ap, format);
	vsnprintf (newstr, len, format, ap);
	va_end (ap);

	return newstr;
}

int xfaddfd (int fd, int fdflags) {
	int orgfd = fcntl (fd, F_GETFD);
	if (orgfd < 0) {
		return -1;
	}
	return fcntl (fd, F_SETFD, orgfd | fdflags);
}

int xfdelfd (int fd, int fdflags) {
	int orgfd = fcntl (fd, F_GETFD);
	if (orgfd < 0) {
		return -1;
	}
	return fcntl (fd, F_SETFD, orgfd & ~(fdflags));
}

int xfaddfl (int fd, int flflags) {
	int orgfl = fcntl (fd, F_GETFL);
	if (orgfl < 0) {
		return -1;
	}
	return fcntl (fd, F_SETFL, orgfl | flflags);
}

int xfdelfl (int fd, int flflags) {
	int orgfl = fcntl (fd, F_GETFL);
	if (orgfl < 0) {
		return -1;
	}
	return fcntl (fd, F_SETFL, orgfl & ~(flflags));
}

char* xreadlink (const char* filename) {
	struct stat st;
	if (lstat (filename, &st) < 0) {
		return NULL;
	}
	size_t bufsize = st.st_size ? st.st_size : 8192;
	char* buf = malloc (bufsize + 1);
	if (buf == NULL) {
		return NULL;
	}
	ssize_t written = readlink (filename, buf, bufsize);
	if (written < 0) {
		free (buf);
		return NULL;
	}
	buf[written] = '\0';
	return buf;
}

char* xgetcwd (void) {
	char *cwd, *result;
	size_t size = pathconf (".", _PC_PATH_MAX);

	size = size > 0 ? size : 256;
	cwd = xmalloc (sizeof (char) * size);

	while ((result = getcwd (cwd, size)) == NULL && errno == ERANGE) {
		size *= 2;
		cwd = xrealloc (cwd, size);
	}

	return cwd;
}

char* xgetexe (void) {
	char* myexec;

#ifdef OS_IS_FREEBSD
	int fb_mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
	int fb_rval;
	size_t fb_size = 256;
	myexec = xmalloc (fb_size);
	while ((fb_rval = sysctl (fb_mib, 4, myexec, &fb_size, NULL, 0)) < 0 &&
		 errno == ENOMEM) {

		fb_size *= 2;
		myexec = xrealloc (myexec, fb_size);
	}
	if (fb_rval >= 0) {
		return myexec;
	} else {
		free (myexec);
	}
#endif

	if ((myexec = xreadlink ("/proc/self/exe")) != NULL) {
		return myexec;
	}
	if ((myexec = xreadlink ("/proc/curproc/exe")) != NULL) {
		return myexec;
	}
	if ((myexec = xreadlink ("/proc/curproc/file")) != NULL) {
		return myexec;
	}

	return NULL;
}

char* xgetres (const char* filename) {
	char *myexec, *myres;
	bool myexec_static = false;

	myexec = xgetexe ();
	if (myexec == NULL) {
		myexec = xgetcwd ();
		if (myexec == NULL) {
			myexec = "./";
			myexec_static = true;
		}
	} else {
		char* dirsep = strrchr (myexec, '/');
		if (dirsep != NULL && dirsep != myexec) {
			*dirsep = '\0';
		}
	}

	if (xstrend (myexec, "/")) {
		myres = xstrcat (myexec, filename, (char*)NULL);
	} else {
		myres = xstrcat (myexec, "/", filename, (char*)NULL);
	}
	if (!myexec_static) {
		free (myexec);
	}

	return myres;
}

size_t xwrite (int fd, const char* str, size_t size) {
	ssize_t wtn = 0;
	if (size <= 0) {
		size = strlen (str);
	}
	size_t rem = size;
	while (rem > 0) {
		wtn = write (fd, str, rem);
		if (wtn < 0) {
			if (errno != EINTR && errno != EAGAIN) {
				break;
			}
			continue;
		}
		str += wtn;
		rem -= wtn;
	}

	rem = rem > 0 ? rem : 0;
	return size - rem;
}

void xbufinit (XBuf* buf) {
	buf->buf_start = 0;
	buf->buf_len = 0;
	buf->buf_line = NULL;
	buf->buf_line_len = 0;
	buf->buf_error = false;
	buf->buf_eof = false;
}

char* xgetline (int fd, XBuf* buf, int delim) {
	if (buf->buf_error || buf->buf_eof) {
		return NULL;
	}

	if (buf->buf_len == 0) {
		int rval = read (fd, buf->buf, XBUFSIZ);
		if (rval < 0) {
			if (errno != EAGAIN && errno != EINTR) {
				buf->buf_error = true;
			}
			return NULL;
		}
		if (rval == 0) {
			buf->buf_eof = true;
			return NULL;
		}
		buf->buf_start = 0;
		buf->buf_len = rval;
	}

	int i;
	for (i = 0; i < buf->buf_len; i++) {
		if (buf->buf[buf->buf_start + i] == delim) {
			break;
		}
	}

	int buf_line_start = buf->buf_line_len;
	buf->buf_line_len += i;
	buf->buf_line = xrealloc (buf->buf_line, buf->buf_line_len + 1);
	memcpy (buf->buf_line + buf_line_start, buf->buf + buf->buf_start, i);
	buf->buf_line[buf->buf_line_len] = '\0';

	/* remove CR if delim is LF and delim is found */
	if (i < buf->buf_len && delim == '\n' && buf->buf_line_len - 1 >= 0 &&
		buf->buf_line[buf->buf_line_len - 1] == '\r') {
		buf->buf_line[buf->buf_line_len - 1] = '\0';
		buf->buf_line_len--;
	}

	int buf_len_saved = buf->buf_len;
	buf->buf_start += i + 1;
	buf->buf_len -= i + 1;
	if (buf->buf_len <= 0) {
		buf->buf_start = 0;
		buf->buf_len = 0;
	}

	if (i < buf_len_saved) {
		/* delim is found */
		char* newstr = buf->buf_line;
		buf->buf_line = NULL;
		buf->buf_line_len = 0;
		memmove (buf->buf, buf->buf + buf->buf_start, buf->buf_len);
		buf->buf_start = 0;
		return newstr;
	}

	return NULL;
}