/*
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 "Debugger.h"
#include <QScrollBar>
#include <QPainter>
#include <QMouseEvent>
#include "DebuggerCoreInterface.h"
#include "Configuration.h"
#include <libdis.h>

//------------------------------------------------------------------------------
// Name: QDisassemblyView(QWidget * parent)
// Desc: constructor
//------------------------------------------------------------------------------
QDisassemblyView::QDisassemblyView(QWidget * parent) : QAbstractScrollArea(parent),
		m_FirstVisibleAddress(0), m_LastVisibleAddress(0), m_AddressOffset(0), 
		m_CurrentAddress(0), m_ShowAddressSeparator(true), m_LongestInstr(8) {
		
	setFont(QFont("Monospace", 10));
	m_Breakpoint			= QPixmap(":/debugger/pics/brkena.png");
	m_CurrentAddressIcon	= QPixmap(":/debugger/pics/greenarrow.png");
	
	x86_init(opt_none, 0, 0);
	
	// TODO: this assumes 32-bit address type
	strncpy(m_AddressFormatString, "%04x:%04x", sizeof(m_AddressFormatString));
	m_AddressFormatString[sizeof(m_AddressFormatString) - 1] = '\0';
}

//------------------------------------------------------------------------------
// Name: ~QDisassemblyView()
// Desc: destructor
//------------------------------------------------------------------------------
QDisassemblyView::~QDisassemblyView() {
	x86_cleanup();
}

//------------------------------------------------------------------------------
// Name: formatAddress(address_t address)
// Desc: 
//------------------------------------------------------------------------------
QString QDisassemblyView::formatAddress(address_t address) {
	// TODO: this assumes 32-bit address type	
	return QString().sprintf(m_AddressFormatString, (address >> 16) & 0xffff, address & 0xffff);
}

//------------------------------------------------------------------------------
// Name: addressShown(address_t address)
// Desc: returns true if a given address is in the visible range
//------------------------------------------------------------------------------
bool QDisassemblyView::addressShown(address_t address) {
	return address >= m_FirstVisibleAddress && address < m_LastVisibleAddress;
}

//------------------------------------------------------------------------------
// Name: setCurrentAddress(address_t address)
// Desc: sets the "current address" (where EIP is usually)
//------------------------------------------------------------------------------
void QDisassemblyView::setCurrentAddress(address_t address) {
	m_CurrentAddress = address;
	viewport()->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;
	m_LongestInstr = 8;
	updateScrollbars();
	viewport()->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: 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;
	
	const Configuration::Syntax syntax = Debugger::config()->syntax;

	m_FirstVisibleAddress = currentLine + m_AddressOffset;

	while(viewableLines >= 0 && currentLine < m_Region.size()) {

		QString byteBuffer;
		quint8 buf[MAX_INSN_SIZE];
		char line[128];
		x86_insn_t insn;
		
		const address_t address = currentLine + m_AddressOffset;

		// draw alternating line backgrounds
		if(row_index & 1) {
			painter.fillRect(0, row, width(), m_FontHeight, QBrush(0xE8E8E8));
		}

		// draw breakpoint icon or eip indicator
		if(address == m_CurrentAddress) {
			painter.drawPixmap(1, row + 2, m_CurrentAddressIcon);
		} else if(Debugger::debuggerBase->findBreakPoint(address)) {
			painter.drawPixmap(1, row + 2, m_Breakpoint);
		}

		// format address
		const QString addressBuffer = formatAddress(address);
		
		Debugger::debuggerBase->readBytes(address, buf, sizeof(buf));
		const int size = x86_disasm(buf, sizeof(buf), address, 0, &insn);
		if (size != 0) {

			if(syntax == Configuration::Intel) {
				x86_format_insn(&insn, line, sizeof(line), intel_syntax);
			} else {
				x86_format_insn(&insn, line, sizeof(line), att_syntax);
			}
		
			x86_oplist_free(&insn);

		} else {
			if(syntax == Configuration::Intel) {
				snprintf(line, sizeof(line), "db %02xh", insn.bytes[0] & 0xff);
			} else {
				snprintf(line, sizeof(line), ".byte %#02x", insn.bytes[0] & 0xff);
			}
		}

		const QString asmBuffer = line;
		
		for(int i = 0; i < insn.size; ++i) {
			if(i >= m_LongestInstr - 2) {
				byteBuffer += "...";
				break;
			}

			byteBuffer += QString().sprintf("%02x", insn.bytes[i] & 0xff);
			if(i != insn.size - 1) {
				byteBuffer += ' ';
			}
		}

		painter.setPen(QPen(Qt::red));
		painter.drawText(m_Breakpoint.width() + 1, row, addressBuffer.length() * m_FontWidth, m_FontHeight, Qt::AlignTop, addressBuffer);

		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);

		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);

		painter.setPen(QPen(Qt::black));
		painter.drawLine(line1(), 0, line1(), height());
		painter.drawLine(line2(), 0, line2(), height());

		row += m_FontHeight;
		currentLine += insn.size;
		++row_index;
		--viewableLines;
		
		lastSize = insn.size;
	}
	
	// last one rendered in the window
	m_LastVisibleAddress = currentLine + m_AddressOffset - lastSize;
}

//------------------------------------------------------------------------------
// 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
	const QFontMetrics fm(f);
	m_FontWidth		= fm.maxWidth();
	m_FontHeight	= fm.height();
	
	updateScrollbars();
	
	// TODO: assert that we are using a fixed font
	// TODO: find out if we care?
	QAbstractScrollArea::setFont(f);
}

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

//------------------------------------------------------------------------------
// Name: 
// 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: 
// Desc: 
//------------------------------------------------------------------------------
int QDisassemblyView::line1() const {
	const unsigned int elements = addressLen();
	return (elements * m_FontWidth) + (m_FontWidth / 2) + m_Breakpoint.width() + 1;
}

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

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

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
int QDisassemblyView::getInstructionSize(uint8_t *buffer, std::size_t size) {
	int ret = 0;
	x86_insn_t insn;
	const int sz = x86_disasm(buffer, size, 0, 0, &insn);
	ret = sz;
	x86_oplist_free(&insn);
	return ret;
}

//------------------------------------------------------------------------------
// Name: 
// Desc: 
//------------------------------------------------------------------------------
void QDisassemblyView::mouseDoubleClickEvent(QMouseEvent *event) {
	if(event->button() == Qt::LeftButton) {
		if(event->x() < line1()) {
			int line = event->y() / m_FontHeight;
			
			address_t offset = verticalScrollBar()->value();
			address_t address = offset;
			
			for(int i = 0; i < line; ++i) {
				quint8 buf[MAX_INSN_SIZE];		
				Debugger::debuggerBase->readBytes(m_AddressOffset + address, buf, sizeof(buf));
				const int size = getInstructionSize(buf, sizeof(buf));
				
				address += (size != 0) ? size : 1;
			}
			
			address += m_AddressOffset;
			
			emit breakPointToggled(address);
			viewport()->repaint();
		}
	}
}
