/*
Copyright (C) 2006 Evan Teran
                   eteran@alum.rit.edu

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "QDisassemblyView.h"
#include "Util.h"
#include "Debugger.h"
#include <QScrollBar>
#include <QPainter>
#include <QMouseEvent>
#include <QToolTip>
#include <QPixmap>
#include "DebuggerCoreInterface.h"
#include "ArchProcessor.h"
#include <QAbstractItemDelegate>

//------------------------------------------------------------------------------
// Name: QDisassemblyView(QWidget * parent)
// Desc: constructor
//------------------------------------------------------------------------------
QDisassemblyView::QDisassemblyView(QWidget * parent) : QAbstractScrollArea(parent),
		m_AddressOffset(0), m_CurrentAddress(0), m_ShowAddressSeparator(true), m_LongestInstr(8), 
		m_FontMetrics(QFont()), m_SelectedInstructionAddress(0), m_SelectedInstructionSize(0) {
		
	setFont(QFont("Monospace", 10));
	m_Breakpoint			= new QPixmap(":/debugger/pics/brkena.png");
	m_CurrentAddressIcon	= new QPixmap(":/debugger/pics/greenarrow.png");
	
	
#if QT_POINTER_SIZE == 4
	strncpy(m_AddressFormatString, "%04x:%04x", sizeof(m_AddressFormatString));
#elif QT_POINTER_SIZE == 8
	strncpy(m_AddressFormatString, "%08x:%08x", sizeof(m_AddressFormatString));
#endif
	m_AddressFormatString[sizeof(m_AddressFormatString) - 1] = '\0';
}

//------------------------------------------------------------------------------
// Name: ~QDisassemblyView()
// Desc: destructor
//------------------------------------------------------------------------------
QDisassemblyView::~QDisassemblyView() {
	safeDelete(m_Breakpoint);
	safeDelete(m_CurrentAddressIcon);
}

//------------------------------------------------------------------------------
// Name: formatAddress(address_t address)
// Desc: 
//------------------------------------------------------------------------------
QString QDisassemblyView::formatAddress(address_t address) {
#if QT_POINTER_SIZE == 4
	return QString().sprintf(m_AddressFormatString, (address >> 16) & 0xffff, address & 0xffff);
#elif QT_POINTER_SIZE == 8
	return QString().sprintf(m_AddressFormatString, (address >> 32) & 0xffffffff, address & 0xffffffff);
#endif
}

//------------------------------------------------------------------------------
// Name: repaint()
// Desc: 
//------------------------------------------------------------------------------
void QDisassemblyView::repaint() {
	viewport()->repaint();
}

//------------------------------------------------------------------------------
// Name: addressShown(address_t address)
// Desc: returns true if a given address is in the visible range
//------------------------------------------------------------------------------
bool QDisassemblyView::addressShown(address_t address) {
	return m_ShowAddresses.contains(address);
}

//------------------------------------------------------------------------------
// Name: setCurrentAddress(address_t address)
// Desc: sets the "current address" (where EIP is usually)
//------------------------------------------------------------------------------
void QDisassemblyView::setCurrentAddress(address_t address) {
	m_CurrentAddress = address;
	repaint();
}

//------------------------------------------------------------------------------
// Name: setRegion(const MemoryRegions::Region &r)
// Desc: sets the memory region we are viewing
//------------------------------------------------------------------------------
void QDisassemblyView::setRegion(const MemoryRegions::Region &r) {
	m_Region = r;
	updateScrollbars();
	repaint();
}

//------------------------------------------------------------------------------
// Name: clear()
// Desc: clears the display
//------------------------------------------------------------------------------
void QDisassemblyView::clear() {
	setRegion(MemoryRegions::Region());
}

//------------------------------------------------------------------------------
// Name: setAddressOffset(address_t address)
// Desc: 
//------------------------------------------------------------------------------
void QDisassemblyView::setAddressOffset(address_t address) {
	m_AddressOffset = address;
}

//------------------------------------------------------------------------------
// Name: scrollTo(address_t address)
// Desc: 
//------------------------------------------------------------------------------
void QDisassemblyView::scrollTo(address_t address) {
	verticalScrollBar()->setValue(address - m_AddressOffset);
}

//------------------------------------------------------------------------------
// Name: formatInstructionBytes(const uint8_t *buf, int size, int maxStringPix)
// Desc: 
//------------------------------------------------------------------------------
QString QDisassemblyView::formatInstructionBytes(const uint8_t *buf, int size, int maxStringPix) {
	QString byteBuffer;
	
	if(buf != 0 && size > 0) {
	
		const uint8_t *const buf_end = buf + size - 1;
	
		while(buf != buf_end) {
			byteBuffer += QString().sprintf("%02x ", *buf++ & 0xff);
		}
		
		byteBuffer += QString().sprintf("%02x", *buf & 0xff);
	}	
#if QT_VERSION < 0x040200
	return QAbstractItemDelegate::elidedText(m_FontMetrics, maxStringPix, Qt::ElideRight, byteBuffer);
#else
	return m_FontMetrics.elidedText(byteBuffer, Qt::ElideRight, maxStringPix);
#endif
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
QString QDisassemblyView::formatInstruction(const Instruction &insn, const uint8_t *buf, std::size_t size) const {
	if(insn.valid()) {
		return insn.format().c_str();
	} else {
		if(insn.size() <= size) {
			switch(insn.size()) {
			case 0:
			case 1:
				return QString().sprintf("db %02x",buf[0] & 0xff);
			case 2:
				return QString().sprintf("dw %04x", *reinterpret_cast<const uint16_t *>(&buf[0]) & 0xffff);
			case 4:
				return QString().sprintf("dd %08x", *reinterpret_cast<const uint32_t *>(&buf[0]));
			case 8:
				return QString().sprintf("dq %016llx", *reinterpret_cast<const uint64_t *>(&buf[0]));
			default:
				// we tried...didn't we?
				return "invalid";
			}
		}
		
		return "invalid";
	}
}

//------------------------------------------------------------------------------
// Name: paintEvent(QPaintEvent *)
// Desc: 
//------------------------------------------------------------------------------
void QDisassemblyView::paintEvent(QPaintEvent *) {
	
	QPainter painter(viewport());
		
	int viewableLines	= viewport()->height() / m_FontHeight;
	int currentLine		= verticalScrollBar()->value();
	int row_index		= 0;
	int row				= 0;
	int lastSize		= 0;
	
	//TODO: reimplement me 
	// const Configuration::Syntax syntax = Debugger::config()->syntax;
	const int regionSize = m_Region.size();
	
	m_ShowAddresses.clear();
	m_ShowAddresses.insert(m_AddressOffset + currentLine);
	
	const int bytesWidth = line2() - line1();

	while(viewableLines >= 0 && currentLine < regionSize) {

		uint8_t buf[Instruction::MAX_SIZE];

		const address_t address = m_AddressOffset + currentLine;

		// do the longest read we can while still not breaking the read
		int sz = sizeof(buf);			
		if(address + sz > m_Region.end) {
			sz = m_Region.end - address;
		}
		
		const bool ok = Debugger::getInstructionBytes(address, buf, sz);

		// ok we were able to read at least one byte, try to disassemble it
		if(ok) {
			const Instruction insn(buf, sz, address, std::nothrow);
			
			int insn_size = insn.size();
			if(insn_size == 0) {
				++insn_size;
			}
		
			const QString asmBuffer		= formatInstruction(insn, buf, sz);
			const QString byteBuffer	= formatInstructionBytes(buf, insn_size, bytesWidth);
			const QString addressBuffer	= formatAddress(address);
			
			// draw alternating line backgrounds
			if(row_index & 1) {
				painter.fillRect(0, row, width(), m_FontHeight, QBrush(0x00E8E8E8));
			}
			
			// draw breakpoint icon or eip indicator
			if(address == m_CurrentAddress) {
				painter.drawPixmap(1, row + 2, *m_CurrentAddressIcon);
			} else if(Debugger::debuggerBase->findBreakpoint(address) != 0) {
				painter.drawPixmap(1, row + 2, *m_Breakpoint);
			}
			
			// draw the address
			painter.setPen(QPen(Qt::red));
			painter.drawText(m_Breakpoint->width() + 1, row, addressBuffer.length() * m_FontWidth, m_FontHeight, Qt::AlignTop, addressBuffer);
			
			// draw the data bytes
			painter.setPen(QPen(Qt::black));
			painter.drawText(m_Breakpoint->width() + 1 + (addressBuffer.length() + 1) * m_FontWidth, row, byteBuffer.length() * m_FontWidth, m_FontHeight, Qt::AlignTop, byteBuffer);

			// draw the disassembly
			if(Debugger::archProcessor()->isFilling(insn, QByteArray::fromRawData(reinterpret_cast<const char *>(buf), insn_size))) {
				painter.setPen(QPen(Qt::gray));
			} else {
				painter.setPen(QPen(Qt::blue));
			}
			
			painter.drawText(m_Breakpoint->width() + 1 + ((addressBuffer.length() + 1) + m_LongestInstr * 3)  * m_FontWidth, row, asmBuffer.length() * m_FontWidth, m_FontHeight, Qt::AlignTop, asmBuffer);
			
			
			m_ShowAddresses.insert(address);
			
			lastSize = insn_size;
			currentLine += insn_size;
		} else {
			lastSize = 0;
			break;
		}
			
		row += m_FontHeight;
		++row_index;
		--viewableLines;
	}
	
	painter.setPen(QPen(Qt::black));
	painter.drawLine(line1(), 0, line1(), height());
	painter.drawLine(line2(), 0, line2(), height());
}

//------------------------------------------------------------------------------
// Name: setFont(const QFont &f)
// Desc: overloaded version of setFont, calculates font metrics for later
//------------------------------------------------------------------------------
void QDisassemblyView::setFont(const QFont &f) {

	// recalculate all of our metrics/offsets
	m_FontMetrics	= QFontMetrics(f);
	m_FontWidth		= m_FontMetrics.maxWidth();
	m_FontHeight	= m_FontMetrics.height();
	
	updateScrollbars();
	
	// TODO: assert that we are using a fixed font
	// TODO: find out if we care?
	QAbstractScrollArea::setFont(f);
}

//------------------------------------------------------------------------------
// Name: resizeEvent (QResizeEvent *)
// Desc: 
//------------------------------------------------------------------------------
void QDisassemblyView::resizeEvent (QResizeEvent *) {
	updateScrollbars();
}

//------------------------------------------------------------------------------
// Name: updateScrollbars()
// Desc: 
//------------------------------------------------------------------------------
void QDisassemblyView::updateScrollbars() {
	const unsigned int totalLines		= m_Region.size();
	const unsigned int viewableLines	= viewport()->height() / m_FontHeight;
	const unsigned int scrollMax 		= (totalLines > viewableLines) ? totalLines - 1 : 0;
		
	verticalScrollBar()->setMaximum(scrollMax);
}

//------------------------------------------------------------------------------
// Name: line1() const
// Desc: 
//------------------------------------------------------------------------------
int QDisassemblyView::line1() const {
	const unsigned int elements = addressLen();
	return (elements * m_FontWidth) + (m_FontWidth / 2) + m_Breakpoint->width() + 1;
}

//------------------------------------------------------------------------------
// Name: line2() const
// Desc: 
//------------------------------------------------------------------------------
int QDisassemblyView::line2() const {
	return line1() + (m_LongestInstr * 3) * m_FontWidth;
}

//------------------------------------------------------------------------------
// Name: addressLen() const
// Desc: 
//------------------------------------------------------------------------------
int QDisassemblyView::addressLen() const {
	const unsigned int addressLength = (sizeof(address_t) * CHAR_BIT) / 4;
	return addressLength + (m_ShowAddressSeparator ? 1 : 0);
}

//------------------------------------------------------------------------------
// Name: getInstructionSize(uint8_t *buffer, std::size_t size)
// Desc: 
//------------------------------------------------------------------------------
int QDisassemblyView::getInstructionSize(uint8_t *buffer, std::size_t size) {
	Instruction insn(buffer, size, 0, std::nothrow);
	return insn.size();
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
QDisassemblyView::address_t QDisassemblyView::addressFromPoint(const QPoint &pos) const {
	return addressFromCoord(pos.x(), pos.y()) + m_AddressOffset;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
int QDisassemblyView::getInstructionSize(address_t address, bool &ok) const {
	int ret = 0;
	uint8_t buf[Instruction::MAX_SIZE];
	
	// do the longest read we can while still not breaking the read
	std::size_t sz = sizeof(buf);			
	if(m_Region.end != 0 && address + sz > m_Region.end) {
		sz = m_Region.end - address;
	}
	
	ok = Debugger::debuggerBase->readBytes(address, buf, sz);
	while(!ok && sz) {
		ok = Debugger::debuggerBase->readBytes(address, buf, --sz);
	}
		
	if(ok) {
		ret = getInstructionSize(buf, sizeof(buf));
	}
	
	return ret;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
QDisassemblyView::address_t QDisassemblyView::addressFromCoord(int x, int y) const {
	Q_UNUSED(x);
	
	const int line = y / m_FontHeight;
	const address_t offset = verticalScrollBar()->value();
	address_t address = offset;

	// add up all the instructions sizes up to the line we want
	for(int i = 0; i < line; ++i) {
		bool ok;
		int size = getInstructionSize(m_AddressOffset + address, ok);
		if(ok) {
			address += (size != 0) ? size : 1;
		} else {
			address += 1;
		}
	}
	
	return address;
}

//------------------------------------------------------------------------------
// Name: mouseDoubleClickEvent(QMouseEvent *event)
// Desc: 
//------------------------------------------------------------------------------
void QDisassemblyView::mouseDoubleClickEvent(QMouseEvent *event) {
	if(event->button() == Qt::LeftButton) {
		if(event->x() < line1()) {
		
			const address_t address = addressFromPoint(event->pos());
			
			emit breakPointToggled(address);
			repaint();
		}
	}
}

//------------------------------------------------------------------------------
// Name: event(QEvent * event)
// Desc: 
//------------------------------------------------------------------------------
bool QDisassemblyView::event(QEvent * event) {

	if (event->type() == QEvent::ToolTip) {
		bool show = false;
	
		QHelpEvent *const helpEvent = static_cast<QHelpEvent *>(event);

		if(helpEvent->x() >= line1() && helpEvent->x() < line2()) {
		
			const address_t address = addressFromPoint(helpEvent->pos());
			
			uint8_t buf[Instruction::MAX_SIZE];
			if(Debugger::debuggerBase->readBytes(address, buf, sizeof(buf))) {
				const int size = getInstructionSize(buf, sizeof(buf));
				if(size >= m_LongestInstr) {
					// 0xffff is an arbitrary value which is intended to mean "no limit"
					// I wanted INT_MAX, but QT seems to choke on this. :(
					const QString byteBuffer = formatInstructionBytes(buf, size, 0xffff);
					QToolTip::showText(helpEvent->globalPos(), byteBuffer);
					show = true;
				}
			}
		}
		
		if(!show) {
			QToolTip::showText(helpEvent->globalPos(), "");
		}
	}
	
	return QAbstractScrollArea::event(event);
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void QDisassemblyView::mousePressEvent(QMouseEvent *event) {
	bool ok;
	const address_t address	= addressFromPoint(event->pos());
	const int size			= getInstructionSize(address, ok);
	
	if(ok) {
		m_SelectedInstructionAddress	= address;
		m_SelectedInstructionSize		= size;
	} else {
		m_SelectedInstructionAddress	= 0;
		m_SelectedInstructionSize		= 0;
	}
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
QDisassemblyView::address_t QDisassemblyView::selectedAddress() const {
	return m_SelectedInstructionAddress;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
int QDisassemblyView::selectedSize() const {
	return m_SelectedInstructionSize;
}
