/*
 * x86_printf.c - A printf function for x86 (debug only).
 *
 * Copyright (c) 2017 Maxime Villard
 *
 * This file is part of ALMOS-MKH.
 *
 * ALMOS-MKH is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2.0 of the License.
 *
 * ALMOS-MKH is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ALMOS-MKH; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <hal_types.h>
#include <hal_boot.h>
#include <hal_internal.h>
#include <hal_apic.h>

#include <memcpy.h>
#include <thread.h>
#include <string.h>
#include <process.h>
#include <printk.h>
#include <vmm.h>
#include <core.h>
#include <cluster.h>

#define roundup(x, y) ((((x)+((y)-1))/(y))*(y))
#define CONS_X_SIZE	80
#define CONS_Y_SIZE	26

static char cons_buffer[CONS_X_SIZE * 2 * CONS_Y_SIZE] __in_kdata;
extern intptr_t iom_base;
size_t cons_ptr __in_kdata = 0;

/* -------------------------------------------------------------------------- */

static uint32_t x86_lock_val __in_kdata = 0;

void x86_lock()
{
	while (!hal_atomic_cas(&x86_lock_val, 0, 1)) {
		/* wait */
	}
}

void x86_unlock()
{
	while (!hal_atomic_cas(&x86_lock_val, 1, 0)) {
		/* wait */
	}
}

/* -------------------------------------------------------------------------- */

void x86_panic(char *msg)
{
	x86_lock();
	x86_printf("!!!!! PANIC !!!!!\n");
	x86_printf("-> %s\n", msg);
	x86_printf("!!!!!!!!!!!!!!!!!\n");
	x86_unlock();
	while (1);
}

static void check_scroll()
{
	char *base = (char *)iom_base + (0xB8000 - IOM_BEGIN);
	char *src, *dst;
	size_t i;

	if (cons_ptr < (CONS_X_SIZE * 2) * CONS_Y_SIZE) {
		return;
	}

	for (i = 0; i < CONS_Y_SIZE - 1; i++) {
		dst = (char *)&cons_buffer[0] + i * (CONS_X_SIZE * 2);
		src = (char *)&cons_buffer[0] + (i + 1) * (CONS_X_SIZE * 2);

		memcpy(dst, src, (CONS_X_SIZE * 2));
	}

	memset(&cons_buffer[0] + (CONS_X_SIZE * 2) * (CONS_Y_SIZE - 1),
	    0, (CONS_X_SIZE * 2));

	cons_ptr -= (CONS_X_SIZE * 2);
	memcpy(base, &cons_buffer[0], (CONS_X_SIZE * 2) * (CONS_Y_SIZE - 1));
}

void x86_putc(char c)
{
	hal_com_send(0, c);

	if (c == '\n') {
		cons_ptr = roundup(cons_ptr, CONS_X_SIZE * 2);
		check_scroll();
		return;
	}

	char *video = (char *)iom_base + (0xB8000 - IOM_BEGIN) + cons_ptr;
	char *buf = &cons_buffer[cons_ptr];
	*video = c;
	*buf = c;
	cons_ptr++, video++, buf++;
	*video = 0x7;
	*buf = 0x7;
	cons_ptr++, video++, buf++;

	check_scroll();
}

static void x86_itoa(char *buf, unsigned long int n, int base)
{
	unsigned long int tmp;
	int i, j;

	tmp = n;
	i = 0;

	do {
		tmp = n % base;
		buf[i++] = (tmp < 10) ? (tmp + '0') : (tmp + 'a' - 10);
	} while (n /= base);
	buf[i--] = 0;

	for (j = 0; j < i; j++, i--) {
		tmp = buf[j];
		buf[j] = buf[i];
		buf[i] = tmp;
	}
}

static void x86_ztoa(char *buf, uint64_t n, uint64_t base)
{
	uint64_t tmp;
	int i, j;

	tmp = n;
	i = 0;

	do {
		tmp = n % base;
		buf[i++] = (tmp < 10) ? (tmp + '0') : (tmp + 'a' - 10);
	} while (n /= base);
	buf[i--] = 0;

	for (j = 0; j < i; j++, i--) {
		tmp = buf[j];
		buf[j] = buf[i];
		buf[i] = tmp;
	}
}

static char alphabet[] = "abcdefghijklmnopqrstuvwxyz";

void x86_printf(char *s, ...)
{
	va_list ap;

	char buf[64];
	int i, j, size, buflen, neg;

	unsigned char c;
	int ival;
	unsigned int uival;
	uint64_t zval;

	va_start(ap, s);

	while ((c = *s++)) {
		size = 0;
		neg = 0;

		if (c == 0)
			break;
		else if (c == '%') {
			c = *s++;
			if (c >= '0' && c <= '9') {
				size = c - '0';
				c = *s++;
			}

			if (c == 'c') {
				ival = va_arg(ap, int);
				if ((ival - 97) > sizeof(alphabet)) {
					x86_printf("(unknown:%d)", ival);
				} else {
					x86_putc(alphabet[ival - 97]);
				}
			} else if (c == 'z') {
				zval = va_arg(ap, uint64_t);
				x86_ztoa(buf, zval, 10);

				buflen = strlen(buf);
				if (buflen < size)
					for (i = size, j = buflen; i >= 0; i--, j--)
						buf[i] = (j >= 0) ? buf[j] : '0';

				x86_printf(buf);
			} else if (c == 'Z') {
				zval = va_arg(ap, uint64_t);
				x86_ztoa(buf, zval, 16);

				buflen = strlen(buf);
				if (buflen < size)
					for (i = size, j = buflen; i >= 0; i--, j--)
						buf[i] = (j >= 0) ? buf[j] : '0';

				x86_printf("0x%s", buf);
			} else if (c == 'd') {
				ival = va_arg(ap, int);
				if (ival < 0) {
					uival = 0 - ival;
					neg++;
				} else
					uival = ival;
				x86_itoa(buf, uival, 10);

				buflen = strlen(buf);
				if (buflen < size)
					for (i = size, j = buflen; i >= 0;
					     i--, j--)
						buf[i] =
						    (j >=
						     0) ? buf[j] : '0';

				if (neg)
					x86_printf("-%s", buf);
				else
					x86_printf(buf);
			} else if (c == 'u') {
				uival = va_arg(ap, int);
				x86_itoa(buf, uival, 10);

				buflen = strlen(buf);
				if (buflen < size)
					for (i = size, j = buflen; i >= 0;
					     i--, j--)
						buf[i] =
						    (j >=
						     0) ? buf[j] : '0';

				x86_printf(buf);
			} else if (c == 'x' || c == 'X') {
				uival = va_arg(ap, int);
				x86_itoa(buf, uival, 16);

				buflen = strlen(buf);
				if (buflen < size)
					for (i = size, j = buflen; i >= 0;
					     i--, j--)
						buf[i] =
						    (j >=
						     0) ? buf[j] : '0';

				x86_printf("0x%s", buf);
			} else if (c == 'p') {
				uival = va_arg(ap, int);
				x86_itoa(buf, uival, 16);
				size = 8;

				buflen = strlen(buf);
				if (buflen < size)
					for (i = size, j = buflen; i >= 0;
					     i--, j--)
						buf[i] =
						    (j >=
						     0) ? buf[j] : '0';

				x86_printf("0x%s", buf);
			} else if (c == 's') {
				x86_printf((char *) va_arg(ap, uint64_t));
			}
		} else
			x86_putc(c);
	}

	return;
}
