/* $Id: bitmap_con.c,v 1.52 2015/07/07 04:26:44 deuce Exp $ */

#include <stdarg.h>
#include <stdio.h>		/* NULL */
#include <stdlib.h>
#include <string.h>

#include "threadwrap.h"
#include "semwrap.h"
#include "gen_defs.h"
#include "genwrap.h"
#include "dirwrap.h"
#include "xpbeep.h"

#if (defined CIOLIB_IMPORTS)
 #undef CIOLIB_IMPORTS
#endif
#if (defined CIOLIB_EXPORTS)
 #undef CIOLIB_EXPORTS
#endif

#include "ciolib.h"
#include "vidmodes.h"
#include "bitmap_con.h"

static char *screen=NULL;
int screenwidth=0;
int screenheight=0;
#define PIXEL_OFFSET(x,y)	( (y)*screenwidth+(x) )

static int default_font=-99;
static int current_font[4]={-99, -99, -99, -99};
static int bitmap_initialized=0;
pthread_mutex_t vmem_lock;
struct video_stats vstat;

struct bitmap_callbacks {
	void	(*drawrect)		(int xpos, int ypos, int width, int height, unsigned char *data);
	void	(*flush)		(void);
};

pthread_mutex_t		vstatlock;
pthread_mutex_t		screenlock;
static struct bitmap_callbacks callbacks;
static unsigned char *font[4];
static unsigned char space=' ';
int force_redraws=0;

struct rectangle {
	int x;
	int y;
	int width;
	int height;
};

static int update_rect(int sx, int sy, int width, int height, int force);

static __inline void *locked_screen_check(void)
{
	void *ret;
	pthread_mutex_lock(&screenlock);
	ret=screen;
	pthread_mutex_unlock(&screenlock);
	return(ret);
}

static struct vstat_vmem *lock_vmem(struct video_stats *vs)
{
	struct vstat_vmem *ret;
	pthread_mutex_lock(&vstatlock);
	ret = get_vmem(vs);
	pthread_mutex_unlock(&vstatlock);
	pthread_mutex_lock(&vmem_lock);
	return ret;
}

static void unlock_vmem(struct vstat_vmem *vm)
{
	pthread_mutex_unlock(&vmem_lock);
	release_vmem(vm);
}

/* Blinker Thread */
static void blinker_thread(void *data)
{
	int count=0;

	SetThreadName("Blinker");
	while(1) {
		do {
			SLEEP(10);
		} while(locked_screen_check()==NULL);
		count++;
		if(count==50) {
			pthread_mutex_lock(&vstatlock);
			if(vstat.blink)
				vstat.blink=FALSE;
			else
				vstat.blink=TRUE;
			count=0;
			pthread_mutex_unlock(&vstatlock);
		}
		if(force_redraws)
			update_rect(0,0,0,0,force_redraws--);
		else
			update_rect(0,0,0,0,FALSE);
		callbacks.flush();
	}
}

/*
 * MUST be called only once and before any other bitmap functions
 */
int bitmap_init(void (*drawrect_cb) (int xpos, int ypos, int width, int height, unsigned char *data)
				,void (*flush_cb) (void))
{
	if(bitmap_initialized)
		return(-1);
	pthread_mutex_init(&vmem_lock, NULL);
	pthread_mutex_init(&vstatlock, NULL);
	pthread_mutex_init(&screenlock, NULL);
	pthread_mutex_lock(&vmem_lock);
	vstat.vmem=NULL;
	pthread_mutex_unlock(&vmem_lock);

	callbacks.drawrect=drawrect_cb;
	callbacks.flush=flush_cb;
	bitmap_initialized=1;
	_beginthread(blinker_thread,0,NULL);

	return(0);
}

/*
 * This function is intended to be called from the driver.
 * as a result, it cannot block waiting for driver status
 *
 * Care MUST be taken to avoid deadlocks...
 */
