/************************************************************************\
 * File Version Information
 * $Header: /Altair32v2/windbg_log.c 25    11/08/06 9:22p Racini $
 ************************************************************************/
/************************************************************************\
  MITS Altair Emulator
  Debugger command log window
  
  Main debugger support for the Solace Sol-20 emulator
  Copyright (c) Jim Battle, 2000, 2001

  Modifications for the Altair32 Emulator
  Copyright (c) 2001-2006 Richard A. Cini


Change Log:
  2001/11/07  RAC -- Initial changes
  2001/12/14  RAC -- RELEASE MARKER -- v2.1
  2002/01/02  RAC -- Synched with Solace 3.0
  2002/01/31  RAC -- RELEASE MARKER -- v2.2
  2002/07/07  RAC -- RELEASE MARKER -- v2.3
  2002/08/23  RAC -- RELEASE MARKER -- v2.30.10
  2002/11/15  RAC -- RELEASE MARKER -- v2.40.2100
  2003/04/26  RAC -- RELEASE MARKER -- v2.50.2045
  2004/01/20  RAC -- Diff'ed changes from FJS (Fred J. Scipione)
  2004/07/30  RAC -- RELEASE MARKER -- v3.00.0135
  2006/05/12  RAC -- RELEASE MARKER -- v3.10.0200
  2006/11/15  RAC -- RELEASE MARKER -- v3.20.0400
\************************************************************************/
#include <windows.h>
#include <windowsx.h>	// message cracker macros
#include <stdlib.h>
#include <stdio.h>		// C-lib - I/O
#include <string.h>
#include "altair32.h"
#include "windbg.h"
#include "windbg_sub.h"
#include "extensions.h" // corrections for bad functions in windowsx


#define LOG_LINES   500	// max # of lines in log window

static struct tagLOGSTATE {
    int active;			// boolean
    int lines;			// # bytes in log window output buffer
    int firstline;		// first line of window
    int maxwidth;		// width of longest line in buffer
    char *text[LOG_LINES];	// text for all lines in log
    int  bold[LOG_LINES];	// this line should be highlighted
    int  win_x;			// window width, in pixels
    int  chars_x;		// # chars wide (rounded up)
    int  chars_y;		// # chars high (rounded up)
    int  xoff;			// horizontal scrolling offset
} logstate;


static BOOL
OnCreateLog(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
    register int i;
    logstate.lines     = 0;
    logstate.firstline = 0;
    logstate.maxwidth  = 40;	// default

	// ADDED FJS
	for (i = 0; i < LOG_LINES; )
		logstate.text[i++] = NULL;

    return TRUE;
}


static void
VScrollRangeLog(void)
{
    int oldmin, oldmax;
    int maxvscroll = max(0, (logstate.lines - logstate.chars_y + 1));

    GetScrollRange(dbgstate.hLogWnd, SB_VERT, &oldmin, &oldmax);

    if (oldmax != maxvscroll)
	SetScrollRange(dbgstate.hLogWnd, SB_VERT, 0x0000, maxvscroll, TRUE);

    ShowScrollBar(dbgstate.hLogWnd, SB_VERT, (maxvscroll > 0));
}


// return the width of the longest line in the buffer
static void
HScrollRangeLog(void)
{
    int w = 40;	// claim width is at least 40 chars
    int oldmin, oldmax, maxhscroll;
    register int i;	//JFS

// ADDED JFS
	for (i = 0; (i < LOG_LINES) && (logstate.text[i] /* != NULL */); i++) {
		int len;
		len = strlen(logstate.text[i]);
		if (len > w) w = len;
    }
    logstate.maxwidth = (w + 2);	// a bit of margin

/* DELETED JFS
    for(i=0; i<LOG_LINES; i++) {
	int len;
	if (logstate.text[i] == NULL)
	    break;
	len = strlen(logstate.text[i]);
	if (len > w)
	    w = len;
    }
    logstate.maxwidth = (w + 2);	// a bid of margin
*/

    // adjust so that max scroll puts end of longest
    // line on right side of window
    GetScrollRange(dbgstate.hLogWnd, SB_HORZ, &oldmin, &oldmax);
    maxhscroll = max(0, (logstate.maxwidth - logstate.chars_x));
    if (oldmax != maxhscroll)
	SetScrollRange(dbgstate.hLogWnd, SB_HORZ, 0, maxhscroll, TRUE);

    ShowScrollBar(dbgstate.hLogWnd, SB_HORZ, (maxhscroll > 0));
}


