//console.c

#include <stdio.h>
#include <stdarg.h>
#include "ds2io.h"
#include "memory.h"
#include "font_dot.h"

#define STRING_SIZE		2048

#define CONSOLE_WIDTH	32
#define CONSOLE_HEIGHT	24
#define TAB_SIZE		3

static void ConsoleView(void);
static void ConsoleDrawfontall(void);

static int console_init_done = 0;
static unsigned short f_color;
static unsigned short b_color;
static enum SCREEN_ID console_id;

static int print_row;
static int print_col;
static int print_row_saved;
static int print_col_saved;

static unsigned char*	console_buf;
static unsigned int		console_buf_size;
static unsigned char*	console_buf_front;
static unsigned char*	console_buf_end;
static unsigned char*	console_print_header;
static unsigned short*	console_screen;
static unsigned char*	print_header_saved;

static void ConsoleFlush(void)
{
	unsigned short* screen_addr;
	enum SCREEN_ID id;

	if(console_id & UP_MASK) {
		screen_addr = up_screen_addr;
		id = UP_SCREEN;
	}
	else {
		screen_addr = down_screen_addr;
		id = DOWN_SCREEN;
	}

	memcpy((void*)screen_addr, (void*)console_screen, SCREEN_WIDTH*SCREEN_HEIGHT*2);
	ds2_flipScreen(id, 1);
}

static void ConsoleClearscreen(void)
{
	unsigned short *scr;
	unsigned int i;

	scr = console_screen;
	i = 0;
	while(i < SCREEN_WIDTH*SCREEN_HEIGHT)
		scr[i++] = b_color;
}

static void ConsoleMovewin(int dir, int sw_screen)
{
	unsigned char *pt;
	
	if(dir || sw_screen)
		ConsoleClearscreen();

	//switch to another screen to dispaly text
	if(sw_screen)
	{
		ConsoleFlush();
		//now up screen
		if(console_id & UP_MASK)	{
			console_screen = down_screen_addr;
			console_id = DOWN_SCREEN;
		}
		//switch to up screen
		else
		{
			console_screen = up_screen_addr;
			console_id = UP_SCREEN;
		}
	}

	pt = console_print_header + dir*CONSOLE_WIDTH;

	//screen scroll down
	if(dir > 0)
	{
		if(console_buf_end > console_print_header) {
			if(pt > console_buf_end)
				pt = console_buf_end;
			console_print_header = pt;
		}
		else if(console_buf_end < console_print_header)	{
			if((pt - console_buf) >= console_buf_size) {
				pt -= console_buf_size;
				if(pt > console_buf_end)
					pt = console_buf_end;
			}
			console_print_header = pt;
		}
	}
	//screen scroll up
	else if(dir < 0)
	{
		if(console_buf_front > console_print_header) {
			if(pt < console_buf) {
				pt += console_buf_size;
				if(pt < console_buf_front)
					pt = console_buf_front;
			}
			console_print_header = pt;
		}
		else if(console_buf_front < console_print_header) {
			if(pt < console_buf_front)
				pt = console_buf_front;
			console_print_header = pt;
		}
	}

	if(dir || sw_screen)
	{
		print_row_saved = 0;	//redraw entire screen
		print_col_saved = 0;

		ConsoleDrawfontall();
		ConsoleFlush();
	}
}

void ConsoleClr(int mode)
{
	unsigned char *pt, *pt_end;
	unsigned int i;

	//Clear current screen buffer
	if(0 == mode)
	{
		if(print_col > 0) {
			console_buf_end += CONSOLE_WIDTH;
			if((console_buf_end - console_buf) >= console_buf_size)
				console_buf_end -= console_buf_size;
		}

		console_print_header = console_buf_end;
		print_row = 0;
		print_col = 0;
		print_row_saved = 0;
		print_col_saved = 0;
	}
	//Clear all
	else if(1 == mode)
	{
		console_buf_front = console_buf;
		console_buf_end = console_buf;
		console_print_header = console_buf;
		print_row = 0;
		print_col = 0;
		print_row_saved = 0;
		print_col_saved = 0;

		pt = console_buf;
		pt_end = console_buf + console_buf_size;
		while(pt < pt_end)
		{
			pt[0] = '\0';
			pt += CONSOLE_WIDTH;
		}
	}

	ConsoleClearscreen();
	ConsoleFlush();
}