int bitmap_init_mode(int mode, int *width, int *height)
{
    int i;
	char *newscreen;

	if(!bitmap_initialized)
		return(-1);

	pthread_mutex_lock(&screenlock);
	pthread_mutex_lock(&vstatlock);
	pthread_mutex_lock(&vmem_lock);

	if(load_vmode(&vstat, mode)) {
		pthread_mutex_unlock(&vmem_lock);
		pthread_mutex_unlock(&vstatlock);
		pthread_mutex_unlock(&screenlock);
		return(-1);
	}

	/* Initialize video memory with black background, white foreground */
	for (i = 0; i < vstat.cols*vstat.rows; ++i)
	    vstat.vmem->vmem[i] = 0x0700;
	pthread_mutex_unlock(&vmem_lock);

	screenwidth=vstat.charwidth*vstat.cols;
	if(width)
		*width=screenwidth;
	screenheight=vstat.charheight*vstat.rows;
	if(height)
		*height=screenheight;
	newscreen=realloc(screen, screenwidth*screenheight);
	if(!newscreen) {
		pthread_mutex_unlock(&vstatlock);
		pthread_mutex_unlock(&screenlock);
		return(-1);
	}
	screen=newscreen;
	memset(screen,vstat.palette[0],screenwidth*screenheight);
	pthread_mutex_unlock(&vstatlock);
	pthread_mutex_unlock(&screenlock);
	for (i=0; i<sizeof(current_font)/sizeof(current_font[0]); i++)
		current_font[i]=default_font;
	bitmap_loadfont(NULL);

	cio_textinfo.attribute=7;
	cio_textinfo.normattr=7;
	cio_textinfo.currmode=mode;
	cio_textinfo.screenheight=vstat.rows;
	cio_textinfo.screenwidth=vstat.cols;
	cio_textinfo.curx=1;
	cio_textinfo.cury=1;
	cio_textinfo.winleft=1;
	cio_textinfo.wintop=1;
	cio_textinfo.winright=cio_textinfo.screenwidth;
	cio_textinfo.winbottom=cio_textinfo.screenheight;

    return(0);
}

/*
 * Send by ciolib side, should not block in driver
 * Generally, if the driver may block on a rectangle draw, the updates
 * should be cached until flush is called.
 */
void send_rectangle(struct video_stats *vs, int xoffset, int yoffset, int width, int height, int force)
{
	unsigned char *rect;
	int pixel=0;
	int inpixel;
	int x,y;

	if(!bitmap_initialized)
		return;
	pthread_mutex_lock(&screenlock);
	if(callbacks.drawrect) {
		if(xoffset < 0 || xoffset >= screenwidth || yoffset < 0 || yoffset >= screenheight || width <= 0 || width > screenwidth || height <=0 || height >screenheight)
			goto end;

		rect=(unsigned char *)malloc(width*height*sizeof(unsigned char));
		if(!rect)
			goto end;

		for(y=0; y<height; y++) {
			inpixel=PIXEL_OFFSET(xoffset, yoffset+y);
			for(x=0; x<width; x++)
				rect[pixel++]=vs->palette[screen[inpixel++]];
		}
		pthread_mutex_unlock(&screenlock);
		callbacks.drawrect(xoffset,yoffset,width,height,rect);
		return;
	}
end:
	pthread_mutex_unlock(&screenlock);
}

/********************************************************/
/* High Level Stuff										*/
/********************************************************/

/* Called from main thread only (Passes Event) */

void bitmap_getcustomcursor(int *s, int *e, int *r, int *b, int *v)
{
	pthread_mutex_lock(&vstatlock);
	if(s)
		*s=vstat.curs_start;
	if(e)
		*e=vstat.curs_end;
	if(r)
		*r=vstat.charheight;
	if(b)
		*b=vstat.curs_blink;
	if(v)
		*v=vstat.curs_visible;
	pthread_mutex_unlock(&vstatlock);
}

void bitmap_setcustomcursor(int s, int e, int r, int b, int v)
{
	double ratio;

	pthread_mutex_lock(&vstatlock);
	if(r==0)
		ratio=0;
	else
		ratio=vstat.charheight/r;
	if(s>=0)
		vstat.curs_start=s*ratio;
	if(e>=0)
		vstat.curs_end=e*ratio;
	if(b>=0)
		vstat.curs_blink=b;
	if(v>=0)
		vstat.curs_visible=v;
	pthread_mutex_unlock(&vstatlock);
}

