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

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

  The window has the following behaviors:
	1) can be "disabled" (while CPU is running)
	2) supports normal scrollbar behavior
	3) supports uparrow/downarrow/pageup/pagedown/home keys
	4) the left column indicates lines that have breakpoints
	5) the current line is in bold
	6) double clicking on a line causes the CPU to begin
	   running and sets a breakpoint on the line clicked on
	7) right clicking brings up a breakpoint dialog


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/05/17  RAC -- Added source code file overlay support from Solace v3.1
  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 "extensions.h"	// corrections for bad functions in windowsx
#include "srcdbg.h"
#include "i8080.h"			//Di80() macro
#include "windbg.h"
#include "windbg_sub.h"


// this structure is used to describe what the heck is on each line of
// the dasm window.  one complicating factor is that one address may
// have to be generated through disassembly, or it may be one line that
// comes from a source file, or it may have multiple source lines.
// an ops_t represents where to find the information to display the
// given line.
typedef struct tagOPS {
    int valid;		// 0 if there is no matching line to display
    int addr;		// which address is the "root" of this line
    int len;		// number of bytes in overlay
    ovr_stat_t mode;	// one of OVERLAY_{OFF,ON,BOTH}
    int lines;		// how many lines of text are associated with addr
    int offset;		// this row is which line of source
    int match;		// 0=overlay didn't match actual bytes
    int final;		// last line of a multiline op
    char *src;		// pointer to text string; terminated by '\n' or '\0'
} ops_t;


static struct tagDASMSTATE {
    BOOL active;	// FALSE=disabled
    WORD toppc;		// PC of instruction at top of window
	int  topoffset;	// text offset of first line of window
    int  win_x;		// window width, in pixels
    int  chars_x;	// # chars wide (rounded up)
    int  chars_y;	// # chars high (rounded up)
	ops_t *ops;		// address of each op of each line
    int  xoff;		// horizontal scrolling offset

    // this is ugly, but here goes.  when the right mouse button
    // goes down to create the popup menu, the handler figures out
    // which PC the mouse is clicking on, and tucks it away here.
    // later when the menu command is received by that WM_COMMAND
    // handler, it looks at it here.
    word rightbut_addr;
    word rightbut_brknum;
} dasmstate;


