/* Mednafen - Multi-system Emulator
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "main.h"
#include <trio/trio.h>
#include "gfxdebugger.h"
#include "memdebugger.h"
#include "prompt.h"

static bool NeedInit = 1;
static bool NeedHooksInstalled = TRUE;
static bool WatchLogical = 1; // Watch logical memory addresses, not physical
static bool IsActive = 0;

static unsigned int WhichMode = 0; // 0 = normal, 1 = gfx, 2 = memory

static std::string ReadBreakpoints, IOReadBreakpoints, AuxReadBreakpoints;
static std::string WriteBreakpoints, IOWriteBreakpoints, AuxWriteBreakpoints;

static void MemPoke(uint32 A, uint32 V, uint32 Size, bool hl, bool logical)
{
 AddressSpaceType *found = NULL;

 if(logical)
 {
  for(unsigned int x = 0; x < CurGame->Debugger->AddressSpaces.size(); x++)
   if(!strcasecmp(CurGame->Debugger->AddressSpaces[x].name, "logical"))
   {
    found = &CurGame->Debugger->AddressSpaces[x]; 
    break;
   }
 }
 else
 {
  for(unsigned int x = 0; x < CurGame->Debugger->AddressSpaces.size(); x++)
   if(!strcasecmp(CurGame->Debugger->AddressSpaces[x].name, "physical"))
   {
    found = &CurGame->Debugger->AddressSpaces[x];
    break;
   }
 }

 if(!found)
 {
  for(unsigned int x = 0; x < CurGame->Debugger->AddressSpaces.size(); x++)
   if(!strcasecmp(CurGame->Debugger->AddressSpaces[x].name, "cpu"))
   {
    found = &CurGame->Debugger->AddressSpaces[x];
    break;
   }
 }

 if(found)
 {
  uint8 tmp_buffer[4];

  // FIXME if we ever add a non-LSB-first system!
  tmp_buffer[0] = V;
  tmp_buffer[1] = V >> 8;
  tmp_buffer[2] = V >> 16;
  tmp_buffer[3] = V >> 24;
  found->PutAddressSpaceBytes(found->name, A, Size, Size, hl, tmp_buffer);
 }
 else
  puts("Error");
}

static uint32 ParsePhysAddr(const char *za)
{
 uint32 ret = 0;

 if(strchr(za, ':'))
 {
  unsigned int bank = 0, offset = 0;

  if(!strcasecmp(CurGame->shortname, "wswan"))
  {
   trio_sscanf(za, "%04x:%04x", &bank, &offset);
   ret = ((bank << 4) + offset) & 0xFFFFF;
  }
  else
  {
   trio_sscanf(za, "%02x:%04x", &bank, &offset);
   ret = (bank * 8192) | (offset & 0x1FFF);
  }
 }
 else
  trio_sscanf(za, "%08x", &ret);

 return(ret);
}

static void ParseBreakpoints(const std::string &Breakpoints, int type)
{
 LockGameMutex(1);

 size_t len = Breakpoints.size();
 const char *zestring = Breakpoints.c_str();
 unsigned int last_x, x;
 bool two_parter = 0;
 bool logical = 1;

 CurGame->Debugger->FlushBreakPoints(type);

 for(last_x = x = 0; x < len; x++)
 {
  if(zestring[x] == '-')
   two_parter = 1;
  else if(zestring[x] == '*')
  {
   logical = 0;
   last_x++;
  }
  else if(zestring[x] == ' ' || x == len - 1)
  {
   uint32 A1, A2;

   if(two_parter)
   {
    char sa1[64], sa2[64];

    if(!logical)
    {
     if(trio_sscanf(zestring + last_x, "%63[^-]%*[-]%63s", sa1, sa2) < 2) continue;

     //printf("%s %s\n", sa1, sa2);
     A1 = ParsePhysAddr(sa1);
     A2 = ParsePhysAddr(sa2);
    }
    else
     if(trio_sscanf(zestring + last_x, "%08x%*[-]%08x", &A1, &A2) < 2) continue;
   }
   else
   {
    if(!logical)
    {
     char sa1[64];

     trio_sscanf(zestring + last_x, "%s", sa1);

     A1 = ParsePhysAddr(sa1);
    }
    else
     if(trio_sscanf(zestring + last_x, "%08x", &A1) != 1) continue;

    A2 = A1;
   }
   //printf("%04x %04x %d\n", A1, A2, logical);
   CurGame->Debugger->AddBreakPoint(type, A1, A2, logical);
   last_x = x + 1;
   logical = 1;
  }
 }
 LockGameMutex(0);
}

static unsigned int RegsPos = 0;
static uint32 InRegs = 0;
static uint32 RegsCols = 0;
static uint32 RegsColsCounts[5];
static uint32 RegsColsPixOffset[5];
static uint32 RegsTotalWidth;

#define MK_COLOR_A(r,g,b,a) ( ((a)<<surface->format->Ashift) | ((r)<<surface->format->Rshift) | ((g) << surface->format->Gshift) | ((b) << surface->format->Bshift))
#define DIS_ENTRIES	28

static std::vector<uint32> PCBreakPoints;
static void RedoPCBreakPoints(void)
{
 LockGameMutex(1);
 CurGame->Debugger->FlushBreakPoints(BPOINT_PC);

 for(unsigned int x = 0; x < PCBreakPoints.size(); x++)
 {
  CurGame->Debugger->AddBreakPoint(BPOINT_PC, PCBreakPoints[x], PCBreakPoints[x], 1);
 }
 LockGameMutex(0);
}

static INLINE bool IsPCBreakPoint(uint32 A)
{
 unsigned int max = PCBreakPoints.size();

 for(unsigned int x = 0; x < max; x++)
  if(PCBreakPoints[x] == A)
   return(1);
 return(0);
}

static void TogglePCBreakPoint(uint32 A)
{
 for(unsigned int x = 0; x < PCBreakPoints.size(); x++)
 {
  if(PCBreakPoints[x] == A)
  {
   PCBreakPoints.erase(PCBreakPoints.begin() + x);
   RedoPCBreakPoints();
   return;
  }
 }
 PCBreakPoints.push_back(A);
 RedoPCBreakPoints();
}


static uint32 WatchAddr = 0x0000, WatchAddrPhys = 0x0000;
static uint32 DisAddr = 0x0000;
static int NeedDisAddrChange = 0;

static bool NeedPCBPToggle = 0;
static volatile int NeedStep = 0;
static volatile int NeedRun = 0;
static bool NeedBreak = 0;
volatile bool InSteppingMode = 0; // R/W in game thread, read in main thread(only when GameMutex is held!)

static std::string CurRegLongName;
static std::string CurRegDetails;

static void DrawRegs(RegGroupType *rg, SDL_Surface *surface, uint32 *pixels, int highlight)
{
  uint32 pitch32 = surface->pitch >> 2;
  uint32 *row = pixels;
  unsigned int meowcow = 0;
  RegType *rec = rg->Regs;

  while(rec->bsize)
  {
   char nubuf[256];
   uint32 color = MK_COLOR_A(0xFF, 0xFF, 0xFF, 0xFF);
   uint32 rname_color = MK_COLOR_A(0xE0, 0xFF, 0xFF, 0xFF);

   std::string *details_ptr = NULL;

   if(highlight != -1)
   {
    if((unsigned int)highlight == meowcow)
    {
     CurRegLongName = rec->long_name;
     rname_color = color = MK_COLOR_A(0xFF, 0x00, 0x00, 0xFF);
     details_ptr = &CurRegDetails;
    }
   }
   int prew = DrawTextTrans(row, surface->pitch, 128, (UTF8*)rec->name.c_str(), rname_color, 0, 1);

   if(rec->bsize == 4)
    snprintf(nubuf, 256, ": %08X", rg->GetRegister(rec->name, details_ptr));
   else if(rec->bsize == 3)
    snprintf(nubuf, 256, ": %06X", rg->GetRegister(rec->name, details_ptr));
   else if(rec->bsize == 2)
    snprintf(nubuf, 256, ": %04X", rg->GetRegister(rec->name, details_ptr));
   else if(rec->bsize == 1)
    snprintf(nubuf, 256, ": %02X", rg->GetRegister(rec->name, details_ptr));

   DrawTextTrans(row + prew, surface->pitch, 64, (UTF8*)nubuf, color, 0, 1);
   row += 7 * pitch32;
   rec++;
   meowcow++;
  }
}

typedef enum
{
 None = 0,
 DisGoto,
 WatchGoto,
 EditRegs0,
 EditRegs1,
 EditRegs2,
 PokeMe,
 PokeMeHL,
 ReadBPS,
 WriteBPS,
 IOReadBPS,
 IOWriteBPS,
 AuxReadBPS,
 AuxWriteBPS,
 ForceInt
} PromptType;
static PromptType InPrompt = None;

class DebuggerPrompt : public HappyPrompt
{
	public:

	DebuggerPrompt(const std::string &ptext, const std::string &zestring) : HappyPrompt(ptext, zestring)
	{

	}
	~DebuggerPrompt()
	{

	}
	private:
	void TheEnd(const std::string &pstring)
	{
                  char *tmp_c_str = strdup(pstring.c_str());

                  if(InPrompt == DisGoto)
                  {
                   trio_sscanf(tmp_c_str, "%08X", &DisAddr);
                   DisAddr &= ((1ULL << CurGame->Debugger->LogAddrBits) - 1);
                  }
                  else if(InPrompt == ReadBPS)
                  {
                   ReadBreakpoints = std::string(tmp_c_str);
                   ParseBreakpoints(ReadBreakpoints, BPOINT_READ);
                  }
                  else if(InPrompt == WriteBPS)
                  {
                   WriteBreakpoints = std::string(tmp_c_str);
                   ParseBreakpoints(WriteBreakpoints, BPOINT_WRITE);
                  }
                  else if(InPrompt == IOReadBPS)
                  {
                   IOReadBreakpoints = std::string(tmp_c_str);
                   ParseBreakpoints(IOReadBreakpoints, BPOINT_IO_READ);
                  }
                  else if(InPrompt == IOWriteBPS)
                  {
                   IOWriteBreakpoints = std::string(tmp_c_str);
                   ParseBreakpoints(IOWriteBreakpoints, BPOINT_IO_WRITE);
                  }
                  else if(InPrompt == AuxReadBPS)
                  {
                   AuxReadBreakpoints = std::string(tmp_c_str);
                   ParseBreakpoints(AuxReadBreakpoints, BPOINT_AUX_READ);
                  }
                  else if(InPrompt == AuxWriteBPS)
                  {
                   AuxWriteBreakpoints = std::string(tmp_c_str);
                   ParseBreakpoints(AuxWriteBreakpoints, BPOINT_AUX_WRITE);
                  }
                  else if(InPrompt == ForceInt)
                  {
		   LockGameMutex(1);
                   CurGame->Debugger->IRQ(atoi(tmp_c_str));
		   LockGameMutex(0);
                  }
                  else if(InPrompt == PokeMe)
                  {
                   uint32 A = 0,V = 0,S = 1;
                   bool logical = 1;

                   char *meow_str = tmp_c_str;

                   if(meow_str[0] == '*')
                   {
                    meow_str++;
                    logical = 0;
                   }

                   int ssf_ret;

                   if(logical)
                    ssf_ret = trio_sscanf(tmp_c_str, "%08X %08X %d", &A, &V, &S);
                   else
                   {
                    char sa[64];

                    ssf_ret = trio_sscanf(tmp_c_str, "%63s %08X %d", sa, &V, &S);

                    A = ParsePhysAddr(sa);
                   }

                   if(ssf_ret >= 2) // Allow size to be omitted, implicit as '1'
                   {
                    A &= ((1ULL << CurGame->Debugger->LogAddrBits) - 1);
                    if(S < 1) S = 1;
                    if(S > 4) S = 4;

                    LockGameMutex(1);
		    MemPoke(A, V, S, 0, logical);
                    //CurGame->Debugger->MemPoke(A, V, S, 0, logical);
                    LockGameMutex(0);
                   }
                  }
                  else if(InPrompt == PokeMeHL)
                  {
                   uint32 A = 0,V = 0,S = 1;
                   bool logical = 1;

                   char *meow_str = tmp_c_str;

                   if(meow_str[0] == '*')
                   {
                    meow_str++;
                    logical = 0;
                   }

                   if(trio_sscanf(meow_str, "%08X %08X %d", &A, &V, &S) >= 2) // Allow size to be omitted, implicit as '1'
                   {
                    A &= ((1ULL << CurGame->Debugger->LogAddrBits) - 1);

                    if(S < 1) S = 1;
                    if(S > 4) S = 4;

                    LockGameMutex(1);
		    MemPoke(A, V, S, 1, logical);
//                    CurGame->Debugger->MemPoke(A, V, S, 1, logical);
                    LockGameMutex(0);
                   }
                  }
                  else if(InPrompt == EditRegs0 || InPrompt == EditRegs1 || InPrompt == EditRegs2)
                  {
                   uint32 RegValue = 0;

                   trio_sscanf(tmp_c_str, "%08X", &RegValue);
                   LockGameMutex(1);
                   CurGame->Debugger->RegGroups[(InRegs - 1)]->SetRegister(PromptText, RegValue);
                   LockGameMutex(0);
                  }
                  else if(InPrompt == WatchGoto)
                  {
                   if(WatchLogical)
                   {
                    trio_sscanf(tmp_c_str, "%08X", &WatchAddr);
                    WatchAddr &= 0xFFF0;
                   }
                   else
                   {
                    trio_sscanf(tmp_c_str, "%08X", &WatchAddrPhys);
                    WatchAddrPhys &= (((uint64)1 << CurGame->Debugger->PhysAddrBits) - 1);
                    WatchAddrPhys &= ~0xF;
                   }
                  }
                  free(tmp_c_str);
                  InPrompt = None;

	}
};

static DebuggerPrompt *myprompt = NULL;

// Call this function from the main thread
void Debugger_Draw(SDL_Surface *surface, SDL_Rect *rect, const SDL_Rect *screen_rect)
{
 if(!IsActive) return;

 if(WhichMode == 1)
 {
  SDL_FillRect(surface, rect, 0);
  GfxDebugger_Draw(surface, rect, screen_rect);
  return;
 }
 else if(WhichMode == 2)
 {
  SDL_FillRect(surface, rect, MK_COLOR_A(0, 0, 0, 0xc0));
  MemDebugger_Draw(surface, rect, screen_rect);
  return;  
 }
 uint32 * pixels = (uint32 *)surface->pixels;
 uint32 pitch32 = surface->pitch >> 2;

 for(unsigned int y = 0; y < rect->h; y++)
 {
  uint32 *row = pixels + y * pitch32;
  for(unsigned int x = 0; x < rect->w; x++)
  {
   //printf("%d %d %d\n", y, x, pixels);
   row[x] = MK_COLOR_A(0, 0, 0, 0xc0);
   //row[x] = MK_COLOR_A(0x00, 0x00, 0x00, 0x7F);
  }
 }


 LockGameMutex(1);

 // We need to disassemble (maximum_instruction_size * (DIS_ENTRIES / 2) * 3)
 // bytes to make sure we can center our desired DisAddr, and
 // that we have enough disassembled datums for displayaling and
 // worshipping cactus mules.

 int PreBytes = CurGame->Debugger->MaxInstructionSize * (DIS_ENTRIES / 2) * 2;
 int DisBytes = CurGame->Debugger->MaxInstructionSize * (DIS_ENTRIES / 2) * 3;

 uint32 A = (DisAddr - PreBytes) & ((1ULL << CurGame->Debugger->LogAddrBits) - 1);

 std::vector<std::string> DisBliss;
 std::vector<uint32> DisBlissA;
 int indexcow = 0;

 while(DisBytes > 0)
 {
  uint32 lastA = A;

  //printf("%08x %08x\n", A, DisAddr);
  if(A == DisAddr)
   indexcow = DisBliss.size();

  DisBlissA.push_back(A);
  DisBliss.push_back(CurGame->Debugger->Disassemble(A, DisAddr)); // A is passed by reference to Disassemble()

  if(A > DisAddr && lastA < DisAddr) // Err, oops, resynch to DisAddr if necessary
  {
   A = DisAddr;
  }

  int64 consumed = (int64)A - lastA;  
  if(consumed < 0)
  {
   // Fixme
   consumed += 1ULL << CurGame->Debugger->LogAddrBits;
  }
  DisBytes -= consumed;
 }

 for(int x = 0; x < DIS_ENTRIES; x++)
 {
  int32 dbi = indexcow - (DIS_ENTRIES / 2) + x;

  if(dbi < 0 || dbi >= DisBliss.size())
  {
   puts("Disassembly error!");
   break;
  }

  std::string dis_str = DisBliss[dbi];
  uint32 A = DisBlissA[dbi];

  char addr_text[64];
  uint32 color = MK_COLOR_A(0xFF, 0xFF, 0xFF, 0xFF);
  uint32 addr_color = MK_COLOR_A(0xa0, 0xa0, 0xFF, 0xFF);

  if(CurGame->Debugger->LogAddrBits == 32)
   snprintf(addr_text, 256, " %08X: ", A);
  else
   snprintf(addr_text, 256, " %04X: ", A);
  
  if(A == DisAddr)
  {
   addr_text[0] = '>';
   if(!InRegs)
    addr_color = color = MK_COLOR_A(0xFF, 0x00, 0x00, 0xFF);

   if(NeedPCBPToggle)
   {
    TogglePCBreakPoint(A);
    NeedPCBPToggle = 0;
   }
  }
  if(IsPCBreakPoint(A))
   addr_text[0] = addr_text[0] == '>' ? '#' : '*';

  DrawTextTrans(pixels + x * 7 * pitch32, surface->pitch, rect->w, (UTF8*)addr_text, addr_color, 0, 1);
  DrawTextTrans(pixels + x * 7 * pitch32 + strlen(addr_text) * 5, surface->pitch, rect->w - strlen(addr_text) * 5, (UTF8*)dis_str.c_str(), color, 0, 1);
 }

 if(NeedDisAddrChange)
 {
  if((indexcow + NeedDisAddrChange) >= DisBlissA.size())
  {
   puts("Error, gack!");
  }
  else
  {
   DisAddr = DisBlissA[indexcow + NeedDisAddrChange];
   NeedDisAddrChange = 0;
  }
 }

 CurRegLongName = "";
 CurRegDetails = "";

 for(unsigned int rp = 0; rp < CurGame->Debugger->RegGroups.size(); rp++)
  DrawRegs(CurGame->Debugger->RegGroups[rp], surface, pixels + rect->w - RegsTotalWidth + RegsColsPixOffset[rp], (InRegs == rp + 1) ? (int)RegsPos : -1); // 175

 DrawTextTrans(pixels + 28 * 7 * pitch32, surface->pitch, surface->w, (UTF8*)CurRegLongName.c_str(), MK_COLOR_A(0xa0, 0xa0, 0xFF, 0xFF), TRUE, 1);
 DrawTextTrans(pixels + 29 * 7 * pitch32, surface->pitch, surface->w, (UTF8*)CurRegDetails.c_str(), MK_COLOR_A(0x60, 0xb0, 0xFF, 0xFF), TRUE, 1);

 if(!InRegs && CurGame->Debugger->GetBranchTrace)
 {
  std::vector<std::string> btrace = CurGame->Debugger->GetBranchTrace();
  uint32 *btpixels = pixels + (28 * 7) * pitch32;
  char strbuf[256];
  int strbuf_len = 0;

  for(unsigned int y = 0; y < btrace.size() / 2; y++)
  {
   strbuf_len += sprintf(strbuf + strbuf_len, "%s→", btrace[y].c_str());
  }
  DrawTextTrans(btpixels, surface->pitch, rect->w, (UTF8*)strbuf, MK_COLOR_A(0x60, 0xb0, 0xfF, 0xFF), TRUE, 1);

  strbuf_len = 0;
  for(unsigned int y = btrace.size() / 2; y < btrace.size(); y++)
  {
   strbuf_len += sprintf(strbuf + strbuf_len, "%s→", btrace[y].c_str());
  }
  strbuf[strbuf_len - 1] = 0; // Get rid of the trailing →
  DrawTextTrans(btpixels + 7 * pitch32, surface->pitch, rect->w, (UTF8*)strbuf, MK_COLOR_A(0x60, 0xb0, 0xfF, 0xFF), TRUE, 1);
 }

 // Draw memory watch section
 {
  uint32 *watchpixels = pixels + ((30 * 7)) * pitch32;
 
  for(int y = 0; y < 4; y++)
  {
   uint32 *row = watchpixels + y * pitch32 * 7;
   char tbuf[256];
   char asciistr[32 + 1];
   uint32 ewa;
   uint32 ewa_bits;
   uint32 ewa_mask;

   asciistr[16] = 0;  

   if(WatchLogical)
   {
    ewa_bits = CurGame->Debugger->LogAddrBits;
    ewa = WatchAddr;
   }
   else
   {
    ewa_bits = CurGame->Debugger->PhysAddrBits;
    ewa = WatchAddrPhys;
   }
   
   ewa_mask = ((uint64)1 << ewa_bits) - 1;

   if(ewa_bits <= 16)
    snprintf(tbuf, 256, "%04X: ", (ewa + y * 32) & ewa_mask);
   else if(ewa_bits <= 20)
    snprintf(tbuf, 256, "%05X: ", (ewa + y * 32) & ewa_mask);
   else if(ewa_bits <= 24)
    snprintf(tbuf, 256, "%06X: ", (ewa + y * 32) & ewa_mask);
   else
    snprintf(tbuf, 256, "%08X: ", (ewa + y * 32) & ewa_mask);

   row += DrawTextTrans(row, surface->pitch, rect->w, (UTF8 *)tbuf, MK_COLOR_A(0xa0, 0xa0, 0xFF, 0xFF), 0, 1);
   for(int x = 0; x < 32; x++)
   {
    uint8 zebyte = CurGame->Debugger->MemPeek((ewa + y * 32 + x) & ewa_mask, 1, 1, WatchLogical);
    uint32 bcolor = MK_COLOR_A(0xFF, 0xFF, 0xFF, 0xFF);

    if(x & 1)
     bcolor = MK_COLOR_A(0xD0, 0xFF, 0xF0, 0xFF);
    if(!(x & 0x7))
     bcolor = MK_COLOR_A(0xFF, 0x80, 0xFF, 0xFF);
    asciistr[x] = zebyte;
    if(zebyte & 0x80 || !zebyte)
     asciistr[x] = '.';

    if(x == 16) row += 7;

    snprintf(tbuf, 256, "%02X", zebyte);
    row += DrawTextTrans(row, surface->pitch, rect->w, (UTF8*)tbuf, bcolor, 0, 1) + 2;
   }
   DrawTextTrans(row + 5, surface->pitch, rect->w, (UTF8 *)asciistr, MK_COLOR_A(0xFF, 0xFF, 0xFF, 0xFF), 0, 1);
  }
 }  

 if(InPrompt)
  myprompt->Draw(surface, rect);
 else if(myprompt)
 {
  delete myprompt;
  myprompt = NULL;
 }
 LockGameMutex(0);
}

// Function called from game thread
static void CPUCallback(uint32 PC)
{
 if((NeedStep == 2 && !InSteppingMode) || NeedBreak)
 {
  DisAddr = PC;
  NeedStep = 0;
  InSteppingMode = 1;
  NeedBreak = 0;
 }

 if(NeedStep == 1)
 {
  DisAddr = PC;
  NeedStep = 0;
 }

 while(InSteppingMode && GameThreadRun)
 {
  DebuggerFudge();
  if(NeedStep == 2)
  {
   NeedStep--;
   break;
  }
  if(NeedRun)
  {
   NeedStep = 0;
   NeedRun = 0;
   InSteppingMode = 0;
  }
 }
 if(NeedRun) NeedRun = 0;
}

// Function called from game thread
static void BPCallback(uint32 PC)
{
 NeedBreak = 1;
 IsActive = 1;
}

// Function called from game thread, input driver code.
void Debugger_ForceStepIfStepping(void)
{
 if(InSteppingMode)
  NeedStep = 2;
}

// Call this function from any thread:
// Only call with w or h non-NULL in main thread
bool Debugger_IsActive(unsigned int *w, unsigned int *h)
{
 switch(WhichMode)
 {
  default:
  case 0: if(w) *w = 640; if(h) *h = 240; break;
  case 1: if(w) *w = 384; if(h) *h = 320; break;
  case 2: if(w) *w = 320; if(h) *h = 240; break;
 }

 return(IsActive);
}


// Call this function from the game thread:
bool Debugger_Toggle(void)
{
 if(CurGame->Debugger)
 {
  IsActive = !IsActive;

  if(IsActive)
  {
   if(NeedInit)
   {
    NeedInit = FALSE;
    WatchAddr = CurGame->Debugger->DefaultWatchAddr;
    DisAddr = CurGame->Debugger->RegGroups[0]->GetRegister("PC", NULL);

    RegsCols = 0;
    RegsTotalWidth = 0;

    memset(RegsColsCounts, 0, sizeof(RegsColsCounts));

    int pw_offset = 0;
    for(unsigned int r = 0; r < CurGame->Debugger->RegGroups.size(); r++)
    {
     uint32 pw = 0;

     for(int x = 0; CurGame->Debugger->RegGroups[r]->Regs[x].bsize; x++)
     {
      uint32 tmp_pw = 5 * (strlen(CurGame->Debugger->RegGroups[r]->Regs[x].name.c_str()) + CurGame->Debugger->RegGroups[r]->Regs[x].bsize * 2 + 2 + 2);
      if(tmp_pw > pw)
       pw = tmp_pw;

      RegsColsCounts[r]++;
     }

     RegsCols++;
     RegsColsPixOffset[r] = pw_offset;
     pw_offset += pw;
    }
    RegsTotalWidth = pw_offset;
    NeedHooksInstalled = TRUE;
   }

   if(NeedHooksInstalled)
   {
    NeedHooksInstalled = FALSE;
    CurGame->Debugger->SetCPUCallback(CPUCallback);
    CurGame->Debugger->SetBPCallback(BPCallback);
    ParseBreakpoints(ReadBreakpoints, BPOINT_READ);
    ParseBreakpoints(WriteBreakpoints, BPOINT_WRITE);
    ParseBreakpoints(IOReadBreakpoints, BPOINT_IO_READ);
    ParseBreakpoints(IOWriteBreakpoints, BPOINT_IO_WRITE);
    ParseBreakpoints(AuxReadBreakpoints, BPOINT_AUX_READ);
    ParseBreakpoints(AuxReadBreakpoints, BPOINT_AUX_READ);

    if(WhichMode == 1)
     GfxDebugger_SetActive(TRUE);
    else if(WhichMode == 2)
     MemDebugger_SetActive(TRUE);
   }
  }
  else // Disabled, yahyahyah
  {
   // Only uninstall our hooks if we don't have any active breakpoints.
   if(!ReadBreakpoints.size() && !WriteBreakpoints.size() && !IOReadBreakpoints.size() && 
	!IOWriteBreakpoints.size() && !AuxReadBreakpoints.size() && !AuxWriteBreakpoints.size())
   {
    NeedHooksInstalled = TRUE;
    CurGame->Debugger->SetCPUCallback(FALSE);
    CurGame->Debugger->SetBPCallback(FALSE);
    GfxDebugger_SetActive(FALSE);
    MemDebugger_SetActive(FALSE);
   }
  }
 }
 return(IsActive);
}


// Called from the main thread
void Debugger_Event(const SDL_Event *event)
{
  if(event->type == SDL_KEYDOWN && event->key.keysym.mod & KMOD_ALT)
  {
   switch(event->key.keysym.sym)
   {
    case SDLK_1: WhichMode = 0; 
		 GfxDebugger_SetActive(FALSE);
		 MemDebugger_SetActive(FALSE);
		 break;
    case SDLK_2: WhichMode = 1;
		 GfxDebugger_SetActive(TRUE); 
		 MemDebugger_SetActive(FALSE);
		 break;
    case SDLK_3: if(CurGame->Debugger->AddressSpaces.size())
		 {
		  WhichMode = 2;
		  GfxDebugger_SetActive(FALSE);
		  MemDebugger_SetActive(TRUE);
		 }
		 break;

    default: break;
   }
  }
  if(WhichMode == 1)
  {
   GfxDebugger_Event(event);
   return;
  }
  else if(WhichMode == 2)
  {
   MemDebugger_Event(event);
   return;
  }
  switch(event->type)
  {
   case SDL_KEYDOWN:
        if(event->key.keysym.mod & KMOD_ALT)
         break;

        if(InPrompt)
        {
	 myprompt->Event(event);

	 if(event->key.keysym.sym == SDLK_ESCAPE)
	 {
	  delete myprompt;
	  myprompt = NULL; 
	  InPrompt = None;
	 }
	}
        else switch(event->key.keysym.sym)
        {
	 default: break;
	 case SDLK_HOME:
		if(event->key.keysym.mod & KMOD_SHIFT)
		{
		 if(WatchLogical)
		  WatchAddr = 0;
		 else
		  WatchAddrPhys = 0;
		}
		else
		 DisAddr = 0x0000;
		break;
	 case SDLK_END:
		if(event->key.keysym.mod & KMOD_SHIFT)
		{
                 if(WatchLogical)
                  WatchAddr = ((1ULL << CurGame->Debugger->LogAddrBits) - 1) & ~0x7F;
                 else
                  WatchAddrPhys = (((uint64)1 << CurGame->Debugger->PhysAddrBits) - 1) & ~0x7F;
		}
		else
		 DisAddr = ((1ULL << CurGame->Debugger->LogAddrBits) - 1);
		break;

         case SDLK_PAGEUP:
          if(event->key.keysym.mod & KMOD_SHIFT)
	  {
	   if(WatchLogical)
            WatchAddr = (WatchAddr - 0x80) & ((1ULL << CurGame->Debugger->LogAddrBits) - 1);
	   else
	    WatchAddrPhys = (WatchAddrPhys - 0x80) & (((uint64)1 << CurGame->Debugger->PhysAddrBits) - 1);
	  }
          else
	   NeedDisAddrChange = -11;
          break;
	 case SDLK_PAGEDOWN: 
          if(event->key.keysym.mod & KMOD_SHIFT) 
	  {
	   if(WatchLogical)
            WatchAddr = (WatchAddr + 0x80) & ((1ULL << CurGame->Debugger->LogAddrBits) - 1);
	   else
	    WatchAddrPhys = (WatchAddrPhys + 0x80) & (((uint64)1 << CurGame->Debugger->PhysAddrBits) - 1);
	  }
          else  
	   NeedDisAddrChange = 11;
          break;

	case SDLK_m:
	   WatchLogical = !WatchLogical;
	   break;

	case SDLK_LEFT:
		if(!InRegs)
		 InRegs = RegsCols;		
		else
		 InRegs = InRegs - 1;

		if(InRegs && RegsPos >= RegsColsCounts[InRegs - 1])
 		 RegsPos = RegsColsCounts[InRegs - 1] - 1;

		break;
	case SDLK_RIGHT:
		InRegs = (InRegs + 1) % (RegsCols + 1);
                if(InRegs && RegsPos >= RegsColsCounts[InRegs - 1])
                  RegsPos = RegsColsCounts[InRegs - 1] - 1;
		break;
        case SDLK_UP:
	  if(InRegs)
	  {
		if(RegsPos)
		 RegsPos--;
	  }
	  else
	  {
           if(event->key.keysym.mod & KMOD_SHIFT)
	   {
	    if(WatchLogical)
             WatchAddr = (WatchAddr - 0x10) & ((1ULL << CurGame->Debugger->LogAddrBits) - 1);
	    else
	     WatchAddrPhys = (WatchAddrPhys - 0x10) & (((uint64)1 << CurGame->Debugger->PhysAddrBits) - 1);
	   }
           else
	   {
            NeedDisAddrChange = -1;
	   }
	  }
          break;
         case SDLK_DOWN:
	  if(InRegs)
	  {
                if(RegsPos < (RegsColsCounts[InRegs - 1] - 1))
		 RegsPos++;
	  }
	  else
	  {
           if(event->key.keysym.mod & KMOD_SHIFT)
	   {
	    if(WatchLogical)
             WatchAddr = (WatchAddr + 0x10) & ((1ULL << CurGame->Debugger->LogAddrBits) - 1);
	    else
	     WatchAddrPhys = (WatchAddrPhys + 0x10) & (((uint64)1 << CurGame->Debugger->PhysAddrBits) - 1);
	   }
           else
	   {
	    NeedDisAddrChange = 1;
	   }
	  }
          break;

	 case SDLK_t:
		if(CurGame->Debugger->ToggleSyntax)
		{
		 LockGameMutex(1);
		 CurGame->Debugger->ToggleSyntax();
		 LockGameMutex(0);
		}
		break;

         case SDLK_SPACE:
		NeedPCBPToggle = 1;
		break;
	 case SDLK_s:
		LockGameMutex(1);
		NeedStep = 2;
		LockGameMutex(0);
		break;
	 case SDLK_w:
                if(event->key.keysym.mod & KMOD_SHIFT)
                {
		 if(event->key.keysym.mod & KMOD_CTRL)
		 {
		  InPrompt = IOWriteBPS;
		  myprompt = new DebuggerPrompt("I/O Write Breakpoints", IOWriteBreakpoints);
		 }
		 else
		 {
                  InPrompt = WriteBPS;
		  myprompt = new DebuggerPrompt("Write Breakpoints", WriteBreakpoints);
                 }
                }
                else if(event->key.keysym.mod & KMOD_CTRL)
                {
                 InPrompt = AuxWriteBPS;
                 myprompt = new DebuggerPrompt("Aux Write Breakpoints", AuxWriteBreakpoints);
                }
		break;

	 case SDLK_r:
		if(event->key.keysym.mod & KMOD_SHIFT)
		{
		 if(event->key.keysym.mod & KMOD_CTRL)
		 {
		  InPrompt = IOReadBPS;
		  myprompt = new DebuggerPrompt("I/O Read Breakpoints", IOReadBreakpoints);
		 }
		 else
		 {
		  InPrompt = ReadBPS;
		  myprompt = new DebuggerPrompt("Read Breakpoints", ReadBreakpoints);
		 }
		}
                else if(event->key.keysym.mod & KMOD_CTRL)
                {
                 InPrompt = AuxReadBPS;
                 myprompt = new DebuggerPrompt("Aux Read Breakpoints", AuxReadBreakpoints);
                }
		else
		 NeedRun = 1;
		break;

	 case SDLK_i:
	 	if(!InPrompt && CurGame->Debugger->IRQ)
		{
		 InPrompt = ForceInt;
		 myprompt = new DebuggerPrompt("Force Interrupt", "");
		}
		break;
	 case SDLK_p:
		if(!InPrompt)
		{
		 if(event->key.keysym.mod & KMOD_SHIFT)
		 {
		  InPrompt = PokeMeHL;
		  myprompt = new DebuggerPrompt("HL Poke(address value size)", "");
		 }
		 else
		 {
		  InPrompt = PokeMe;
		  myprompt = new DebuggerPrompt("Poke(address value size)", "");
		 }
		}
		break;

	  case SDLK_RETURN:
		 {
		  char buf[64];
		  std::string ptext;

		  if(event->key.keysym.mod & KMOD_SHIFT)
		  {
		   InPrompt = WatchGoto;
		   ptext = "Watch Address";
                   snprintf(buf, 64, "%08X", WatchLogical ? WatchAddr : WatchAddrPhys);
	 	  }
		  else
		  {
		   if(InRegs)
		   {
		    InPrompt = (PromptType)(EditRegs0 + InRegs - 1);
		    ptext = CurGame->Debugger->RegGroups[InRegs - 1]->Regs[RegsPos].name;
		    int len = CurGame->Debugger->RegGroups[InRegs - 1]->Regs[RegsPos].bsize;

		    LockGameMutex(1);
		    uint32 RegValue = CurGame->Debugger->RegGroups[InRegs - 1]->GetRegister(ptext, NULL);
		    LockGameMutex(0);

		    if(len == 1)
		     snprintf(buf, 64, "%02X", RegValue);
		    else if(len == 2)
		     snprintf(buf, 64, "%04X", RegValue);
		    else
		     snprintf(buf, 64, "%08X", RegValue);
		   }
		   else
		   {
		    InPrompt = DisGoto;
		    ptext = "Disassembly Address";
                    snprintf(buf, 64, "%04X", DisAddr);
		   }
		  }

                  myprompt = new DebuggerPrompt(ptext, buf);
		 }
	         break;
         }
         break;
  }
}

