/*
	Copyright 2021 flyinghead

	This file is part of Flycast.

    Flycast 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.

    Flycast 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 Flycast.  If not, see <https://www.gnu.org/licenses/>.
 */
#include "types.h"

#ifdef GDB_SERVER
#include "gdb_server.h"
#include "debug_agent.h"
#include "network/net_platform.h"
#include "cfg/option.h"
#include <stdexcept>
#include <thread>
#include <chrono>
#include <mutex>
#include <cassert>

#define MAX_PACKET_LEN 4096

namespace debugger {

static void emuEventCallback(Event event, void *);

class GdbServer
{
public:
	struct Error : public std::runtime_error {
		Error(const char *reason) : std::runtime_error(reason) {}
	};

	void init(int port)
	{
		if (VALID(serverSocket))
			return;

		serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (!VALID(serverSocket))
			throw Error("gdb: Cannot create server socket");

		int option = 1;
		setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, (const char *)&option, sizeof(option));

		struct sockaddr_in serveraddr;
		memset(&serveraddr, 0, sizeof(serveraddr));
		serveraddr.sin_family = AF_INET;
		serveraddr.sin_port = htons(port);

		if (::bind(serverSocket, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
		{
			closesocket(serverSocket);
			throw Error("gdb: bind() failed");
		}
		if (::listen(serverSocket, 5) < 0)
		{
			closesocket(serverSocket);
			throw Error("gdb: listen() failed");
		}
		EventManager::listen(Event::Resume, emuEventCallback);
		EventManager::listen(Event::Terminate, emuEventCallback);

		initialised = true;
	}

	void term()
	{
		if (!initialised)
			return;
		EventManager::unlisten(Event::Resume, emuEventCallback);
		EventManager::unlisten(Event::Terminate, emuEventCallback);
		stop();
		if (VALID(clientSocket))
		{
			closesocket(clientSocket);
			clientSocket = INVALID_SOCKET;
		}
		if (VALID(serverSocket))
		{
			closesocket(serverSocket);
			serverSocket = INVALID_SOCKET;
		}
	}

	void run()
	{
		if (!initialised || thread.joinable())
			return;
		DEBUG_LOG(COMMON, "GdbServer starting");
		thread = std::thread(&GdbServer::serverThread, this);
		if (config::GDBWaitForConnection)
		{
			DEBUG_LOG(COMMON, "Waiting for GDB connection...");
			agentInterrupt();
		}
	}

	void stop()
	{
		if (!initialised)
			return;
		if (thread.joinable())
		{
			DEBUG_LOG(COMMON, "GdbServer stopping");
			agent.resetAgent();
			stopRequested = true;
			thread.join();
		}
	}

	bool isRunning() const {
		return initialised && thread.joinable();
	}

	// called on the emu thread
	void debugTrap(u32 event)
	{
		if (!attached)
			return;
		agent.debugTrap(event);
		reportException();
		postDebugTrapNeeded = true;
		throw Stop();
	}

private:
	const u32 EXCEPT_NONE = 1;

	void serverThread()
	{
		while (!stopRequested)
		{
			fd_set fds;
			FD_ZERO(&fds);
			sock_t max_fd = serverSocket;
			FD_SET(serverSocket, &fds);
			if (VALID(clientSocket))
			{
				max_fd = std::max(max_fd, clientSocket);
				FD_SET(clientSocket, &fds);
			}
			timeval tv;
			tv.tv_sec = 0;
			tv.tv_usec = 100 * 1000;
			if (::select(max_fd + 1, &fds, nullptr, nullptr, &tv) > 0)
			{
				if (FD_ISSET(serverSocket, &fds))
				{
					try {
						acceptClientConnection();
					} catch (const Error& e) {
						ERROR_LOG(COMMON, "%s", e.what());
						closesocket(serverSocket);
						serverSocket = INVALID_SOCKET;
						break;
					}
				}
				else if (FD_ISSET(clientSocket, &fds))
				{
					readCommand();
				}
			}
		}
		if (VALID(clientSocket))
		{
			closesocket(clientSocket);
			clientSocket = INVALID_SOCKET;
		}
		attached = false;
		stopRequested = false;
	}

	void acceptClientConnection()
	{
		if (VALID(clientSocket))
			closesocket(clientSocket);
		sockaddr_in src_addr{};
		socklen_t addr_len = sizeof(src_addr);
		clientSocket = ::accept(serverSocket, (sockaddr *)&src_addr, &addr_len);
		if (!VALID(clientSocket))
		{
			if (get_last_error() != L_EAGAIN && get_last_error() != L_EWOULDBLOCK)
				throw Error("accept failed");
		}
		else
		{
			NOTICE_LOG(NETWORK, "gdb: client connection");
			attached = true;
			agentInterrupt();
		}
	}

	void readCommand()
	{
        try {
			if (postDebugTrapNeeded)
			{
				postDebugTrapNeeded = false;
				try {
					agent.postDebugTrap();
				} catch (const FlycastException& e) {
					throw Error(e.what());
				}
			}
			std::string packet = recvPacket();
			if (packet.empty())
				return;

			DEBUG_LOG(NETWORK, "gdb: recv %s", packet.c_str());
			switch (packet[0])
			{
			case '!':	// Enable extended mode
				sendPacket("OK");
				break;
			case '?':	// Sent when connection is first established to query the reason the target halted
				reportException();
				break;
			case 'A':	// Initialized argv[] array passed into program. not supported
				sendPacket("E01");
				break;;
			case 'b':	// Change the serial line speed to baud. deprecated
				break;
			case 'B':	// Set or clear a breakpoint at addr. deprecated
				break;
			case 'c':	// Continue at addr, which is the address to resume.
						// If addr is omitted, resume at current address
				sendContinue(packet);
				break;
			case 'C':	// Continue with signal sig
				sendContinue(packet);
				break;
			case 'd':	// Toggle debug flag. deprecated
				break;
			case 'D':	// Detach GDB from the remote system
				sendPacket("OK");
				agent.detach();
				break;
			case 'F':	// File-I/O protocol extension not currently supported
				break;;
			case 'g':	// Read general registers
				readAllRegs();
				break;
			case 'G':	// Write general registers
				writeAllRegs(packet);
				break;
			case 'H':	// Set thread for subsequent operations
				sendPacket("OK");
				break;
			case 'i':	// Step the remote target by a single clock cycle
			case 'I':	// Signal, then cycle step
				// not supported
				sendPacket("");
				break;
			case 'k':	// Kill request. Stop process/system
				agent.kill();
				break;
			case 'm':	// Read length addressable memory units
				readMem(packet);
				break;
			case 'M':	// Write length addressable memory units
				writeMem(packet);
				break;
			case 'p':	// Read the value of register
				readReg(packet);
				break;
			case 'P':	// Write register
				writeReg(packet);
				break;
			case 'q':	// General query packets
				query(packet);
				break;
			case 'Q':	// General set packets
				set(packet);
				break;
			case 'r':	// Reset the entire system. Deprecated (use 'R' instead)
				break;
			case 'R':	// Restart the program being debugged.
				restart();
				break;
			case 's':	// Single step
				step(EXCEPT_NONE);
				break;
			case 'S':	// Step with signal
				step();
				break;
			case 't':	// Search backwards. unsupported
				break;
			case 'T':	// Find out if the thread is alive
				sendPacket("OK");
				break;
			case 'v':	// 'v' packets to control execution
				vpacket(packet);
				break;
			case 'X':	// Write binary data to memory
				writeMemBin(packet);
				break;
			case 'z':	// Remove a breakpoint/watchpoint.
				removeMatchpoint(packet);
				break;
			case 'Z':	// Insert a breakpoint/watchpoint.
				insertMatchpoint(packet);
				break;
			case 3:
				interrupt();
				break;
			default:
				// Unknown commands are ignored
				WARN_LOG(COMMON, "Unknown gdb command: %s", packet.c_str());
				break;;
			}
		} catch (const Error& e) {
			ERROR_LOG(COMMON, "%s", e.what());
			closesocket(clientSocket);
			clientSocket = INVALID_SOCKET;
			attached = false;
		}
	}

	void reportException()
	{
		char s[4];
		sprintf(s, "S%02X", agent.currentException());
		sendPacket(s);
	}

	void sendContinue(const std::string& pkt)
	{
		if (pkt[0] != 'c') {
			WARN_LOG(COMMON, "Continue with signal not supported");
			return;
		}

		if (pkt == "c")
			agent.doContinue();
		else
		{
			// Get the pc at which to resume
			u32 addr;
			if (sscanf(pkt.c_str(), "c%x", &addr) != 1)
			{
				WARN_LOG(COMMON, "Continue address invalid %s", pkt.c_str());
				return;
			}
			agent.doContinue(addr);
		}
	}

	void readAllRegs()
	{
		u32 *regs;
		int c = agent.readAllRegs(&regs);
		std::string outpkt;
		for (int i = 0; i < c; i++)
			outpkt += pack(regs[i]);
		sendPacket(outpkt);
	}

	void writeAllRegs(const std::string& pkt)
	{
		std::vector<u32> regs;
		for (auto it = pkt.begin() + 1; it <= pkt.end() - 8; it += 8)
			regs.push_back(unpack(&*it, 8));

		agent.writeAllRegs(regs);
		sendPacket("OK");
	}

	void readMem(const std::string& pkt)
	{
		u32 addr;
		u32 len;
		if (sscanf(pkt.c_str(), "m%x,%x:", &addr, &len) != 2)
		{
			WARN_LOG(COMMON, "readMem: invalid packet %s", pkt.c_str());
			sendPacket("E01");
			return;
		}
		const u8 *mem = agent.readMem(addr, len);
		std::string outpkt;
		for (u32 i = 0; i < len; i++)
		{
			char s[3];
			sprintf(s,"%02x", mem[i]);
			outpkt += s;
		}
		sendPacket(outpkt);
	}

	void writeMem(const std::string& pkt)
	{
		u32 addr;
		u32 len;
		if (sscanf(pkt.c_str(), "M%x,%x:", &addr, &len) != 2)
		{
			WARN_LOG(COMMON, "writeMem: invalid packet %s", pkt.c_str());
			sendPacket("E01");
			return;
		}
		std::vector<u8> data(len);
		const char *p = &pkt[pkt.find(':')] + 1;
		for (u32 i = 0; i < len; i++, p += 2)
		{
			u32 b;
			sscanf(p,"%2x", &b);
			data[i] = (u8)b;
		}
		agent.writeMem(addr, data);
		sendPacket("OK");
	}

	void writeMemBin(const std::string& pkt)
	{
		u32 addr;
		u32 len;
		if (sscanf(pkt.c_str(), "X%x,%x:", &addr, &len) != 2)
		{
			WARN_LOG(COMMON, "writeMemBin invalid command: %s", pkt.c_str());
			sendPacket("E01");
			return;
		}
		const char *p = &pkt[pkt.find(':')] + 1;
		std::vector<u8> data;
		data.reserve(pkt.length() - (p - &pkt[0]));
		for (; p <= &pkt.back(); p++)
		{
			u8 b = *p;
			if (b == '}')
			{
				b = *++p ^ 0x20;
			}
			data.push_back(b);
		}
		agent.writeMem(addr, data);
		sendPacket("OK");
	}

	void readReg(const std::string& pkt)
	{
		u32 regNum;
		if (sscanf(pkt.c_str(), "p%x", &regNum) != 1)
		{
			WARN_LOG(COMMON, "readReg: invalid packet %s", pkt.c_str());
			sendPacket("E01");
			return;
		}
		u32 v = agent.readReg(regNum);
		sendPacket(pack(v));
	}

	void writeReg(const std::string& pkt)
	{
		u32 regNum;
		char vstr[9];
		if (sscanf(pkt.c_str(), "P%x=%8s", &regNum, vstr) != 2)
		{
			WARN_LOG(COMMON, "writeReg: invalid packet %s", pkt.c_str());
			sendPacket("E01");
			return;
		}
		agent.writeReg(regNum, unpack(vstr, 8));
		sendPacket("OK");
	}

	void query(const std::string& pkt)
	{
		if (pkt == "qC")
			// Return the current thread ID. 0 is "any thread"
			sendPacket("QC0.01");
		else if (pkt.rfind("qCRC", 0) == 0)
		{
			WARN_LOG(COMMON, "CRC compute not supported %s", pkt.c_str());
			sendPacket("E01");
		}
		else if (pkt == "qfThreadInfo")
			// Obtain a list of all active thread IDs (first call)
			sendPacket("m0");
		else if (pkt == "qsThreadInfo")
			// Obtain a list of all active thread IDs (subsequent calls -> 'l' == end of list)
			sendPacket("l");
		else if (pkt.rfind("qGetTLSAddr:", 0) == 0)
			// Fetch the address associated with thread local storage
			sendPacket("");
		else if (pkt.rfind("qL", 0) == 0)
			// Obtain thread information. deprecated
			sendPacket("qM001");
		else if (pkt == "qOffsets")
			// Get section offsets. Not supported
			sendPacket("");
		else if (pkt.rfind("qP", 0) == 0)
			// Returns information on thread. deprecated
			sendPacket("");
		else if (pkt.rfind("qRcmd,", 0) == 0)
		{
			std::string customCmd;
			for (const char *p = pkt.c_str() + 6; *p != '\0'; p += 2)
				customCmd += (char)unpack(p, 2);
			DEBUG_LOG(COMMON, "query: custom command %s", customCmd.c_str());
			if (customCmd == "reset")
				restart();
			else if (customCmd == "stack")
			{
				u32 len;
				const u32 *data = agent.getStack(len);
				len /= 4;

#if _MSC_VER // Non-const array size is a GCC extension
				assert((len * 9 * 2 + 1) < MAX_PACKET_LEN);
				char reply[MAX_PACKET_LEN];
#else
				char reply[len * 9 * 2 + 1];
#endif
				char *r = reply;
				for (u32 i = 0; i < len; i++)
				{
					char n[10];
					sprintf(n, "%08x ", *data++);
					for (char *p = n; *p != 0; p++)
					{
						*r++ = packnb((*p >> 4) & 0xf);
						*r++ = packnb(*p & 0xf);
					}
				}
				*r = 0;
				sendPacket(reply);
			}
			else
				sendPacket("");
		}
		else if (pkt.rfind("qSupported", 0) == 0)
		{
			// Tell the remote stub about features supported by GDB,
			// and query the stub for features it supports
			char qsupported[128];
			snprintf(qsupported, 128, "PacketSize=%i;vContSupported+", MAX_PACKET_LEN);
			sendPacket(qsupported);
		}
		else if (pkt.rfind("qSymbol:", 0) == 0)
			// Notify the target that GDB is prepared to serve symbol lookup requests
			sendPacket("OK");
		else if (pkt.rfind("qThreadExtraInfo,", 0) == 0)
		{
			// Obtain from the target OS a printable string description of thread attributes
			char s[19];
			sprintf(s, "%02x%02x%02x%02x%02x%02x%02x%02x%02x", 'R', 'u', 'n', 'n', 'a', 'b', 'l', 'e', 0);
			sendPacket(std::string(s, 18));
		}
		else if (pkt.rfind("qXfer:", 0) == 0)
			// Read uninterpreted bytes from the target’s special data area identified by the keyword object
			sendPacket("");
		else if (pkt.rfind("qAttached", 0) == 0)
			// Return an indication of whether the remote server attached to an existing process
			// or created a new process
			sendPacket("1");  // existing process
		else if (pkt.rfind("qTfV", 0) == 0)
			// request data about trace state variables
			sendPacket("");
		else if (pkt.rfind("qTfP", 0) == 0)
			// request data about tracepoints
			sendPacket("");
		else if (pkt.rfind("qTStatus", 0) == 0)
			// Ask the stub if there is a trace experiment running right now
			sendPacket("");
		else
			WARN_LOG(COMMON, "query not supported %s", pkt.c_str());
	}

	void set(const std::string& pkt)
	{
		if (pkt.rfind("QPassSignals:", 0) == 0)
			// Passing signals not supported
			sendPacket("");
		else if (pkt.rfind("QTDP", 0) == 0
				|| pkt.rfind("QFrame", 0) == 0
				|| pkt.rfind("QTStart", 0) == 0
				|| pkt.rfind("QTStop", 0) == 0
				|| pkt.rfind("QTinit", 0) == 0
				|| pkt.rfind("QTro", 0) == 0)
			// No tracepoint feature supported
			sendPacket("");
		else
			WARN_LOG(COMMON, "set not supported %s", pkt.c_str());
	}

	void vpacket(const std::string& pkt)
	{
		if (pkt.rfind("vAttach;", 0) == 0)
			sendPacket("S05");
		else if (pkt.rfind("vCont?", 0) == 0)
			// supported vCont actions - (c)ontinue, (C)ontinue with signal, (s)tep, (S)tep with signal, (r)ange-step
			sendPacket("vCont;c;C;s;S;t;r");
		else if (pkt.rfind("vCont", 0) == 0)
		{
			std::string vContCmd = pkt.substr(strlen("vCont;"));
			switch (vContCmd[0])
			{
			case 'c':
			case 'C':
				sendContinue(vContCmd);
				break;
			case 's':
				step(EXCEPT_NONE);
				break;
			case 'S':
				step();
			case 'r':
			{
				u32 from, to;
				if (sscanf(vContCmd.c_str(), "r%x,%x", &from, &to) == 2)
				{
					stepRange(from, to);
				}
				else
				{
					WARN_LOG(COMMON, "Unsupported vCont:r format %s", pkt.c_str());
					sendContinue("c");
				}

				break;
			}
			default:
				WARN_LOG(COMMON, "vCont action not supported %s", pkt.c_str());
			}
		}
		else if (pkt.rfind("vFile:", 0) == 0)
			// not supported
			sendPacket("");
		else if (pkt.rfind("vFlashErase:", 0) == 0)
			// not supported
			sendPacket("E01");
		else if (pkt.rfind("vFlashWrite:", 0) == 0)
			// not supported
			sendPacket("E01");
		else if (pkt.rfind("vFlashDone:", 0) == 0)
			// not supported
			sendPacket("E01");
		else if (pkt.rfind("vRun;", 0) == 0)
		{
			if (pkt != "vRun;")
				WARN_LOG(COMMON, "unexpected vRun args ignored: %s", pkt.c_str());
			agent.restart();
			sendPacket("S05");
		}
		else if (pkt.rfind("vKill", 0) == 0)
		{
			sendPacket("OK");
			agent.kill();
		}
		else
		{
			WARN_LOG(COMMON, "unknown v packet: %s", pkt.c_str());
			sendPacket("");
		}
	}

	void restart()
	{
		agent.restart();
	}

	void step(u32 what = 0)
	{
		try {
			agent.step();
			sendPacket("S05");
		} catch (const FlycastException& e) {
			throw Error(e.what());
		}
	}

	void stepRange(u32 from, u32 to)
	{
		try {
			sendPacket("OK");
			agent.stepRange(from, to);
			sendPacket("S05");
		} catch (const FlycastException& e) {
			throw Error(e.what());
		}
	}

	void insertMatchpoint(const std::string& pkt)
	{
		u32 type;
		u32 addr;
		u32 len;
		if (sscanf(pkt.c_str(), "Z%1d,%x,%1d", &type, &addr, &len) != 3) {
			WARN_LOG(COMMON, "insertMatchpoint: unknown packet: %s", pkt.c_str());
			sendPacket("E01");
		}
		switch (type) {
			case DebugAgent::Breakpoint::BP_TYPE_SOFTWARE_BREAK:		// soft bp
		    	if (agent.insertMatchpoint(DebugAgent::Breakpoint::BP_TYPE_SOFTWARE_BREAK,
		    			addr, len))
		    		sendPacket("OK");
		    	else
		    		sendPacket("E01");
		    	break;
		    case DebugAgent::Breakpoint::BP_TYPE_HARDWARE_BREAK:		// hardware bp
		    	sendPacket("");
		    	break;
		    case DebugAgent::Breakpoint::BP_TYPE_WRITE_WATCHPOINT:	// write watchpoint
		    	sendPacket("");
		    	break;
		    case DebugAgent::Breakpoint::BP_TYPE_READ_WATCHPOINT:		// read watchpoint
		    	sendPacket("");
		    	break;
		    case DebugAgent::Breakpoint::BP_TYPE_ACCESS_WATCHPOINT:	// access watchpoint
		    	sendPacket("");
		    	break;
		    default:
		    	sendPacket("");
		    	break;
		}
	}

	void removeMatchpoint(const std::string& pkt)
	{
		u32 type;
		u32 addr;
		u32 len;
		if (sscanf(pkt.c_str(), "z%1d,%x,%1d", &type, &addr, &len) != 3) {
			WARN_LOG(COMMON, "removeMatchpoint: unknown packet: %s", pkt.c_str());
			sendPacket("E01");
		}
		switch (type) {
		    case 0:		// soft bp
		    	if (agent.removeMatchpoint(DebugAgent::Breakpoint::BP_TYPE_SOFTWARE_BREAK,
		    			addr, len))
		    		sendPacket("OK");
		    	else
		    		sendPacket("E01");
		    	break;
		    case 1:		// hardware bp
		    	sendPacket("");
		    	break;
		    case 2:		// write watchpoint
		    	sendPacket("");
		    	break;
		    case 3:		// read watchpoint
		    	sendPacket("");
		    	break;
		    case 4:		// access watchpoint
		    	sendPacket("");
		    	break;
		    default:
		    	sendPacket("");
		    	break;
		}
	}

	void interrupt()
	{
		u32 signal = agentInterrupt();
		char s[10];
		sprintf(s, "S%02x", signal);
		sendPacket(s);
	}

	char recvChar()
	{
		char c;
		int rc = ::recv(clientSocket, &c, 1, 0);
		if (rc <= 0)
			throw Error("gdb: I/O error");
		return c;
	}

	void sendChar(char c)
	{
		std::unique_lock<std::mutex> lock(outMutex);
		int rc = ::send(clientSocket, &c, 1, 0);
		if (rc <= 0)
			throw Error("gdb: I/O error");
	}

	u8 unpack(char c)
	{
		c = std::tolower(c);
		if (c <= '9')
			return c - '0';
		else
			return c - 'a' + 10;
	}

	char packnb(u8 b)
	{
		if (b < 10)
			return '0' + b;
		else
			return 'a' + b - 10;
	}

	std::string packb(u8 v)
	{
		std::string s(1, packnb((v >> 4) & 0xf));
		s += packnb(v & 0xf);
		return s;
	}

	std::string pack(u32 v)
	{
		return packb(v & 0xff) + packb((v >> 8) & 0xff)
				+ packb((v >> 16) & 0xff) + packb((v >> 24) & 0xff);
	}

	u32 unpack(const char *s, int l)
	{
		u32 r = 0;
		for (int i = 0; i < l && *s != '\0'; i += 2, s += 2)
		{
			r |= (unpack(s[0]) << 4 | unpack(s[1])) << (i * 4);
		}
		return r;
	}

	std::string recvPacket()
	{
		std::string pkt;
		// look for start character ('$') or BREAK
		char c = recvChar();
		if (c == 3)
			return std::string("\03");
		if (c != '$')
			return pkt;

		// read until '#'
		u8 checksum = 0;
		while (!stopRequested)
		{
			c = recvChar();
			if (c == '$')
			{
				checksum = 0;
				pkt.clear();

				continue;
			}

			if (c == '#')
				break;

			checksum += (u8)c;
			pkt.push_back(c);
		}
		if (stopRequested)
		{
			pkt.clear();
			return pkt;
		}
		u8 recvchk = unpack(recvChar()) << 4;
		recvchk |= unpack(recvChar());

		// If the checksums don't match print a warning, and put the
		// negative ack back to the client. Otherwise put a positive ack.
		if (checksum != recvchk)
		{
			sendChar('-');	// Failed checksum
			return "";
		}
		else
		{
			sendChar('+');	// Successful transfer
			return pkt;
		}
	}

	void sendPacket(const std::string& pkt)
	{	DEBUG_LOG(NETWORK, "gdb: sending pkt");
		std::unique_lock<std::mutex> lock(outMutex);
		std::string data{'$'};
		u8 checksum = 0;
		for (char c : pkt)
		{
			if (c == '$' || c == '#' || c == '*' || c == '}')
			{
				c ^= 0x20;
				checksum += (u8)'}';
				data.push_back('}');
			}
			checksum += (u8)c;
			data.push_back(c);
		}
		data.push_back('#');
		char s[9];
		sprintf(s, "%02x", checksum);
		data += s;
		DEBUG_LOG(NETWORK, "gdb: sent %s", data.c_str());
		int ret = ::send(clientSocket, data.c_str(), data.length(), 0);
		if (ret < (int)data.length())
			throw Error("I/O error");
	}

	u32 agentInterrupt()
	{
		try {
			return agent.interrupt();
		} catch (const FlycastException& e) {
			throw Error(e.what());
		}
	}

	bool initialised = false;
	bool stopRequested = false;
	bool attached = false;
	bool postDebugTrapNeeded = false;
	sock_t serverSocket = INVALID_SOCKET;
	sock_t clientSocket = INVALID_SOCKET;
	std::thread thread;
	std::mutex outMutex;
public:
	DebugAgent agent;
};

static GdbServer gdbServer;

void init(int port)
{
	gdbServer.init(port);
}

void term()
{
	gdbServer.term();
}

void debugTrap(u32 event)
{
	gdbServer.debugTrap(event);
}

void subroutineCall()
{
	gdbServer.agent.subroutineCall();
}

void subroutineReturn()
{
	gdbServer.agent.subroutineReturn();
}

static void emuEventCallback(Event event, void *)
{
	switch (event)
	{
	case Event::Resume:
		try {
			if (!gdbServer.isRunning())
				gdbServer.run();
		} catch (const GdbServer::Error& e) {
			ERROR_LOG(COMMON, "%s", e.what());
		}
		break;
	case Event::Terminate:
		gdbServer.stop();
		break;
	default:
		break;
	}
}

}
#endif
