/**
 * video.c
 *
 * Text video routines
 *
 * Exports:
 *  blink();
 *  select_vc()
 *  putch_help()
 *  putch()
 *  init_video();
 *
 * Imports:
 *  main.c	printf();
 */

/**
 * TODO
 *
 * Fuck me with a blind melon...when the screen scrolls
 * too much, it generates a panic of type invalid opcode.
 */

#include <string.h> /* memcpy(), memsetw() */
#include <ctype.h> /* isdigit() */
#include <x86.h> /* outportb(), inportb() */
#include <stdint.h>
#include "_krnl.h" /* MAX_VC, console_t */

/**
 * Imports
 */
void printf(const char *fmt, ...);

#define	VGA_MISC_READ	0x3CC

console_t _vc[MAX_VC];
static unsigned _num_vcs;
static console_t *_curr_vc;

static unsigned short *_vga_fb_adr;
static unsigned _crtc_io_adr, _vc_width, _vc_height;

unsigned curr_vtty;

/**
 * blink()
 *
 */
void blink(void)
{
	(*(unsigned char *)_vga_fb_adr)++;
}

/**
 * get_current_vc()
 *
 */
unsigned get_current_vc()
{
	return curr_vtty;
}

/**
 * scroll()
 *
 */
static void scroll(console_t *con)
{
	unsigned short *fb_adr;
	unsigned blank, temp;

	blank = 0x20 | ((unsigned)con->attrib << 8);
	fb_adr = con->fb_adr;

	/**
	 * scroll up
	 */
	if(con->csr_y >= _vc_height)
	{
		temp = con->csr_y - _vc_height + 1;
		memcpy(fb_adr, fb_adr + temp * _vc_width,
			(_vc_height - temp) * _vc_width * 2);

		/**
		 * blank bottom line of screen
		 */
		memsetw(fb_adr + (_vc_height - temp) * _vc_width,
			blank, _vc_width);
		con->csr_y = _vc_height - 1;
	}

	//for(i = 0; i < 0x1000000; i++) ;
}

/**
 * set_attrib()
 *
 */
static void set_attrib(console_t *con, unsigned att)
{
	static const unsigned ansi_to_vga[] =
	{
		0, 4, 2, 6, 1, 5, 3, 7
	};

	unsigned new_att;

	new_att = con->attrib;
	if(att == 0)
		new_att &= ~0x08;		/* bold off */
	else if(att == 1)
		new_att |= 0x08;		/* bold on */
	else if(att >= 30 && att <= 37)
	{
		att = ansi_to_vga[att - 30];
		new_att = (new_att & ~0x07) | att;/* fg color */
	}
	else if(att >= 40 && att <= 47)
	{
		att = ansi_to_vga[att - 40] << 4;
		new_att = (new_att & ~0x70) | att;/* bg color */
	}
	con->attrib = new_att;
}

/**
 * move_csr()
 *
 */
static void move_csr(void)
{
	unsigned temp;

	temp = (_curr_vc->csr_y * _vc_width + _curr_vc->csr_x) +
		(_curr_vc->fb_adr - _vga_fb_adr);
	outportb(_crtc_io_adr + 0, 14);
	outportb(_crtc_io_adr + 1, temp >> 8);
	outportb(_crtc_io_adr + 0, 15);
	outportb(_crtc_io_adr + 1, temp);
}


/**
 * select_vc()
 *
 */
void select_vc(unsigned which_vc)
{
	unsigned i;

	if(which_vc >= _num_vcs)
		return;
	_curr_vc = _vc + which_vc;
	i = _curr_vc->fb_adr - _vga_fb_adr;
	outportb(_crtc_io_adr + 0, 12);
	outportb(_crtc_io_adr + 1, i >> 8);
	outportb(_crtc_io_adr + 0, 13);
	outportb(_crtc_io_adr + 1, i);

	curr_vtty = which_vc;

	move_csr();
}

/**
 * putch_help()
 *
 */
