/* Copyright (C) 2003 by Alex Kompel <shurikk@pacbell.net> */
/* NetHack may be freely redistributed.  See license for details. */

#include "mhtxtbuf.h"

/* Collect Nethack text messages and render text into edit box.
   Wrap text if necessary.
   Recognize formatted lines as having more that 4 consecutive.
   spaces inside the string.
   Strip leading and trailing spaces.
   Always break at the original line end (do not merge text that comes 
   from NetHack engine)
*/

/*----------------------------------------------------------------*/
#define NHTEXT_BUFFER_INCREMENT 10
/*----------------------------------------------------------------*/
struct text_buffer_line {
	int		attr;
	short	beg_padding;
	short	end_padding;
	BOOL	formatted;
	char*   text;
};
/*----------------------------------------------------------------*/
typedef struct mswin_nethack_text_buffer {
	BOOL b_wrap_text;
	int n_size;
	int n_used;
	struct text_buffer_line *text_buffer_line;
} NHTextBuffer, *PNHTextBuffer;
/*----------------------------------------------------------------*/
#define NHTextLine(pb,i)  ((pb)->text_buffer_line[(i)])
static TCHAR* nh_append( TCHAR* s, int* size, const char* ap );
/*----------------------------------------------------------------*/
PNHTextBuffer mswin_init_text_buffer(BOOL wrap_text)
{
	PNHTextBuffer pb = (PNHTextBuffer)malloc(sizeof(NHTextBuffer));
	if( !pb ) panic("Out of memory");
	
	ZeroMemory(pb, sizeof(NHTextBuffer));
	pb->b_wrap_text = wrap_text;
	pb->n_size = 0;
	pb->n_used = 0;
	pb->text_buffer_line = NULL;
	return pb;
}
/*----------------------------------------------------------------*/
void mswin_free_text_buffer(PNHTextBuffer pb)
{
	int i;
	
	if( !pb ) return;

	for(i=0; i<pb->n_used; i++ ) {
		free(pb->text_buffer_line[i].text);
	}
	free( pb->text_buffer_line );
	free( pb );
}
/*----------------------------------------------------------------*/
void mswin_add_text(PNHTextBuffer pb, int attr, const char* text)
{
	char* p;
	struct text_buffer_line* new_line;

	/* grow buffer */
	if( pb->n_used >= pb->n_size ) {
		pb->n_size += NHTEXT_BUFFER_INCREMENT;
		pb->text_buffer_line =
			(struct text_buffer_line*)realloc( pb->text_buffer_line, pb->n_size*sizeof(struct text_buffer_line) );
		if( !pb->text_buffer_line ) panic("Memory allocation error");
	}

	/* analyze the new line of text */
	new_line = &NHTextLine(pb, pb->n_used);
	new_line->attr = attr;
	new_line->beg_padding = 0;
	new_line->text = strdup(text);
	for( p = new_line->text; *p && isspace(*p); p++ ) {
		new_line->beg_padding++;
	}
	if( *p ) {
		memmove(new_line->text,
				new_line->text + new_line->beg_padding,
				strlen(new_line->text) - new_line->beg_padding + 1
				);
		for( p = new_line->text+strlen(new_line->text); 
			 p>=new_line->text && isspace(*p); 
			 p-- ) {
			new_line->end_padding++;
			*p = 0;
		}

		/* if there are 3 (or more) consecutive spaces inside the string 
		   consider it formatted */
		new_line->formatted = (strstr(new_line->text, "   ")!=NULL);
	} else {
		new_line->end_padding = 0;
		new_line->text[0] = 0;
		new_line->formatted = FALSE;
	}
	pb->n_used++;
}
/*----------------------------------------------------------------*/
static TCHAR* nh_append( TCHAR* s, int* size, const char* ap )
{
	int tlen, tnewlen;

	if( !(ap && *ap) ) return s;

	/* append the calculated line to the text buffer */
	tlen = s? _tcslen(s) : 0;
	tnewlen = tlen+strlen(ap);
	if( tnewlen>=*size ) {
		*size = max(tnewlen, *size + BUFSZ);
		s = (TCHAR*)realloc(s, *size * sizeof(TCHAR));
		if( !s ) panic("Out of memory");
		ZeroMemory(s+tlen, (*size-tlen)*sizeof(TCHAR));
	}
	if( strcmp(ap, "\r\n")==0 ) {
		_tcscat(s, TEXT("\r\n"));
	} else {
		NH_A2W(ap, s+tlen, strlen(ap));
		s[tnewlen] = 0;
	}
	return s;
}
/*----------------------------------------------------------------*/
void mswin_render_text(PNHTextBuffer pb, HWND edit_control)
{
	RECT rt_client;      /* boundaries of the client area of the edit control */
	SIZE size_text;      /* size of the edit control */
	RECT rt_text;        /* calculated text rectangle for the visible line */
	char buf[BUFSZ];     /* buffer for the visible line */
	TCHAR tbuf[BUFSZ];   /* temp buffer for DrawText */
	TCHAR* pText = NULL; /* resulting text (formatted) */
	int pTextSize = 0;   /* resulting text size */
	char* p_cur = NULL;  /* current position in the NHTextBuffer->text_buffer_line->text */
	char* p_buf_cur = NULL; /* current position in the visible line buffer */
	int i;
	HDC hdcEdit;		 /* device context for the edit control */
	HFONT hFont, hOldFont; /* edit control font */

	GetClientRect(edit_control, &rt_client );
	size_text.cx = rt_client.right - rt_client.left;
	size_text.cy = rt_client.bottom - rt_client.top;
	size_text.cx -= GetSystemMetrics(SM_CXVSCROLL); /* add a slight right margin - the text looks better that way */
	hdcEdit = GetDC(edit_control);
	hFont = (HFONT)SendMessage(edit_control, WM_GETFONT, 0, 0);
	if( hFont ) hOldFont = SelectObject(hdcEdit, hFont);

	/* loop through each line (outer loop) and wrap it around (inner loop) */
	ZeroMemory(buf, sizeof(buf));
	p_buf_cur = buf;
	for( i=0; i<pb->n_used; i++ ) {
		if( pb->b_wrap_text ) {
			p_cur = NHTextLine(pb,i).text;

			/* insert an line break for the empty string */
			if( !NHTextLine(pb,i).text[0] ) {
				pText = nh_append(pText, &pTextSize, "\r\n");
				continue;
			}

			/* add margin to the "formatted" line of text */
			if( NHTextLine(pb,i).formatted ) {
				strcpy(buf, "   ");
				p_buf_cur += 3;
			}

			/* scroll thourgh the current line of text and wrap it
			   so it fits to width of the edit control */
			while( *p_cur ) {
				char *p_word_pos = p_buf_cur;

				/* copy one word into the buffer */
				while( *p_cur && isspace(*p_cur) ) 
					if( p_buf_cur!=buf ) *p_buf_cur++ = *p_cur++;
					else p_cur++;

				while( *p_cur && !isspace(*p_cur) ) 
					*p_buf_cur++ = *p_cur++;

				/* check if it fits */
				SetRect( &rt_text, 0, 0, size_text.cx, size_text.cy );
				DrawText(hdcEdit, NH_A2W(buf, tbuf, p_buf_cur-buf), p_buf_cur-buf, &rt_text, DT_CALCRECT | DT_LEFT | DT_SINGLELINE | DT_NOCLIP);
				if( (rt_text.right - rt_text.left)>=size_text.cx ) {
					/* Backtrack. 
					   Only backtrack if the last word caused the overflow -
					   do not backtrack if the entire current line does not fit the visible area.
					   Otherwise it is a infinite loop.
					*/
					if( p_word_pos>buf ) {
						p_cur -= (p_buf_cur-p_word_pos);
						p_buf_cur = p_word_pos;
					}
					*p_buf_cur = 0;  /* break the line */

					/* append the calculated line to the text buffer */
					pText = nh_append(pText, &pTextSize, buf);
					pText = nh_append(pText, &pTextSize, "\r\n");
					ZeroMemory(buf, sizeof(buf));
					p_buf_cur = buf;
				}
			}

			/* always break the line at the end of the buffer text */
			if( p_buf_cur != buf ) {
				/* flush the current buffrer */
				*p_buf_cur = 0;  /* break the line */
				pText = nh_append(pText, &pTextSize, buf);
				pText = nh_append(pText, &pTextSize, "\r\n");
				ZeroMemory(buf, sizeof(buf));
				p_buf_cur = buf;
			}
		} else { /* do not wrap text */
			int j;
			for( j=0; j<NHTextLine(pb,i).beg_padding; j++ ) 
				pText = nh_append(pText, &pTextSize, " ");
			pText = nh_append(pText, &pTextSize, NHTextLine(pb,i).text);
			pText = nh_append(pText, &pTextSize, "\r\n");
		}
	}
	
	/* cleanup */
	if( hFont ) SelectObject(hdcEdit, hOldFont);
	ReleaseDC(edit_control, hdcEdit);

	/* update edit control text */
	if( pText ) {
		SendMessage(edit_control, EM_FMTLINES, 1, 0 );
		SetWindowText(edit_control, pText);
		free(pText);
	}
}
/*----------------------------------------------------------------*/