// handle window resize events
static void
OnSizeLog(HWND hwnd, UINT state, int cx, int cy)
{
    logstate.win_x   = cx;
    logstate.chars_x = (cx + dbgstate.fix_x - 1) / dbgstate.fix_x;
    logstate.chars_y = (cy + dbgstate.fix_y - 1) / dbgstate.fix_y;

    VScrollRangeLog();
    HScrollRangeLog();
}


// to minimize flashing, the window class does no automatically
// erase the background on region invalidations; instead, we have
// to be sure to touch every pixel on a repaint.
static void
OnPaintLog(HWND hwnd)
{
    PAINTSTRUCT	ps;
    HDC hdc = BeginPaint(hwnd, &ps);

    if (!logstate.active) {

	// color the display gray & disable scrollbars
	GrayOutWindow(hwnd, hdc);

    } else {

	COLORREF gray  = RGB(0xF0, 0xF0, 0xF0),
		 normcolor;
	HBRUSH graybrush  = CreateSolidBrush(gray),
	       normbrush;
	HPEN   graypen    = CreatePen(PS_SOLID, 1, gray),
//	       normpen, oldpen;	// DELETED FJS
		   normpen;

	int saved = SaveDC(hdc);
	int pixoff = dbgstate.fix_x;	// offset 1 column
	int i;

	// establish the normal background color
	normcolor = GetBkColor(hdc);
	normpen   = CreatePen(PS_SOLID, 1, normcolor);
//	oldpen    = SelectObject(hdc, normpen);	//DELETED FJS
	(void)SelectObject(hdc, normpen);		//ADDED FJS

	SelectFont(hdc, dbgstate.fixed_font);

	for(i=0; i<logstate.chars_y; i++) {

	    int topy =    i * dbgstate.fix_y;	// top    pixel row
	    int boty = topy + dbgstate.fix_y;	// bottom pixel row
	    int len, dobold;
	    char *line;

	    if (i + logstate.firstline >= LOG_LINES)
		// we're off the end of the buffer
		continue;

	    line = logstate.text[i + logstate.firstline];
	    if (line != NULL) {
		len = strlen(line);
		dobold = logstate.bold[i + logstate.firstline];
	    } else
		len = dobold = 0;

	    // account for horizontal scrolling
	    len -= logstate.xoff;
	    if (len < 0) len = 0;

	    if (dobold) {
		SetBkColor(hdc, gray);
		normbrush = SelectObject(hdc, graybrush);
		SelectObject(hdc, graypen);
		Rectangle(hdc, 0, i*dbgstate.fix_y,			// l, t
			  logstate.win_x, (i+1)*dbgstate.fix_y);	// r, b
	    }

	    Rectangle(hdc, 0, topy,		// l, t
			   pixoff, boty);	// r, b

	    if (len > 0)
		TextOut(hdc, pixoff, topy, (line + logstate.xoff), len);

	    // clear to end of line
	    Rectangle(hdc, pixoff+len*dbgstate.fix_x, topy,	// l, t
			   logstate.win_x, boty);		// r, b

	    if (dobold) {
		SetBkColor(hdc, normcolor);
		SelectObject(hdc, normbrush);
		SelectObject(hdc, normpen);
	    }

	} // for i

	RestoreDC(hdc, saved);
	DeleteObject(graybrush);
	DeleteObject(graypen);
	DeleteObject(normpen);
    }

    EndPaint(hwnd, &ps);
}


static void
OnKeyDownLog(HWND hwnd, UINT vk, BOOL down, int repeat, UINT flags)
{
    switch (vk) {

	case VK_UP:    LogWindowAction(WA_LINEUP, 0);    return;
	case VK_DOWN:  LogWindowAction(WA_LINEDOWN, 0);  return;
	case VK_PRIOR: LogWindowAction(WA_PAGEUP, 0);    return;
	case VK_NEXT:  LogWindowAction(WA_PAGEDOWN, 0);  return;
	case VK_HOME:  LogWindowAction(WA_GOTO, 0xFFFF); return;

	case VK_F5:
	case VK_F6:
	case VK_F7:
	    handle_fkey(vk);
	    return;

	default: break;
    }

    // use default message handler
    FORWARD_WM_KEYDOWN(hwnd, vk, repeat, flags, DefWindowProc);
}