// given the address of the first op in the window and a phase of
// which line of source should appear on that first line,
// calculate the ops structure for each row of the display.
// off=0 means to show the first line of text associated with
// the line, off=1 means show the second line, etc; normally the
// largest off will be (#lines-1), except in the case where the
// overlay mode is OVERLAY_BOTH, where off=#lines means to just
// disassemble the current PC.
static void
RegenerateOps(word pc, int off)
{
    int   validop;	// if validop is true, the following are valid:
    char *text;		// a multi-line string of source code
    int   lines;	// how many lines there are in text
    int   offset;	// which line of *text we are on
    int   mode;		// how the text should be displayed
    int   match;	// 1=overlay matches actual bytes
    int   final;	// last line of a multiline op
    int   valid;	// this row has valid information
    int   execop;	// executable op

    int oplen;		// number of bytes spanned by op
    int row, hit;
    char *p;

    validop = 0;
    valid   = 1;
    offset  = 0;

    for(row=0; row<dasmstate.chars_y; row++) {

	// learn what we can about the next op
	if (valid && !validop) {
	    hit = OvlHit(pc);
	    switch (hit) {

		case OVLHIT_NO:
		    text    = NULL;
		    lines   = 1;
		    offset  = 0;
		    mode    = OVERLAY_OFF;
		    match   = 0;
		    validop = 0;
		    execop  = 1;	// assume so
		    oplen   = OpLen(pc);
		    break;

		case OVLHIT_YES:
		case OVLHIT_FLAWED:
		    text   = OvlSrc(pc);
		    lines  = OvlLines(pc);
		    mode   = OvlMode(pc);
		    execop = OvlOpType(pc);
		    oplen  = OvlOpLen(pc);
// FIXME: collapse these into one call for efficiency
		    offset = 0;
		    match  = (hit == OVLHIT_YES);
		    validop = 1;
		    if (row == 0) {
			int line = 0;
			offset = off;
			if (off >= lines) {
			    ASSERT(mode == OVERLAY_BOTH);
			    mode = OVERLAY_OFF;	// just disassemble
			    text = NULL;
			} else {
			    while (line != off) {
				ASSERT(*text != '\0');
				line += (*text == '\n');
				text++;
			    }
			}
		    }
		    // don't disassemble non-executable locations
		    if (!execop && (mode == OVERLAY_BOTH))
			mode = OVERLAY_ON;
		    break;

		default:
		    ASSERT(0);
	    } // switch

	    p = text;

	} // if (!validop)


	// if we are in BOTH mode, show the actually disassembly
	// after all lines of the source code is done.
	if (mode == OVERLAY_BOTH && (offset == lines))
	    mode = OVERLAY_OFF;	// just disassemble

	final =  (mode == OVERLAY_OFF) ||
		((mode == OVERLAY_ON) && (offset == lines-1));

	// copy the information to the current row info struct
	dasmstate.ops[row].valid  = valid;
	dasmstate.ops[row].addr   = pc;
	dasmstate.ops[row].len    = oplen;
	dasmstate.ops[row].mode   = mode;
	dasmstate.ops[row].lines  = lines;
	dasmstate.ops[row].offset = offset;
	dasmstate.ops[row].final  = final;
	dasmstate.ops[row].match  = match;
	dasmstate.ops[row].src    = p;


	// see if we are done with this address
	if (final) {
	    if (pc < 0x10000-oplen)
		pc += oplen;
	    else
		valid = 0;	// no more valid instructions after 0xFFFF
	    validop = 0;
	} else if (mode != OVERLAY_OFF) {
	    // skip to next line of text for this address
	    while (*p && (*p != '\n'))
		p++;
	    p++;
	    offset++;
	}

    } // for row
}


static BOOL
OnCreateDasm(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
    // set range for scrollbars
    // vertical runs from 0000 to FFFF
    SetScrollRange(hwnd, SB_VERT, 0x0000, 0xFFFF, FALSE);
    SetScrollPos(hwnd, SB_VERT, (int)I80Regs.nPC.W, TRUE);

    return TRUE;
}


// figure out horizontal scrollbar enable/disable/size
void
RecomputeHScrollbarDasm(void)
{
    HWND hwnd = dbgstate.hDasmWnd;
    int maxlinelen = OvlMaxLineLen();

    if (dasmstate.chars_x < maxlinelen) {
	SetScrollRange(hwnd, SB_HORZ, 0, maxlinelen-dasmstate.chars_x, FALSE);
	ShowScrollBar(hwnd, SB_HORZ, TRUE);
    } else {
	ShowScrollBar(hwnd, SB_HORZ, FALSE);
	dasmstate.xoff = 0;	// in case we had one coming in
    }

    SetScrollPos(hwnd, SB_HORZ, 0, TRUE);
}


// handle window resize events
static void
OnSizeDasm(HWND hwnd, UINT state, int cx, int cy)
{
    dasmstate.win_x = cx;

    dasmstate.chars_x = (cx + dbgstate.fix_x - 1) / dbgstate.fix_x;
    dasmstate.chars_y = (cy + dbgstate.fix_y - 1) / dbgstate.fix_y;
    if (dasmstate.chars_y < 1)
	dasmstate.chars_y = 1;

    RecomputeHScrollbarDasm();

    // store the starting PC for each instruction in the window
    if (dasmstate.ops != NULL)
	free(dasmstate.ops);
    dasmstate.ops = malloc(dasmstate.chars_y * sizeof(ops_t));
    ASSERT(dasmstate.ops != NULL);
    RegenerateOps(dasmstate.toppc, dasmstate.topoffset);
}


