// video.cpp
// Copyright (C) 2009 Willow Schlanger

#include "options.h"

//U8 base_lo_1mb = 0;
#define base_lo_1mb kernel_load_address

// We will want to be able to print to the screen.
// API:
// - video_init()--called by init.cpp to initialize text-mode video.
//   we assume page number 0 is active. the number of lines on the
//   screen, and the current cursor position, is determined from
//   the BIOS Data Area.
// - video_disable()--called by the last processor when it is ready
// to boot the primary os. in this case, all writes go to a virtual
// screen instead of the real screen (80 columns x 25/50 lines).
// - video_enable()--before this is called by the last processor, that
// processor must set the video mode to mode 3.
// in between video_disable() and video_enable() calls, the last
// processor must back-up and restore the low 640kb of memory. this
// includes the BDA.
// - video_gotoxy(x, y). This sets the cursor to the designated postion.
// - video_putch('c')
// - video_printf(s, ...)
// - video_puts(s)
// - video_cprintf(color, s, ...)
// These routines may only be called by one processor at a time. This
// is generally the last processor--except early in the boot process
// before the other processors have started up, in which case they are
// called by the first (boot) processor.
// Each of the above 5 functions calls enter_critical_section() and
// leave_critical_section() before being used. This disables the scheduler
// (which runs at 128Hz via IRQ 9) for the current CPU.

struct Video
{
	UINT xpos;
	UINT ypos;
	UINT height;
	bool enabled;
	U1 vscr[4000];
	
	void cputch(char c, U1 color);
	void clear(U1 color);
	void gotoxy(int x, int y);
	void update_cursor();
};

void Video::update_cursor()
{
	// Note. If the display is disabled, do nothing here!
	if(!enabled)
		return;

	unsigned temp;	
	temp = ypos * 80 + xpos;
	U4 port = 0x3d4;
	outportb(port + 0, 14);
	outportb(port + 1, temp >> 8);
	outportb(port + 0, 15);
	outportb(port + 1, temp);
}

void Video::cputch(char c, U1 color)
{
	UINT addr_b8000 = base_lo_1mb + 0xb8000L;
	
	// Note. If the display is disabled switch to another buffer.
	if(!enabled)
		addr_b8000 = (UINT)(vscr);
	
	UINT off = addr_b8000 + 2 * xpos + 160 * ypos;
	
	if(c == '\n')
	{
		xpos = 0;
		++ypos;
	}
	else
	{
		U2 value = ((U2)(color) << 8) + (U1)(c);
		*(U2 *)(off) = value;
		++xpos;
	}
	
	if(xpos == 80)
	{
		xpos = 0;
		++ypos;
	}
	
	if(ypos == height)
	{
		// Scroll the screen by one line.
		U1 *dest = (U1 *)(addr_b8000);
		U1 *src = (U1 *)(addr_b8000 + 80 * 2);
		for(UINT i = 0; i < (24 * 80 * 2); ++i)
			dest[i] = src[i];
		for(UINT i = 0; i < 160; i += 2)
		{
			dest[(24 * 80 * 2) + i] = 0x20;
			dest[(24 * 80 * 2) + i + 1] = color;
		}

		ypos = height - 1;
	}
	
	update_cursor();
}

void Video::clear(U1 color)
{
	U2 *scr = (U2 *)(base_lo_1mb + 0xb8000L);

	// Note -- use virtual screen if display is disabled!
	if(!enabled)
		scr = (U2 *)(vscr);

	U2 value = ((U2)(color) << 8) + 0x20;
	int count = height * 80;
	for(int i = 0; i < count; ++i)
		*(scr++) = value;
	gotoxy(0, 0);
}

void Video::gotoxy(int x, int y)
{
	xpos = x;
	ypos = y;
	update_cursor();
}

Video video;

void video_putch(char c)
{
	enter_critical_section();
	video.cputch(c, 0x07);
	leave_critical_section();
}

