/*-*-c++-*-
 * $Id: cdumpviewdisplay.cpp,v 1.4 2002/04/24 10:21:26 holzheu Exp $
 *
 * This file is part of qlcrash, a GUI for the dump-analysis tool lcrash.
 *
 * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *
 * Authors:
 * Michael Geselbracht (let@users.sourceforge.net)
 * Fritz Elfert (elfert@de.ibm.com)
 * Michael Holzheu (holzheu@de.ibm.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version. See the file COPYING for more
 * information.
 */
#include "osdep.h"

#include "cdumpviewdisplay.h"

#include "qlcrash.h"
#include "qlcrashdoc.h"
#include "cmainwin.h"
#include "cconfigmanager.h"
#include "cpaintarea.h"
#include "cprintexprdialog.h"
#include "cdumpview.h"

#include <qtimer.h>
#include <qpixmap.h>
#include <qpopupmenu.h>
#include <qlabel.h>
#include <qclipboard.h>
#include <qpainter.h>

#include <stdio.h>
#include <ctype.h>
#include <math.h> // pow()

using namespace qlc;

CDumpViewDisplay::CDumpViewDisplay(QLcrashApp* app, QWidget *parent, const char *name)
	: QScrollView(parent, name)
	, oApp(app)
	, oDumpFormat(qlc::Word)
	, oDumpType(qlc::Hex)
	, oAddr((UINT64)0)
	, oBlocksize(0)
	, oSelection(false) // nothing selected
	, oDeleting(false)
{
	setFrameStyle(QFrame::Panel | QFrame::Sunken);
	setResizePolicy(AutoOne);
		
	oArea = new CPaintArea(this);
	oArea->setBackgroundColor(white);
	oArea->setFrameStyle(QFrame::Box | QFrame::Plain);	
	addChild(oArea);
	
	checkEndianity();
	oWordSize = oApp->document()->targetWordSize();
	
	oTimer = new QTimer(this);
	connect(oTimer, SIGNAL(timeout()), SLOT(slotTimeout()));
	connect(oArea, SIGNAL(sigMousePressed(QMouseEvent*)), SLOT(slotAreaMousePressed(QMouseEvent*)));
	connect(oArea, SIGNAL(sigMouseMoved(QMouseEvent*)), SLOT(slotAreaMouseMoved(QMouseEvent*)));
	connect(oArea, SIGNAL(sigMouseDoubleClicked(QMouseEvent*)), SLOT(slotAreaMouseDoubleClicked(QMouseEvent*)));
	connect(oArea, SIGNAL(sigPaintEvent()), SLOT(slotAreaPaintEvent()));
}

CDumpViewDisplay::~CDumpViewDisplay()
{
	oDeleting = true;
}

void
CDumpViewDisplay::resizeEvent(QResizeEvent* e)
{
	QScrollView::resizeEvent(e);
	oArea->resize(oArea->width(), visibleHeight() - 2);
	postUpdate();
}

void
CDumpViewDisplay::paintEvent(QPaintEvent*)
{
	postUpdate();
}

void
CDumpViewDisplay::drawContents(QPainter*, int, int, int, int)
{
	postUpdate();
}

void
CDumpViewDisplay::updateSelection(QMouseEvent* e)
{
	oSelection = false;
	
	if (e->y() < 5 || e->x() < 5 || e->y() > visibleHeight() || e->x() > visibleWidth()) {
		return;
	}
	
	// column
	int col = e->x() - 5 - oWidthAddr - 10 + oWidthDumpSpace;
	if (col > oWidthDump) {
		return;
	}
	
	if (col > 0) {
		col = col / ((oDigitsPerBlock + 1) * oWidthDumpSpace);
	}
	else {
		if (col < -oWidthAddr - 5) {
			return;
		}
		col = -1;
	}
	
	// line
	UINT64 addr = (e->y() - 5) / oFontHeight * 16 + oAddr;
	
	oMarkedCol = col;
	oMarkedRow = addr;
	
	oMarkedAddr = addr + ((col > 0) ? col * oBlocksize : 0);
	
	char buf[30];
#ifdef WIN32
	sprintf(buf, (oWordSize == 4) ? "%.8I64x" : "%.16I64x", oMarkedAddr);
#else
	sprintf(buf, (oWordSize == 4) ? "%08qx" : "%016qx", oMarkedAddr);
#endif
	if (oBlocksize > 0) {
		emit sigMessage(tr("Address") + ":" + buf, 0);
	}
	
	oSelection = true;
	
	updateDump();
}