int bitmap_getvideoflags(void)
{
	int flags=0;

	pthread_mutex_lock(&vstatlock);
	if(vstat.bright_background)
		flags |= CIOLIB_VIDEO_BGBRIGHT;
	if(vstat.no_bright)
		flags |= CIOLIB_VIDEO_NOBRIGHT;
	if(vstat.bright_altcharset)
		flags |= CIOLIB_VIDEO_ALTCHARS;
	if(vstat.no_blink)
		flags |= CIOLIB_VIDEO_NOBLINK;
	if(vstat.blink_altcharset)
		flags |= CIOLIB_VIDEO_BLINKALTCHARS;
	pthread_mutex_unlock(&vstatlock);
	return(flags);
}

void bitmap_setvideoflags(int flags)
{
	pthread_mutex_lock(&vstatlock);
	if(flags & CIOLIB_VIDEO_BGBRIGHT)
		vstat.bright_background=1;
	else
		vstat.bright_background=0;

	if(flags & CIOLIB_VIDEO_NOBRIGHT)
		vstat.no_bright=1;
	else
		vstat.no_bright=0;

	if(flags & CIOLIB_VIDEO_ALTCHARS)
		vstat.bright_altcharset=1;
	else
		vstat.bright_altcharset=0;

	if(flags & CIOLIB_VIDEO_NOBLINK)
		vstat.no_blink=1;
	else
		vstat.no_blink=0;

	if(flags & CIOLIB_VIDEO_BLINKALTCHARS)
		vstat.blink_altcharset=1;
	else
		vstat.blink_altcharset=0;
	pthread_mutex_unlock(&vstatlock);
}

int bitmap_movetext(int x, int y, int ex, int ey, int tox, int toy)
{
	int	direction=1;
	int	cy;
	int	destoffset;
	int	sourcepos;
	int width=ex-x+1;
	int height=ey-y+1;
	struct vstat_vmem *vmem_ptr;

	if(		   x<1
			|| y<1
			|| ex<1
			|| ey<1
			|| tox<1
			|| toy<1
			|| x>cio_textinfo.screenwidth
			|| ex>cio_textinfo.screenwidth
			|| tox>cio_textinfo.screenwidth
			|| y>cio_textinfo.screenheight
			|| ey>cio_textinfo.screenheight
			|| toy>cio_textinfo.screenheight)
		return(0);

	if(toy > y)
		direction=-1;

	sourcepos=(y-1)*cio_textinfo.screenwidth+(x-1);
	destoffset=(((toy-1)*cio_textinfo.screenwidth+(tox-1))-sourcepos);

	vmem_ptr = lock_vmem(&vstat);
	for(cy=(direction==-1?(height-1):0); cy<height && cy>=0; cy+=direction) {
		sourcepos=((y-1)+cy)*cio_textinfo.screenwidth+(x-1);
		memmove(&(vmem_ptr->vmem[sourcepos+destoffset]), &(vmem_ptr->vmem[sourcepos]), sizeof(vmem_ptr->vmem[0])*width);
	}
	unlock_vmem(vmem_ptr);
	return(1);
}

void bitmap_clreol(void)
{
	int pos,x;
	WORD fill=(cio_textinfo.attribute<<8)|space;
	struct vstat_vmem *vmem_ptr;

	pos=(cio_textinfo.cury+cio_textinfo.wintop-2)*cio_textinfo.screenwidth;
	vmem_ptr = lock_vmem(&vstat);
	for(x=cio_textinfo.curx+cio_textinfo.winleft-2; x<cio_textinfo.winright; x++)
		vmem_ptr->vmem[pos+x]=fill;
	unlock_vmem(vmem_ptr);
}

void bitmap_clrscr(void)
{
	int x,y;
	WORD fill=(cio_textinfo.attribute<<8)|space;
	struct vstat_vmem *vmem_ptr;

	vmem_ptr = lock_vmem(&vstat);
	for(y=cio_textinfo.wintop-1; y<cio_textinfo.winbottom; y++) {
		for(x=cio_textinfo.winleft-1; x<cio_textinfo.winright; x++)
			vmem_ptr->vmem[y*cio_textinfo.screenwidth+x]=fill;
	}
	unlock_vmem(vmem_ptr);
}

