#include <curses.h>
#include <malloc.h>
#include <stddef.h>
#include <vsprintf.h>
#include <string.h>

/** @file
 *
 * MuCurses: lightweight xcurses implementation for PXE ROMs
 *
 */

#define WRAP 0
#define NOWRAP 1

unsigned short _COLS;
unsigned short _LINES;
unsigned short _COLOURS;
unsigned int *_COLOUR_PAIRS; /* basically this is an array, but as its
			       length is determined only when initscr
			       is run, I can only think to make it a
			       pointer and malloc the array into being
			       ... */

struct cursor_pos {
	unsigned int y, x;
};

struct _softlabel {
	/* Format of soft label 
	   0: left justify
	   1: centre justify
	   2: right justify
	 */
	int fmt;
	// label string
	char *label;
};

struct _softlabelkeys {
	struct _softlabel fkeys[12];
	attr_t attrs;
	unsigned int fmt;
	unsigned int maxlablen;
};

struct _softlabelkeys *slks;

WINDOW _stdscr = {
	.attrs = A_DEFAULT,
	.ori_y = 0,
	.ori_x = 0,
	.curs_y = 0,
	.curs_x = 0,
	.scr = curscr,
};

/*
 *  Primitives
 */

/**
 * Write a single character rendition to a window
 *
 * @v *win	window in which to write
 * @v ch	character rendition to write
 * @v wrap	wrap "switch"
 */
static void _wputch ( WINDOW *win, chtype ch, int wrap ) {
	/* make sure we set the screen cursor to the right position
	   first! */
	win->scr->movetoyx( win->scr, win->ori_y + win->curs_y,
				      win->ori_x + win->curs_x );
	win->scr->putc(win->scr, ch);
	if ( ++(win->curs_x) == win->width ) {
		if ( wrap == WRAP ) {
			win->curs_x = 0;
			/* specification says we should really scroll,
			   but we have no buffer to scroll with, so we
			   can only overwrite back at the beginning of
			   the window */
			if ( ++(win->curs_y) == win->height )
				win->curs_y = 0;
		} else {
			(win->curs_x)--;
		}
	}
}

/**
 * Write a chtype string to a window
 *
 * @v *win	window in which to write
 * @v *chstr	chtype string
 * @v wrap	wrap "switch"
 * @v n		write at most n chtypes
 */
static void _wputchstr ( WINDOW *win, const chtype *chstr, int wrap, int n ) {
	for ( ; *chstr && n-- ; chstr++ ) {
		_wputch(win,*chstr,wrap);
	}
}

/**
 * Write a standard c-style string to a window
 *
 * @v *win	window in which to write
 * @v *str	string
 * @v wrap	wrap "switch"
 * @v n		write at most n chars from *str
 */
static void _wputstr ( WINDOW *win, const char *str, int wrap, int n ) {
	for ( ; *str && n-- ; str++ ) {
		_wputch( win, *str | win->attrs, wrap );
	}
}

/**
 * Restore cursor position from encoded backup variable
 *
 * @v *win	window on which to operate
 * @v *pos	pointer to struct in which original cursor position is stored
 */
static void _restore_curs_pos ( WINDOW *win, struct cursor_pos *pos ){
	win->curs_y = pos->y;
	win->curs_x = pos->x;
	win->scr->movetoyx ( win->scr, win->curs_y, win->curs_x );
}

/**
 * Store cursor position for later restoration
 *
 * @v *win	window on which to operate
 * @v *pos	pointer to struct in which to store cursor position
 */
static void _store_curs_pos ( WINDOW *win, struct cursor_pos *pos ) {
	pos->y = win->curs_y;
	pos->x = win->curs_x;
}

/**
 * Move a window's cursor to the specified position
 *
 * @v *win	window to be operated on
 * @v y		Y position
 * @v x		X position
 * @ret rc	return status code
 */
int wmove ( WINDOW *win, int y, int x ) {
	/* chech for out-of-bounds errors */
	if ( ( ( (unsigned)x - win->ori_x ) > win->width ) ||
	     ( ( (unsigned)y - win->ori_y ) > win->height ) ) {
		return ERR;
	}

	win->curs_y = y;
	win->curs_x = x;
	win->scr->movetoyx( win->scr, win->ori_y + win->curs_y, 
			    	      win->ori_x + win->curs_x );
	return OK;
}


