// license:BSD-3-Clause
// copyright-holders:Andrew Gardner, Vas Crabb
/*********************************************************************

    dvbpoints.cpp

    Breakpoint debugger view.

***************************************************************************/

#include "emu.h"
#include "debugger.h"
#include "dvbpoints.h"
#include "points.h"

#include <algorithm>
#include <iomanip>



// Sorting functors for the qsort function
static bool cIndexAscending(const debug_breakpoint *a, const debug_breakpoint *b)
{
	return a->index() < b->index();
}

static bool cIndexDescending(const debug_breakpoint *a, const debug_breakpoint *b)
{
	return cIndexAscending(b, a);
}

static bool cEnabledAscending(const debug_breakpoint *a, const debug_breakpoint *b)
{
	return !a->enabled() && b->enabled();
}

static bool cEnabledDescending(const debug_breakpoint *a, const debug_breakpoint *b)
{
	return cEnabledAscending(b, a);
}

static bool cCpuAscending(const debug_breakpoint *a, const debug_breakpoint *b)
{
	return strcmp(a->debugInterface()->device().tag(), b->debugInterface()->device().tag()) < 0;
}

static bool cCpuDescending(const debug_breakpoint *a, const debug_breakpoint *b)
{
	return cCpuAscending(b, a);
}

static bool cAddressAscending(const debug_breakpoint *a, const debug_breakpoint *b)
{
	return a->address() < b->address();
}

static bool cAddressDescending(const debug_breakpoint *a, const debug_breakpoint *b)
{
	return cAddressAscending(b, a);
}

static bool cConditionAscending(const debug_breakpoint *a, const debug_breakpoint *b)
{
	return strcmp(a->condition(), b->condition()) < 0;
}

static bool cConditionDescending(const debug_breakpoint *a, const debug_breakpoint *b)
{
	return cConditionAscending(b, a);
}

static bool cActionAscending(const debug_breakpoint *a, const debug_breakpoint *b)
{
	return strcmp(a->action(), b->action()) < 0;
}

static bool cActionDescending(const debug_breakpoint *a, const debug_breakpoint *b)
{
	return cActionAscending(b, a);
}


//**************************************************************************
//  DEBUG VIEW BREAK POINTS
//**************************************************************************

static const int tableBreaks[] = { 5, 9, 31, 45, 63, 80 };


//-------------------------------------------------
//  debug_view_breakpoints - constructor
//-------------------------------------------------

debug_view_breakpoints::debug_view_breakpoints(running_machine &machine, debug_view_osd_update_func osdupdate, void *osdprivate)
	: debug_view(machine, DVT_BREAK_POINTS, osdupdate, osdprivate)
	, m_sortType(cIndexAscending)
{
	// fail if no available sources
	enumerate_sources();
	if (m_source_list.empty())
		throw std::bad_alloc();
}


//-------------------------------------------------
//  ~debug_view_breakpoints - destructor
//-------------------------------------------------

debug_view_breakpoints::~debug_view_breakpoints()
{
}


//-------------------------------------------------
//  enumerate_sources - enumerate all possible
//  sources for a disassembly view
//-------------------------------------------------

void debug_view_breakpoints::enumerate_sources()
{
	// start with an empty list
	m_source_list.clear();

	// iterate over devices with disassembly interfaces
	for (device_disasm_interface &dasm : disasm_interface_enumerator(machine().root_device()))
	{
		m_source_list.emplace_back(
				std::make_unique<debug_view_source>(
					util::string_format("%s '%s'", dasm.device().name(), dasm.device().tag()),
					&dasm.device()));
	}

	// reset the source to a known good entry
	if (!m_source_list.empty())
		set_source(*m_source_list[0]);
}


//-------------------------------------------------
//  view_click - handle a mouse click within the
//  current view
//-------------------------------------------------

void debug_view_breakpoints::view_click(const int button, const debug_view_xy& pos)
{
	bool clickedTopRow = (m_topleft.y == pos.y);

	if (clickedTopRow)
	{
		if (pos.x < tableBreaks[0])
			m_sortType = (m_sortType == &cIndexAscending) ? &cIndexDescending : &cIndexAscending;
		else if (pos.x < tableBreaks[1])
			m_sortType = (m_sortType == &cEnabledAscending) ? &cEnabledDescending : &cEnabledAscending;
		else if (pos.x < tableBreaks[2])
			m_sortType = (m_sortType == &cCpuAscending) ? &cCpuDescending : &cCpuAscending;
		else if (pos.x < tableBreaks[3])
			m_sortType = (m_sortType == &cAddressAscending) ? &cAddressDescending : &cAddressAscending;
		else if (pos.x < tableBreaks[4])
			m_sortType = (m_sortType == &cConditionAscending) ? &cConditionDescending : &cConditionAscending;
		else if (pos.x < tableBreaks[5])
			m_sortType = (m_sortType == &cActionAscending) ? &cActionDescending : &cActionAscending;
	}
	else
	{
		// Gather a sorted list of all the breakpoints for all the CPUs
		gather_breakpoints();

		int bpIndex = pos.y - 1;
		if ((bpIndex >= m_buffer.size()) || (bpIndex < 0))
			return;

		// Enable / disable
		const_cast<debug_breakpoint &>(*m_buffer[bpIndex]).setEnabled(!m_buffer[bpIndex]->enabled());

		machine().debug_view().update_all(DVT_DISASSEMBLY);
	}

	begin_update();
	m_update_pending = true;
	end_update();
}