void
CDumpViewDisplay::slotDisplayFormat(int f)
{
	oDumpFormat = (qlc::DumpFormat) f;
	
	// keep marked position
	if (oMarkedCol != -1) {
		int nb = 0;
		switch (oDumpFormat) {
			case Byte:
				nb = 1;
				break;
			case HalfWord:
				nb = 2;
				break;
			case Word:
				nb = 4;
				break;
			case DoubleWord:
				nb = 8;
				break;
		}
		assert(nb != 0);
		int s = oBlocksize * oMarkedCol / nb;
		oMarkedCol = s;
	}
	
	parentWidget()->setFocus();
	updateDump();
}

void
CDumpViewDisplay::slotDisplayType(int t)
{
	oDumpType = (qlc::DumpType) t;
	parentWidget()->setFocus();
	updateDump();
}

void
CDumpViewDisplay::slotDisplayByteOrder(int bo)
{
	bool w = oUseLittleEndian;
	oUseLittleEndian = bo == 0;
	
	if (w != oUseLittleEndian) {
		updateDump();
	}
	
	parentWidget()->setFocus();
}

void
CDumpViewDisplay::displayAddress(UINT64 addr)
{
	char buf[30];
	oAddr = addr;
#ifdef WIN32
	sprintf(buf, "%I64x", addr);
#else
	sprintf(buf, "%qx", addr);
#endif
	setCaption(buf);
	updateDump();
}