/**
 * get terminal baud rate
 *
 * @ret bps	return baud rate in bits per second
 */
int baudrate ( void ) {
	return OK;
}

/**
 * Audible (or visual) signal
 *
 * @ret rc	return status code
 */
int beep ( void ) {
	printf("\a");
	return OK;
}

/**
 * Draw borders from single-byte characters and renditions around a
 * window
 *
 * @v *win	window to be bordered
 * @v verch	vertical chtype
 * @v horch	horizontal chtype
 * @ret rc	return status code
 */
int box ( WINDOW *win, chtype verch, chtype horch ) {
	int corner = '+' | win->attrs; /* default corner character */
	return wborder( win, verch, verch, horch, horch,
			corner, corner, corner, corner );
}

/**
 * Indicates whether the underlying terminal device is capable of
 * having colours redefined
 *
 * @ret bool	returns boolean
 */
bool can_change_colour ( void ) {
	return (bool)TRUE;
}

/**
 * Identify the RGB components of a given colour value
 *
 * @v colour	colour value
 * @v *red	address to store red component
 * @v *green	address to store green component
 * @v *blue	address to store blue component
 * @ret rc	return status code
 */
int colour_content ( short colour, short *red, short *green, short *blue ) {
	/* we do not have a particularly large range of colours (3
	   primary, 3 secondary and black), so let's just put in a
	   basic switch... */
	switch(colour) {
	case COLOUR_BLACK:
		*red = 0; *green = 0; *blue = 0;
		break;
	case COLOUR_BLUE:
		*red = 0; *green = 0; *blue = 1000;
		break;
	case COLOUR_GREEN:
		*red = 0; *green = 1000; *blue = 0;
		break;
	case COLOUR_CYAN:
		*red = 0; *green = 1000; *blue = 1000;
		break;
	case COLOUR_RED:
		*red = 1000; *green = 0; *blue = 0;
		break;
	case COLOUR_MAGENTA:
		*red = 1000; *green = 0; *blue = 1000;
		break;
	case COLOUR_YELLOW:
		*red = 1000; *green = 1000; *blue = 0;
		break;
	}
	return OK;
}

/**
 * Delete a window
 *
 * @v *win	pointer to window being deleted
 * @ret rc	return status code
 */
int delwin ( WINDOW *win ) {
	if ( win == NULL )
		goto err;
	/* must free descendants first, but I haven't implemented descendants yet
	   ... */
	free(win);
	return OK;
 err:
	return ERR;
}

/**
 * Get the background rendition attributes for a window
 *
 * @v *win	subject window
 * @ret ch	chtype rendition representation
 */
inline chtype getbkgd ( WINDOW *win ) {
	return win->attrs;
}

/**
 * Initialise console environment
 *
 * @ret *win	return pointer to stdscr
 */
WINDOW *initscr ( void ) {
	/* determine console size */
	/* initialise screen */
	stdscr->width = 80;
	stdscr->height = 25;
	/* set previously unknown window attributes */
	/* refresh screen */
	return stdscr;
}

/**
 * Create new WINDOW
 *
 * @v nlines	number of lines
 * @v ncols	number of columns
 * @v begin_y	column origin
 * @v begin_x	line origin
 * @ret *win	return pointer to new window
 */
WINDOW *newwin ( int nlines, int ncols, int begin_y, int begin_x ) {
	WINDOW *win = calloc( 1, sizeof(WINDOW) );
	win->ori_y = begin_y;
	win->ori_x = begin_x;
	win->height = nlines;
	win->width = ncols;
	win->scr = stdscr->scr;
	win->parent = NULL;
	win->child = NULL;
	return win;
}

/**
 * Return the attribute used for the soft function keys
 *
 * @ret attrs	the current attributes of the soft function keys
 */
attr_t slk_attr ( void ) {
	return ( slks == NULL ? 0 : slks->attrs );
}

/**
 * Turn off soft function key attributes
 *
 * @v attrs	attribute bit mask
 * @ret rc	return status code
 */
int slk_attroff ( const chtype attrs ) {
	if ( slks == NULL ) 
		return ERR;
	slks->attrs &= ~( attrs & A_ATTRIBUTES );
	return OK;
}

/**
 * Turn on soft function key attributes
 *
 * @v attrs	attribute bit mask
 * @ret rc	return status code
 */