//Draw part of the screen
static void ConsoleDrawfont(void)
{
	unsigned char *pt, *dot_map;
	unsigned char ch, dot;
	unsigned short *dst, *pt_r;
	unsigned int row, col;
	unsigned int x, j, k;

	pt_r = console_screen;
	row = print_row_saved;
	col = print_col_saved;
	x = col*8;

	pt = console_print_header + row * CONSOLE_WIDTH;

	while(row != print_row || col != print_col)
	{
		ch = pt[col++] & 0x7F;
		//'\n'
		if('\n' == ch || '\0' == ch || col > CONSOLE_WIDTH) {
			pt += CONSOLE_WIDTH;
			if((pt - console_buf) >= console_buf_size) pt -= console_buf_size;
			col = 0;
			row += 1;
			x = 0;
		}
		//character not '\n' nor '\0'
		else {
			dot_map = (unsigned char*)font_map[ch];

			for(j= 0; j < 8; j++)
			{
				dot = *dot_map++;
				dst = pt_r + (row*8+j)*SCREEN_WIDTH + x;
				for(k = 0; k < 8; k++)
					*dst++ = (dot & (0x80>>k)) ? f_color : b_color;
			}
			x += 8;
		}
	}
}

//Redraw the hole screen
static void ConsoleDrawfontall(void)
{
	unsigned char *pt, *end_pt, *dot_map;
	unsigned int i, j, k;
	unsigned char ch, dot;
	unsigned short *dst, *pt_r;
	unsigned int x, y;

	//Clear screen to b_color
	pt_r = console_screen;
	i = 0;
	while(i < SCREEN_WIDTH*SCREEN_HEIGHT)
		pt_r[i++] = b_color;

	pt = console_print_header;
	end_pt = console_buf_end;
	x = 0;
	y = 0;
	i = 0;
	while(pt != end_pt)
	{
		ch = pt[i++] & 0x7F;
		//'\n'
		if('\n' == ch || '\0' == ch || i > CONSOLE_WIDTH) {
			pt += CONSOLE_WIDTH;
			if((pt - console_buf) >= console_buf_size) pt -= console_buf_size;
			i = 0;
			x = 0;
			y += 1;
			if(y >= CONSOLE_HEIGHT) break;
		}
		//character not '\n' nor '\0'
		else {
			dot_map = (unsigned char*)font_map[ch];

			for(j= 0; j < 8; j++)
			{
				dot = *dot_map++;
				dst = pt_r + (y*8+j)*SCREEN_WIDTH + x;
				for(k = 0; k < 8; k++)
					*dst++ = (dot & (0x80>>k)) ? f_color : b_color;
			}
			x += 8;
		}
	}
}

static void ConsoleNewline(void)
{
	print_row += 1;
	if(print_row >= CONSOLE_HEIGHT)
	{
		print_row -= 1;
		console_print_header += CONSOLE_WIDTH;
		if((console_print_header - console_buf) >= console_buf_size)
			console_print_header = console_buf;

		print_row_saved = 0;
		print_col_saved = 0;

		ConsoleClearscreen();
	}

	console_buf_end += CONSOLE_WIDTH;
	if((console_buf_end - console_buf) >= console_buf_size)
		console_buf_end = console_buf;

	//scrollback
	if(console_buf_end == console_buf_front)
	{
		console_buf_front += CONSOLE_WIDTH;
		if((console_buf_front - console_buf) >= console_buf_size)
			console_buf_front = console_buf;

		console_buf_end[0] = '\0';
	}
}