int bitmap_puttext(int sx, int sy, int ex, int ey, void *fill)
{
	int x,y;
	unsigned char *out;
	WORD	sch;
	struct vstat_vmem *vmem_ptr;

	if(!bitmap_initialized)
		return(0);
	if(		   sx < 1
			|| sy < 1
			|| ex < 1
			|| ey < 1
			|| sx > cio_textinfo.screenwidth
			|| sy > cio_textinfo.screenheight
			|| sx > ex
			|| sy > ey
			|| ex > cio_textinfo.screenwidth
			|| ey > cio_textinfo.screenheight
			|| fill==NULL)
		return(0);

	vmem_ptr = lock_vmem(&vstat);
	out=fill;
	for(y=sy-1;y<ey;y++) {
		for(x=sx-1;x<ex;x++) {
			sch=*(out++);
			sch |= (*(out++))<<8;
			vmem_ptr->vmem[y*cio_textinfo.screenwidth+x]=sch;
		}
	}
	unlock_vmem(vmem_ptr);
	return(1);
}

/* Called from main thread only */
int bitmap_gettext(int sx, int sy, int ex, int ey, void *fill)
{
	int x,y;
	unsigned char *out;
	WORD	sch;
	struct vstat_vmem *vmem_ptr;

	if(!bitmap_initialized)
		return(0);

	if(		   sx < 1
			|| sy < 1
			|| ex < 1
			|| ey < 1
			|| sx > ex
			|| sy > ey
			|| ex > cio_textinfo.screenwidth
			|| ey > cio_textinfo.screenheight
			|| fill==NULL)
		return(0);

	vmem_ptr = lock_vmem(&vstat);
	out=fill;
	for(y=sy-1;y<ey;y++) {
		for(x=sx-1;x<ex;x++) {
			sch=vmem_ptr->vmem[y*cio_textinfo.screenwidth+x];
			*(out++)=sch & 0xff;
			*(out++)=sch >> 8;
		}
	}
	unlock_vmem(vmem_ptr);
	return(1);
}

/* Called from ciolib thread only */
void bitmap_setcursortype(int type)
{
	if(!bitmap_initialized)
		return;
	pthread_mutex_lock(&vstatlock);
	switch(type) {
		case _NOCURSOR:
			vstat.curs_start=0xff;
			vstat.curs_end=0;
			break;
		case _SOLIDCURSOR:
			vstat.curs_start=0;
			vstat.curs_end=vstat.charheight-1;
			break;
		default:
		    vstat.curs_start = vstat.default_curs_start;
		    vstat.curs_end = vstat.default_curs_end;
			break;
	}
	pthread_mutex_unlock(&vstatlock);
}