int slk_attron ( const chtype attrs ) {
	if ( slks == NULL )
		return ERR;
	slks->attrs |= ( attrs & A_ATTRIBUTES );
	return OK;
}

/**
 * Set soft function key attributes
 *
 * @v attrs	attribute bit mask
 * @ret rc	return status code
 */
int slk_attrset ( const chtype attrs ) {
	if ( slks == NULL ) 
		return ERR;
	slks->attrs = ( attrs & A_ATTRIBUTES );
	return OK;
}

/**
 * Turn off soft function key attributes
 *
 * @v attrs	attribute bit mask
 * @v *opts	undefined (for future implementation)
 * @ret rc	return status code
 */
int slk_attr_off ( const attr_t attrs, void *opts __unused ) {
	return slk_attroff( attrs );
}

/**
 * Turn on soft function key attributes
 *
 * @v attrs	attribute bit mask
 * @v *opts	undefined (for future implementation)
 * @ret rc	return status code
 */
int slk_attr_on ( attr_t attrs, void *opts __unused ) {
	return slk_attron( attrs );
}

/**
 * Set soft function key attributes
 *
 * @v attrs			attribute bit mask
 * @v colour_pair_number	colour pair integer
 * @v *opts			undefined (for future implementation)
 * @ret rc			return status code
 */
int slk_attr_set ( const attr_t attrs, short colour_pair_number,
		   void *opts __unused ) {
	if ( slks == NULL ) 
		return ERR;

	if ( ( unsigned short )colour_pair_number > COLORS )
		return ERR;

	slks->attrs = ( (unsigned short)colour_pair_number << CPAIR_SHIFT ) |
		( attrs & A_ATTRIBUTES );
	return OK;
}

/**
 * Clear the soft function key labels from the screen
 *
 * @ret rc	return status code
 */
int slk_clear ( void ) {
	if ( slks == NULL )
		return ERR;

	wmove(stdscr,stdscr->height-1,0);
	wclrtoeol(stdscr);
	return 0;
}

/**
 * Initialise the soft function keys
 *
 * @v fmt	format of keys
 * @ret rc	return status code
 */
int slk_init ( int fmt ) {
	if ( (unsigned)fmt > 3 ) {
		return ERR;
	}

	slks = malloc(sizeof(struct _softlabelkeys));
	slks->attrs = A_DEFAULT;
	slks->fmt = fmt;
	slks->maxlablen = 5;
	return OK;
}

/**
 * Return the label for the specified soft key
 *
 * @v labnum	soft key identifier
 * @ret label	return label
 */
char* slk_label ( int labnum ) {
	if ( slks == NULL ) 
		return NULL;

	return slks->fkeys[labnum].label;
}

/**
 * Restore soft function key labels to the screen
 *
 * @ret rc	return status code
 */
int slk_restore ( void ) {
	if ( slks == NULL ) 
		return ERR;

	return OK;
}

/**
 * Configure specified soft key
 *
 * @v labnum	soft label position to configure
 * @v *label	string to use as soft key label
 * @v fmt	justification format of label
 * @ret rc	return status code
 */
int slk_set ( int labnum, const char *label, int fmt ) {
	if ( slks == NULL ) 
		return ERR;
	if ( labnum == 0 || (unsigned)labnum > 12 )
		return ERR;
	if ( (unsigned)fmt >= 3 )
		return ERR;
	if ( strlen(label) > slks->maxlablen )
		return ERR;

	strcpy( slks->fkeys[labnum].label, label );
	slks->fkeys[labnum].fmt = fmt;

	return OK;
}

struct printw_context {
	struct printf_context ctx;
	WINDOW *win;
};

static void _printw_handler ( struct printf_context *ctx, unsigned int c ) {
	struct printw_context *wctx =
		container_of ( ctx, struct printw_context, ctx );

	_wputch( wctx->win, c | wctx->win->attrs, WRAP );
}

/**
 * Print formatted output in a window
 *
 * @v *win	subject window
 * @v *fmt	formatted string
 * @v varglist	argument list
 * @ret rc	return status code
 */
int vw_printw ( WINDOW *win, const char *fmt, va_list varglist ) {
	struct printw_context wctx = {
		.win = win,
		.ctx = { .handler = _printw_handler, },
	};

	vcprintf ( &(wctx.ctx), fmt, varglist );
	return OK;
}