static void
OnPaintDasm(HWND hwnd)
{
    PAINTSTRUCT	ps;
    HDC hdc;

    hdc = BeginPaint(hwnd, &ps);

    if (!dasmstate.active) {

	// gray out the surface
	GrayOutWindow(hwnd, hdc);

    } else {

	int saved = SaveDC(hdc);
	int row;

	COLORREF white  = RGB(0xFF, 0xFF, 0xFF);
	COLORREF gray   = RGB(0x80, 0x80, 0x80);
	COLORREF black  = RGB(0x00, 0x00, 0x00);
	COLORREF yellow = RGB(0xFF, 0xFF, 0x00);
	COLORREF red    = RGB(0xFF, 0x00, 0x00);

	HBRUSH whitebrush  = CreateSolidBrush(      white);
	HPEN   whitepen    = CreatePen(PS_SOLID, 1, white);
	HBRUSH yellowbrush = CreateSolidBrush(      yellow);
	HPEN   yellowpen   = CreatePen(PS_SOLID, 1, yellow);
	HBRUSH redbrush    = CreateSolidBrush(      red);
	HPEN   redpen      = CreatePen(PS_SOLID, 1, red);

	SelectFont(hdc, dbgstate.fixed_font);

	for(row=0; row<dasmstate.chars_y; row++) {

	    char dasmbuf[80], dasmbuf2[80];
	    word dasmpc = dasmstate.ops[row].addr;
	    BOOL curpc = (dasmpc == I80Regs.nPC.W)
		       & dasmstate.ops[row].final;
	    int bp;	// 1 if breakpoint is set on this PC
	    int len;	// number of bytes in intruction
	    int match;	// 1=source code matches actual bytes
	    int j;

	    // fill in address/databytes
	    if (dasmstate.ops[row].final) {
		int oplen = dasmstate.ops[row].len;
		oplen = (oplen > 5) ? 5 : oplen;
		sprintf(dasmbuf, "  %04X ", dasmpc);  // left 2 spaces open
		for(j=0; j<oplen; j++)
		    sprintf(&dasmbuf[7+2*j], "%02X", Di80((word)(dasmpc+j)));
		for(j=7+2*oplen; j<18; j++)
		    dasmbuf[j] = ' ';
		bp = check_brk(dasmpc, BRK_OP);
	    } else {
		for(j=0; j<18; j++)
		    dasmbuf[j] = ' ';
		bp = 0;
	    }

	    if (dasmstate.ops[row].mode == OVERLAY_OFF) {
		(void)DAsm(dasmbuf2, dasmpc, 0);
		len = 18 + sprintf(&dasmbuf[18], " %s", dasmbuf2);
		match  = 1;
	    } else {
		// just show the text
		char *p = dasmstate.ops[row].src;
		for(len=18; *p && (*p != '\n'); len++)
		    dasmbuf[len] = *p++;
		match = dasmstate.ops[row].match;
	    }

	    len -= dasmstate.xoff;
	    if (len < 0)
		len = 0;

	    if (!match)
		SetTextColor(hdc, red);
	    else if ((dasmstate.ops[row].mode == OVERLAY_BOTH) &&
	             (dasmstate.ops[row].offset < dasmstate.ops[row].lines))
		SetTextColor(hdc, gray);
	    else
		SetTextColor(hdc, black);

	    if (dasmstate.ops[row].valid && curpc) {
		SetBkColor(hdc, yellow);
		SelectObject(hdc, yellowbrush);
		SelectObject(hdc, yellowpen);
	    } else {
		SetBkColor(hdc, white);
		SelectObject(hdc, whitebrush);
		SelectObject(hdc, whitepen);
	    }

	    if (!dasmstate.ops[row].valid) {
		Rectangle(hdc,
			  0, row*dbgstate.fix_y,			// l, t
			  dasmstate.win_x, (row+1)*dbgstate.fix_y);	// r, b
	    } else {
		// erase from the end of the text to the RHS of the window
		Rectangle(hdc,
			  len*dbgstate.fix_x, row*dbgstate.fix_y,	// l, t
			  dasmstate.win_x, (row+1)*dbgstate.fix_y);	// r, b

		if (len > 0)
		    TextOut(hdc, 0, row*dbgstate.fix_y,
				 &dasmbuf[dasmstate.xoff], len);
	    }

	    // if dasmpc == breakpoint, draw stop sign in left column
	    if (dasmstate.ops[row].valid && bp) {

		// calculate circle coordinates
		int dia = min(dbgstate.fix_x, dbgstate.fix_y);
		int ctr_2y = ((row+row+1)*dbgstate.fix_y);
		int ctr_2x = 2*dbgstate.fix_x;

		SelectObject(hdc, redpen);

		if (bp > 0)
		    SelectObject(hdc, redbrush);

		Ellipse(hdc,
			(ctr_2x - dia)/2, (ctr_2y - dia)/2,	// l, t
			(ctr_2x + dia)/2, (ctr_2y + dia)/2);	// r, b
	    } // if (bp)

	} // for row

	RestoreDC(hdc, saved);

	DeleteObject(whitebrush);
	DeleteObject(whitepen);
	DeleteObject(yellowbrush);
	DeleteObject(yellowpen);
	DeleteObject(redbrush);
	DeleteObject(redpen);
    }

    EndPaint(hwnd, &ps);
}

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

	case VK_UP:    DasmWindowAction(WA_LINEUP, 0);          return;
	case VK_DOWN:  DasmWindowAction(WA_LINEDOWN, 0);        return;
	case VK_PRIOR: DasmWindowAction(WA_PAGEUP, 0);          return;
	case VK_NEXT:  DasmWindowAction(WA_PAGEDOWN, 0);        return;
	case VK_HOME:  DasmWindowAction(WA_GOTO, I80Regs.nPC.W); 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
