/* 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 "memdebugger.h"
#include "debugger.h"
#include "prompt.h"

#include <stdlib.h>
#include <ctype.h>
#include <trio/trio.h>

static bool IsActive = 0;
static uint32 ASpacePos[16] = { 0 };
static uint32 ASpaceScroll[16] = { 0 };
static int CurASpace = 0;
static bool LowNib = FALSE;
static bool InEditMode = FALSE;
static std::string BSS_String, RS_String;
static char *error_string;
static uint32 error_time;

typedef enum
{
 None = 0,
 Goto,
 ByteStringSearch,
 RelSearch,
 DumpMem,
 LoadMem
} PromptType;

static PromptType InPrompt = None;

class MemDebuggerPrompt : public HappyPrompt
{
        public:

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

        }
        ~MemDebuggerPrompt()
        {

        }
        private:
        void TheEnd(const std::string &pstring)
        {
	 if(error_string)
	 {
	  free(error_string);
	  error_string = NULL;
	 }
         if(InPrompt == Goto)
         {
	  unsigned long NewAddie;

	  if(trio_sscanf(pstring.c_str(), "%08lx", &NewAddie) == 1)
	  {
	   ASpacePos[CurASpace] = NewAddie;
	   ASpaceScroll[CurASpace] = NewAddie &~ 0xF; // - 0x80;
	   LowNib = FALSE;
	   InEditMode = FALSE;
	  }
         }
         else if(InPrompt == DumpMem)
         {
          uint32 A1, A2, tmpsize;
          char fname[256];
	  bool acceptable = FALSE;

	  
          if(trio_sscanf(pstring.c_str(), "%08x %08x %255[^\r\n]", &A1, &A2, fname) == 3)
	   acceptable = TRUE;
          else if(trio_sscanf(pstring.c_str(), "%08x +%08x %255[^\r\n]", &A1, &tmpsize, fname) == 3)
	  {
	   acceptable = TRUE;
	   A2 = A1 + tmpsize - 1;
	  }
	  if(acceptable)
          {
           FILE *fp = fopen(fname, "wb");
           if(fp)
           {
            LockGameMutex(1);
            for(uint64 a = A1; a <= A2; a++)
            {
	     uint8 c;
	     //FIXME:  Optimize!
             CurGame->Debugger->AddressSpaces[CurASpace].GetAddressSpaceBytes(CurGame->Debugger->AddressSpaces[CurASpace].name, a, 1, &c);

             fputc(c, fp);
            }
            LockGameMutex(0);
            fclose(fp);
           }
          }
         }
         else if(InPrompt == LoadMem)
         {
          uint32 A1, A2, tmpsize;
          char fname[256];
          bool acceptable = FALSE;


          if(trio_sscanf(pstring.c_str(), "%08x %08x %255[^\r\n]", &A1, &A2, fname) == 3)
           acceptable = TRUE;
          else if(trio_sscanf(pstring.c_str(), "%08x +%08x %255[^\r\n]", &A1, &tmpsize, fname) == 3)
          {
           acceptable = TRUE;
           A2 = A1 + tmpsize - 1;
          }
          if(acceptable)
          {
           FILE *fp = fopen(fname, "rb");
           if(fp)
           {
            LockGameMutex(1);
            for(uint64 a = A1; a <= A2; a++)
            {
             uint8 c = fgetc(fp);
             //FIXME:  Optimize!
             CurGame->Debugger->AddressSpaces[CurASpace].PutAddressSpaceBytes(CurGame->Debugger->AddressSpaces[CurASpace].name, a, 1, 1, TRUE, &c);
            }
            LockGameMutex(0);
            fclose(fp);
           }
          }
         }
	 else if(InPrompt == ByteStringSearch || InPrompt == RelSearch)
	 {
          uint32 TotalBits = CurGame->Debugger->AddressSpaces[CurASpace].TotalBits;
          uint32 zemask =  ((uint64)1 << TotalBits) - 1;

	  if(InPrompt == RelSearch)
	   RS_String = pstring;
	  else
	   BSS_String = pstring;

	  size_t byte_count;
	  size_t nib_count;
	  uint8 thebytes[pstring.size() / 2];

	  memset(thebytes, 0, sizeof(thebytes));

	  nib_count = 0;
	  for(size_t x = 0; x < pstring.size(); x++)
	  {
	   int c = tolower(pstring[x]);
	   if(c >= '0' && c <= '9')
	    c -= '0';
	   else if(c >= 'a' && c <= 'f')
	   {
	    c = c - 'a' + 0xa;
	   }
	   else if(c == ' ')
	   {
	    continue;
	   }
	   else
	   {
	    error_string = trio_aprintf("Invalid character '%c' in bytestring.", c);
	    error_time = SDL_GetTicks();
	    InPrompt = None;
	    return;
	   }
	   thebytes[nib_count >> 1] |= c << ((nib_count & 1) ? 0 : 4);
	   nib_count++;
	  }

	  if(nib_count & 1)
	  {
           error_string = trio_aprintf("Invalid number of characters in bytestring.");
           error_time = SDL_GetTicks();
           InPrompt = None;
           return;
	  }
	  byte_count = nib_count >> 1;

	  bool found = FALSE;
	  LockGameMutex(1);
	
	  if(InPrompt == RelSearch)
	  {
           const uint32 start_a = (ASpacePos[CurASpace] - 1) & zemask;
           uint32 a = start_a;
	   do
	   {
	    uint8 bbuffer[byte_count + 1];
            CurGame->Debugger->AddressSpaces[CurASpace].GetAddressSpaceBytes(CurGame->Debugger->AddressSpaces[CurASpace].name, a, byte_count + 1, bbuffer);
	    bool match = TRUE;

	    for(int i = 1; i <= byte_count; i++)
	    {
	     if(((bbuffer[i] - bbuffer[i - 1]) & 0xFF) != thebytes[i - 1])
	     {
	      match = FALSE;
	      break;
	     }
	    }
	    if(match)
	    {
	     found = TRUE;
             ASpacePos[CurASpace] = (a + 1) & zemask;
             ASpaceScroll[CurASpace] = ((a + 1) & zemask) &~0xF;	    
	     break;
	    }
	    a = (a + 1) & zemask;
	   } while(a != start_a);
	  }
	  else
	  {
           const uint32 start_a = ASpacePos[CurASpace] & zemask;
           uint32 a = start_a;
	   do
	   {
	    uint8 bbuffer[byte_count];
	    CurGame->Debugger->AddressSpaces[CurASpace].GetAddressSpaceBytes(CurGame->Debugger->AddressSpaces[CurASpace].name, a, byte_count, bbuffer);
	    if(!memcmp(bbuffer, thebytes, byte_count))
	    {
	     ASpacePos[CurASpace] = a;
	     ASpaceScroll[CurASpace] = a &~0xF;
	     found = TRUE;
	     //printf("Found: %08x\n", a);
	     break;
	    }
	    a = (a + 1) & zemask;
	   } while(a != start_a);
	  }
	  LockGameMutex(0);
	  if(!found)
	  {
           error_string = trio_aprintf("Bytestring \"%s\" not found.", pstring.c_str());
           error_time = SDL_GetTicks();
           InPrompt = None;
           return;
	  }
	 }
         InPrompt = None;
	}
};

static MemDebuggerPrompt *myprompt = NULL;

// Call this function from either thread.
void MemDebugger_SetActive(bool newia)
{
 if(CurGame->Debugger)
 {
  LockGameMutex(1);

  IsActive = newia;
  if(!newia)
  {
   InEditMode = FALSE;
   LowNib = FALSE;
  }
  LockGameMutex(0);
 }
}

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

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

 uint32 * pixels = (uint32 *)surface->pixels;
 uint32 pitch32 = surface->pitch >> 2;
 uint32 TotalBits = CurGame->Debugger->AddressSpaces[CurASpace].TotalBits;
 LockGameMutex(1);

 DrawTextTrans(pixels, surface->pitch, rect->w, (UTF8*)CurGame->Debugger->AddressSpaces[CurASpace].long_name, MK_COLOR_A(0x20, 0xFF, 0x20, 0xFF), 1, 1);
 pixels += 10 * pitch32;

 uint32 A = ASpaceScroll[CurASpace] & ((uint64)1 << TotalBits) - 1;
 uint32 Ameow; // A meow for a cat

 Ameow = A &~ 0xF;

 for(int y = 0; y < 16; y++)
 {
  uint8 byte_buffer[16];
  char abuf[32];

  Ameow &= ((uint64)1 << TotalBits) - 1;
  CurGame->Debugger->AddressSpaces[CurASpace].GetAddressSpaceBytes(CurGame->Debugger->AddressSpaces[CurASpace].name, Ameow, 16, byte_buffer);

  if(TotalBits <= 16)
   trio_snprintf(abuf, 32, "%04X:", Ameow);
  else if(TotalBits <= 24)
   trio_snprintf(abuf, 32, "%06X:", Ameow);
  else
   trio_snprintf(abuf, 32, "%08X:", Ameow);

  uint32 alen = DrawTextTrans(pixels, surface->pitch, rect->w, (UTF8*)abuf, MK_COLOR_A(0xA0, 0xA0, 0xFF, 0xFF), 0, 1);
  alen += 3;

  uint8 ascii_str[17];
  ascii_str[16] = 0;

  for(int x = 0; x < 16; x++)
  {
   uint32 bcolor = MK_COLOR_A(0xFF, 0xFF, 0xFF, 0xFF);
   char quickbuf[16];

   ascii_str[x] = byte_buffer[x];

   if(ascii_str[x] < 0x20 || ascii_str[x] >= 128)
    ascii_str[x] = '.';

   trio_snprintf(quickbuf, 16, "%02X", byte_buffer[x]);

   if(Ameow == (ASpacePos[CurASpace]&(((uint64)1 << TotalBits) - 1)))
   {
    if(InEditMode)
     if(SDL_GetTicks() & 0x80)
      DrawTextTrans(pixels + alen + (LowNib ? 5 : 0) + x * 12, surface->pitch, rect->w, (UTF8*)"▉", MK_COLOR_A(0xFF, 0xFF, 0xFF, 0xFF), 0, 1);

    bcolor = MK_COLOR_A(0xFF, 0x00, 0x00, 0xFF);
    //else
    // bcolor = MK_COLOR_A(0xFF, 0x80, 0x80, 0xFF);
   }
   DrawTextTrans(pixels + alen + x * 12, surface->pitch, rect->w, (UTF8*)quickbuf, bcolor, 0, 1);
   Ameow++;
  }
  DrawTextTrans(pixels + alen + 16 * 12, surface->pitch, rect->w, (UTF8*)ascii_str, MK_COLOR_A(0xA0, 0xA0, 0xA0, 0xFF), 0, 1);
  pixels += 9 * pitch32;
 }

 {
  char cpstr[32];
  uint32 curpos = ASpacePos[CurASpace];
  uint8 zebytes[4];
  uint32 tmpval;

  CurGame->Debugger->AddressSpaces[CurASpace].GetAddressSpaceBytes(CurGame->Debugger->AddressSpaces[CurASpace].name, curpos, 4, zebytes);

  pixels += 8 + 10 * pitch32;

  if(TotalBits <= 16)
   trio_snprintf(cpstr, 32, "%04X", curpos);
  else if(TotalBits <= 24)
   trio_snprintf(cpstr, 32, "%06X", curpos);
  else
   trio_snprintf(cpstr, 32, "%08X", curpos);

  uint32 cpplen;

  cpplen = DrawTextTrans(pixels, surface->pitch, rect->w, (UTF8*)"Cursor position: ", MK_COLOR_A(0xa0, 0xa0, 0xFF, 0xFF), 0, 1);
  DrawTextTrans(pixels + cpplen, surface->pitch, rect->w, (UTF8*)cpstr , MK_COLOR_A(0xFF, 0xFF, 0xFF, 0xFF), 0, 1);

  pixels += 5 + 10 * pitch32;
  tmpval = zebytes[0];
  trio_snprintf(cpstr, 32, "%02x(%u, %d)", tmpval, (uint8)tmpval, (int8)tmpval);
  cpplen = DrawTextTrans(pixels, surface->pitch, rect->w, (UTF8*)"1-byte value: ", MK_COLOR_A(0xA0, 0xA0, 0xFF, 0xFF), 0, 1);
  DrawTextTrans(pixels + cpplen, surface->pitch, rect->w, (UTF8*)cpstr , MK_COLOR_A(0xFF, 0xFF, 0xFF, 0xFF), 0, 1);

  pixels += 10 * pitch32;
  tmpval = zebytes[0] | (zebytes[1] << 8);
  trio_snprintf(cpstr, 32, "%04x(%u, %d)", tmpval, (uint16)tmpval, (int16)tmpval);
  cpplen = DrawTextTrans(pixels, surface->pitch, rect->w, (UTF8*)"2-byte value(LSB): ", MK_COLOR_A(0xA0, 0xA0, 0xFF, 0xFF), 0, 1);
  DrawTextTrans(pixels + cpplen, surface->pitch, rect->w, (UTF8*)cpstr , MK_COLOR_A(0xFF, 0xFF, 0xFF, 0xFF), 0, 1);

  pixels += 10 * pitch32;
  tmpval = zebytes[0] | (zebytes[1] << 8) | (zebytes[2] << 16) | (zebytes[3] << 24);
  trio_snprintf(cpstr, 32, "%08x(%u, %d)", tmpval, (uint32)tmpval, (int32)tmpval);
  cpplen = DrawTextTrans(pixels, surface->pitch, rect->w, (UTF8*)"4-byte value(LSB): ", MK_COLOR_A(0xA0, 0xA0, 0xFF, 0xFF), 0, 1);
  DrawTextTrans(pixels + cpplen, surface->pitch, rect->w, (UTF8*)cpstr , MK_COLOR_A(0xFF, 0xFF, 0xFF, 0xFF), 0, 1);

  pixels += 10 * pitch32;
  tmpval = zebytes[1] | (zebytes[0] << 8);
  trio_snprintf(cpstr, 32, "%04x(%u, %d)", tmpval, (uint16)tmpval, (int16)tmpval);
  cpplen = DrawTextTrans(pixels, surface->pitch, rect->w, (UTF8*)"2-byte value(MSB): ", MK_COLOR_A(0xA0, 0xA0, 0xFF, 0xFF), 0, 1);
  DrawTextTrans(pixels + cpplen, surface->pitch, rect->w, (UTF8*)cpstr , MK_COLOR_A(0xFF, 0xFF, 0xFF, 0xFF), 0, 1);

  pixels += 10 * pitch32;
  tmpval = zebytes[3] | (zebytes[2] << 8) | (zebytes[1] << 16) | (zebytes[0] << 24);
  trio_snprintf(cpstr, 32, "%08x(%u, %d)", tmpval, (uint32)tmpval, (int32)tmpval);
  cpplen = DrawTextTrans(pixels, surface->pitch, rect->w, (UTF8*)"4-byte value(MSB): ", MK_COLOR_A(0xA0, 0xA0, 0xFF, 0xFF), 0, 1);
  DrawTextTrans(pixels + cpplen, surface->pitch, rect->w, (UTF8*)cpstr , MK_COLOR_A(0xFF, 0xFF, 0xFF, 0xFF), 0, 1);
 }
 LockGameMutex(0);

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

 if(error_string)
 {
  if(SDL_GetTicks() >= (error_time + 4000))
  {
   free(error_string);
   error_string = NULL;
  }
  else
  {
   DrawTextTrans((uint32*)surface->pixels + (rect->h - 7) * pitch32, surface->pitch, rect->w, (UTF8*)error_string, MK_COLOR_A(0xFF, 0x00, 0x00, 0xFF), 1, 1);
  }
 }
}

// Call this from the main thread
int MemDebugger_Event(const SDL_Event *event)
{
 if(!InPrompt && myprompt)
 {
  delete myprompt;
  myprompt = NULL;
 }

 switch(event->type)
 {
  case SDL_KEYDOWN:
	if(event->key.keysym.mod & KMOD_ALT)
	 break;

	if(InEditMode && ((event->key.keysym.sym >= SDLK_0 && event->key.keysym.sym <= SDLK_9)	|| 
	   (event->key.keysym.sym >= SDLK_a && event->key.keysym.sym <= SDLK_f)))
	{
         uint8 tc = 0;
         uint8 meowbyte = 0;

         if(event->key.keysym.sym >= SDLK_0 && event->key.keysym.sym <= SDLK_9)
          tc = 0x0 + event->key.keysym.sym - SDLK_0;
         else if(event->key.keysym.sym >= SDLK_a && event->key.keysym.sym <= SDLK_f)
          tc = 0xA + event->key.keysym.sym - SDLK_a;
         CurGame->Debugger->AddressSpaces[CurASpace].GetAddressSpaceBytes(CurGame->Debugger->AddressSpaces[CurASpace].name, ASpacePos[CurASpace], 1, &meowbyte);
         meowbyte &= 0xF << ((LowNib) * 4);
         meowbyte |= tc << ((!LowNib) * 4);
         CurGame->Debugger->AddressSpaces[CurASpace].PutAddressSpaceBytes(CurGame->Debugger->AddressSpaces[CurASpace].name, ASpacePos[CurASpace], 1, 1, TRUE, &meowbyte);
         LowNib = !LowNib;
         if(!LowNib)
         {
          ASpacePos[CurASpace]++;
          if((ASpacePos[CurASpace] &~0xF) == (ASpaceScroll[CurASpace] + 256))
           ASpaceScroll[CurASpace] += 16;
         }
        }
	else if(InPrompt)
	{
         myprompt->Event(event);
	}
	else switch(event->key.keysym.sym)
	{
	 default: break;

	 case SDLK_d:
                InPrompt = DumpMem;
                myprompt = new MemDebuggerPrompt("Dump Memory(start end filename)", "");
		break;
	 case SDLK_l:
                InPrompt = LoadMem;
                myprompt = new MemDebuggerPrompt("Load Memory(start end filename)", "");
                break;
	 case SDLK_s:
	        if(CurGame->Debugger->AddressSpaces[CurASpace].TotalBits > 24)
                {
                 error_string = trio_aprintf("Address space is too large to search!");
                 error_time = SDL_GetTicks();
                }
		else
		{
		 InPrompt = ByteStringSearch;
		 myprompt = new MemDebuggerPrompt("Byte String Search", BSS_String);
		}
		break;
	 case SDLK_r:
                if(CurGame->Debugger->AddressSpaces[CurASpace].TotalBits > 24)
                {
                 error_string = trio_aprintf("Address space is too large to search!");
                 error_time = SDL_GetTicks();
                }
                else
                {
		 InPrompt = RelSearch;
		 myprompt = new MemDebuggerPrompt("Byte String Relative/Delta Search", RS_String);
		}
		break;
	 case SDLK_g:
		InPrompt = Goto;
		myprompt = new MemDebuggerPrompt("Goto Address", "");
		break;

	 case SDLK_INSERT:
		InEditMode = !InEditMode;
		LowNib = FALSE;
		break;

	 case SDLK_MINUS:
		       break;
	 case SDLK_EQUALS:
		       break;
	 case SDLK_END: ASpacePos[CurASpace] = ASpaceScroll[CurASpace] = ((uint64)1 << 32) - 16*16; break;
	 case SDLK_HOME: ASpacePos[CurASpace] = ASpaceScroll[CurASpace] = 0;
			 LowNib = FALSE;
			 break;
         case SDLK_UP: 
                        if((ASpacePos[CurASpace] &~0xF) == ASpaceScroll[CurASpace])
                         ASpaceScroll[CurASpace] -= 16;
			ASpacePos[CurASpace] -= 16;
			LowNib = FALSE;
			break;

         case SDLK_PAGEUP:
			ASpacePos[CurASpace] -= 16 * 16;
			ASpaceScroll[CurASpace] -= 16 * 16;
			LowNib = FALSE;
                        break;

	 case SDLK_PAGEDOWN:			 
                        ASpacePos[CurASpace] += 16 * 16;
			ASpaceScroll[CurASpace] += 16 * 16;
			LowNib = FALSE;
			break;

	 case SDLK_DOWN:
                        ASpacePos[CurASpace] += 16;
                        if((ASpacePos[CurASpace] &~0xF) == (ASpaceScroll[CurASpace] + 256))
                         ASpaceScroll[CurASpace] += 16;
			//printf("%08x %08x\n", ASpacePos[CurASpace], ASpaceScroll[CurASpace]);
			LowNib = FALSE;
			break;

	 case SDLK_LEFT:
                        if(ASpacePos[CurASpace] == ASpaceScroll[CurASpace])
                         ASpaceScroll[CurASpace] -= 16;
                        ASpacePos[CurASpace]--;
			LowNib = FALSE;
			break;

	 case SDLK_RIGHT:
                        ASpacePos[CurASpace]++;

                        if((ASpacePos[CurASpace] &~0xF) == (ASpaceScroll[CurASpace] + 256))
                         ASpaceScroll[CurASpace] += 16;
			LowNib = FALSE;
			break;

	 case SDLK_COMMA: 
			if(CurASpace)
			 CurASpace--;
			else
			 CurASpace = CurGame->Debugger->AddressSpaces.size() - 1;
			LowNib = FALSE;
			break;
	 case SDLK_PERIOD:
			CurASpace = (CurASpace + 1) % CurGame->Debugger->AddressSpaces.size();
			LowNib = FALSE;
			break;
	}
	break;
 }
 return(1);
}