/**
 * Add a single-byte character and rendition to a window and advance
 * the cursor
 *
 * @v *win	window to be rendered in
 * @v ch	character to be added at cursor
 * @ret rc	return status code
 */
int waddch ( WINDOW *win, const chtype ch ) {
	_wputch( win, ch, WRAP );
	return OK;
}

/**
 * Add string of single-byte characters and renditions to a window
 *
 * @v *win	window to be rendered in
 * @v *chstr	pointer to first chtype in "string"
 * @v n		max number of chars from chstr to render
 * @ret rc	return status code
 */
int waddchnstr ( WINDOW *win, const chtype *chstr, int n ) {
	struct cursor_pos pos;	

	_store_curs_pos( win, &pos );
	_wputchstr( win, chstr, NOWRAP, n );
	_restore_curs_pos( win, &pos );
	return OK;
}

/**
 * Add string of single-byte characters to a window
 *
 * @v *win	window to be rendered in
 * @v *str	standard c-style string
 * @v n		max number of chars from string to render
 * @ret rc	return status code
 */
int waddnstr ( WINDOW *win, const char *str, int n ) {
	_wputstr( win, str, WRAP, n );
	return OK;
}

/**
 * Turn off attributes in a window
 *
 * @v win	subject window
 * @v attrs	attributes to enable
 * @ret rc	return status code
 */
int wattroff ( WINDOW *win, int attrs ) {
	win->attrs &= ~attrs;
	return OK;
}

/**
 * Turn on attributes in a window
 *
 * @v win	subject window
 * @v attrs	attributes to enable
 * @ret rc	return status code
 */
int wattron ( WINDOW *win, int attrs ) {
	win->attrs |= attrs;
	return OK;
}

/**
 * Set attributes in a window
 *
 * @v win	subject window
 * @v attrs	attributes to enable
 * @ret rc	return status code
 */
int wattrset ( WINDOW *win, int attrs ) {
	win->attrs = ( attrs | ( win->attrs & A_COLOR ) );
	return OK;
}

/**
 * Get attributes and colour pair information
 *
 * @v *win	window to obtain information from
 * @v *attrs	address in which to store attributes
 * @v *pair	address in which to store colour pair
 * @v *opts	undefined (for future implementation)
 * @ret rc	return status cude
 */
int wattr_get ( WINDOW *win, attr_t *attrs, short *pair, 
		void *opts __unused ) {
	*attrs = win->attrs & A_ATTRIBUTES;
	*pair = (short)(( win->attrs & A_COLOR ) >> CPAIR_SHIFT);
	return OK;
}

/**
 * Turn off attributes in a window
 *
 * @v *win	subject window
 * @v attrs	attributes to toggle
 * @v *opts	undefined (for future implementation)
 * @ret rc	return status code
 */
int wattr_off ( WINDOW *win, attr_t attrs, 
		void *opts __unused ) {
	wattroff( win, attrs );
	return OK;
}

/**
 * Turn on attributes in a window
 *
 * @v *win	subject window
 * @v attrs	attributes to toggle
 * @v *opts	undefined (for future implementation)
 * @ret rc	return status code
 */
int wattr_on ( WINDOW *win, attr_t attrs, 
	       void *opts __unused ) {
	wattron( win, attrs );
	return OK;
}

/**
 * Set attributes and colour pair information in a window
 *
 * @v *win	subject window
 * @v attrs	attributes to set
 * @v cpair	colour pair to set
 * @v *opts	undefined (for future implementation)
 * @ret rc	return status code
 */
int wattr_set ( WINDOW *win, attr_t attrs, short cpair, 
		void *opts __unused ) {
	wattrset( win, attrs | ( ( (unsigned short)cpair ) << CPAIR_SHIFT ) );
	return OK;
}

/**
 * Draw borders from single-byte characters and renditions around a
 * window
 *
 * @v *win	window to be bordered
 * @v ls	left side
 * @v rs	right side
 * @v ts	top
 * @v bs	bottom
 * @v tl	top left corner
 * @v tr	top right corner
 * @v bl	bottom left corner
 * @v br	bottom right corner
 * @ret rc	return status code
 */