void
CDumpViewDisplay::updateDump()
{
	if (oDeleting)
		return;
	QPixmap pm(oArea->size());
	pm.fill(oArea->backgroundColor());
	QPainter p(&pm);
	p.setBackgroundColor(oArea->backgroundColor());
	p.drawRect(0, 0, oArea->width(), oArea->height());
	
	//! set font - update option menu
	CConfigManager* cm = oApp->document()->configManager();
	QString fname = cm->item(CFG_GRP_CONSOLE, CFG_ATT_CONSOLE_FONTFAMILY, "Courier");
	int fsize = cm->item(CFG_GRP_CONSOLE, CFG_ATT_CONSOLE_FONTSIZE, font().pointSize());
	p.setFont(QFont(fname, fsize));
	QFontMetrics fm(p.font());
	oFontHeight = fm.height();
	oFontAscent = fm.ascent();
	
	switch (oDumpFormat) {
		case DoubleWord:
			oBlocksize = 8;
			break;
		case Word:
			oBlocksize = 4;
			break;
		case HalfWord:
			oBlocksize = 2;
			break;
		case Byte:
			oBlocksize = 1;
			break;
	}
		
	// format line
	const char* aHex[] = { "2", "4", "8", "0", "16" };
	const char* aDec[] = { "3", "5", "10", "0", "20" };
	const char* aOct[] = { "3", "6", "11", "0", "22" };
	const int idx = oBlocksize / 2;
	switch (oDumpType) {
		case Hex:
#ifdef WIN32
			oFmt = QString("%.") + aHex[idx] + "I64x";
#else
			oFmt = QString("%0") + aHex[idx] + "llx";
#endif
			oDigitsPerBlock = atoi(aHex[idx]);
			break;
		case Decimal:
#ifdef WIN32
			oFmt = QString("%.") + aDec[idx] + "I64u";
#else
			oFmt = QString("%0") + aDec[idx] + "llu";
#endif
			oDigitsPerBlock = atoi(aDec[idx]);
			break;
		case Octal:
#ifdef WIN32
			oFmt = QString("%.") + aOct[idx] + "I64o";
#else
			oFmt = QString("%0") + aOct[idx] + "llo";
#endif
			oDigitsPerBlock = atoi(aOct[idx]);
			break;
	}
	
	// calculate required view width
	oNSpace = (oDumpFormat==DoubleWord)?1:(oDumpFormat==Word)?3:(oDumpFormat==HalfWord)?7:15;
	oWidthAddr = fm.maxWidth() * (1 + 2*oWordSize); // address + ':'
	oWidthASCII = fm.maxWidth() * 16;
	oWidthDumpSpace = fm.maxWidth();
	oWidthDump = fm.maxWidth() * (oNSpace+1) * oDigitsPerBlock + oNSpace * oWidthDumpSpace;
	oArea->resize(
		oWidthAddr + oWidthDump + oWidthASCII + 2*oWidthDumpSpace + 15,
		oArea->height()
	);

	// paint sections
	QColor secColor(220, 220, 220);
	p.fillRect(3, 3, oWidthAddr + 2, oArea->height() - 6, secColor);
	p.fillRect(oWidthAddr + 3 + oWidthDumpSpace - 2, 3, oWidthDump + 4, oArea->height() - 6, secColor);
	p.fillRect(oWidthAddr + 3 + oWidthDumpSpace + oWidthDump + oWidthDumpSpace, 3, oWidthASCII + 6, oArea->height() - 6, secColor);
	
	// draw lines
	const int rows = oArea->height() / fm.height() - 1;
	for (int i = 0; ((i < rows) && (!oDeleting)); i++) {
		paintCell(&p, i);
	}
	if (oDeleting)
		return;

	bitBlt(oArea, 0, 0, &pm, 0, 0, pm.width(), pm.height(), CopyROP, true);
}

void
CDumpViewDisplay::paintCell(QPainter* p, int row)
{
	UINT64 addr = oAddr + 16*row;
	char buf[30], selection[30];
	selection[0] = '\0';
	
	// do not exceed the address space of the target machine
	if (addr + 15 >= ((unsigned long)-1)) {
		return;
	}
	
	// address selected ?
	if (oSelection && oMarkedCol == -1 && addr == oMarkedRow) { // yes
		p->fillRect(5, 5 + row * oFontHeight, oWidthAddr, oFontHeight, yellow);
#ifdef WIN32
		sprintf(selection, "0x%I64x", addr);
#else
		sprintf(selection, "0x%qx", addr);
#endif
	}
	
	// draw address
	if (oWordSize == 4) { // 32Bit
#ifdef WIN32
		sprintf(buf, "%.8I64x", addr);
#else
		sprintf(buf, "%08qx:", addr);
#endif
	}
	else { // 64Bit
#ifdef WIN32
		sprintf(buf, "%.16I64x", addr);
#else
		sprintf(buf, "%016qx:", addr);
#endif
	}
	p->drawText(5, 5 + row*oFontHeight + oFontAscent, buf);
	
	// draw dump
	
#ifdef WIN32
	sprintf(buf, "%I64x", addr);
#else
	sprintf(buf, "%qx", addr);
#endif
	const unsigned char* line = oApp->document()->getDump(buf);
	if (line == 0) {
		return;
	}	
	QString str = "";
	QCString number;
	for (int n = 0; n < 16; n += oBlocksize) {
		if (n > 0) {
			str += " ";
		}

		UINT64 val = getValue(oUseLittleEndian, addr, n, oBlocksize);
		sprintf(buf, oFmt, val);
		str += buf;
		
		if (oSelection && oMarkedRow == addr && oMarkedCol == n / oBlocksize) {
			strcpy(selection, buf);
		}
	}
	
	// selection?
	if (oSelection && addr == oMarkedRow && oMarkedCol != -1) {
		int x = 5 + oWidthAddr + oWidthDumpSpace + oMarkedCol * oWidthDumpSpace * oDigitsPerBlock
			+ oMarkedCol * oWidthDumpSpace;
		if (x < 5 + oWidthAddr + oWidthDumpSpace + oWidthDump) {
			// dump
			p->fillRect(
				x,
				5 + row * oFontHeight,
				oWidthDumpSpace * oDigitsPerBlock,
				oFontHeight,
				yellow
			);
			// ascii (is this useful?)
			x = 5 + oWidthAddr + oWidthDumpSpace + oWidthDump + oWidthDumpSpace
				+ oMarkedCol * oBlocksize * oWidthDumpSpace;
			p->fillRect(
				x,
				5 + row * oFontHeight,
				oWidthDumpSpace * oBlocksize,
				oFontHeight,
				yellow
			);
		}
	}
	
	p->drawText(5 + oWidthAddr + oWidthDumpSpace, 5 + row * oFontHeight + oFontAscent, str);
	
	// print ascii
	str = "";
	for (int i = 0; i < 16; i++) {
		unsigned char c = line[i];
		str += (c >= 32 && c < 127) ? c : '.';
	}
	
	p->drawText(5 + oWidthAddr + oWidthDumpSpace + oWidthDump + oWidthDumpSpace, 5 + row * oFontHeight + oFontAscent, str);
	
	// copy selection to clipboard
	if (selection[0] != '\0') {
		QApplication::clipboard()->setText(selection);
	}
}