int bitmap_setfont(int font, int force, int font_num)
{
	int changemode=0;
	int	newmode=-1;
	struct text_info ti;
	char	*old;
	int		ow,oh;
	int		row,col;
	char	*new;
	int		attr;
	char	*pold;
	char	*pnew;

	if(!bitmap_initialized)
		return(-1);
	if(font < 0 || font>(sizeof(conio_fontdata)/sizeof(struct conio_font_data_struct)-2))
		return(-1);

	if(conio_fontdata[font].eight_by_sixteen!=NULL)
		newmode=C80;
	else if(conio_fontdata[font].eight_by_fourteen!=NULL)
		newmode=C80X28;
	else if(conio_fontdata[font].eight_by_eight!=NULL)
		newmode=C80X50;

	pthread_mutex_lock(&vstatlock);
	switch(vstat.charheight) {
		case 8:
			if(conio_fontdata[font].eight_by_eight==NULL) {
				if(!force)
					goto error_return;
				else
					changemode=1;
			}
			break;
		case 14:
			if(conio_fontdata[font].eight_by_fourteen==NULL) {
				if(!force)
					goto error_return;
				else
					changemode=1;
			}
			break;
		case 16:
			if(conio_fontdata[font].eight_by_sixteen==NULL) {
				if(!force)
					goto error_return;
				else
					changemode=1;
			}
			break;
	}
	if(changemode && (newmode==-1 || font_num > 1))
		goto error_return;
	switch(font_num) {
		case 0:
			default_font=font;
			/* Fall-through */
		case 1:
			current_font[0]=font;
			if(font==36 /* ATARI */)
				space=0;
			else
				space=' ';
			break;
		case 2:
		case 3:
		case 4:
			current_font[font_num-1]=font;
			break;
	}
	pthread_mutex_unlock(&vstatlock);

	if(changemode) {
		gettextinfo(&ti);

		attr=ti.attribute;
		ow=ti.screenwidth;
		oh=ti.screenheight;

		old=malloc(ow*oh*2);
		if(old) {
			gettext(1,1,ow,oh,old);
			textmode(newmode);
			new=malloc(ti.screenwidth*ti.screenheight*2);
			if(!new) {
				free(old);
				return -1;
			}
			pold=old;
			pnew=new;
			for(row=0; row<ti.screenheight; row++) {
				for(col=0; col<ti.screenwidth; col++) {
					if(row < oh) {
						if(col < ow) {
							*(new++)=*(old++);
							*(new++)=*(old++);
						}
						else {
							*(new++)=space;
							*(new++)=attr;
						}
					}
					else {
						*(new++)=space;
						*(new++)=attr;
					}
				}
				if(row < oh) {
					for(;col<ow;col++) {
						old++;
						old++;
					}
				}
			}
			puttext(1,1,ti.screenwidth,ti.screenheight,new);
			free(pnew);
			free(pold);
		}
	}
	bitmap_loadfont(NULL);
	return(0);

error_return:
	pthread_mutex_unlock(&vstatlock);
	return(-1);
}

int bitmap_getfont(void)
{
	return(current_font[0]);
}

void bitmap_setscaling(int new_value)
{
	pthread_mutex_lock(&vstatlock);
	if(new_value > 0)
		vstat.scaling = new_value;
	pthread_mutex_unlock(&vstatlock);
}

int bitmap_getscaling(void)
{
	int ret;

	pthread_mutex_lock(&vstatlock);
	ret = vstat.scaling;
	pthread_mutex_unlock(&vstatlock);
	return ret;
}