static void
OnVScrollLog(HWND hwnd, HWND hctl, UINT code, int pos)
{

    SetFocus(hwnd);	// make keyboard input go to this sub-win

    switch (code) {

	case SB_LINEUP:   LogWindowAction(WA_LINEUP,   0); break;
	case SB_LINEDOWN: LogWindowAction(WA_LINEDOWN, 0); break;
	case SB_PAGEUP:   LogWindowAction(WA_PAGEUP,   0); break;
	case SB_PAGEDOWN: LogWindowAction(WA_PAGEDOWN, 0); break;

#if 1
	case SB_THUMBTRACK:
#else
	// choose this case if display is too slow to scroll in real-time
	case SB_THUMBPOSITION:
#endif
	    // the position reported might be in the middle of a real op.
	    // Perhaps the best thing to do is to back up N instructions
	    // and start at the nearest real op.
	    pos &= 0xFFFF;	// although passed as int, really signed short
	    SetScrollPos(hwnd, SB_VERT, (int)pos, TRUE);
	    LogWindowAction(WA_GOTO, pos);
	    return;
	default: return;
    }
}


static void
OnHScrollLog(HWND hwnd, HWND hctl, UINT code, int pos)
{
    SetFocus(hwnd);	// make keyboard input go to this subwin

    switch (code) {

	case SB_LINEUP:   logstate.xoff--; break;
	case SB_LINEDOWN: logstate.xoff++; break;
	case SB_PAGEUP:   logstate.xoff -= logstate.chars_x/2; break;
	case SB_PAGEDOWN: logstate.xoff += logstate.chars_x/2; break;
#if 1
	case SB_THUMBTRACK:
#else
	case SB_THUMBPOSITION:
#endif
	    logstate.xoff = pos;
	    break;
	default: return;
    }

#if 1
    // limit scroll such that max offset leaves right side
    // of the longest line on the RHS of the window
    if (logstate.xoff > logstate.maxwidth-logstate.chars_x)
	logstate.xoff = logstate.maxwidth-logstate.chars_x;
#endif
    if (logstate.xoff < 0)
	logstate.xoff = 0;

    SetScrollPos(hwnd, SB_HORZ, (int)logstate.xoff, TRUE);
    InvalidateRect(hwnd, NULL, TRUE);
}


// just set focus
static void
OnLButtonUpLog(HWND hWnd, int xClient, int yClient, UINT keyflags)
{
    SetFocus(hWnd);
}


static void
OnDestroyLog(HWND hwnd)
{
    int i;

    // release buffer
    for(i=0; i<LOG_LINES; i++) {
	if (logstate.text[i]) {
	    free(logstate.text[i]);
	    logstate.text[i] = NULL;
	}
    }

    dbgstate.hLogWnd = NULL;
}


// debugger command window handler
static LRESULT CALLBACK
WndProcLog(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
    switch (iMsg) {
	HANDLE_MSG(hWnd, WM_CREATE,    OnCreateLog);
	HANDLE_MSG(hWnd, WM_PAINT,     OnPaintLog);
	HANDLE_MSG(hWnd, WM_SIZE,      OnSizeLog);
	HANDLE_MSG(hWnd, WM_KEYDOWN,   OnKeyDownLog);
	HANDLE_MSG(hWnd, WM_VSCROLL,   OnVScrollLog);
	HANDLE_MSG(hWnd, WM_HSCROLL,   OnHScrollLog);
	HANDLE_MSG(hWnd, WM_DESTROY,   OnDestroyLog);
	HANDLE_MSG(hWnd, WM_LBUTTONUP, OnLButtonUpLog);

	case WM_CHAR:	// forward to cmd edit window
	    windbg_cmd_fwdchar(wParam, lParam);
	    break;

	case WM_GETDLGCODE:
	    return DLGC_WANTALLKEYS /* | DLGC_WANTCHARS to get WM_CHAR messages */;
    }

    return DefWindowProc(hWnd, iMsg, wParam, lParam);
}


void
RegisterLogClass(void)
{
    WNDCLASSEX  wndclass;

    wndclass.cbSize        = sizeof(wndclass);
    wndclass.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wndclass.lpfnWndProc   = WndProcLog;
    wndclass.cbClsExtra    = 0;
    wndclass.cbWndExtra    = 0;
    wndclass.hInstance     = winstate.hInst;
    wndclass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor       = NULL; //LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = NULL; //(HBRUSH)(COLOR_WINDOW + 1);
    wndclass.lpszMenuName  = NULL;
    wndclass.lpszClassName = "solacelog";
    wndclass.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    RegisterClassEx(&wndclass);
}