OnDestroyDasm(HWND hwnd)
{
    if (dasmstate.ops != NULL) {
	free(dasmstate.ops);
	dasmstate.ops = NULL;
    }

    dbgstate.hDasmWnd = NULL;
}


static void
OnVScrollDasm(HWND hwnd, HWND hctl, UINT code, int pos)
{
    word pc;

    SetFocus(hwnd);	// make keyboard input go to this subwin

    switch (code) {

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

	case SB_THUMBTRACK:
	    {
		// show current value in real time, centered
		HDC hdc = GetDC(hwnd);
		COLORREF bg       = RGB(0x00, 0xFF, 0xFF);
		COLORREF oldcolor = SetBkColor(hdc, bg);
		char buf[10];
		int len = sprintf(buf, " %04X  ", (pos & 0xFFFF));

		TextOut(hdc, dbgstate.fix_x * (dasmstate.chars_x-4)/2,
			     dbgstate.fix_y * (dasmstate.chars_y)  /2,
			     &buf[dasmstate.xoff], len);

		SetBkColor(hdc, oldcolor);
		ReleaseDC(hwnd, hdc);
	    }
	    return;

	case SB_THUMBPOSITION:
	    // 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
	    pc = (pos < 3*10) ? 0 : (pos - 3*10);	// 10 max length ops
	    // FIXME: something smarter that uses overlay information better?
	    while (pc < pos) {
		int len = OvlOpLen(pc);
		if (pc+len > pos)
		    break;
		pc += len;
	    }
	    SetScrollPos(hwnd, SB_VERT, (int)pc, TRUE);
	    DasmWindowAction(WA_GOTO, pc);
	    return;
	default:
		return;
    }

    // we get here from SB_{LINE,PAGE}{UP,DOWN}
    // get current top line and set scrollbar position
    pc = dasmstate.ops[0].addr;
    SetScrollPos(hwnd, SB_VERT, (int)pc, TRUE);

}


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

    switch (code) {

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

    if (dasmstate.xoff > 80-dasmstate.chars_x)
	dasmstate.xoff = 80-dasmstate.chars_x;

    if (dasmstate.xoff < 0)
	dasmstate.xoff = 0;

    SetScrollPos(hwnd, SB_HORZ, (int)dasmstate.xoff, TRUE);
    
	// FIXME: it would probably be smoother to scroll instead of redrawing everything...
	InvalidateRect(hwnd, NULL, TRUE);
}


