/*
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 <QAbstractItemDelegate>
#include "DebuggerCoreInterface.h"
#include "ArchProcessorInterface.h"

#define DEFAULT_BYTE_WIDTH 8

//------------------------------------------------------------------------------
// Name: QDisassemblyView(QWidget * parent)
// Desc: constructor
//------------------------------------------------------------------------------
QDisassemblyView::QDisassemblyView(QWidget * parent) : QAbstractScrollArea(parent),
		m_AddressOffset(0), m_CurrentAddress(0), 
		m_Breakpoint(":/debugger/pics/brkena.png"),
		m_CurrentAddressIcon(":/debugger/pics/greenarrow.png"),
		m_ShowAddressSeparator(true), m_FontMetrics(QFont()), 
		m_SelectedInstructionAddress(0), m_SelectedInstructionSize(0), 
		m_MovingLine1(false), m_MovingLine2(false), m_Line1(0), m_Line2(0) {
	
#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';
	
	setFont(QFont("Monospace", 8));
	setMouseTracking(true);
	setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
}

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

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

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

//------------------------------------------------------------------------------
// Name: setCurrentAddress(edb::address_t address)
// Desc: sets the "current address" (where EIP is usually)
//------------------------------------------------------------------------------
void QDisassemblyView::setCurrentAddress(edb::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(edb::address_t address)
// Desc: 
//------------------------------------------------------------------------------
void QDisassemblyView::setAddressOffset(edb::address_t address) {
	m_AddressOffset = address;
}

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

//------------------------------------------------------------------------------
// Name: formatInstructionBytes(const quint8 *buf, int size, int maxStringPix) const
// Desc: 
//------------------------------------------------------------------------------
QString QDisassemblyView::formatInstructionBytes(const quint8 *buf, int size, int maxStringPix) const {
	QString byteBuffer;
		
	if(buf != 0 && size > 0) {
	
		const quint8 *const buf_end = buf + size - 1;
		QString tempByte;
	
		while(buf != buf_end) {
			byteBuffer += tempByte.sprintf("%02x ", *buf++ & 0xff);
		}
		
		byteBuffer += tempByte.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 quint8 *buf, std::size_t size, bool upper) const {
	QString ret;
	
	if(insn.valid()) {
		ret = insn.format(upper).c_str();
	} else {
		if(insn.size() <= size) {
			switch(insn.size()) {
			case 0:
			case 1:
				ret.sprintf("db %02x", buf[0] & 0xff);
				break;
			case 2:
				ret.sprintf("dw %04x", *reinterpret_cast<const quint16 *>(&buf[0]) & 0xffff);
				break;
			case 4:
				ret.sprintf("dd %08x", *reinterpret_cast<const quint32 *>(&buf[0]) & 0xffffffff);
				break;
			case 8:
				ret.sprintf("dq %016llx", *reinterpret_cast<const quint64 *>(&buf[0]));
				break;
			default:
				// we tried...didn't we?
				return "invalid";
			}
		} else {
			return "invalid";
		}
	}
	return ret;
}

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

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

		quint8 buf[Instruction::MAX_SIZE];

		const edb::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 = edb::v1::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();
			
			// we need it to be at least 1
			if(insn_size == 0) {
				++insn_size;
			}
		
			const QString asmBuffer		= formatInstruction(insn, buf, sz, uppercase);
			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(edb::v1::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(
				l1 + m_FontWidth / 2,
				row, 
				byteBuffer.length() * m_FontWidth, 
				m_FontHeight, 
				Qt::AlignTop, 
				byteBuffer);


			// for relative jumps draw the jump direction indicators
			switch(insn.type()) {
			case Instruction::OP_JCC:
			case Instruction::OP_JMP:
			case Instruction::OP_LOOP:
			case Instruction::OP_LOOPE:
			case Instruction::OP_LOOPNE:
				if(insn.operand(0).generalType() == Operand::TYPE_REL) {
					painter.drawText(
						l2 + m_FontWidth / 2,
						row, 
						m_FontWidth, 
						m_FontHeight, 
						Qt::AlignTop,
						QString((insn.operand(0).relativeTarget() > address) ? QChar(0x02C7) : QChar(0x02C6))
					);
				}
				break;
			default:
				break;
			}

			// draw the disassembly
			if(edb::v1::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_FontWidth + l2 + m_FontWidth / 2,
				row, 
				asmBuffer.length() * m_FontWidth, 
				m_FontHeight, 
				Qt::AlignTop, 
				asmBuffer);

			lastSize = insn_size;
			currentLine += insn_size;
			
			m_ShowAddresses.insert(address);
			
		} else {
			lastSize = 0;
			break;
		}
			
		row += m_FontHeight;
		++row_index;
		--viewableLines;
	}
	
	painter.setPen(QPen(Qt::black));
	painter.drawLine(l1, 0, l1, height());
	painter.drawLine(l2, 0, l2, 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: autoLine1() const
// Desc: 
//------------------------------------------------------------------------------
int QDisassemblyView::autoLine1() const {
		const unsigned int elements = addressLen();
		return (elements * m_FontWidth) + (m_FontWidth / 2) + m_Breakpoint.width() + 1;
}

//------------------------------------------------------------------------------
// Name: line1() const
// Desc: 
//------------------------------------------------------------------------------
int QDisassemblyView::line1() const {
	if(m_Line1 == 0) {
		return autoLine1();
	} else {
		return m_Line1;
	}
}

//------------------------------------------------------------------------------
// Name: line2() const
// Desc: 
//------------------------------------------------------------------------------
int QDisassemblyView::line2() const {
	if(m_Line2 == 0) {
		return line1() + (DEFAULT_BYTE_WIDTH * 3) * m_FontWidth;
	} else {
		return m_Line2;
	}
}

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

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

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

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
int QDisassemblyView::getInstructionSize(edb::address_t address, bool &ok, quint8 *buf, int &size) const {
	int ret = 0;
	ok = edb::v1::getInstructionBytes(address, buf, size);
		
	if(ok) {
		ret = InstructionSize(buf, size);
	}
	
	return ret;
}

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

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
edb::address_t QDisassemblyView::addressFromCoord(int x, int y) const {
	Q_UNUSED(x);
	
	const int line = y / m_FontHeight;
	const edb::address_t offset = verticalScrollBar()->value();
	edb::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 edb::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;
	
		const QHelpEvent *const helpEvent = static_cast<QHelpEvent *>(event);

		if(helpEvent->x() >= line1() && helpEvent->x() < line2()) {
		
			const edb::address_t address = addressFromPoint(helpEvent->pos());
			
			quint8 buf[Instruction::MAX_SIZE];
			int size = sizeof(buf);
			bool ok;
			size = getInstructionSize(address, ok, buf, size);
			
			if(ok) {
			
				if((line1() + (size * 3) * m_FontWidth) > line2()) {
					// 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(), QString());
		}
	}
	
	return QAbstractScrollArea::event(event);
}

//------------------------------------------------------------------------------
// Name: mouseReleaseEvent(QMouseEvent *event)
// Desc: 
//------------------------------------------------------------------------------
void QDisassemblyView::mouseReleaseEvent(QMouseEvent *event) {

	Q_UNUSED(event);
	
	m_MovingLine1 = false;
	m_MovingLine2 = false;

	setCursor(Qt::ArrowCursor);
	repaint();
}

//------------------------------------------------------------------------------
// Name: updateSelectedAddress(QMouseEvent *event)
// Desc: 
//------------------------------------------------------------------------------
void QDisassemblyView::updateSelectedAddress(QMouseEvent *event) {
	bool ok;
	const edb::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: mousePressEvent(QMouseEvent *event)
// Desc: 
//------------------------------------------------------------------------------
void QDisassemblyView::mousePressEvent(QMouseEvent *event) {
	
	if(event->button() == Qt::LeftButton) {
		if(event->x() == line1()) {
			m_MovingLine1 = true;
		} else if(event->x() == line2()) {
			m_MovingLine2 = true;
		} else {
			updateSelectedAddress(event);
		}
	} else {
		updateSelectedAddress(event);
	}
}

//------------------------------------------------------------------------------
// Name: mouseMoveEvent(QMouseEvent *event)
// Desc: 
//------------------------------------------------------------------------------
void QDisassemblyView::mouseMoveEvent(QMouseEvent *event) {

	if(m_MovingLine1) {
		if(event->x() >= autoLine1() && event->x() + m_FontWidth < line2()) {
			if(m_Line2 == 0) {
				m_Line2 = line2();
			}
			m_Line1 = event->x();
		}
		repaint();
	} else if(m_MovingLine2) {
		if(event->x() > line1() + m_FontWidth && event->x() + 1 < width() - (verticalScrollBar()->width() + 3)) {
			m_Line2 = event->x();
		}
		repaint();
	} else {
		if(event->x() == line1() || event->x() == line2()) {
			setCursor(Qt::SplitHCursor);
		} else {
			setCursor(Qt::ArrowCursor);
		}
	}
}

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

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