HWND
CreateLogWindow(HWND hwnd)
{
    DWORD winstyle = WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL;
    DWORD extstyle = WS_EX_STATICEDGE | 0*WS_EX_CLIENTEDGE;
    HWND  hwndlog;

    hwndlog = CreateWindowEx(
		extstyle,		// extended style
		"solacelog",		// window class name
		0,			// window caption
		winstyle,		// window style
		0,0,			// initial x,y position
		0,0,			// initial x,y size
		hwnd,			// parent window handle
		NULL,			// window menu handle
		winstate.hInst,		// program instance handle
		NULL);			// creation parameters

    ASSERT(hwndlog != NULL);

    return hwndlog;
}


// add a string to end of the log window

static void
CmdLogCore(char *str, int bold)
{
    if (logstate.lines == LOG_LINES) {
	// delete oldest line in buffer
	int oldlen = strlen(logstate.text[0]);
	int i;

	free(logstate.text[0]);
	for(i=0; i<LOG_LINES-1; i++) {
	    logstate.text[i] = logstate.text[i+1];
	    logstate.bold[i] = logstate.bold[i+1];
	}
	logstate.lines--;
	logstate.text[logstate.lines] = NULL;

	if (oldlen == logstate.maxwidth) {
	    // we might have removed longest line: recompute it
	    HScrollRangeLog();	// reset horizontal scroll range
	}
    }

    // add line to end of buffer
    logstate.text[logstate.lines] = strdup(str);
    logstate.bold[logstate.lines] = bold;
    logstate.lines++;
    VScrollRangeLog();
    if ((int)strlen(str) > logstate.maxwidth)
	HScrollRangeLog();	// reset horizontal scroll range

    logstate.firstline = (logstate.lines - (logstate.chars_y-1));
    if (logstate.firstline < 0)
		logstate.firstline = 0;
    SetScrollPos(dbgstate.hLogWnd, SB_VERT, logstate.firstline, TRUE);

    // this is rather drastic, but it is simple and it works
    InvalidateRect(dbgstate.hLogWnd, NULL, TRUE);
}

// split a string separated by newlines into individual strings,
// then log each one.
void
UI_DbgWinLog(char *str, int bold)
{
    char *cp = str;
    char buf[300], *bp;

    while (*cp) {
	// grab the next string
	bp = buf;
	while (*cp && (*cp != '\n')) {
	    *bp++ = *cp++;
	}
	if (*cp == '\n')
	    cp++;
	*bp = '\0';	// terminate string
	CmdLogCore(buf, bold);
    }
}

void
LogWindowAction(win_action_t action, int line)
{
    int topline, linediff;
    int i;

    switch (action) {

    case WA_INIT:
	logstate.active    = FALSE;
	logstate.lines     = 0;
	logstate.firstline = 0;
	logstate.xoff      = 0;
	for(i=0; i<LOG_LINES; i++)
	    logstate.text[i] = NULL;
	return;

    case WA_DISABLE:
	ShowScrollBar(dbgstate.hLogWnd, SB_VERT, FALSE);
	ShowScrollBar(dbgstate.hLogWnd, SB_HORZ, FALSE);
	logstate.active = FALSE;
	// force redraw
	InvalidateRect(dbgstate.hLogWnd, NULL, TRUE);
	return;

    case WA_ENABLE:
	logstate.active = (action == WA_ENABLE);
	// hide/show the scroll bar
	VScrollRangeLog();
	HScrollRangeLog();
	// force redraw
	InvalidateRect(dbgstate.hLogWnd, NULL, TRUE);
	return;

    case WA_LINEUP:   topline = logstate.firstline-1; break;
    case WA_LINEDOWN: topline = logstate.firstline+1; break;

    case WA_PAGEUP:   topline = logstate.firstline - logstate.chars_y + 2; break;
    case WA_PAGEDOWN: topline = logstate.firstline + logstate.chars_y - 2; break;

    case WA_GOTO: topline = line; break;

    default:
	return;
    }

    if (topline > logstate.lines - (logstate.chars_y-1))
		topline = logstate.lines - (logstate.chars_y-1);
    if (topline < 0)
		topline = 0;

    // now scroll the window the desired amount
    linediff = logstate.firstline - topline;

    logstate.firstline = topline;

    ScrollWindow(dbgstate.hLogWnd,
		    0, (linediff * dbgstate.fix_y),	// X,Y scroll
		    NULL, NULL);

    // move vertical scroll bar to indicated position
    SetScrollPos(dbgstate.hLogWnd, SB_VERT, topline, TRUE);
}

/* end of file: windbg_log.c */