// if we control click on a line, we cycle between three states:
//     no breakpoint, enabled breakpoint, disabled breakpoint
static void
OnLButtonUpDasm(HWND hWnd, int xClient, int yClient, UINT keyflags)
{
    int row = yClient / dbgstate.fix_y;
    int brkstate;
    word addr;
    char cmdstr[40];

    if (~keyflags & MK_CONTROL) {
	// control key isn't pressed; just set focus
	SetFocus(hWnd);
	return;
    }

    // figure out which address corresponds to that row.
    // it is a bit dirty to directly access the window state
    // instead of through an abstraction, but what the hell...
	addr = (word)dasmstate.ops[row].addr;

    brkstate = check_brk(addr, BRK_OP);
    if (brkstate == 0) {
	// no breakpoint -- so create one
	sprintf(cmdstr, "bpc %04x", addr);
    } else if (brkstate < 0) {
	// disabled breakpoint exists; remove it
	sprintf(cmdstr, "bkill #%d", -brkstate);
    } else /* (brkstate > 0) */ {
	// active breakpoint exists; disable it
	sprintf(cmdstr, "bdisable #%d", brkstate);
    }

    UI_DbgWinLog(cmdstr, TRUE);	// echo it
    dbg_interp(cmdstr);		// interpret it
}


// right clicking in the Dasm window creates a pop-up menu
static void
OnRButtonDownDasm(HWND hWnd, BOOL dblclk, int xClient, int yClient, UINT keyflags)
{
    int row = yClient / dbgstate.fix_y;
    int brkstate;
    word addr;
    HMENU hmenu;
    POINT mousepos;
    UINT greyed;
    char str[40];

    SetFocus(hWnd);

    hmenu = CreatePopupMenu();
    if (!hmenu)
	return;	// oops

    // figure out which address corresponds to that row.
    // it is a bit dirty to directly access the window state
    // instead of through an abstraction, but what the hell...
	addr = (word)dasmstate.ops[row].addr;
	dasmstate.rightbut_addr   = addr;

    brkstate = check_brk(addr, BRK_OP);
    dasmstate.rightbut_brknum = (brkstate < 0) ? -brkstate : brkstate;

    greyed = (brkstate == 0) ? MF_ENABLED : MF_GRAYED;
    sprintf(str, "&Add Breakpoint at pc=%04X", addr);
    AppendMenu(hmenu, MF_STRING | greyed, ID_ADDBREAKPOINT,     str);

    greyed = (brkstate >  0) ? MF_ENABLED : MF_GRAYED;
    sprintf(str, "&Disable Breakpoint #%d (%04X)", dasmstate.rightbut_brknum, dasmstate.rightbut_addr);
    AppendMenu(hmenu, MF_STRING | greyed, ID_DISABLEBREAKPOINT, str);

    greyed = (brkstate <  0) ? MF_ENABLED : MF_GRAYED;
    sprintf(str, "&Enable Breakpoint #%d (%04X)", dasmstate.rightbut_brknum, dasmstate.rightbut_addr);
    AppendMenu(hmenu, MF_STRING | greyed, ID_ENABLEBREAKPOINT,  str);

    greyed = (brkstate != 0) ? MF_ENABLED : MF_GRAYED;
    sprintf(str, "&Kill Breakpoint #%d (%04X)", dasmstate.rightbut_brknum, dasmstate.rightbut_addr);
    AppendMenu(hmenu, MF_STRING | greyed, ID_KILLBREAKPOINT,    str);

    AppendMenu(hmenu, MF_STRING | MF_SEPARATOR, 0,     NULL);

    sprintf(str, "&Run to pc=%04X", addr);
    AppendMenu(hmenu, MF_STRING, ID_RUNBREAKPOINT,     str);

    AppendMenu(hmenu, MF_STRING | MF_SEPARATOR, 0,     NULL);

    AppendMenu(hmenu, MF_STRING, ID_HOMEPC,            "&Home current PC");

    // convert mouse coords to screen coords for use by TrackPopupMenu
    mousepos.x = xClient;
    mousepos.y = yClient;
    ClientToScreen(hWnd, &mousepos);

    TrackPopupMenu(hmenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON,
		    mousepos.x, mousepos.y, 0, hWnd, NULL);

    DestroyMenu(hmenu);
}