void putch_help(console_t *con, unsigned c)
{
	unsigned short *fb_adr;
	unsigned att;

	att = (unsigned)con->attrib << 8;
	fb_adr = con->fb_adr;

	/**
	 * state machine to handle escape sequences
	 *
	 * ESC
	 */
	if(con->esc == 1)
	{
		if(c == '[')
		{
			con->esc++;
			con->esc1 = 0;
			return;
		}
		/* else fall-through: zero esc and print c */
	}

	/**
	 * ESC[
	 */
	else if(con->esc == 2)
	{
		if(isdigit(c))
		{
			con->esc1 = con->esc1 * 10 + c - '0';
			return;
		}
		else if(c == ';')
		{
			con->esc++;
			con->esc2 = 0;
			return;
		}

		/**
		 * ESC[2J (clear screen)
		 */
		else if(c == 'J')
		{
			if(con->esc1 == 2)
			{
				memsetw(fb_adr, ' ' | att,
					_vc_height * _vc_width);
				con->csr_x = con->csr_y = 0;
			}
		}

		/**
		 * ESC[num1m (set attribute num1)
		 */
		else if(c == 'm')
			set_attrib(con, con->esc1);
		con->esc = 0;	/* anything else with one numeric arg */
		return;
	}

	/**
	 * ESC[num1
	 */
	else if(con->esc == 3)
	{
		if(isdigit(c))
		{
			con->esc2 = con->esc2 * 10 + c - '0';
			return;
		}
		else if(c == ';')
		{
			con->esc++;	/* ESC[num1;num2; */
			con->esc3 = 0;
			return;
		}

		/**
		 * ESC[num1;num2H (move cursor to num1,num2)
		 */
		else if(c == 'H')
		{
			if(con->esc2 < _vc_width)
				con->csr_x = con->esc2;
			if(con->esc1 < _vc_height)
				con->csr_y = con->esc1;
		}

		/**
		 * ESC[num1;num2m (set attributes num1,num2)
		 */
		else if(c == 'm')
		{
			set_attrib(con, con->esc1);
			set_attrib(con, con->esc2);
		}
		con->esc = 0;
		return;
	}
	/**
	 * ESC[num1;num2;num3
	 */
	else if(con->esc == 4)
	{
		if(isdigit(c))
		{
			con->esc3 = con->esc3 * 10 + c - '0';
			return;
		}
		/**
		 * ESC[num1;num2;num3m (set attributes num1,num2,num3)
		 */
		else if(c == 'm')
		{
			set_attrib(con, con->esc1);
			set_attrib(con, con->esc2);
			set_attrib(con, con->esc3);
		}
		con->esc = 0;
		return;
	}
	con->esc = 0;

	/**
	 * escape character
	 */
	if(c == 0x1B)
	{
		con->esc = 1;
		return;
	}
	/**
	 * backspace
	 */
	if(c == 0x08)
	{
		if(con->csr_x != 0)
			con->csr_x--;
	}
	/**
	 * tab
	 */
	else if(c == 0x09)
		con->csr_x = (con->csr_x + 8) & ~(8 - 1);
	/**
	 * carriage return
	 */
	else if(c == '\r')	/* 0x0D */
		con->csr_x = 0;
	/**
	 * line feed
	 */
/*	else if(c == '\n')	*//* 0x0A */
/*		con->csr_y++;*/
	/**
	 * CR/LF
	 */
	else if(c == '\n')	/* ### - 0x0A again */
	{
		con->csr_x = 0;
		con->csr_y++;
	}
	/**
	 * printable ASCII
	 */
	else if(c >= ' ')
	{
		unsigned short *where;

		where = fb_adr + (con->csr_y * _vc_width + con->csr_x);
		*where = (c | att);
		con->csr_x++;
	}
	if(con->csr_x >= _vc_width)
	{
		con->csr_x = 0;
		con->csr_y++;
	}
	scroll(con);
	
	/**
	 * move cursor only if the VC we're writing is the current VC
	 */
	if(_curr_vc == con)
		move_csr();
}

/**
 * putch()
 *
 */
void putch(unsigned c)
{
/* all kernel messages to VC #0 */
//	putch_help(_vc + 0, c);
/* all kernel messages to current VC */
	putch_help(_curr_vc, c);
}

/**
 * init_video()
 *
 */
void init_video(void)
{
	unsigned i;

	/**
	 * check for monochrome or color VGA emulation
	 */
	if((inportb(VGA_MISC_READ) & 0x01) != 0)
	{
		_vga_fb_adr = (unsigned short *)0xB8000L;
		_crtc_io_adr = 0x3D4;
	}
	else
	{
		_vga_fb_adr = (unsigned short *)0xB0000L;
		_crtc_io_adr = 0x3B4;
	}

	/**
	 * read current screen size from BIOS data segment (addresses 400-4FF)
	 */
	_vc_width = *(unsigned short *)0x44A;
	_vc_height = *(unsigned char *)0x484 + 1;

	/**
	 * figure out how many VCs we can have with 32K of display memory.
	 * Use INTEGER division to round down.
	 */
	_num_vcs = 32768L / (_vc_width * _vc_height * 2);
	if(_num_vcs > MAX_VC)
		_num_vcs = MAX_VC;

	/**
	 * init VCs, with a different foreground color for each
	 */
	for(i = 0; i < _num_vcs; i++)
	{
		_curr_vc = _vc + i;
		//_curr_vc->attrib = i + 1;
		
		/* terminal foreground color */
		_curr_vc->attrib = 7;
		_curr_vc->fb_adr = _vga_fb_adr +
			_vc_width * _vc_height * i;

		/**
		 * ESC[2J clears the screen
		 */
		//kprintf("\x1B[2J  this is VC#%u (of 0-%u)\n",
		//	i, _num_vcs - 1);
		printf("\x1B[2J");

		if(i != 0)
			printf("$ ");
	}
	select_vc(0);
	curr_vtty = 0;

	_curr_vc->attrib = 8;
	printf("[ ");
	_curr_vc->attrib = 15;
	printf("init: video %5s emulation, %2ux%2u, framebuffer at 0x%1X ",
			(_crtc_io_adr == 0x3D4) ? "color" : "mono",
			_vc_width, _vc_height, _vga_fb_adr);
	_curr_vc->attrib = 8;
	printf("]................");
	_curr_vc->attrib = 2;
	printf("Ok");
	_curr_vc->attrib = 7;
}