void
CDumpViewDisplay::slotTimeout()
{
	updateDump();
}

void
CDumpViewDisplay::slotAreaPaintEvent()
{
	postUpdate();
}

void
CDumpViewDisplay::slotAreaMouseMoved(QMouseEvent* e)
{
	updateSelection(e);
}

void
CDumpViewDisplay::slotAreaMousePressed(QMouseEvent* e)
{
	// set focus to parent
	if (parentWidget() != 0 && !parentWidget()->hasFocus()) {
		parentWidget()->setFocus();
	}
	
	updateSelection(e);
	
	if (oSelection && e->button() == RightButton) {
		// context menu is allowed for the (main) dump part
		if (e->x() < 10 + oWidthAddr || e->x() > 5 + oWidthAddr + 5 + oWidthDump) {
			return;
		}
		
		// create popup menu
		QPopupMenu* menu = new QPopupMenu(this);
		char buf[30];
#ifdef WIN32
		sprintf(buf, "%I64x", oMarkedAddr);
#else
		sprintf(buf, "%qx", oMarkedAddr);
#endif
		QLabel* title = new QLabel(QString("0x") + buf, menu);
		title->setAutoResize(true);
		title->update();
		menu->insertItem(title);
		menu->insertSeparator();
		menu->insertItem("print", 0);
		menu->insertItem("print*", 1);
		menu->insertSeparator();
		menu->insertItem("strace", 2);
		menu->insertItem("strace*", 3);
		menu->insertSeparator();
		menu->insertItem("findsym", 4);
		menu->insertItem("findsym*", 5);
		menu->insertSeparator();
		menu->insertItem(tr("Display in new window"), 6);
		menu->insertSeparator();
		
		QPopupMenu* sub = new QPopupMenu(menu);
		char c = getValue(oUseLittleEndian, oMarkedRow, oMarkedCol * oBlocksize, 1);
		short s = getValue(oUseLittleEndian, oMarkedRow, oMarkedCol * oBlocksize, 2);
		int i = getValue(oUseLittleEndian, oMarkedRow, oMarkedCol * oBlocksize, 4);
		INT64 l = getValue(oUseLittleEndian, oMarkedRow, oMarkedCol * oBlocksize, 8);
		
		// int8
		QLabel* line = new QLabel(QString("int8:\t") + QString::number(c, 10), sub);
		line->setAutoResize(true);
		line->update();
		sub->insertItem(line);
		
		// int16
		line = new QLabel(QString("int16:\t") + QString::number(s, 10), sub);
		line->setAutoResize(true);
		line->update();
		sub->insertItem(line);
		
		// int32
		line = new QLabel(QString("int32:\t") + QString::number(i, 10), sub);
		line->setAutoResize(true);
		line->update();
		sub->insertItem(line);
		
		// int64
		sprintf(buf, "%lld", l);
		line = new QLabel(QString("int64:\t") + buf, sub);
		line->setAutoResize(true);
		line->update();
		sub->insertItem(line);
		
		// uint8:
		line = new QLabel(QString("uint8:\t") + QString::number((unsigned char) c, 10), sub);
		line->setAutoResize(true);
		line->update();
		sub->insertItem(line);
		
		// uint16
		line = new QLabel(QString("uint16:\t") + QString::number((unsigned short) s, 10), sub);
		line->setAutoResize(true);
		line->update();
		sub->insertItem(line);
		
		// uint32
		line = new QLabel(QString("uint32:\t") + QString::number((unsigned int) i, 10), sub);
		line->setAutoResize(true);
		line->update();
		sub->insertItem(line);
		
		// uint64
		sprintf(buf, "%llu", l);
		line = new QLabel(QString("uint64:\t") + buf, sub);
		line->setAutoResize(true);
		line->update();
		sub->insertItem(line);
		
		menu->insertItem(tr("Standard types"), sub);
		
		int id = menu->exec(e->globalPos());
		delete menu;
		
		const unsigned char* cptr;
		UINT64 addr;
		switch (id) {
			case 0:		// print
				printDialog(false);
				break;
			case 1:
				printDialog(true);	// print*
				break;
			case 2:		// strace
#ifdef WIN32
				sprintf(buf, "%I64x", oMarkedAddr);
#else
				sprintf(buf, "%qx", oMarkedAddr);
#endif
				oApp->document()->slotFromConsole(0, QString("strace 0x") + buf);
				break;
			case 3:		// strace*
				addr = getValue(oTargetLittleEndian, oMarkedRow, oMarkedCol * oBlocksize, oApp->document()->targetWordSize());
#ifdef WIN32
				sprintf(buf, "%I64x", addr);
#else
				sprintf(buf, "%qx", addr);
#endif
				oApp->document()->slotFromConsole(0, QString("strace 0x") + buf);
				break;
			case 4:		// findsym
#ifdef WIN32
				sprintf(buf, "%I64x", oMarkedAddr);
#else
				sprintf(buf, "%qx", oMarkedAddr);
#endif
				oApp->document()->slotFromConsole(0, QString("findsym 0x") + buf);
				break;
			case 5:		// findsym*
				addr = getValue(oTargetLittleEndian, oMarkedRow, oMarkedCol * oBlocksize, oApp->document()->targetWordSize());
#ifdef WIN32
				sprintf(buf, "%I64x", addr);
#else
				sprintf(buf, "%qx", addr);
#endif
				oApp->document()->slotFromConsole(0, QString("findsym 0x") + buf);
				break;
			case 6:
				addr = getValue(oTargetLittleEndian, oMarkedRow, oMarkedCol * oBlocksize, oApp->document()->targetWordSize());
#ifdef WIN32
				sprintf(buf, "%I64x", addr);
#else
				sprintf(buf, "%qx", addr);
#endif
				cptr = oApp->document()->getDump(buf);
				if (line == 0) {
					emit sigMessage(tr("Cannot display address 0x%1").arg(buf), 3000);
				}
				else {
					new CDumpView(addr, oApp, 0);
				}
				break;
		}
		
		postUpdate();
	}
}