// double clicking on a line sets a temporary breakpoint on the
// specified instruction.
static void
OnLButtonDblClkDasm(HWND hWnd, BOOL dblclk, int xClient, int yClient, UINT keyflags)
{
    int row = yClient / dbgstate.fix_y;
    word addr;
    char cmdstr[40];

    if (keyflags & MK_CONTROL)
	return;	// control key is pressed

    addr = (word)dasmstate.ops[row].addr;

    sprintf(cmdstr, "to %04x", addr);
    UI_DbgWinLog(cmdstr, TRUE);	// echo it
    dbg_interp(cmdstr);		// interpret it
}


static void 
OnCommandDasm(HWND hwnd, int id, HWND hctl, UINT codenotify)
{
    char cmdstr[40];

    switch (id) {
	case ID_ADDBREAKPOINT:
	    sprintf(cmdstr, "bpc %04x", dasmstate.rightbut_addr);
	    break;
	case ID_DISABLEBREAKPOINT:
	    sprintf(cmdstr, "bdisable #%d", dasmstate.rightbut_brknum);
	    break;
	case ID_ENABLEBREAKPOINT:
	    sprintf(cmdstr, "benable #%d", dasmstate.rightbut_brknum);
	    break;
	case ID_KILLBREAKPOINT:
	    sprintf(cmdstr, "bkill #%d", dasmstate.rightbut_brknum);
	    break;
	case ID_RUNBREAKPOINT:
	    sprintf(cmdstr, "to %04x", dasmstate.rightbut_addr);
	    break;
	case ID_HOMEPC:
	    DasmWindowAction(WA_GOTO, I80Regs.nPC.W);
	    return;
	default:
	    return;
    }
    UI_DbgWinLog(cmdstr, TRUE);	// echo it
    dbg_interp(cmdstr);		// interpret it

    // we aren't handling it, pass it through to default handler
    FORWARD_WM_COMMAND(hwnd, id, hctl, codenotify, DefWindowProc);
}