static void ConsolePrintchar(unsigned char ch)
{
	int i;

	if(print_col >= CONSOLE_WIDTH) {
		print_col = 0;
		ConsoleNewline();
	}

	switch(ch) {
		case 9:									//'\t'
			if((print_col + TAB_SIZE) < CONSOLE_WIDTH)
			{
				i = print_col % TAB_SIZE;
				i = TAB_SIZE - i;
				while(i--)
				{
					console_buf_end[print_col] = ' ';
					print_col += 1;
				}
			}
			break;
		case 10:								//'\n'
		case 13:								//'\r'
			console_buf_end[print_col] = '\n';
			print_col = 0;
			ConsoleNewline();
			break;
		default:
			console_buf_end[print_col] = ch;
			if(ch != '\0')
				print_col += 1;
			break;
	}
}

void ConsolePrintstring(unsigned char* string)
{
	unsigned char *pt;
	unsigned char ch;

	print_row_saved = print_row;
	print_col_saved = print_col;
	console_print_header = print_header_saved;
//cprintf("print_row %d; print_col %d; [%s]\n", print_row, print_col, string);
	pt = string;
	do
	{
		ch = *pt++;
		ConsolePrintchar(ch);
	}
	while ('\0' != ch);

	print_header_saved = console_print_header;

	ConsoleDrawfont();
	ConsoleFlush();
}

//---------------------------------------------------------------------------------
//parameter:
//	front_color: color of character
//	background_color: background color
//	screen: UP-up screen used as output, DOWN-down screen used as output, 
//			using only one screen at a time
//	buf_size: buffer size to hold the output, it's unit is screen
//---------------------------------------------------------------------------------
int ConsoleInit(unsigned short front_color, unsigned short background_color, enum SCREEN_ID screen, unsigned int buf_size)
{
	unsigned char *pt;
	unsigned int size;
	unsigned short **scr_ppt;

	console_init_done = 0;
	f_color = front_color;
	b_color = background_color;


	//Using only one screen at a time
	if(screen & UP_MASK)
		console_id = UP_SCREEN;
	else
		console_id = DOWN_SCREEN;

	if(!buf_size) buf_size = 1;
	size = buf_size *CONSOLE_WIDTH *CONSOLE_HEIGHT;

	pt = (unsigned char*)Drv_alloc(size);
	if(NULL == pt)
		return -1;							//there is something error

	console_buf = pt;
	memset(console_buf, 0, size);

	print_row = 0;
	print_col = 0;
	print_row_saved = print_row;
	print_col_saved = print_col;
	console_buf_size = size;
	console_buf_front = console_buf;
	console_buf_end = console_buf;
	console_print_header = console_buf;
	print_header_saved = console_print_header;

	console_screen = (unsigned short*)Drv_alloc(SCREEN_WIDTH * SCREEN_HEIGHT*2);
	if(NULL == console_screen) {
		Drv_deAlloc((void*)console_buf);
		return -1;
	}

	ConsoleClr(1);

	console_init_done = 1;

	regist_escape_key(ConsoleView, (KEY_L | KEY_R | KEY_A | KEY_B | KEY_X));
	return 0;
}

void ConsoleView(void)
{
	unsigned int key;

cprintf("enter console mode\n");

	do {
		key = getKey();

		switch(key)
		{
			//screen scroll down 1 line
			case KEY_UP:
					ConsoleMovewin(-1, 0);
				break;

			//screen scroll up 1 line
			case KEY_DOWN:
					ConsoleMovewin(1, 0);
				break;

			//screen scroll done (CONSOLE_HEIGHT-1) line
			case KEY_LEFT:
					ConsoleMovewin(-(CONSOLE_HEIGHT-1), 0);
				break;

			//screen scroll up (CONSOLE_HEIGHT-1) line
			case KEY_RIGHT:
					ConsoleMovewin(CONSOLE_HEIGHT-1, 0);
				break;

			//switch screen
			case KEY_B:
cprintf("switch to another screen\n");
					ConsoleMovewin(0, 1);
				break;

			default: break;
		}

		mdelay(20);
	} while(key != KEY_Y);

cprintf("exit console mode\n");
}

int printf(const char *format, ...)
{
	char string[STRING_SIZE];
	int ret;
	va_list ap;

	if(!console_init_done)
		return 0;

	va_start (ap, format);
	ret = vsnprintf(string, STRING_SIZE, format, ap);
	va_end (ap);

	ConsolePrintstring(string);

	return ret;
}