int video_puts(const char *s)
{
	const char *t;
	enter_critical_section();
	for(t = s; *t != '\0'; ++t)
		video.cputch(*t, 0x07);
	leave_critical_section();
	return t - s;
}

void video_clear()
{
	enter_critical_section();
	video.clear(0x07);
	leave_critical_section();
}

void video_cclear(U1 color)
{
	enter_critical_section();
	video.clear(color);
	leave_critical_section();
}

void video_init()
{
	enter_critical_section();
	video.enabled = true;
	U1 *ram = (U1 *)(base_lo_1mb);
	video.xpos = ram[0x450];
	video.ypos = ram[0x451];
	video.height = ram[0x484] + 1;
	if(video.height > 25)
	{
		video.clear(0x07);
		video.height = 25;
	}
	leave_critical_section();
}

void video_disable()
{
	enter_critical_section();
	U1 *src = (U1 *)(base_lo_1mb + 0xb8000L);
	for(int i = 0; i < 160 * 25; ++i)
		video.vscr[i] = src[i];
	int xpos = video.xpos;
	int ypos = video.ypos;
	video.clear(0x07);
	video.enabled = false;
	video.xpos = xpos;
	video.ypos = ypos;
	leave_critical_section();
}

void video_enable()
{
	enter_critical_section();
	video.enabled = true;
	U1 *dest = (U1 *)(base_lo_1mb + 0xb8000L);
	for(int i = 0; i < 160 * 25; ++i)
		dest[i] = video.vscr[i];
	video.gotoxy(video.xpos, video.ypos);
	leave_critical_section();
}

void video_gotoxy(int x, int y)
{
	enter_critical_section();
	video.gotoxy(x, y);
	leave_critical_section();
}

static int sprintf_help(unsigned c, void **ptr)
{
	char **s = (char **)ptr;
	**s = c;
	++*s;
	return 0;
}

// --- The following code is derrived from OSD, a (now defunct) non-copyrighted
// --- OS tutorial kit.

/*============================================================================
STDARG.H C LIBRARY FUNCTIONS
============================================================================*/

typedef __builtin_va_list __gnuc_va_list;
#define va_start(v,l)	__builtin_va_start(v,l)
#define va_end(v)	__builtin_va_end(v)
#define va_arg(v,l)	__builtin_va_arg(v,l)
typedef __gnuc_va_list va_list;
/*
#define	STACKITEM	int
#define	VA_SIZE(TYPE)					\
	((sizeof(TYPE) + sizeof(STACKITEM) - 1)	\
		& ~(sizeof(STACKITEM) - 1))
*/

#if 0
/* width of stack == width of int */
#define	STACKITEM	SINT

typedef unsigned char *va_list;

/* round up width of objects pushed on stack. The expression before the
& ensures that we get 0 for objects of size 0. */
#define	VA_SIZE(TYPE)					\
	((sizeof(TYPE) + sizeof(STACKITEM) - 1)	\
		& ~(sizeof(STACKITEM) - 1))

/* &(LASTARG) points to the LEFTMOST argument of the function call
(before the ...) */
#define	va_start(AP, LASTARG)	\
	(AP=((va_list)&(LASTARG) + VA_SIZE(LASTARG)))

#define va_end(AP)	/* nothing */

#define va_arg(AP, TYPE)	\
	(AP += VA_SIZE(TYPE), *((TYPE *)(AP - VA_SIZE(TYPE))))
#endif

typedef int (*fnptr_t)(unsigned c, void **helper);

/*============================================================================
STRING.H C LIBRARY FUNCTIONS
============================================================================*/

/*****************************************************************************
*****************************************************************************/
size_t strlen(const char *str)
{
	size_t ret_val;

	for(ret_val = 0; *str != '\0'; str++)
		ret_val++;
	return ret_val;
}

//---

int do_printf(const char *fmt, va_list args, fnptr_t fn, void *ptr);