/* Called from event thread only */
int bitmap_loadfont(char *filename)
{
	static char current_filename[MAX_PATH];
	unsigned int fontsize;
	int fw;
	int fh;
	int i;
	FILE	*fontfile=NULL;

	if(!bitmap_initialized)
		return(-1);
	if(current_font[0]==-99 || current_font[0]>(sizeof(conio_fontdata)/sizeof(struct conio_font_data_struct)-2)) {
		for(i=0; conio_fontdata[i].desc != NULL; i++) {
			if(!strcmp(conio_fontdata[i].desc, "Codepage 437 English")) {
				current_font[0]=i;
				break;
			}
		}
		if(conio_fontdata[i].desc==NULL)
			current_font[0]=0;
	}
	if(current_font[0]==-1)
		filename=current_filename;
	else if(conio_fontdata[current_font[0]].desc==NULL)
		return(-1);

	for (i=1; i<sizeof(current_font)/sizeof(current_font[0]); i++) {
		if(current_font[i] == -1)
			;
		else if (current_font[i] < 0)
			current_font[i]=current_font[0];
		else if(conio_fontdata[current_font[i]].desc==NULL)
			current_font[i]=current_font[0];
	}

	pthread_mutex_lock(&vstatlock);
	fh=vstat.charheight;
	fw=vstat.charwidth/8+(vstat.charwidth%8?1:0);

	fontsize=fw*fh*256*sizeof(unsigned char);

	for (i=0; i<sizeof(font)/sizeof(font[0]); i++) {
		if(font[i])
			FREE_AND_NULL(font[i]);
		if((font[i]=(unsigned char *)malloc(fontsize))==NULL)
			goto error_return;
	}

	if(filename != NULL) {
		if(flength(filename)!=fontsize)
			goto error_return;
		if((fontfile=fopen(filename,"rb"))==NULL)
			goto error_return;
		if(fread(font[0], 1, fontsize, fontfile)!=fontsize)
			goto error_return;
		fclose(fontfile);
		fontfile=NULL;
		current_font[0]=-1;
		if(filename != current_filename)
			SAFECOPY(current_filename,filename);
		for (i=1; i<sizeof(font)/sizeof(font[0]); i++) {
			if (current_font[i]==-1)
				memcpy(font[i], font[0], fontsize);
		}
	}
	for (i=0; i<sizeof(font)/sizeof(font[0]); i++) {
		if (current_font[i] < 0)
			continue;
		switch(vstat.charwidth) {
			case 8:
				switch(vstat.charheight) {
					case 8:
						if(conio_fontdata[current_font[i]].eight_by_eight==NULL) {
							if (i==0)
								goto error_return;
							else
								FREE_AND_NULL(font[i]);
						}
						else
							memcpy(font[i], conio_fontdata[current_font[i]].eight_by_eight, fontsize);
						break;
					case 14:
						if(conio_fontdata[current_font[i]].eight_by_fourteen==NULL) {
							if (i==0)
								goto error_return;
							else
								FREE_AND_NULL(font[i]);
						}
						else
							memcpy(font[i], conio_fontdata[current_font[i]].eight_by_fourteen, fontsize);
						break;
					case 16:
						if(conio_fontdata[current_font[i]].eight_by_sixteen==NULL) {
							if (i==0)
								goto error_return;
							else
								FREE_AND_NULL(font[i]);
						}
						else
							memcpy(font[i], conio_fontdata[current_font[i]].eight_by_sixteen, fontsize);
						break;
					default:
						goto error_return;
				}
				break;
			default:
				goto error_return;
		}
	}

	force_redraws++;
	pthread_mutex_unlock(&vstatlock);
    return(0);

error_return:
	for (i=0; i<sizeof(font)/sizeof(font[0]); i++)
		FREE_AND_NULL(font[i]);
	if(fontfile)
		fclose(fontfile);
	pthread_mutex_unlock(&vstatlock);
	return(-1);
}

static void bitmap_draw_cursor(struct video_stats *vs)
{
	int x;
	int y;
	char attr;
	int pixel;
	int xoffset,yoffset;
	int width;
	int yo, xw, yw;

	if(!bitmap_initialized)
		return;
	if(vs->curs_visible) {
		if(vs->blink || (!vs->curs_blink)) {
			if(vs->curs_start<=vs->curs_end) {
				xoffset=(vs->curs_col-1)*vs->charwidth;
				yoffset=(vs->curs_row-1)*vs->charheight;
				if(xoffset < 0 || yoffset < 0)
					return;
				attr=cio_textinfo.attribute&0x0f;
				width=vs->charwidth;
	
				pthread_mutex_lock(&screenlock);
				for(y=vs->curs_start; y<=vs->curs_end; y++) {
					if(xoffset < screenwidth && (yoffset+y) < screenheight) {
						pixel=PIXEL_OFFSET(xoffset, yoffset+y);
						for(x=0;x<vs->charwidth;x++)
							screen[pixel++]=attr;
						//memset(screen+pixel,attr,width);
					}
				}
				pthread_mutex_unlock(&screenlock);
				yo = yoffset+vs->curs_start;
				xw = vs->charwidth;
				yw = vs->curs_end-vs->curs_start+1;
				send_rectangle(vs, xoffset, yo, xw, yw,FALSE);
				return;
			}
		}
	}
}

/* Called from main thread only */
void bitmap_gotoxy(int x, int y)
{
	if(!bitmap_initialized)
		return;
	/* Move cursor location */
	cio_textinfo.curx=x;
	cio_textinfo.cury=y;
	if(!hold_update) {
		/* Move visible cursor */
		pthread_mutex_lock(&vstatlock);
		vstat.curs_col=x+cio_textinfo.winleft-1;
		vstat.curs_row=y+cio_textinfo.wintop-1;
		pthread_mutex_unlock(&vstatlock);
	}
}

