/* 
 * Copyright 2010 Stefan Lankes, Chair for Operating Systems,
 *                               RWTH Aachen University
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *  This file is part of MetalSVM.
 */

#include <metalsvm/stdio.h>
#include <metalsvm/string.h>
#include <metalsvm/stdarg.h>
#include <metalsvm/spinlocks.h>
#ifdef USE_VGA
#include <asm/vga.h>
#endif

static unsigned int kmsg_counter = 0;
static unsigned char kmessages[KMSG_SIZE];
static spinlock_t kio_lock = SPINLOCK_INIT;

int koutput_init(void)
{
#ifdef USE_VGA
	vga_init();
#endif
	memset(kmessages, 0, sizeof(unsigned char)*KMSG_SIZE);

	return 0;
}

int kputchar(int c)
{
	int ret = 1;

	spinlock_lock(&kio_lock);
	kmessages[kmsg_counter++ % KMSG_SIZE] = c;
#ifdef USE_VGA
	ret = vga_putchar(c);
#endif
	spinlock_unlock(&kio_lock);

	return ret;
}

int kputs(const char *str)
{
	int i;

	spinlock_lock(&kio_lock);
	for(i=0; str[i] != '\0'; i++)
		kmessages[kmsg_counter++ % KMSG_SIZE] = str[i];
#ifdef USE_VGA
	i = vga_puts(str);
#endif
	spinlock_unlock(&kio_lock);

	return i;
}

static void kprintu(unsigned int val, int* count)
{
	int c = val % 10;
	
	val /= 10;
	if (val)
		kprintu(val, count);

	kputchar(c+0x30);

	if (count)
		(*count)++;
}

static void kprintx(unsigned int val, int* count)
{
	int c = val % 16;

	val /= 16;
	if (val)
		kprintu(val, count);

	if (c < 10)
		kputchar(c+0x30);
	else 
		kputchar(c-10+0x61);

	if (count)
		(*count)++;
}

int kprintf(const char* fmt, ...)
{
	va_list ap;
	unsigned int i = 0;
	int count = 0;

	if (BUILTIN_EXPECT(!fmt, 0))
		return 0;

	va_start(ap, fmt);
	while(fmt[i] != '\0') {
		if (fmt[i] == '%') {
			i++;
			switch(fmt[i])
			{
			case '\0':
				continue;
			case 'c': {
					int c = va_arg(ap, int);
					kputchar(c);
					count++;
				}
				break;
			case 's': {
					char* str = va_arg(ap, char*);
					count += kputs(str);
				}
				break;
			case 'd':
			case 'i': {
					int val = va_arg(ap, int);
					int sig = (val < 0);

					if (sig) {
						val = -val;
						kputchar('-');
						count++;
					}

					kprintu((unsigned int)val, &count);
				}
				break;
			case 'u': {
					unsigned int val = va_arg(ap, unsigned int);

					kprintu(val, &count);
				}
				break;
			case 'x': {
					unsigned int val = va_arg(ap, unsigned int);

					kprintx(val, &count);
				}
				break;
			case 'p': {
					size_t val = (size_t) va_arg(ap, void*);

					kputs("0x");
					count += 2;
					kprintx(val, &count);
				}
				break;
			default:
				kputchar('%');
				kputchar(fmt[i]);
				count++;
				break;
			}
		} else {
			kputchar(fmt[i]);
			count++;
		}

		i++;
	}
	va_end(ap);

	return count;
}