/*
 	HEWX.CPP
 	Non-portable functions for wxWindows

	for the Hugo Engine

 	by Kent Tessman (c) 2000-2006

 	(Note that this file hewx.c contains the standard engine interface
	routines; hewxwin.c contains wxWindows user interface functions.)
*/

#include "hewx.h"

#ifndef NO_WXCARET
#include "wx/caret.h"
#endif

#include <float.h>
#if defined (__WXMAC__)
#include <Timer.h>
#elif defined (__WXMSW__)
#include <time.h>
#undef GetProp
#else
#include <sys/time.h>
#endif

extern "C"
{

#include "heheader.h"


/* Function prototypes: */
void hugo_addcommand(void);
void hugo_restorecommand(int);

/* Specific to hewx.cpp: */
void RedrawInputLine(int index);
void ConstrainCursor(void);
void FlushBuffer(void);


#ifdef SCROLLBACK_DEFINED
#define SCROLLBACK_SIZE 32768	// for scrollback window
wxChar scrollback_buffer[SCROLLBACK_SIZE];
int scrollback_pos = 0;
#endif

#define HISTORY_SIZE    16      // for command-line editing
int hcount = 0;
char *history[HISTORY_SIZE];

// wxWINDOWS:
int current_text_col, current_text_row;
int prop_lineheight = 0;
int insert_mode = true;
char waiting_for_key = false;
int text_windowleft, text_windowtop, text_windowright, text_windowbottom,
	text_windowwidth;
int print_cursor = false;       /* probably never be needed */
wxColour *current_text_color, *current_back_color;
int mouse_x = 0, mouse_y = 0;
char tweaking_lineheight = 0;

#if defined (DEBUGGER)
void *AllocMemory(size_t size);
extern int fake_graphics_mode;
#endif


/*
    MEMORY ALLOCATION:

    hugo_blockalloc(), and hugo_blockfree() are necessary because of
    the way MS-DOS handles memory allocation across more than 64K.
    For most systems, these will simply be normal ANSI function calls.
*/

void *hugo_blockalloc(long num)
{
        return malloc(num * sizeof(char));
}

void hugo_blockfree(void *block)
{
        free(block);
}


/*
    OVERWRITE:

    Checks to see if the given filename already exists, and prompts to
    replace it.  Returns true if file may be overwritten.
*/

int hugo_overwrite(char *f)
{
/* Not needed if hugo_getfilename() uses wxOVERWRITE_PROMPT
	FILE *tempfile;

	if (!(tempfile = fopen(f, "rb")))	// if file doesn't exist
		return true;

	fclose(tempfile);

	sprintf(buffer, "Overwrite existing \"%s\"?", f);

	if (wxMessageBox(buffer, "File Exists",
		wxYES_NO | wxICON_QUESTION,
		(wxWindow *)frame)==wxYES)
	{
		return true;
	}

	return false;
*/
	return true;
}


/*
    CLOSEFILES:

    Closes all open files.  NOTE:  If the operating system automatically
    closes any open streams upon exit from the program, this function may
    be left empty.
*/

void hugo_closefiles()
{
/*
        fclose(game);
  	if (script) fclose(script);
  	if (io) fclose(io);
  	if (record) fclose(record);
*/
}


/*
    GETFILENAME:

    Loads the name of the filename to save or restore (as specified by
    the argument <a>) into the line[] array.
*/

void hugo_getfilename(char *usage, char *def_filename)
{
	char *def_ext = NULL, *wildcard = NULL;
	char initial_dir[MAXPATH] = "";
	char drive[MAXDRIVE], dir[MAXDIR], fname[MAXFILENAME], ext[MAXEXT];
	int flags = 0;
	
	sprintf(line, "Select the file %s", usage);

	if (def_filename==scriptfile)
	{
		def_ext = (char *)"txt";	/* transcription */
		wildcard = (char *)"Transcription files (*.txt)|*.txt|All files (*.*)|*.*";
		flags = wxSAVE;
		hugo_splitpath(def_filename, drive, dir, fname, ext);
		hugo_makepath(initial_dir, drive, dir, (char *)"", (char *)"");

		def_filename = (char *)".txt";
	}
	else if (def_filename==recordfile)
	{
		def_ext =  (char *)"rec";	/* record/playback */
		wildcard = (char *)"Hugo recording files (*.rec)|*.rec|All files (*.*)|*.*";
		if (strstr(usage, "record")) flags = wxSAVE | wxOVERWRITE_PROMPT;
		hugo_splitpath(def_filename, drive, dir, fname, ext);
		hugo_makepath(initial_dir, drive, dir, (char *)"", (char *)"");

		def_filename = (char *)".rec";
	}
	else
	{
		def_ext = (char *)"sav";	/* save, restore */
		wildcard = (char *)"Hugo save files (*.sav)|*.sav|All files (*.*)|*.*";
		if (strstr(usage, "save")) flags = wxSAVE;
//		strcpy(initial_dir, current_save_dir);
		strcpy(initial_dir, "");

		def_filename = (char *)".sav";
	}
	
	wxString filename = 
		wxString(wxFileSelector(wxString::FromAscii(line),
			wxString::FromAscii(initial_dir), wxString::FromAscii(def_filename),
			wxString::FromAscii(def_ext), wxString::FromAscii(wildcard),
			flags, (wxWindow *)frame));

	if (filename.IsNull())
		strcpy(line, "");
	else
		strcpy(line, filename.ToAscii());
}


/*
    GETKEY:

    Returns the next keystroke waiting in the keyboard buffer.  It is
    expected that hugo_getkey() will return the following modified
    keystrokes:

	up-arrow        11 (CTRL-K)
	down-arrow      10 (CTRL-J)
	left-arrow       8 (CTRL-H)
	right-arrow     21 (CTRL-U)
*/

#define MAX_KEYPRESSES 128
int keypress_count = 0;
int keypress_queue[MAX_KEYPRESSES];

void PushKeypress(int k)
{
	if (keypress_count==MAX_KEYPRESSES) return;
	keypress_queue[keypress_count++] = k;
}

int PullKeypress(void)
{
	int i, k;

	if (keypress_count==0) return 0;

	k = keypress_queue[0];
	for (i=0; i<keypress_count-1; i++)
		keypress_queue[i] = keypress_queue[i+1];
	keypress_count--;
	keypress_queue[keypress_count] = 0;
	
	// Mouse button
	if (k==1 && keypress_count>=2)
	{
		display_pointer_x = keypress_queue[0];
		display_pointer_y = keypress_queue[1];
		for (i=0; i<keypress_count-2; i++)
			keypress_queue[i] = keypress_queue[i+2];
		keypress_queue[keypress_count-2] = 0;
		keypress_queue[keypress_count-1] = 0;
		keypress_count-=2;
	}

	return k;
}

#ifdef NO_WXCARET
#define BLINK_PERIOD 500	// milliseconds
#endif

long MyMilliseconds()		// milliseconds since initial call
{
	long ms;
#if defined (__WXMAC__)
	static long first_hi = 0, first_lo = 0;
	UnsignedWide usec;
	Microseconds(&usec);
	if (first_hi==0 && first_lo==0)
	{
		first_hi = usec.hi;
		first_lo = usec.lo;
		ms = 0;
	}
	else
	{
		if (usec.lo < first_lo)
			ms = (usec.hi-first_hi)*4294967.0 - (first_lo-usec.lo)/1000.0;
		else
			ms = (usec.hi-first_hi)*4294967.0 + (usec.lo-first_lo)/1000.0;
	}
#elif defined (__WXMSW__)
	static long first_sec = 0, first_msec = 0;
	SYSTEMTIME st;
	GetSystemTime(&st);
	if (first_sec==0 && first_msec==0)
	{
		first_sec = st.wSecond;
		first_msec = st.wMilliseconds;
		ms = 0;
	}
	else
	{
		if (st.wMilliseconds < first_msec)
			ms = (st.wSecond-first_sec)*1000L - (first_msec-st.wMilliseconds);
		else
			ms = (st.wSecond-first_sec)*1000L + (first_msec-st.wMilliseconds);
	}
#else
	static long first_sec = 0, first_usec = 0;
	timeval tv;
	gettimeofday(&tv, 0);
	if (first_sec==0 && first_usec==0)
	{
		first_sec = tv.tv_sec;
		first_usec = tv.tv_usec;
		ms = 0;
	}
	else
	{
		if (tv.tv_usec < first_usec)
			ms = (tv.tv_sec-first_sec)*1000L - (first_usec-tv.tv_usec)/1000L;
		else
			ms = (tv.tv_sec-first_sec)*1000L + (tv.tv_usec-first_usec)/1000L;
	}
#endif

	return ms;
}

int hugo_getkey(void)
{
	int k;

#ifndef NO_WXCARET
	canvas->GetCaret()->Move(current_text_x, current_text_y);
#else
	long last_caret = MyMilliseconds();
#endif

	canvas->Update(TRUE);
	
	while (!(k = PullKeypress()))
	{
		/* If an accelerator key (like Ctrl+Z for Undo) is
		   pressed, it is not treated as a normal keypress
		*/
		if (processed_accelerator_key) return 0;
		
		ProcesswxWinMessages();

#ifdef NO_WXCARET
		// Blink the caret; erasing it if the window has lost focus
		if ((MyMilliseconds()-last_caret > BLINK_PERIOD && canvas->isactive)
			|| (!canvas->isactive && canvas->caret_drawn))
		{
			canvas->DrawCaret(current_text_x, current_text_y);
			last_caret = MyMilliseconds();
		}
#endif
	}
	
	// Erase the caret from the visible view
#ifdef NO_WXCARET
	if (canvas->caret_drawn) canvas->DrawCaret();
#endif

	if (k < 0) k = (unsigned char)k;
	
#ifdef __WXMAC__
	if ((unsigned char)k > 127)
	{
		char *c = strchr(StringMac, k);
		if (c!=NULL) k = StringANSI[c - StringMac];
	}
#endif
	
	return k;
}

/*
    GETLINE

    Gets a line of input from the keyboard, storing it in <buffer>.
*/

char getline_active = false;

void hugo_getline(char *prmpt)
{
	int a, b, thiscommand;
	int c;                          /* c is the character being added */
	int oldx, oldy;
	int tempfont = currentfont;

	hugo_settextcolor(fcolor);
	hugo_setbackcolor(bgcolor);

	strcpy(buffer, "");
	c = 0;
	thiscommand = hcount;
	
	getline_active = true;

	hugo_print(prmpt);
	FlushBuffer();

	/* An italic input font won't space/overwrite properly */
	hugo_font(currentfont &= ~ITALIC_FONT);

	/* i.e., the finishing position afer printing the prompt */
	oldx = current_text_x;
	oldy = current_text_y;

GetKey:

	b = hugo_getkey();

	/* If an accelerator key (like Ctrl+Z for Undo) is pressed, it
	   voids the current input
	*/
	if (processed_accelerator_key) goto BlankLine;

	hugo_settextcolor(icolor);
	hugo_setbackcolor(bgcolor);

	/* Now, start key checking */
	switch (b)
	{
		case (13):                      /* Enter */
		{
			full = 0;

			/* Copy the input to the script file (if open) */
			if (script) fprintf(script, "%s%s\n", prmpt, buffer);

#ifdef SCROLLBACK_DEFINED
			/* Copy the input to the scrollback buffer */
			hugo_sendtoscrollback(prmpt);
			hugo_sendtoscrollback(buffer);
			hugo_sendtoscrollback((char *)"\n");
#endif
			hugo_print((char *)"\r\n");
			
			strcpy(buffer, Rtrim(buffer));
			hugo_addcommand();

			/* Restore original font */
			hugo_font(currentfont = tempfont);
			
			getline_active = false;

			return;
		}
	        case WXK_INSERT:	/* Insert */
		{
			insert_mode = !insert_mode;
			goto GetKey;
		}
	        case BACKSPACE_KEY:
	        case WXK_DELETE:
		{
			if (strlen(buffer)>0)
			{
				if (b==BACKSPACE_KEY)
				{
					if (c==0) goto GetKey;
					c--;

					/* Move backward the width of the
					   deleted character
					*/
					current_text_x-=hugo_charwidth(buffer[c]);
					if (current_text_x < oldx)
						current_text_x = oldx;
				}

				/* Shift the buffer to account for the
				   deleted character
				*/
				for (a=c; a<=(int)strlen(buffer); a++)
					buffer[a] = buffer[a+1];

				RedrawInputLine(c);
			}
			goto GetKey;
		}
		case (8):                       /* left-arrow */
		{
			if (c > 0)
				current_text_x -= hugo_charwidth(buffer[--c]);
			goto GetKey;
		}
		case (21):                      /* right-arrow */
		{
			if (c<(int)strlen(buffer))
				current_text_x += hugo_charwidth(buffer[c++]);
			goto GetKey;
		}
		case CTRL_LEFT_KEY:
		{
			if (c)
			{
				do
				{
					do
					{
						current_text_x -= hugo_charwidth(buffer[--c]);
					}
					while (c && buffer[c-1]!=' ');
				}
				while (c && buffer[c]==' ');
			}
			goto GetKey;
		}
		case CTRL_RIGHT_KEY:
		{
			if (c<(int)strlen(buffer))
			{
				do
				{
					do
					{
						current_text_x += hugo_charwidth(buffer[c++]);
					}
					while (c<(int)strlen(buffer) &&
						buffer[c-1]!=' ');
				}
				while (c<(int)strlen(buffer) && buffer[c]==' ');
			}
			goto GetKey;
		}
		case (27):                      /* Escape */
		case (24):                      /* CTRL-X */
		{
BlankLine:
			strcpy(buffer, "");
			c = 0;
			current_text_x = oldx;
			RedrawInputLine(0);
			processed_accelerator_key = 0;
			goto GetKey;
		}
		case WXK_HOME:
		{
			c = 0;
			current_text_x = oldx;
			goto GetKey;
		}
		case WXK_END:
		{
			c = strlen(buffer);
			current_text_x = oldx + hugo_textwidth(buffer);
			goto GetKey;
		}
		case (11):                      /* up-arrow */
		{
			if (--thiscommand<0)
			{
				thiscommand = 0;
				goto GetKey;
			}
			a = strlen(buffer);
RestoreCommand:
			hugo_restorecommand(thiscommand);
			current_text_x = oldx;
			c = 0;
			RedrawInputLine(0);
			current_text_x = oldx+hugo_textwidth(buffer);
			c = strlen(buffer);
			goto GetKey;
		}
		case (10):                      /* down-arrow */
		{
			a = strlen(buffer);
			if (++thiscommand>=hcount)
			{
				thiscommand = hcount;
				goto BlankLine;
			}
			goto RestoreCommand;
		}
	}

	/* Disallow invalid keystrokes */
	if (b < 32 || b>=256)

	/* Hugo circa v2.2 allowed '^' and '~' for '\n' and '\"',
	   respectively
	*/
	if (game_version<=22 && (b=='^' || b=='~')) goto GetKey;

	/* (In Windows, we have to check both the window width AND
	   if we're going to overrun the buffer, just because a
	   ridiculously small-font/large-screen combination may be
	   in use.)
	*/
	if (current_text_x >= physical_windowright-charwidth ||
		c >= MAXBUFFER*2)
	{
		goto GetKey;
	}


	/* Add the new character: */

	/* If inserting, shift the post-insertion-point part of the buffer */
	buffer[strlen(buffer)+1] = '\0';
	if (c<(int)strlen(buffer) && insert_mode)
	{
		for (a=strlen(buffer); a>c; a--)
			buffer[a] = buffer[a-1];
	}
	buffer[c] = (char)b;

	/* Actually display the new character */
	RedrawInputLine(c);

	current_text_x += hugo_charwidth((char)b);
	c++;

	goto GetKey;
}


/* RedrawInputLine

	Redraws only the changed portion of the input line, i.e., from the
	current text position to the right edge of the window.  <index>
	gives the current position in the buffer (<c> from hugo_getline()).
*/

void RedrawInputLine(int index)
{
	wxMemoryDC dcMem;
	if (bitmap) dcMem.SelectObject(*bitmap);

	dcMem.SetFont(*fontCurrent);
	dcMem.SetTextForeground(*current_text_color);
	dcMem.SetTextBackground(*current_back_color);

	wxBrush brush = wxBrush(*current_back_color, wxSOLID);
	wxPen pen = wxPen(*current_back_color, 1, wxSOLID);
	dcMem.SetBrush(brush);
	dcMem.SetPen(pen);

	dcMem.DrawRectangle(current_text_x, current_text_y,
		physical_windowright-current_text_x+1, lineheight);

	dcMem.SetBrush(wxNullBrush);
	dcMem.SetPen(wxNullPen);

	/* Redraw the input line from the current position to the end
	   (TRANSPARENT so that we don't clip the font--we aren't
	   drawing over anything, so it doesn't matter)
	*/
	dcMem.SetBackgroundMode(wxTRANSPARENT);

#if !defined (__WXMAC__) && !defined (__WXGTK20__)
	/* Prevent overwriting same characters (otherwise it looks like
	   they're becoming bolder with each keystroke)
	*/
	if (index)
		dcMem.DrawText(wxString::FromAscii(buffer+index-1),
			current_text_x-hugo_charwidth(buffer[index-1]),
			current_text_y);
	else
#endif
		dcMem.DrawText(wxString::FromAscii(buffer+index),
			current_text_x, current_text_y);
	dcMem.SetBackgroundMode(wxSOLID);

	// Update only only the dirty rectangle
 	wxClientDC dc(canvas);
	dc.Blit(0, current_text_y, canvas->width, lineheight,
		&dcMem, 0, current_text_y);
}


/* hugo_iskeywaiting

    Returns true if a keypress is waiting to be retrieved.
*/

int hugo_iskeywaiting(void)
{
	FlushBuffer();
	return (keypress_count > 0)?1:0;
}

/*
    WAITFORKEY:

    Provided to be replaced by multitasking systems where cycling while
    waiting for a keystroke may not be such a hot idea.
*/

int hugo_waitforkey(void)
{
	int key;
	
	FlushBuffer();
	canvas->needs_updating = true;
	canvas->Update(TRUE); 	/* sometimes the screen isn't updated */

	waiting_for_key = true;
	key = hugo_getkey();
	waiting_for_key = false;

	return key;
}


/* hugo_timewait

    Waits for 1/n seconds.  Returns false if waiting is unsupported.
*/

int hugo_timewait(int n)
{
	static volatile int repaint_interval = 0;

	long ms1, ms2;
	ms1 = MyMilliseconds();
	do
	{
		ProcesswxWinMessages();
		ms2 = MyMilliseconds();
		
		if (ms2 < ms1) break;	// safety hatch for wraparound
		
	} while (ms2 - ms1 < (long)(1000/n));

	if (canvas->needs_updating)
	{
		/* So that we don't bog things down repainting the
		   screen repeatedly, only do it every 1/10th second
		*/
		if ((repaint_interval+=1000/n) > 100)
		{
			canvas->Update(TRUE);
			repaint_interval = 0;
		}	
	}

	return true;
}


/*
    COMMAND HISTORY:

    To store/retrieve player inputs for editing.
*/

void hugo_addcommand(void)
{
	int i;

	if (!strcmp(buffer, "")) return;

        if (hcount>=HISTORY_SIZE)
	{
		hugo_blockfree(history[0]);
		for (i=0; i<HISTORY_SIZE-1; i++)
			history[i] = history[i+1];
		hcount = HISTORY_SIZE-1;
	}

	/* Because the debugger might use (up to) all available memory for
	   code line storage, a different means of memory allocation is
	   needed (at least in MS-DOS due to limited available memory to
	   begin with).
	*/
#if !defined (DEBUGGER)
	if ((history[hcount] = (char *)hugo_blockalloc((long)((strlen(buffer)+1)*sizeof(char))))==NULL)
#else
	if ((history[hcount] = (char *)AllocMemory((size_t)((strlen(buffer)+1)*sizeof(char))))==NULL)
#endif
	{
		hugo_blockfree(history[0]);
		if (hcount)
		{
			for (i=0; i<hcount; i++)
				history[i] = history[i+1];
			hcount--;
		}
		return;
	}

	for (i=0; i<=(int)strlen(buffer); i++)
		history[hcount][i] = buffer[i];
	hcount++;
}

void hugo_restorecommand(int n)
{
	int i;

	if (n < 0 || (n>=hcount && hcount!=HISTORY_SIZE-1)) return;

	i = 0;
	do
		buffer[i] = history[n][i];
	while (history[n][i++]!='\0');
}


/*
    DISPLAY CONTROL:

    These functions are specific library calls to QuickC/MS-DOS
    for screen control.  Each _function() should be replaced by
    the equivalent call for the operating system in question.  If
    a particular operation is unavailable, in some cases it may be
    left empty.  In other cases, it may be necessary to write a
    brief function to achieve the desired effect.
*/

void hugo_init_screen(void)
{}

void hugo_cleanup_screen(void)
{
// Not necessary, since we're destroying the window
}

void hugo_setgametitle(char *t)
{
#if defined (DEBUGGER)
	/* The array must be larger than MAX_GAME_TITLE in heset.c */
	static char debug_game_title[96];
	sprintf(debug_game_title, "Hugo Debugger - %s", t);
	frame->SetTitle(wxString::FromAscii(debug_game_title));
#else
	frame->SetTitle(wxString::FromAscii(t));
#endif
}

void hugo_clearfullscreen(void)
{
/* Clears everything on the screen, moving the cursor to the top-left
   corner of the screen */

	FlushBuffer();

	wxMemoryDC dcMem;
	if (bitmap) dcMem.SelectObject(*bitmap);

	wxBrush brush = wxBrush(*current_back_color, wxSOLID);
	wxPen pen = wxPen(*current_back_color, 1, wxSOLID);
	dcMem.SetBrush(brush);
	dcMem.SetPen(pen);

	dcMem.SetTextForeground(*current_text_color);
	dcMem.SetTextBackground(*current_back_color);
	dcMem.DrawRectangle(0, 0, canvas->width, canvas->height*2);

	dcMem.SetBrush(wxNullBrush);
	dcMem.SetPen(wxNullPen);
	
	canvas->needs_updating = true;

#ifdef USE_TEXTBUFFER
	TB_Clear(0, 0, canvas->width, canvas->height);
#endif

	/* Must be set: */
	currentpos = 0;
	currentline = 1;
}


void hugo_clearwindow(void)
{
/* Clears the currently defined window, moving the cursor to the top-left
   corner of the window */

	FlushBuffer();

	wxMemoryDC dcMem;
	if (bitmap) dcMem.SelectObject(*bitmap);

	wxBrush brush = wxBrush(*current_back_color, wxSOLID);
	wxPen pen = wxPen(*current_back_color, 1, wxSOLID);
	dcMem.SetBrush(brush);
	dcMem.SetPen(pen);

	dcMem.SetTextForeground(*current_text_color);
	dcMem.SetTextBackground(*current_back_color);
	dcMem.DrawRectangle(physical_windowleft,
		physical_windowtop,
		physical_windowwidth,
		// adjustment for full-screen window?
		physical_windowheight+(inwindow?0:lineheight*2));

	dcMem.SetBrush(wxNullBrush);
	dcMem.SetPen(wxNullPen);

	/* Only forcing updating when not in a window will make for less
	   flickering
	*/
	if (!inwindow) canvas->needs_updating = true;

#ifdef SCROLLBACK_DEFINED
	/* Send a solid line to the scrollback buffer (unless the buffer is empty)... */
	if (!inwindow && scrollback_pos!=0 &&
		// ...preventing duplicate linebreaks
		((scrollback_pos>4) && scrollback_buffer[scrollback_pos-5]!=wxT('_')))
	{
		if (scrollback_buffer[scrollback_pos-1]!=wxT('\n'))
			hugo_sendtoscrollback((char *)"\n");
		memset(line, '_', 20);
		sprintf(line+20, "\n\n");
		hugo_sendtoscrollback(line);
	}
#endif

#ifdef USE_TEXTBUFFER
	TB_Clear(physical_windowleft, physical_windowtop,
		physical_windowright, physical_windowbottom);
#endif

	/* Must be set: */
	currentpos = 0;
	currentline = 1;
}


void hugo_settextmode(void)
{
/* This function does whatever is necessary to set the system up for
   a standard text display */
	/* charwidth and lineheight are set up by hugo_font();
	   otherwise they would be set up here
	*/

	/* Must be set (as character or pixel coordinates, as
	   applicable--pixels, in this case):
	*/
	frame->GetClientSize(&SCREENWIDTH, &SCREENHEIGHT);

	/* Must be set: */
	hugo_settextwindow(1, 1,
		SCREENWIDTH/FIXEDCHARWIDTH, SCREENHEIGHT/FIXEDLINEHEIGHT);
}

void hugo_settextwindow(int left, int top, int right, int bottom)
{
/* Again, coords. are passed as text coordinates with the top corner (1, 1) */

	canvas->needs_updating = true;
	canvas->Update(FALSE);
	FlushBuffer();

	/* Must be set (as pixel coordinates): */
	physical_windowleft = (left-1)*FIXEDCHARWIDTH;
	physical_windowtop = (top-1)*FIXEDLINEHEIGHT;
	physical_windowright = right*FIXEDCHARWIDTH-1;
	physical_windowbottom = bottom*FIXEDLINEHEIGHT-1;

	/* Correct for full-width windows where the right border would
	   otherwise be clipped to a multiple of charwidth, leaving a sliver
	   of the former window at the righthand side.
	*/
	if (right>=SCREENWIDTH/FIXEDCHARWIDTH)
		physical_windowright = canvas->width-1;
	if (bottom>=SCREENHEIGHT/FIXEDLINEHEIGHT)
		physical_windowbottom = canvas->height-1;

	physical_windowwidth = physical_windowright-physical_windowleft+1;
	physical_windowheight = physical_windowbottom-physical_windowtop+1;

	ConstrainCursor();
}

void hugo_settextpos(int x, int y)
{
/* The top-left corner of the current active window is (1, 1).

   (In other words, if the screen is being windowed so that the top row
   of the window is row 4 on the screen, the (1, 1) refers to the 4th
   row on the screen, and (1, 2) refers to the 5th.)

   This function must also properly set currentline and currentpos (where
   currentline is a the current character line, and currentpos may be
   either in pixels or characters, depending on the measure being used).
*/
	FlushBuffer();
	canvas->Update(FALSE);
	// This is hacky:  why do you need to reset needs_updating in
	// order to properly redraw?  Could be that the above call to
	// Update() is required because hugo_print() is called
	// explicitly from PromptMore() in hemisc.c.
	canvas->needs_updating = true;

	/* Must be set: */
	currentline = y;
	currentpos = (x-1)*FIXEDCHARWIDTH;   /* Note:  zero-based */

	/* current_text_x/row are calculated assuming that the
	   character position (1, 1) is the pixel position (0, 0)
	*/
	current_text_x = physical_windowleft + currentpos;
	current_text_y = physical_windowtop + (y-1)*lineheight;
	ConstrainCursor();
}

void ConstrainCursor(void)
{
	if (current_text_x < physical_windowleft)
		current_text_x = physical_windowleft;
//	if (current_text_x > physical_windowright-charwidth)
//		current_text_x = physical_windowright-charwidth;
	if (current_text_x > physical_windowright)
		current_text_x = physical_windowright;
	if (current_text_y > physical_windowbottom-lineheight+1)
		current_text_y = physical_windowbottom-lineheight+1;
	if (current_text_y < physical_windowtop)
		current_text_y = physical_windowtop;
}

#define MAX_FLUSH_BUFFER 512
static char flush_buffer[MAX_FLUSH_BUFFER] = "";
static int flush_x, flush_y, flush_len;
static char last_was_italic = false;
static char fontcurrent_is_italic = false;

/* We use FlushBuffer() so that we can buffer multiple calls to hugo_print()
 * in order to save on more expensive DrawText() GUI calls.
 */
 
void FlushBuffer(void)
{
	if (flush_len)
	{
		wxMemoryDC dcMem;
		if (bitmap) dcMem.SelectObject(*bitmap);

		flush_buffer[flush_len] = '\0';
		dcMem.SetFont(*fontCurrent);
//#ifdef __WXMAC__
//		if (quartz_rendering)
//		{
			long width, height;
			wxString str(wxString::FromAscii(flush_buffer));
			dcMem.GetTextExtent(str, &width, &height);
			wxBrush brush = wxBrush(*current_back_color, wxSOLID);
			wxPen pen = wxPen(*current_back_color, 1, wxSOLID);
			dcMem.SetBrush(brush);
			dcMem.SetPen(pen);
			dcMem.DrawRectangle(flush_x, flush_y, width, lineheight);
			dcMem.SetBrush(wxNullBrush);
			dcMem.SetPen(wxNullPen);
			
			dcMem.SetBackgroundMode(wxTRANSPARENT);
//		}
//		else
//#endif
//		if (last_was_italic)
//			dcMem.SetBackgroundMode(wxTRANSPARENT);
//		else
//			dcMem.SetBackgroundMode(wxSOLID);
		dcMem.SetOptimization(TRUE);
		dcMem.SetTextForeground(*current_text_color);
		dcMem.SetTextBackground(*current_back_color);
		dcMem.DrawText(wxString::FromAscii(flush_buffer), flush_x, flush_y);
		dcMem.SetLogicalFunction(wxCOPY);
		if (fontcurrent_is_italic)
			last_was_italic = true;
		else
			last_was_italic = false;
		canvas->needs_updating = true;
		flush_len = 0;
	}
}

void hugo_print(char *a)
{
	int i, len;

	len = strlen(a);

	for (i=0; i<len; i++)
	{
		/* If we've passed the bottom of the window, align to the bottom edge */
		if (current_text_y > physical_windowbottom-lineheight)
		{
			int temp_lh = lineheight;
			FlushBuffer();
			lineheight = current_text_y - physical_windowbottom+lineheight;
			current_text_y -= lineheight;
			if (inwindow) lineheight--;
			tweaking_lineheight = true;
			hugo_scrollwindowup();
			tweaking_lineheight = false;
			lineheight = temp_lh;
		}

		switch (a[i])
		{
			case '\n':
				FlushBuffer();
				current_text_y += lineheight;
				last_was_italic = false;
				break;
			case '\r':
				FlushBuffer();
				current_text_x = physical_windowleft;
				last_was_italic = false;
				break;
			default:
			{
				if (flush_len==0)
				{
					flush_x = current_text_x;
					if (inwindow)
						flush_y = current_text_y;
					else
						flush_y = current_text_y+canvas->scroll_offset;
				}
				flush_buffer[flush_len++] = a[i];
				if (flush_len>=MAX_FLUSH_BUFFER-2) FlushBuffer();

				/* Increment the horizontal screen position by
				   the character width
				*/
				current_text_x += hugo_charwidth(a[i]);
			}
		}
		
		/* Check again after printing */
		if (current_text_y > physical_windowbottom-lineheight)
		{
			int temp_lh = lineheight;
			FlushBuffer();
			lineheight = current_text_y - physical_windowbottom+lineheight;
			current_text_y -= lineheight;
			if (inwindow) lineheight--;
			tweaking_lineheight = true;
			hugo_scrollwindowup();
			tweaking_lineheight = false;
			lineheight = temp_lh;
		}
	}
}


/* hugo_sendtoscrollback

	For copying printed text to the scrollback window buffer.
*/

#ifdef SCROLLBACK_DEFINED
void hugo_sendtoscrollback(char *a)
{
	int i;

	/* If the scrollback buffer is getting close to running out of room,
	   shift it backward, truncating at the head of the buffer
	*/
	if (scrollback_pos >= SCROLLBACK_SIZE-(int)strlen(a))
	{
		wxStrcpy(scrollback_buffer, scrollback_buffer+2048);
		scrollback_pos -= 2048;
	}
	
	for (i=0; i<(int)strlen(a); i++)
	{
		if (a[i]=='\n')
		{
#ifndef __WXMAC__
			scrollback_buffer[scrollback_pos++] = wxT('\r');
#endif
			scrollback_buffer[scrollback_pos++] = wxT('\n');
		}
		else if ((unsigned char)a[i]>=' ')
		{
			char c = a[i];
#ifdef __WXMAC__
			char *k = strchr(StringANSI, c);
			if (k!=NULL) c = StringMac[k - StringANSI];
#endif
			scrollback_buffer[scrollback_pos++] = (wxChar)c;
		}
	}
	scrollback_buffer[scrollback_pos] = wxT('\0');
}
#endif	// SCROLLBACK_DEFINED


void hugo_scrollwindowup()	/* one "text" line */
{
	static int old_canvas_width, old_canvas_height;
	int source_x, source_y, dest_x, dest_y, width, height;
	wxColor *scroll_back_color;

#ifdef USE_TEXTBUFFER
	if (lineheight >= ((currentfont&PROP_FONT)?prop_lineheight:FIXEDLINEHEIGHT)/2)
		TB_Scroll();
#endif

	wxMemoryDC dcMem;
	if (bitmap) dcMem.SelectObject(*bitmap);

	scroll_back_color = hugo_color(default_bgcolor);

	wxBrush brush = wxBrush(*scroll_back_color, wxSOLID);
	wxPen pen = wxPen(*scroll_back_color, 1, wxSOLID);
	dcMem.SetBrush(brush);
	dcMem.SetPen(pen);

	/* If not in a window, just move the "window" on the bitmap;
	 * we'll actually physically scroll it later with canvas->Update()
	 */
	if (!inwindow)
	{
		update_bgcolor = hugo_color(default_bgcolor);
		canvas->scroll_offset+=lineheight;
		/* Erase the leading/next line-plus in the scroll area */
		dcMem.DrawRectangle(0, canvas->height+canvas->scroll_offset-lineheight,
			canvas->width, lineheight*2);

		goto FinishedScrolling;
	}
	
	/* Basically just copies a hunk of screen to an upward-
	   shifted position and fills in the screen behind/below it
	*/
	source_x = physical_windowleft;
	source_y = physical_windowtop+lineheight;
	dest_x = physical_windowleft;
	dest_y = physical_windowtop;
	width = physical_windowwidth;
	height = physical_windowheight;

	dcMem.Blit(dest_x, dest_y, width, height,
		&dcMem, source_x, source_y);

	dcMem.DrawRectangle(physical_windowleft,
		physical_windowbottom-lineheight,
		physical_windowwidth,
		lineheight);
		
FinishedScrolling:

	// Restore the default brush and pen
	dcMem.SetBrush(wxNullBrush);
	dcMem.SetPen(wxNullPen);

	old_canvas_width = canvas->width;
	old_canvas_height = canvas->height;

	if (!fast_scrolling && !tweaking_lineheight) canvas->Update(TRUE);
}


// We use precalc_charwidth because dcMem->GetTextExtent() as used in
// hugo_charwidth() is a bit of a bottleneck in the text-rendering
// process.
#define PRECALC_CHARS 52
const wxChar *precalc_charset[PRECALC_CHARS] = {
wxT("A"),wxT("B"),wxT("C"),wxT("D"),wxT("E"),wxT("F"),wxT("G"),
wxT("H"),wxT("I"),wxT("J"),wxT("K"),wxT("L"),wxT("M"),
wxT("N"),wxT("O"),wxT("P"),wxT("Q"),wxT("R"),wxT("S"),wxT("T"),
wxT("U"),wxT("V"),wxT("W"),wxT("X"),wxT("Y"),wxT("Z"),
wxT("a"),wxT("b"),wxT("c"),wxT("d"),wxT("e"),wxT("f"),wxT("g"),
wxT("h"),wxT("i"),wxT("j"),wxT("k"),wxT("l"),wxT("m"),
wxT("n"),wxT("o"),wxT("p"),wxT("q"),wxT("r"),wxT("s"),wxT("t"),
wxT("u"),wxT("v"),wxT("w"),wxT("x"),wxT("y"),wxT("z")
};
long int precalc_charwidth[PRECALC_CHARS];

void hugo_font(int f)
{
	char *facename;
	int family, size;
	int bold = wxNORMAL, italic = wxNORMAL;
	bool underline = FALSE;
	static int last_font_created = -1;

	if (f==last_font_created) return;
	last_font_created = f;

	FlushBuffer();

	if (f & BOLD_FONT)
		bold = wxBOLD;
	if (f & UNDERLINE_FONT)
		underline = TRUE;

	if (f & ITALIC_FONT)
	{
		italic = wxITALIC;
		fontcurrent_is_italic = true;
	}
	else
		fontcurrent_is_italic = false;

	if (f & PROP_FONT)
		family = familyProp, facename = faceProp, size = sizeProp;
	else
		family = familyFixed, facename = faceFixed, size = sizeFixed;

//	if (fontCurrent) delete fontCurrent;
	if (fontCurrent) wxTheFontList->RemoveFont(fontCurrent);
	fontCurrent = wxTheFontList->FindOrCreateFont(size, family, italic, bold, underline, wxString::FromAscii(facename));

	wxMemoryDC dcMem;
	if (bitmap) dcMem.SelectObject(*bitmap);

	if (fontCurrent)
	{
		dcMem.SetFont(*fontCurrent);
		
		lineheight = dcMem.GetCharHeight();
		charwidth = dcMem.GetCharWidth();
		
		if (f & PROP_FONT)
		{
			prop_lineheight = lineheight;
		}
		else
		{
			FIXEDCHARWIDTH = charwidth;
			FIXEDLINEHEIGHT = lineheight;
		}
		
		// May cause display problems with [MORE] prompt if the proportional
		// font is shorter than the fixed font
		if (lineheight < FIXEDLINEHEIGHT)
			prop_lineheight = lineheight = FIXEDLINEHEIGHT;

#ifndef NO_WXCARET
		wxCaret *caret = new wxCaret(canvas, 1, lineheight);
		canvas->SetCaret(caret);
		caret->Show();
#endif
	}

	// Create the precalc_charwidth array for plain proportional font
	if (f==PROP_FONT && !((f&BOLD_FONT) || (f&ITALIC_FONT)))
	{
		int i;
		long int height;

		for (i=0; i<PRECALC_CHARS; i++)
			dcMem.GetTextExtent(precalc_charset[i], 
				&precalc_charwidth[i], &height);
	}
}

void hugo_settextcolor(int c)   /* foreground (print) color */
{
	FlushBuffer();
	current_text_color = hugo_color(c);
}

void hugo_setbackcolor(int c)   /* background color */
{
	FlushBuffer();
	current_back_color = hugo_color(c);
}

#if wxCHECK_VERSION(2, 5, 0)
#define FINDCOLOR(a) wxTheColourDatabase->Find(a)
#else
// deprecated in < 2.5
#define FINDCOLOR(a) wxTheColourDatabase->FindColour(a)
#endif

wxColour *hugo_color(int c)
{
	/* Color-setting functions should always pass the color through
	   hugo_color() in order to properly set default fore/background
	   colors:
	*/

	if (c==16)	return def_fcolor;	// default foreground
	else if (c==17)	return def_bgcolor;	// default background
	else if (c==18) return def_slfcolor;	// statusline foreground
	else if (c==19) return def_slbgcolor;	// statusline background
	else if (c==20) return hugo_color(fcolor);

	switch (c)
	{
		case 0:  return FINDCOLOR(wxT("BLACK"));
		case 1:  return FINDCOLOR(wxT("NAVY"));
		case 2:  return FINDCOLOR(wxT("DARK GREEN"));
		case 3:  return FINDCOLOR(wxT("DARK TURQUOISE"));
		case 4:  return FINDCOLOR(wxT("FIREBRICK"));
		case 5:  return FINDCOLOR(wxT("PURPLE"));
		case 6:  return FINDCOLOR(wxT("BROWN"));
		case 7:  return FINDCOLOR(wxT("LIGHT GREY"));
		case 8:  return FINDCOLOR(wxT("GREY"));
		case 9:  return FINDCOLOR(wxT("BLUE"));
		case 10: return FINDCOLOR(wxT("GREEN"));
		case 11: return FINDCOLOR(wxT("CYAN"));
		case 12: return FINDCOLOR(wxT("RED"));
		case 13: return FINDCOLOR(wxT("MAGENTA"));
		case 14: return FINDCOLOR(wxT("YELLOW"));
		case 15: return FINDCOLOR(wxT("WHITE"));
	}
	
	return FINDCOLOR(wxT("BLACK"));	// never get here, though
}


/* CHARACTER AND TEXT MEASUREMENT */

int hugo_charwidth(char a)
{
	if (a==FORCED_SPACE) a = ' ';

	if ((unsigned char)a >= ' ') /* alphanumeric characters */
	{
		/* proportional */
		if (currentfont & PROP_FONT)
		{
			wxChar b[2];
			long int x, y;

			// Some of the plain prop font has been precalculated
			if (currentfont==PROP_FONT &&
				// But bold or italic will throw the measurement off,
				// so don't use the precalcs for those
				!(currentfont&BOLD_FONT || currentfont&ITALIC_FONT))
			{
				if (a>=wxT('A') && a<=wxT('Z'))
					return (int)precalc_charwidth[a-'A'];
				if (a>=wxT('a') && a<=wxT('z'))
					return (int)precalc_charwidth[26+a-'a'];
			}
			b[0] = (wxChar)a;
			b[1] = '\0';

			wxMemoryDC dcMem;
			if (bitmap) dcMem.SelectObject(*bitmap);
			dcMem.SetFont(*fontCurrent);
			dcMem.GetTextExtent(wxString(b), &x, &y);
			return x;
		}

		/* fixed-width */
		else
			return FIXEDCHARWIDTH;
	}
	
	return 0;
}

int hugo_textwidth(char *a)
{
	int i, slen, len = 0;

	slen = strlen(a);

	for (i=0; i<slen; i++)
	{
		if (a[i]==COLOR_CHANGE) i+=2;
		else if (a[i]==FONT_CHANGE) i++;
		else
			len += hugo_charwidth(a[i]);
	}

	return len;
}

int hugo_strlen(char *a)
{
	int i, slen, len = 0;

	slen = strlen(a);

	for (i=0; i<slen; i++)
	{
		if (a[i]==COLOR_CHANGE) i+=2;
		else if (a[i]==FONT_CHANGE) i++;
		else len++;
	}

	return len;
}

}	// extern "C"