// debugger disassembler window handler
static LRESULT CALLBACK
WndProcDasm(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
    switch (iMsg) {

	HANDLE_MSG(hWnd, WM_CREATE,        OnCreateDasm);
	HANDLE_MSG(hWnd, WM_SIZE,          OnSizeDasm);
	HANDLE_MSG(hWnd, WM_PAINT,         OnPaintDasm);
	HANDLE_MSG(hWnd, WM_KEYDOWN,       OnKeyDownDasm);
	HANDLE_MSG(hWnd, WM_VSCROLL,       OnVScrollDasm);
	HANDLE_MSG(hWnd, WM_HSCROLL,       OnHScrollDasm);
	HANDLE_MSG(hWnd, WM_DESTROY,       OnDestroyDasm);
	HANDLE_MSG(hWnd, WM_LBUTTONUP,     OnLButtonUpDasm);
	HANDLE_MSG(hWnd, WM_RBUTTONDOWN,   OnRButtonDownDasm);
	HANDLE_MSG(hWnd, WM_LBUTTONDBLCLK, OnLButtonDblClkDasm);
	HANDLE_MSG(hWnd, WM_COMMAND,       OnCommandDasm);

	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
RegisterDasmClass(void)	// ADDED JFS
{
    WNDCLASSEX  wndclass;

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

    RegisterClassEx(&wndclass);
}


HWND
CreateDasmWindow(HWND hwnd)
{
    DWORD winstyle = WS_CHILD | WS_VSCROLL | WS_HSCROLL | WS_VISIBLE;
    DWORD extstyle = WS_EX_CLIENTEDGE;
    HWND  hwnddasm;

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

    ASSERT(hwnddasm != NULL);

    return hwnddasm;
}


// find which line of which address preceeds the one passed in.
// return 1 if there is a previous, 0 if there isn't.
int
prev_dasm_line(int addr, int offset, int *prev_addr, int *prev_offset)
{
    int prev, lines, mode, execop;

    ASSERT(addr >=0 && addr <= 0xFFFF);

    if (offset > 0) {
	*prev_addr   = addr;
	*prev_offset = offset-1;
	return 1;
    }

    prev   = OvlPreviousOp((word)addr);
    lines  = OvlLines(prev);
    mode   = OvlMode(prev);
    execop = OvlOpType(prev);
    *prev_addr = prev;
    if (mode == OVERLAY_BOTH && execop)
	*prev_offset = lines;
    else
	*prev_offset = lines-1;

    return (prev != addr) || (offset != lines-1);
}


// find which line of which address follows the one passed in.
// return 1 if there is a next, 0 if there isn't.
int
next_dasm_line(int addr, int offset, int *next_addr, int *next_offset)
{
    int len, lines;

    ASSERT(addr >=0 && addr <= 0xFFFF);

    lines  = OvlLines(addr);

    if (offset < (lines - 1)) {
	*next_addr   = addr;
	*next_offset = offset+1;
	return 1;
    }

    if (offset == (lines-1)) {
	int mode   = OvlMode(addr);
	int execop = OvlOpType(addr);
	if (mode == OVERLAY_BOTH && execop) {
	    *next_addr   = addr;
	    *next_offset = offset+1;
	    return 1;
	}
    }

    // just decode the next instruction
    len = OvlOpLen((word)addr);
    if (addr + len > 0xFFFF) {
	*next_addr   = addr;
	*next_offset = offset;
	return 0;	// no successor
    }

    *next_addr   = addr+len;
    *next_offset = 0;
    return 1;
}


void
DasmWindowAction(win_action_t action, word addr)
{
    int dasmpc, dasmoffset = 0;
    int linediff = 0;
    int jump = 0;
    int i = 0;

    switch (action) {

    case WA_INIT:
	dasmstate.ops = NULL;
	dasmstate.active = FALSE;
	dasmstate.xoff = 0;
	dasmstate.toppc = 0;
	dasmstate.topoffset = 0;
	return;

    case WA_DISABLE:
    case WA_ENABLE:
	dasmstate.active = (action == WA_ENABLE);
	// hide/show the scroll bar
	ShowScrollBar(dbgstate.hDasmWnd, SB_VERT, dasmstate.active);
	ShowScrollBar(dbgstate.hDasmWnd, SB_HORZ, dasmstate.active);
	// force redraw
	InvalidateRect(dbgstate.hDasmWnd, NULL, TRUE);
	return;

    case WA_LINEUP:
	linediff = prev_dasm_line( dasmstate.ops[0].addr,
				   dasmstate.ops[0].offset,
				  &dasmpc, &dasmoffset);
	break;

    case WA_PAGEUP:
	linediff = 0;
	dasmpc     = dasmstate.ops[0].addr;
	dasmoffset = dasmstate.ops[0].offset;
	for(i=0; i<dasmstate.chars_y-2; i++)
	    linediff += prev_dasm_line(  dasmpc,  dasmoffset,
					&dasmpc, &dasmoffset);
	break;

    case WA_LINEDOWN:
	linediff = -next_dasm_line(dasmstate.ops[0].addr,
				   dasmstate.ops[0].offset,
				   &dasmpc, &dasmoffset);
	break;

    case WA_PAGEDOWN:
	linediff = 0;
	dasmpc     = dasmstate.ops[0].addr;
	dasmoffset = dasmstate.ops[0].offset;
	for(i=0; i<dasmstate.chars_y-2; i++)
	    linediff -= next_dasm_line(  dasmpc,  dasmoffset,
					&dasmpc, &dasmoffset);
	break;

    case WA_GOTO:
	// see if the actual op line is in the window
	for(i=0; i<dasmstate.chars_y-1; i++) {
	    if ((dasmstate.ops[i].addr == addr) &&
		(dasmstate.ops[i].final)) {
		// the indicated line is already in the window, but
		// may need to redraw to indicate current position
		InvalidateRect(dbgstate.hDasmWnd, NULL, TRUE);
		return;
	    }
	}

	jump = 1;	// give repositioning more leeway later

	// start screen three lines back to give more context for
	// the current instruction.  however, if the current line
	// is more than one line long, give less preamble.
	dasmpc     = addr;
	dasmoffset = 0;
	{
	    int skip = 4 - (OvlLines(addr)>>1);
	    for(i=0; i<skip; i++)
		(void)prev_dasm_line( dasmpc,  dasmoffset,
				     &dasmpc, &dasmoffset);
	}
	linediff = +999;	// force redraw
	break;

    case WA_DASMREFRESH:
	// redraw everything in case of mode change, like overlay on/off
	dasmpc     = dasmstate.ops[0].addr;
	dasmoffset = dasmstate.ops[0].offset;
	break;

    default:
	ASSERT(0);
	return;
    }

    // figure out what falls on each row of the window
    RegenerateOps((word)dasmpc, dasmoffset);

#if 1
    // make sure we haven't scrolled up such that there are no valid
    // ops at the bottom of the screen.
    if ((dasmstate.chars_y > 2) &&
	!dasmstate.ops[dasmstate.chars_y-2].valid) {
	for(i=dasmstate.chars_y-2; i>0; i--) {
	    if (!dasmstate.ops[i].valid) {
		linediff += prev_dasm_line( dasmpc,  dasmoffset,
					   &dasmpc, &dasmoffset);
	    }
	}
	RegenerateOps((word)dasmpc, dasmoffset);
    }
#endif

    // if jump is true, then scroll up or down to optimize the location
    // of the actual op: first, if there is more than one line for the
    // op, try and fit it all on the screen, and if that can't be done,
    // at least make sure the final part of the op is visible.
    if (jump) {
	int reposition = 1;
	for(i=0; i<dasmstate.chars_y-1; i++) {
	    if ((dasmstate.ops[i].addr == addr) &&
		(dasmstate.ops[i].final)) {
		reposition = 0;
		break;
	    }
	}
	if (reposition) {
	    // the bottom isn't there; scroll until it is onscreen
	    int diff = dasmstate.ops[dasmstate.chars_y-1].lines
	             - dasmstate.ops[dasmstate.chars_y-1].offset;
	    while (diff > 0) {
		(void)next_dasm_line( dasmpc,  dasmoffset,
				     &dasmpc, &dasmoffset);
		diff--;
	    }
	    RegenerateOps((word)dasmpc, dasmoffset);
	}
    }

    // move vertical scroll bar to indicated position
    dasmstate.toppc     = dasmpc;	// save in case of resize
    dasmstate.topoffset = dasmoffset;	// save in case of resize
    SetScrollPos(dbgstate.hDasmWnd, SB_VERT, (int)dasmpc, TRUE);

    if ((linediff > 100) || (linediff < -100))
	InvalidateRect(dbgstate.hDasmWnd, NULL, TRUE);
    else
	ScrollWindow(dbgstate.hDasmWnd,
			0, (linediff * dbgstate.fix_y),	// X,Y scroll
			NULL, NULL);
}
/* end of file: windbg_dasm.c */