int sprintf(char *s, const char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	int count;
	count = do_printf(fmt, args, sprintf_help, s);
	s[count] = '\0';
	va_end(args);
	return count;
}

static int printf_help(unsigned c, void **ptr)
{
	video.cputch(c, **(U1 **)(ptr));
	return 0;
}

int video_printf(const char *fmt, ...)
{
	enter_critical_section();
	va_list args;
	va_start(args, fmt);
	U1 color = 0x07;
	int count;
	count = do_printf(fmt, args, printf_help, &color);
	va_end(args);
	leave_critical_section();
	return count;
}

int video_cprintf(U1 color, const char *fmt, ...)
{
	enter_critical_section();
	va_list args;
	va_start(args, fmt);
	int count;
	U1 colorT = color;
	count = do_printf(fmt, args, printf_help, &colorT);
	va_end(args);
	leave_critical_section();
	return count;
}

/*============================================================================
STDIO.H C LIBRARY FUNCTIONS
============================================================================*/
/*****************************************************************************
name:	do_printf
action:	minimal subfunction for ?printf, calls function
	'fn' with arg 'ptr' for each character to be output
returns:total number of characters output
notes:	does not handle long long (64-bit) values, far pointers, floats,
	precision part of field width, leading sign, or leading blanks.
*****************************************************************************/
/* flags used in processing format string */
#define		PR_LJ	0x01	/* left justify */
#define		PR_CA	0x02	/* use A-F instead of a-f for hex */
#define		PR_SG	0x04	/* signed numeric conversion (%d vs. %u) */
#define		PR_32	0x08	/* long (32-bit) numeric conversion */
#define		PR_16	0x10	/* short (16-bit) numeric conversion */
#define		PR_WS	0x20	/* PR_SG set and num was < 0 */
#define		PR_LZ	0x40	/* pad left with '0' instead of ' ' */
#define		PR_FP	0x80	/* pointers are far */

/* largest number handled is 2^32-1, lowest radix handled is 8.
2^32-1 in base 8 has 11 digits (add 5 for trailing NUL and for slop) */
#define		PR_BUFLEN	16