static int bitmap_draw_one_char(struct video_stats *vs, unsigned int xpos, unsigned int ypos)
{
	int		fg;
	int		bg;
	int		xoffset=(xpos-1)*vs->charwidth;
	int		yoffset=(ypos-1)*vs->charheight;
	int		x;
	int		y;
	int		fontoffset;
	int		altfont;
	unsigned char *this_font;
	WORD	sch;
	struct vstat_vmem *vmem_ptr;

	if(!bitmap_initialized)
		return(-1);

	if(!vs->vmem) {
		return(-1);
	}

	pthread_mutex_lock(&screenlock);

	if(!screen) {
		pthread_mutex_unlock(&screenlock);
		return(-1);
	}

	vmem_ptr = lock_vmem(vs);

	sch=vmem_ptr->vmem[(ypos-1)*cio_textinfo.screenwidth+(xpos-1)];

	unlock_vmem(vmem_ptr);

	altfont = (sch>>11 & 0x01) | ((sch>>14) & 0x02);
	if(vs->bright_background) {
		bg=(sch&0xf000)>>12;
		fg=(sch&0x0f00)>>8;
	}
	else {
		bg=(sch&0x7000)>>12;
		if(sch&0x8000 && vs->blink && (!vs->no_blink))
			fg=bg;
		else
			fg=(sch&0x0f00)>>8;
	}
	if (!vs->bright_altcharset)
		altfont &= ~0x01;
	if (!vs->blink_altcharset)
		altfont &= ~0x02;
	this_font=font[altfont];
	if (this_font == NULL)
		this_font = font[0];
	if(vs->no_bright)
		fg &= 0x07;
	fontoffset=(sch&0xff)*vs->charheight;

	for(y=0; y<vs->charheight; y++) {
		memset(&screen[PIXEL_OFFSET(xoffset, yoffset+y)],bg,vs->charwidth);
		for(x=0; x<vs->charwidth; x++) {
			if(this_font[fontoffset] & (0x80 >> x))
				screen[PIXEL_OFFSET(xoffset+x, yoffset+y)]=fg;
		}
		fontoffset++;
	}
	pthread_mutex_unlock(&screenlock);

	return(0);
}

