#pragma once
#include <bzsnet/BufferedSocket.h>
#include <bzscore/buffer.h>
#include <bzscore/string.h>
#include <map>
#include <vector>
#include <string>
#include <hash_map>

#define USE_REG_WORKAROUND_DLL

namespace VBoxGDB
{
	//! Controls VirtualBox debugger over a TCP/IP connection
	/*! This class allows controlling the VirtualBox debugger over the TCP/IP connection.
		The key method of this class is ExecuteCommand() that sends an arbitrary commands to
		the debugger. Almost all other methods are using ExecuteCommand() and parsing the reply.

		\attention The class is not thread-safe. All methods except RequestStopAsync() should be
				   called from the same thread.
	*/
	class VBoxClient
	{
	private:
		BazisLib::Network::TCPSocketEx m_Socket;
		BazisLib::DynamicStringA m_Buffer;
		size_t m_BufferAllocationSize;
		bool m_b64Bit;
		int m_CPUCount;
		int m_BreakpointIDOfLastStopEvent;	//-1 if not a breakpoint

		typedef std::hash_map<int, ULONGLONG> BreakpointAddressMap;
		BreakpointAddressMap m_BreakpointMap;

		BazisLib::BasicBuffer m_LastBlockReadBuffer;
		ULONGLONG m_LastBlockReadAddr;	//-1LL if not valid

		BazisLib::Network::InternetAddress m_TargetAddr;

	public:
		//! Creates an instance of the VirtualBox debugger client connected to a debugger over TCP/IP
		/*!
			\attention After an instance of this class, the Initialize() method should be called.
		*/
		VBoxClient(const BazisLib::Network::InternetAddress &addr);
		~VBoxClient();

		//! Initializes the debugger, detects parameters and checks for errors.
		bool Initialize();

	private:
		bool RecvTillCommandPrompt();
		bool ReadMemoryBlockAligned(ULONGLONG address, BazisLib::BasicBuffer &buffer);	//The read size is determined by VirtualBox; address should be aligned to 4 bytes
		void AdjustPCAfterBreakpoint(int breakpointNumber);
		ULONGLONG GetCurrentPC();

	public:
		std::vector<BazisLib::DynamicStringA> ExecuteCommand(const char *pCommand);

	public:
		//! Reads the values of all registers from the target
		std::map<BazisLib::DynamicStringA, ULONGLONG> ReadRegisters();
		//! Writes the value of a single target register
		bool WriteRegister(const char *pName, ULONGLONG value);
		//! Reads a value of a single target register
		bool ReadRegister(const char *pName, ULONGLONG *pValue);

		//! Waits until VirtualBox reports a stop event
		bool WaitForStop();

		//! Requests the VirtualBox debugger to stop executing the target (i.e. forces a breakpoint)
		void RequestStopAsync();

		//! Sends the "exit" command and closes the connection to the VirtualBox debugger
		/*!
			\attention Due to a bug in VirtualBox debugger just closing the controlling socket will
					   leave the debugger in an inaccessible state. Thus, if the VBoxGDB process is
					   about to exit unexpectedly (e.g. via Ctrl+Break), it should still try to call
					   this method.
		*/
		void Disconnect();

		//! Reads target memory
		/*!
			\param allowCaching If this argument is set to TRUE, VBoxClient won't send another memory reading request to VirtualBox
								if the requested data was already contained in the previous VirtualBox reply. Note that calling
								WriteMemory() resets the cache.

			\remarks The caching is employed as VirtualBox debugger can only read memory in fixed-size blocks. Assume that GDB requests
					 10 consequent blocks 4 bytes each. The VirtualBox reply for the command reading the first address will already contain
					 enough data to serve all 9 remaining requests. Caching and reusing this data significantly improves performance.
		*/
		bool ReadMemory(ULONGLONG address, void *pData, size_t *pLength, bool allowCaching = true);

		//! Writes target memory
		bool WriteMemory(ULONGLONG address, const void *pData, size_t length);

		//! Executes a single step and waits for it to complete
		bool StepAndWait();

		//! Resumes the target execution and waits for a debug event
		bool ContinueAndWait();

		//! Returns false if the provided reply contains the "error:" string
		/*!
			\param reply Contains a reply returned by ExecuteCommand() method.
			\param report If this argument is TRUE and the reply contains an error message,
						  the method will report it to the user by calling ReportError().
		*/
		bool IsSuccessfulReply(const std::vector<BazisLib::DynamicStringA> &reply, bool report = true);

		//! Creates a software breakpoint using the "bp" command
		/*!
			\remarks Note that just inserting "int 3" instruction at a certain address won't cause VirtualBox to stop there.
					 Thus, we need to support creating software breakpoint explicitly
			\return If the method succeeds, the breakpoint numer is returned. If the method fails, it returns -1.
		*/
		int CreateBreakpoint(ULONGLONG addr);
		
		//! Removes a previously created breakpoint
		bool RemoveBreakpoint(int idx);


	protected:
		//! Reports an error to the user.
		/*! This method is called when VBoxClient encounters an error and needs to report it to the user.
			The current implementation simply calls printf() to report it. 
		*/
		virtual void ReportError(const char *pErrorDescription);
	};
}
