/*
Copyright (C) 2015-2019 The University of Notre Dame
This software is distributed under the GNU General Public License.
See the file LICENSE for details.
*/

#include "console.h"
#include "serial.h"
#include "graphics.h"
#include "kmalloc.h"
#include "string.h"

struct console {
	struct window *window;
	struct graphics *gx;
	int xsize;
	int ysize;
	int xpos;
	int ypos;
	int onoff;
	int refcount;
};

struct console console_root = {0};

static struct graphics_color bgcolor = { 0, 0, 0 };
static struct graphics_color fgcolor = { 255, 255, 255 };

struct console * console_create_root()
{
	console_root.window = window_create_root();
	console_root.gx = window_graphics(console_root.window);
	console_reset(&console_root);
	console_putstring(&console_root,"\nconsole: initialized\n");
	return &console_root;
}

void console_reset( struct console *d )
{
	if(!d || !d->gx) return;
	d->xpos = d->ypos = 0;
	d->xsize = graphics_width(d->gx) / 8;
	d->ysize = graphics_height(d->gx) / 8;
	d->onoff = 0;
	graphics_fgcolor(d->gx, fgcolor);
	graphics_bgcolor(d->gx, bgcolor);
	graphics_clear(d->gx, 0, 0, graphics_width(d->gx), graphics_height(d->gx));
}

void console_heartbeat( struct console *d )
{
	char c = d->onoff ? ' ' : '_';
	graphics_char(d->gx, d->xpos * 8, d->ypos * 8, c );
	d->onoff = !d->onoff;
}

int console_post( struct console *c, const char *data, int size )
{
	int total = 0;

	struct event e;
	e.type = EVENT_KEY_DOWN;
	e.x = 0;
	e.y = 0;

	while(size>0) {
		e.code = *data;
		window_post_events(c->window,&e,sizeof(e));
		size--;
		data++;
		total++;
	}

	return total;
}

int console_write( struct console *d, const char *data, int size )
{
	graphics_char(d->gx, d->xpos * 8, d->ypos * 8, ' ');

	int i;
	for(i = 0; i < size; i++) {
		char c = data[i];
		switch (c) {
		case 13:
		case 10:
			d->xpos = 0;
			d->ypos++;
			break;
		case '\f':
			d->xpos = d->ypos = 0;
			d->xsize = graphics_width(d->gx) / 8;
			d->ysize = graphics_height(d->gx) / 8;
			graphics_fgcolor(d->gx, fgcolor);
			graphics_bgcolor(d->gx, bgcolor);
			graphics_clear(d->gx, 0, 0, graphics_width(d->gx), graphics_height(d->gx));
			break;
		case '\b':
			d->xpos--;
			break;
		default:
			graphics_char(d->gx, d->xpos * 8, d->ypos * 8, c);
			d->xpos++;
			break;
		}

		if(d->xpos < 0) {
			d->xpos = d->xsize - 1;
			d->ypos--;
		}

		if(d->xpos >= d->xsize) {
			d->xpos = 0;
			d->ypos++;
		}

		if(d->ypos >= d->ysize) {
			d->xpos = d->ypos = 0;
			d->xsize = graphics_width(d->gx) / 8;
			d->ysize = graphics_height(d->gx) / 8;
			graphics_fgcolor(d->gx, fgcolor);
			graphics_bgcolor(d->gx, bgcolor);
			graphics_clear(d->gx, 0, 0, graphics_width(d->gx), graphics_height(d->gx));
		}

	}
	graphics_char(d->gx, d->xpos * 8, d->ypos * 8, '_');
	return i;
}

int console_read( struct console *c, char *data, int length )
{
	int total=0;

	struct event e;
	while(length>0 && window_read_events(c->window,&e,sizeof(e))) {
		if(e.type==EVENT_KEY_DOWN) {
			*data = e.code;
			length--;
			total++;
			data++;
		}
	}

	return total;
}

int console_read_nonblock( struct console *c, char *data, int length )
{
	int total=0;

	struct event e;
	while(length>0 && window_read_events_nonblock(c->window,&e,sizeof(e))) {
		if(e.type==EVENT_KEY_DOWN) {
			*data = e.code;
			length--;
			total++;
			data++;
		}
	}

	return total;
}

int console_getchar( struct console *c )
{
	char ch;
	if(console_read(c,&ch,1)>0) {
		return ch;
	} else {
		return 0;
	}
}

void console_putchar( struct console *c, char ch )
{
	console_write(c,&ch,1);
}

void console_putstring( struct console *c, const char *str)
{
	console_write(c,str,strlen(str));
}

struct console *console_create( struct window *w )
{
	struct console *c = kmalloc(sizeof(*c));
	c->window = window_addref(w);
	c->gx = window_graphics(w);
	c->refcount = 1;
	console_reset(c);
	return c;
}

struct console *console_addref( struct console *c )
{
	c->refcount++;
	return c;
}

void console_delete( struct console *c )
{
	if(c==&console_root) return;

	c->refcount--;
	if(c->refcount==0) {
		window_delete(c->window);
		kfree(c);
	}
}

void console_size( struct console *c, int *xsize, int *ysize )
{
	*xsize = c->xsize;
	*ysize = c->ysize;
}