static int update_rect(int sx, int sy, int width, int height, int force)
{
	int x,y;
	unsigned int pos;
	int	redraw_cursor=0;
	int	lastcharupdated=0;
	int fullredraw=0;
	static unsigned short *last_vmem=NULL;
	static unsigned short *this_vmem=NULL;
	static struct video_stats vs;
	struct video_stats cvstat;
	struct vstat_vmem cvmem;
	struct rectangle this_rect;
	int this_rect_used=0;
	struct rectangle last_rect;
	int last_rect_used=0;
	struct vstat_vmem *vmem_ptr;

	if(!bitmap_initialized)
		return(-1);

	if(sx==0 && sy==0 && width==0 && height==0)
		fullredraw=1;

	if(sx<=0)
		sx=1;
	if(sy<=0)
		sy=1;
	if(width<=0 || width>cio_textinfo.screenwidth)
		width=cio_textinfo.screenwidth;
	if(height<=0 || height>cio_textinfo.screenheight)
		height=cio_textinfo.screenheight;

	pthread_mutex_lock(&vstatlock);
	if(vs.cols!=vstat.cols || vs.rows != vstat.rows || last_vmem==NULL || this_vmem == NULL) {
		unsigned short *p;

		p=(unsigned short *)realloc(last_vmem, vstat.cols*vstat.rows*sizeof(unsigned short));
		if(p==NULL)
			return(-1);
		last_vmem=p;
		memset(last_vmem, 255, vstat.cols*vstat.rows*sizeof(unsigned short));
		sx=1;
		sy=1;
		width=vstat.cols;
		height=vstat.rows;
		force=1;
		vs.cols=vstat.cols;
		vs.rows=vstat.rows;
		p=(unsigned short *)realloc(this_vmem, vstat.cols*vstat.rows*sizeof(unsigned short));
		if(p==NULL)
			return(-1);
		this_vmem = p;
	}

	/* Redraw cursor */
	if(vstat.blink != vs.blink
			|| vstat.curs_col!=vs.curs_col
			|| vstat.curs_row!=vs.curs_row
			|| vstat.curs_start!=vs.curs_start
			|| vstat.curs_end!=vs.curs_end)
		redraw_cursor=1;
	cvstat = vstat;
	vmem_ptr = get_vmem(&vstat);
	pthread_mutex_unlock(&vstatlock);
	pthread_mutex_lock(&vmem_lock);
	cvstat.vmem = &cvmem;
	cvstat.vmem->refcount = 1;
	cvstat.vmem->vmem = this_vmem;
	memcpy(cvstat.vmem->vmem, vmem_ptr->vmem, vstat.cols*vstat.rows*sizeof(unsigned short));
	pthread_mutex_unlock(&vmem_lock);

	for(y=0;y<height;y++) {
		pos=(sy+y-1)*cvstat.cols+(sx-1);
		for(x=0;x<width;x++) {
			if(force
					|| (last_vmem[pos] != cvstat.vmem->vmem[pos]) 					/* Different char */
					|| ((cvstat.blink != vs.blink) && (cvstat.vmem->vmem[pos]>>15) && (!cvstat.no_blink)) 	/* Blinking char */
					|| (redraw_cursor && ((vs.curs_col==sx+x && vs.curs_row==sy+y) || (cvstat.curs_col==sx+x && cvstat.curs_row==sy+y)))	/* Cursor */
					) {
				last_vmem[pos] = cvstat.vmem->vmem[pos];
				bitmap_draw_one_char(&cvstat, sx+x,sy+y);

				if(!redraw_cursor && sx+x==cvstat.curs_col && sy+y==cvstat.curs_row)
					redraw_cursor=1;

				if(lastcharupdated) {
					this_rect.width+=cvstat.charwidth;
					lastcharupdated++;
				}
				else {
					if(this_rect_used) {
						send_rectangle(&cvstat, this_rect.x, this_rect.y, this_rect.width, this_rect.height,FALSE);
					}
					this_rect.x=(sx+x-1)*cvstat.charwidth;
					this_rect.y=(sy+y-1)*cvstat.charheight;
					this_rect.width=cvstat.charwidth;
					this_rect.height=cvstat.charheight;
					this_rect_used=1;
					lastcharupdated++;
				}
			}
			else {
				if(last_rect_used) {
					send_rectangle(&cvstat, last_rect.x, last_rect.y, last_rect.width, last_rect.height, FALSE);
					last_rect_used=0;
				}
				if(this_rect_used) {
					send_rectangle(&cvstat, this_rect.x, this_rect.y, this_rect.width, this_rect.height,FALSE);
					this_rect_used=0;
				}
				lastcharupdated=0;
			}
			pos++;
		}
		/* If ALL chars in the line were used, add to last_rect */
		if(lastcharupdated==width) {
			if(last_rect_used) {
				last_rect.height += cvstat.charheight;
				this_rect_used=0;
			}
			else {
				last_rect=this_rect;
				last_rect_used=1;
				this_rect_used=0;
			}
		}
		/* Otherwise send any stale line buffers */
		else
		{
			if(last_rect_used) {
				send_rectangle(&cvstat, last_rect.x, last_rect.y, last_rect.width, last_rect.height, FALSE);
				last_rect_used=0;
			}
			if(this_rect_used) {
				send_rectangle(&cvstat, this_rect.x, this_rect.y, this_rect.width, this_rect.height, FALSE);
				this_rect_used=0;
			}
		}
		lastcharupdated=0;
	}

	if(last_rect_used)
		send_rectangle(&cvstat, last_rect.x, last_rect.y, last_rect.width, last_rect.height, FALSE);
	if(this_rect_used)
		send_rectangle(&cvstat, this_rect.x, this_rect.y, this_rect.width, this_rect.height, FALSE);

	vs = cvstat;

	/* Did we redraw the cursor?  If so, update cursor info */
	if(redraw_cursor)
		bitmap_draw_cursor(&cvstat);

	return(0);
}