int do_printf(const char *fmt, va_list args, fnptr_t fn, void *ptr)
{
	unsigned state, flags, radix, actual_wd, count, given_wd;
	char *where, buf[PR_BUFLEN];
	long num;

	state = flags = count = given_wd = 0;
/* begin scanning format specifier list */

	for(; *fmt; fmt++)
	{
		switch(state)
		{
/* STATE 0: AWAITING % */
		case 0:
			if(*fmt != '%')	/* not %... */
			{
				fn(*fmt, &ptr);	/* ...just echo it */
				count++;
				break;
			}
/* found %, get next char and advance state to check if next char is a flag */
			state++;
			fmt++;
			/* FALL THROUGH */
/* STATE 1: AWAITING FLAGS (%-0) */
		case 1:
			if(*fmt == '%')	/* %% */
			{
				fn(*fmt, &ptr);
				count++;
				state = flags = given_wd = 0;
				break;
			}
			if(*fmt == '-')
			{
				if(flags & PR_LJ)/* %-- is illegal */
					state = flags = given_wd = 0;
				else
					flags |= PR_LJ;
				break;
			}
/* not a flag char: advance state to check if it's field width */
			state++;
/* check now for '%0...' */
			if(*fmt == '0')
			{
				flags |= PR_LZ;
				fmt++;
			}
			/* FALL THROUGH */
/* STATE 2: AWAITING (NUMERIC) FIELD WIDTH */
		case 2:
			if(*fmt >= '0' && *fmt <= '9')
			{
				given_wd = 10 * given_wd +
					(*fmt - '0');
				break;
			}
/* not field width: advance state to check if it's a modifier */
			state++;
			/* FALL THROUGH */
/* STATE 3: AWAITING MODIFIER CHARS (FNlh) */
		case 3:
			if(*fmt == 'F')
			{
				flags |= PR_FP;
				break;
			}
			if(*fmt == 'N')
				break;
			if(*fmt == 'l')
			{
				flags |= PR_32;
				break;
			}
			if(*fmt == 'h')
			{
				flags |= PR_16;
				break;
			}
/* not modifier: advance state to check if it's a conversion char */
			state++;
			/* FALL THROUGH */
/* STATE 4: AWAITING CONVERSION CHARS (Xxpndiuocs) */
		case 4:
			where = buf + PR_BUFLEN - 1;
			*where = '\0';
			switch(*fmt)
			{
			case 'X':
				flags |= PR_CA;
				/* FALL THROUGH */
/* xxx - far pointers (%Fp, %Fn) not yet supported */
			case 'x':
			case 'p':
			case 'n':
				radix = 16;
				goto DO_NUM;
			case 'd':
			case 'i':
				flags |= PR_SG;
				/* FALL THROUGH */
			case 'u':
				radix = 10;
				goto DO_NUM;
			case 'o':
				radix = 8;
/* load the value to be printed. l=long=32 bits: */
DO_NUM:				if(flags & PR_32)
					num = va_arg(args, unsigned long);
/* h=short=16 bits (signed or unsigned) */
				else if(flags & PR_16)
				{
					if(flags & PR_SG)
						num = (signed short)(va_arg(args, int));
					else
						num = (unsigned short)(va_arg(args, unsigned int));
				}
/* no h nor l: sizeof(int) bits (signed or unsigned) */
				else
				{
					if(flags & PR_SG)
						num = va_arg(args, int);
					else
						num = va_arg(args, unsigned int);
				}
/* take care of sign */
				if(flags & PR_SG)
				{
					if(num < 0)
					{
						flags |= PR_WS;
						num = -num;
					}
				}
/* convert binary to octal/decimal/hex ASCII
OK, I found my mistake. The math here is _always_ unsigned */
				do
				{
					unsigned long temp;

					temp = (unsigned long)num % radix;
					where--;
					if(temp < 10)
						*where = temp + '0';
					else if(flags & PR_CA)
						*where = temp - 10 + 'A';
					else
						*where = temp - 10 + 'a';
					num = (unsigned long)num / radix;
				}
				while(num != 0);
				goto EMIT;
			case 'c':
/* disallow pad-left-with-zeroes for %c */
				flags &= ~PR_LZ;
				where--;
				*where = (char)va_arg(args, int);
				actual_wd = 1;
				goto EMIT2;
			case 's':
/* disallow pad-left-with-zeroes for %s */
				flags &= ~PR_LZ;
				where = va_arg(args, char *);
EMIT:
				actual_wd = strlen(where);
				if(flags & PR_WS)
					actual_wd++;
/* if we pad left with ZEROES, do the sign now */
				if((flags & (PR_WS | PR_LZ)) ==
					(PR_WS | PR_LZ))
				{
					fn('-', &ptr);
					count++;
				}
/* pad on left with spaces or zeroes (for right justify) */
EMIT2:				if((flags & PR_LJ) == 0)
				{
					while(given_wd > actual_wd)
					{
						fn(flags & PR_LZ ? '0' :
							' ', &ptr);
						count++;
						given_wd--;
					}
				}
/* if we pad left with SPACES, do the sign now */
				if((flags & (PR_WS | PR_LZ)) == PR_WS)
				{
					fn('-', &ptr);
					count++;
				}
/* emit string/char/converted number */
				while(*where != '\0')
				{
					fn(*where++, &ptr);
					count++;
				}
/* pad on right with spaces (for left justify) */
				if(given_wd < actual_wd)
					given_wd = 0;
				else given_wd -= actual_wd;
				for(; given_wd; given_wd--)
				{
					fn(' ', &ptr);
					count++;
				}
				break;
			default:
				break;
			}
		default:
			state = flags = given_wd = 0;
			break;
		}
	}

	return count;
}