void
CDumpViewDisplay::slotAreaMouseDoubleClicked(QMouseEvent* e)
{
	if (oSelection && e->button() == LeftButton && oMarkedCol >= 0) {
		// do not jump in double word view on a 32 bit architectue
		if (oWordSize == 4 && oBlocksize == 8) {
			emit sigMessage(tr("Cannot jump in doubleword view"), 3000);
			return;
		}
		
		// read value at address 'oMarkedAddr'
		bool w;
		UINT64 addr = getValue(
			oTargetLittleEndian,
			oMarkedRow,
			oMarkedCol * oBlocksize,
			oWordSize,
			&w
		);
		
		if (!w) {
			emit sigMessage(tr("Not possible"), 3000);
			return;
		}
		
		// o.k. jump to that address
		if (e->state() == ShiftButton) { // open a new window
			new CDumpView(addr, oApp, 0);
		}
		else {
			displayAddress(addr);
			emit sigAddrJump();
		}
	}
}

void
CDumpViewDisplay::postUpdate()
{
	oTimer->start(20, true);
	
	// the timer won't start if there is an open dialog somewhere
	if (!oTimer->isActive()) {
		slotTimeout();
	}
}

void
CDumpViewDisplay::checkEndianity()
{
	volatile short test = 1;
	oLittleEndian = (*((char*)&test) == 1) ? true : false;
	oTargetLittleEndian = oApp->document()->targetLittleEndian();
}