void debug_view_breakpoints::pad_ostream_to_length(std::ostream& str, int len)
{
	auto const current = str.tellp();
	if (current < decltype(current)(len))
		str << std::setw(decltype(current)(len) - current) << "";
}


void debug_view_breakpoints::gather_breakpoints()
{
	m_buffer.resize(0);
	for (auto &source : m_source_list)
	{
		// Collect
		device_debug &debugInterface = *source->device()->debug();
		for (const auto &bpp : debugInterface.breakpoint_list())
			m_buffer.push_back(bpp.second.get());
	}

	// And now for the sort
	if (!m_buffer.empty())
		std::stable_sort(m_buffer.begin(), m_buffer.end(), m_sortType);
}


//-------------------------------------------------
//  view_update - update the contents of the
//  breakpoints view
//-------------------------------------------------

void debug_view_breakpoints::view_update()
{
	// Gather a list of all the breakpoints for all the CPUs
	gather_breakpoints();

	// Set the view region so the scroll bars update
	m_total.x = tableBreaks[std::size(tableBreaks) - 1];
	m_total.y = m_buffer.size() + 1;
	if (m_total.y < 10)
		m_total.y = 10;

	// Draw
	debug_view_char     *dest = &m_viewdata[0];
	util::ovectorstream linebuf;
	linebuf.reserve(std::size(tableBreaks) - 1);

	// Header
	if (m_visible.y > 0)
	{
		linebuf.clear();
		linebuf.rdbuf()->clear();
		linebuf << "ID";
		if (m_sortType == &cIndexAscending) linebuf.put('\\');
		else if (m_sortType == &cIndexDescending) linebuf.put('/');
		pad_ostream_to_length(linebuf, tableBreaks[0]);
		linebuf << "En";
		if (m_sortType == &cEnabledAscending) linebuf.put('\\');
		else if (m_sortType == &cEnabledDescending) linebuf.put('/');
		pad_ostream_to_length(linebuf, tableBreaks[1]);
		linebuf << "CPU";
		if (m_sortType == &cCpuAscending) linebuf.put('\\');
		else if (m_sortType == &cCpuDescending) linebuf.put('/');
		pad_ostream_to_length(linebuf, tableBreaks[2]);
		linebuf << "Address";
		if (m_sortType == &cAddressAscending) linebuf.put('\\');
		else if (m_sortType == &cAddressDescending) linebuf.put('/');
		pad_ostream_to_length(linebuf, tableBreaks[3]);
		linebuf << "Condition";
		if (m_sortType == &cConditionAscending) linebuf.put('\\');
		else if (m_sortType == &cConditionDescending) linebuf.put('/');
		pad_ostream_to_length(linebuf, tableBreaks[4]);
		linebuf << "Action";
		if (m_sortType == &cActionAscending) linebuf.put('\\');
		else if (m_sortType == &cActionDescending) linebuf.put('/');
		pad_ostream_to_length(linebuf, tableBreaks[5]);

		auto const &text(linebuf.vec());
		for (u32 i = m_topleft.x; i < (m_topleft.x + m_visible.x); i++, dest++)
		{
			dest->byte = (i < text.size()) ? text[i] : ' ';
			dest->attrib = DCA_ANCILLARY;
		}
	}

	for (int row = 1; row < m_visible.y; row++)
	{
		// Breakpoints
		int bpi = row + m_topleft.y - 1;
		if ((bpi < m_buffer.size()) && (bpi >= 0))
		{
			const debug_breakpoint *const bp = m_buffer[bpi];

			linebuf.clear();
			linebuf.rdbuf()->clear();
			util::stream_format(linebuf, "%2X", bp->index());
			pad_ostream_to_length(linebuf, tableBreaks[0]);
			linebuf.put(bp->enabled() ? 'X' : 'O');
			pad_ostream_to_length(linebuf, tableBreaks[1]);
			linebuf << bp->debugInterface()->device().tag();
			pad_ostream_to_length(linebuf, tableBreaks[2]);
			util::stream_format(linebuf, "%0*X", bp->debugInterface()->logaddrchars(), bp->address());
			pad_ostream_to_length(linebuf, tableBreaks[3]);
			if (strcmp(bp->condition(), "1"))
				linebuf << bp->condition();
			pad_ostream_to_length(linebuf, tableBreaks[4]);
			linebuf << bp->action();
			pad_ostream_to_length(linebuf, tableBreaks[5]);

			auto const &text(linebuf.vec());
			for (u32 i = m_topleft.x; i < (m_topleft.x + m_visible.x); i++, dest++)
			{
				dest->byte = (i < text.size()) ? text[i] : ' ';
				dest->attrib = DCA_NORMAL;

				// Color disabled breakpoints red
				if ((i >= tableBreaks[0]) && (i < tableBreaks[1]) && !bp->enabled())
					dest->attrib |= DCA_CHANGED;
			}
		}
		else
		{
			// Fill the remaining vertical space
			for (u32 i = m_topleft.x; i < (m_topleft.x + m_visible.x); i++, dest++)
			{
				dest->byte = ' ';
				dest->attrib = DCA_NORMAL;
			}
		}
	}
}