int wborder ( WINDOW *win, chtype ls, chtype rs,
	      chtype ts, chtype bs, chtype tl,
	      chtype tr, chtype bl, chtype br ) {

	wmove(win,0,0);

	_wputch(win,tl,WRAP);
	while ( ( win->width - 1 ) - win->curs_x ) {
		_wputch(win,ts,WRAP);
	}
	_wputch(win,tr,WRAP);

	while ( ( win->height - 1 ) - win->curs_y ) {
		_wputch(win,ls,WRAP);
		wmove(win,win->curs_y,(win->width)-1);
		_wputch(win,rs,WRAP);
	}

	_wputch(win,bl,WRAP);
	while ( ( win->width -1 ) - win->curs_x ) {
		_wputch(win,bs,WRAP);
	}
	_wputch(win,br,NOWRAP); /* do not wrap last char to leave
				   cursor in last position */

	return OK;
}

/**
 * Clear a window to the bottom
 *
 * @v *win	subject window
 * @ret rc	return status code
 */
int wclrtobot ( WINDOW *win ) {
	struct cursor_pos pos;

	_store_curs_pos( win, &pos );
	do {
		_wputch( win, (unsigned)' ', WRAP );
	} while ( win->curs_y + win->curs_x );
	_restore_curs_pos( win, &pos );

	return OK;
}

/**
 * Clear a window to the end of the current line
 *
 * @v *win	subject window
 * @ret rc	return status code
 */
int wclrtoeol ( WINDOW *win ) {
	struct cursor_pos pos;

	_store_curs_pos( win, &pos );
	while ( ( win->curs_y - pos.y ) == 0 ) {
		_wputch( win, (unsigned)' ', WRAP );
	}
	_restore_curs_pos( win, &pos );

	return OK;
}

/**
 * Set colour pair for a window
 *
 * @v *win			subject window
 * @v colour_pair_number	colour pair integer
 * @v *opts			undefined (for future implementation)
 * @ret rc			return status code
 */
int wcolour_set ( WINDOW *win, short colour_pair_number, 
		  void *opts __unused ) {
	if ( ( unsigned short )colour_pair_number > COLORS )
		return ERR;

	win->attrs = ( (unsigned short)colour_pair_number << CPAIR_SHIFT ) |
		( win->attrs & A_ATTRIBUTES );
	return OK;
}

/**
 * Delete character under the cursor in a window
 *
 * @v *win	subject window
 * @ret rc	return status code
 */
int wdelch ( WINDOW *win ) {
	struct cursor_pos pos;

	_store_curs_pos( win, &pos );
	_wputch( win, (unsigned)' ', NOWRAP );
	_restore_curs_pos( win, &pos );

	return OK;
}

/**
 * Delete line under a window's cursor
 *
 * @v *win	subject window
 * @ret rc	return status code
 */
int wdeleteln ( WINDOW *win ) {
	/* let's just set the cursor to the beginning of the line and
	   let wclrtoeol do the work :) */
	wmove( win, win->curs_y, 0 );
	wclrtoeol( win );
	return OK;
}

/**
 * Create a horizontal line in a window
 *
 * @v *win	subject window
 * @v ch	rendition and character
 * @v n		max number of chars (wide) to render
 * @ret rc	return status code
 */
int whline ( WINDOW *win, chtype ch, int n ) {
	struct cursor_pos pos;

	_store_curs_pos ( win, &pos );
	while ( ( win->curs_x - win->width ) && n-- ) {
		_wputch ( win, ch, NOWRAP );
	}
	_restore_curs_pos ( win, &pos );

	return OK;
}

/**
 * Print formatted output to a window
 *
 * @v *win	subject window
 * @v *fmt	formatted string
 * @v ...	string arguments
 * @ret rc	return status code
 */
int wprintw ( WINDOW *win, const char *fmt, ... ) {
	va_list args;
	int i;

	va_start ( args, fmt );
	i = vw_printw ( win, fmt, args );
	va_end ( args );
	return i;
}

/**
 * Create a vertical line in a window
 *
 * @v *win	subject window
 * @v ch	rendition and character
 * @v n		max number of lines to render
 * @ret rc	return status code
 */
int wvline ( WINDOW *win, chtype ch, int n ) {
	struct cursor_pos pos;

	_store_curs_pos ( win, &pos );
	while ( ( win->curs_y - win->height ) && n-- ) {
		_wputch ( win, ch, NOWRAP );
		wmove( win, ++(win->curs_y), pos.x);
	}
	_restore_curs_pos ( win, &pos );

	return OK;
}