UINT64
CDumpViewDisplay::getValue(bool le, UINT64 base, int offset, int size, bool* success)
{
	assert(offset < 16 && offset >= 0);
	assert(size > 0 && size <= 8);
	UINT64 retval = (UINT64)0;
	char buf[30];
#ifdef WIN32
	sprintf(buf, "%I64x", base);
#else
	sprintf(buf, "%qx", base);
#endif
	const unsigned char* line = oApp->document()->getDump(buf);
	
	if (line != 0) {
		unsigned char* cptr = (unsigned char*) &retval;
		if (!oLittleEndian) { // big endian is evil...
			cptr += sizeof(UINT64) - size;
		}
		for (int i = 0; i < size; i++) {
			*cptr++ = line[offset + i];
			if (offset + i == 15) {
#ifdef WIN32
				sprintf(buf, "%I64x", base + (UINT64)16);
#else
				sprintf(buf, "%qx", base + 16ULL);
#endif
				line = oApp->document()->getDump(buf);
				if (line == 0) {
					if (success != 0) {
						*success = false;
					}
					return 0UL;
				}
				offset = 0;
			}
		}
		
		// byte order ?
		if (le != oLittleEndian) {
			// swap...
			cptr = (unsigned char*) &retval;
			if (!oLittleEndian) {
				cptr += sizeof(UINT64) - size;
			}
			for (int i = 0; i < size/2 ; i++) {
				unsigned char tmp = cptr[i];
				cptr[i] = cptr[size-i-1];
				cptr[size-i-1] = tmp;
			}
		}
		
		if (success != 0) {
			*success = true;
		}
	}
	else {
		if (success != 0) {
			*success = false;
		}
	}
	
	return retval;
}

void
CDumpViewDisplay::printDialog(bool pointer)
{
	UINT64 addr;
	char buf[30];
	QString str;
	QLcrashDoc* doc = oApp->document();
	if (pointer) {
		addr = getValue(
		oTargetLittleEndian,
		oMarkedRow,
		oMarkedCol * oBlocksize,
		doc->targetWordSize()
		);
	} else  {
		addr = oMarkedAddr;
	}
#ifdef WIN32
	sprintf(buf, "0x%I64x", addr);
#else
	sprintf(buf, "0x%qx", addr);
#endif
	str = buf;

	oApp->view()->printDumpDialog()->setText(buf,true);
	int ret = oApp->view()->printDumpDialog()->exec();
	if (ret == QDialog::Accepted && !oApp->view()->printDumpDialog()->text().isEmpty()) {
		oApp->document()->slotFromConsole(0, "graph print" + doc->printFormat() + oApp->view()->printDumpDialog()->text());
	}
}
