// VisualBoyAdvance - Nintendo Gameboy/GameboyAdvance (TM) emulator.
// Copyright (C) 1999-2003 Forgotten
// Copyright (C) 2004 Forgotten and the VBA development team

// 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, 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.

// MemoryViewer.cpp : implementation file
//

#include "stdafx.h"
#include "vba.h"
#include "MemoryViewer.h"

#include "../System.h"
extern int emulating;

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// MemoryViewer

bool MemoryViewer::isRegistered = false;

MemoryViewer::MemoryViewer()
{
  address = 0;
  addressSize = 0;
  dataSize = 0;
  editAddress = 0;
  editNibble = 0;
  displayedLines = 0;
  hasCaret = false;
  maxNibble = 0;
  font = (HFONT)GetStockObject(SYSTEM_FIXED_FONT);
  fontSize.cx = fontSize.cy = 0;
  beginAscii = 0;
  beginHex = 0;
  dlg = NULL;
  registerClass();
}

MemoryViewer::~MemoryViewer()
{
}


BEGIN_MESSAGE_MAP(MemoryViewer, CWnd)
  //{{AFX_MSG_MAP(MemoryViewer)
  ON_WM_ERASEBKGND()
  ON_WM_PAINT()
  ON_WM_VSCROLL()
  ON_WM_GETDLGCODE()
  ON_WM_LBUTTONDOWN()
  ON_WM_SETFOCUS()
  ON_WM_KILLFOCUS()
  ON_WM_KEYDOWN()
  //}}AFX_MSG_MAP
  ON_MESSAGE(WM_CHAR, OnWMChar)
  END_MESSAGE_MAP()


  /////////////////////////////////////////////////////////////////////////////
// MemoryViewer message handlers

void MemoryViewer::setDialog(IMemoryViewerDlg *d)
{
  dlg = d;
}


void MemoryViewer::setAddress(u32 a)
{
  address = a;
  if(displayedLines) {
    if(addressSize) {
      u16 addr = address;
      if((u16)(addr+(displayedLines<<4)) < addr) {
        address = 0xffff - (displayedLines<<4) + 1;
      }
    } else {
      if((address+(displayedLines<<4)) < address) {
        address = 0xffffffff - (displayedLines<<4) + 1;
      }
    }
  }
  if(addressSize)
    address &= 0xffff;
  setCaretPos();
  InvalidateRect(NULL, TRUE);
}


void MemoryViewer::setSize(int s)
{
  dataSize = s;
  if(s == 0)
    maxNibble = 1;
  else if(s == 1)
    maxNibble = 3;
  else
    maxNibble = 7;

  InvalidateRect(NULL, TRUE);
}

BOOL MemoryViewer::OnEraseBkgnd(CDC* pDC)
{
  return TRUE;
}

void MemoryViewer::updateScrollInfo(int lines)
{
  int page = lines * 16;
  SCROLLINFO si;
  ZeroMemory(&si, sizeof(si));
  si.cbSize = sizeof(si);
  si.fMask = SIF_PAGE | SIF_RANGE | SIF_DISABLENOSCROLL | SIF_POS;
  si.nMin = 0;
  if(addressSize) {
    si.nMax = 0x10000/page;
    si.nPage = 1;
  } else {
    si.nMax = 0xa000000 / page;
    si.nPage = page;
  }

  si.nPos = address / page;
  SetScrollInfo(SB_VERT,
                &si,
                TRUE);
}

void MemoryViewer::OnPaint()
{
  CPaintDC dc(this); // device context for painting

  RECT rect;
  GetClientRect(&rect);
  int w = rect.right - rect.left;
  int h = rect.bottom - rect.top - 6;

  CDC memDC;
  memDC.CreateCompatibleDC(&dc);
  CBitmap bitmap, *pOldBitmap;
  bitmap.CreateCompatibleBitmap(&dc, w, rect.bottom - rect.top);
  pOldBitmap = memDC.SelectObject(&bitmap);

  memDC.FillRect(&rect, CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH)));
  memDC.DrawEdge(&rect, EDGE_ETCHED, BF_RECT);

  CFont *oldFont = memDC.SelectObject(CFont::FromHandle(font));

  fontSize = memDC.GetTextExtent("0", 1);

  int lines = h / fontSize.cy;

  displayedLines = lines;

  updateScrollInfo(lines);

  u32 addr = address;

  memDC.SetTextColor(RGB(0,0,0));

  u8 data[32];

  RECT r;
  r.top = 3;
  r.left = 3;
  r.bottom = r.top+fontSize.cy;
  r.right = rect.right-3;

  int line = 0;

  for(int i = 0; i < lines; i++) {
    CString buffer;
    if(addressSize)
      buffer.Format("%04X", addr);
    else
      buffer.Format("%08X", addr);
    memDC.DrawText(buffer, &r, DT_TOP | DT_LEFT | DT_NOPREFIX);
    r.left += 10*fontSize.cx;
    beginHex = r.left;
    readData(addr, 16, data);

    int j;

    if(dataSize == 0) {
      for(j = 0; j < 16; j++) {
        buffer.Format("%02X", data[j]);
        memDC.DrawText(buffer, &r, DT_TOP | DT_LEFT | DT_NOPREFIX);
        r.left += 3*fontSize.cx;
      }
    }
    if(dataSize == 1) {
      for(j = 0; j < 16; j += 2) {
        buffer.Format("%04X", data[j] | data[j+1]<<8);
        memDC.DrawText(buffer, &r, DT_TOP | DT_LEFT | DT_NOPREFIX);
        r.left += 5*fontSize.cx;
      }
    }
    if(dataSize == 2) {
      for(j = 0; j < 16; j += 4) {
        buffer.Format("%08X", data[j] | data[j+1]<<8 |
                      data[j+2] << 16 | data[j+3] << 24);
        memDC.DrawText(buffer, &r, DT_TOP | DT_LEFT | DT_NOPREFIX);
        r.left += 9*fontSize.cx;
      }
    }

    line = r.left;

    r.left += fontSize.cx;
    beginAscii = r.left;
    buffer.Empty();
    for(j = 0; j < 16; j++) {
      char c = data[j];
      if(c >= 32 && c <= 127) {
        buffer += c;
      } else
        buffer += '.';
    }

    memDC.DrawText(buffer, &r, DT_TOP | DT_LEFT | DT_NOPREFIX);
    addr += 16;
    if(addressSize)
      addr &= 0xffff;
    r.top += fontSize.cy;
    r.bottom += fontSize.cy;
    r.left = 3;
  }
  CPen pen;
  pen.CreatePen(PS_SOLID, 1, RGB(0,0,0));
  CPen *old = memDC.SelectObject(&pen);

  memDC.MoveTo(3+fontSize.cx*9, 3);
  memDC.LineTo(3+fontSize.cx*9, 3+displayedLines*fontSize.cy);

  memDC.MoveTo(line, 3);
  memDC.LineTo(line, 3+displayedLines*fontSize.cy);

  memDC.SelectObject(old);
  pen.DeleteObject();

  memDC.SelectObject(oldFont);

  dc.BitBlt(0, 0, w, rect.bottom - rect.top, &memDC, 0, 0, SRCCOPY);

  memDC.SelectObject(pOldBitmap);
  memDC.DeleteDC();
  bitmap.DeleteObject();
}

void MemoryViewer::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
  int address = this->address;
  switch(nSBCode) {
  case SB_BOTTOM:
    address = 0xffffff00;
    break;
  case SB_LINEDOWN:
    address += 0x10;
    break;
  case SB_LINEUP:
    address -= 0x10;
    break;
  case SB_PAGEDOWN:
    address += (displayedLines<<4);
    break;
  case SB_PAGEUP:
    address -= (displayedLines<<4);
    break;
  case SB_TOP:
    address = 0;
    break;
  case SB_THUMBTRACK:
    {
      int page = displayedLines * 16;
      SCROLLINFO si;
      ZeroMemory(&si, sizeof(si));
      si.cbSize = sizeof(si);
      si.fMask = SIF_TRACKPOS;
      GetScrollInfo(SB_VERT, &si);
      address = page * si.nTrackPos;
    }
    break;
  }
  setAddress(address);
}

UINT MemoryViewer::OnGetDlgCode()
{
  return DLGC_WANTALLKEYS;
}

void MemoryViewer::createEditCaret(int w, int h)
{
  if(!hasCaret || caretWidth != w || caretHeight != h) {
    hasCaret = true;
    caretWidth = w;
    caretHeight = h;
    ::CreateCaret(m_hWnd, (HBITMAP)0, w, h);
  }
}


void MemoryViewer::destroyEditCaret()
{
  hasCaret = false;
  DestroyCaret();
}

void MemoryViewer::setCaretPos()
{
  if(GetFocus() != this) {
    destroyEditCaret();
    return;
  }

  if(dlg)
    dlg->setCurrentAddress(editAddress);

  if(editAddress < address || editAddress > (address -1 + (displayedLines<<4))) {
    destroyEditCaret();
    return;
  }

  int subAddress = (editAddress - address);

  int x = 3+10*fontSize.cx+editNibble*fontSize.cx;
  int y = 3+fontSize.cy*((editAddress-address)>>4);

  if(editAscii) {
    x = beginAscii + fontSize.cx*(subAddress&15);
  } else {
    switch(dataSize) {
    case 0:
      x += 3*fontSize.cx*(subAddress & 15);
      break;
    case 1:
      x += 5*fontSize.cx*((subAddress>>1) & 7);
      break;
    case 2:
      x += 9*fontSize.cx*((subAddress>>2) & 3);
      break;
    }
  }

  RECT r;
  GetClientRect(&r);
  r.right -= 3;
  if(x >= r.right) {
    destroyEditCaret();
    return;
  }
  int w = fontSize.cx;
  if((x+fontSize.cx)>=r.right)
    w = r.right - x;
  createEditCaret(w, fontSize.cy);
  ::SetCaretPos(x, y);
  ShowCaret();
}

void MemoryViewer::OnLButtonDown(UINT nFlags, CPoint point)
{
  int x = point.x;
  int y = point.y;
  int line = (y-3)/fontSize.cy;
  int beforeAscii = beginHex;
  int inc = 1;
  int sub = 3*fontSize.cx;
  switch(dataSize) {
  case 0:
    beforeAscii += 47*fontSize.cx;
    break;
  case 1:
    beforeAscii += 39*fontSize.cx;
    inc = 2;
    sub = 5*fontSize.cx;
    break;
  case 2:
    beforeAscii += 35*fontSize.cx;
    inc = 4;
    sub = 9*fontSize.cx;
    break;
  }

  editAddress = address + (line<<4);
  if(x >= beginHex && x < beforeAscii) {
    x -= beginHex;
    editNibble = 0;
    while(x > 0) {
      x -= sub;
      if(x >= 0)
        editAddress += inc;
      else {
        editNibble = (x + sub)/fontSize.cx;
      }
    }
    editAscii = false;
  } else if(x >= beginAscii) {
    int afterAscii = beginAscii+16*fontSize.cx;
    if(x >= afterAscii)
      x = afterAscii-1;
    editAddress += (x-beginAscii)/fontSize.cx;
    editNibble = 0;
    editAscii = true;
  } else {
    return;
  }

  if(editNibble > maxNibble)
    editNibble = maxNibble;
  SetFocus();
  setCaretPos();
}

void MemoryViewer::OnSetFocus(CWnd* pOldWnd)
{
  setCaretPos();
  InvalidateRect(NULL, TRUE);
}

void MemoryViewer::OnKillFocus(CWnd* pNewWnd)
{
  destroyEditCaret();
  InvalidateRect(NULL, TRUE);
}

void MemoryViewer::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
  bool isShift = (GetKeyState(VK_SHIFT) & 0x80000000) == 0x80000000;

  switch(nChar) {
  case VK_RIGHT:
    if(editAscii)
      moveAddress(1,0);
    else if(isShift)
      moveAddress((maxNibble+1)>>1,0);
    else
      moveAddress(0, 1);
    break;
  case VK_LEFT:
    if(editAscii)
      moveAddress(-1, 0);
    else if(isShift)
      moveAddress(-((maxNibble+1)>>1),0);
    else
      moveAddress(0, -1);
    break;
  case VK_DOWN:
    moveAddress(16, 0);
    break;
  case VK_UP:
    moveAddress(-16, 0);
    break;
  case VK_TAB:
    GetNextDlgTabItem(GetParent(), isShift)->SetFocus();
    break;
  }
}

void MemoryViewer::moveAddress(s32 offset, int nibbleOff)
{
  if(offset == 0) {
    if(nibbleOff == -1) {
      editNibble--;
      if(editNibble == -1) {
        editAddress -= (maxNibble + 1) >> 1;
        editNibble = maxNibble;
      }
      if(address == 0 && (editAddress >= (u32)(displayedLines<<4))) {
        editAddress = 0;
        editNibble = 0;
        beep();
      }
      if(editAddress < address)
        setAddress(address - 16);
    } else {
      editNibble++;
      if(editNibble > maxNibble) {
        editNibble = 0;
        editAddress += (maxNibble + 1) >> 1;
      }
      if(editAddress < address) {
        editAddress -= (maxNibble + 1) >> 1;
        editNibble = maxNibble;
        beep();
      }
      if(editAddress >= (address+(displayedLines<<4)))
        setAddress(address+16);
    }
  } else {
    editAddress += offset;
    if(offset < 0 && editAddress > (address-1+(displayedLines<<4))) {
      editAddress -= offset;
      beep();
      return;
    }
    if(offset > 0 && (editAddress < address)) {
      editAddress -= offset;
      beep();
      return;
    }
    if(editAddress < address) {
      if(offset & 15)
        setAddress((address+offset-16) & ~15);
      else
        setAddress(address+offset);
    } else if(editAddress > (address - 1 + (displayedLines<<4))) {
      if(offset & 15)
        setAddress((address+offset+16) & ~15);
      else
        setAddress(address+offset);
    }
  }

  setCaretPos();
}

LRESULT MemoryViewer::OnWMChar(WPARAM wParam, LPARAM LPARAM)
{ // The WM_CHAR message uses Unicode Transformation Format (UTF)-16.
  if(OnEditInput((UINT)(wParam & 0xFFFF)))
    return 0;
  return 1;
}

bool MemoryViewer::OnEditInput(UINT c)
{
  if(c > 255 || !emulating) {
    beep();
    return false;
  }

  if(!editAscii)
    c = tolower(c);

  u32 value = 256;

  if(c >= 'a' && c <= 'f')
    value = 10 + (c - 'a');
  else if(c >= '0' && c <= '9')
    value = (c - '0');
  if(editAscii) {
    editData(editAddress, 8, 0, c);
    moveAddress(1, 0);
    InvalidateRect(NULL, TRUE);
  } else {
    if(value != 256) {
      value <<= 4*(maxNibble-editNibble);
      u32 mask = ~(15 << 4*(maxNibble - editNibble));
      switch(dataSize) {
      case 0:
        editData(editAddress, 8, mask, value);
        break;
      case 1:
        editData(editAddress, 16, mask, value);
        break;
      case 2:
        editData(editAddress, 32, mask, value);
        break;
      }
      moveAddress(0, 1);
      InvalidateRect(NULL, TRUE);
    }
  }
  return true;
}

void MemoryViewer::beep()
{
  MessageBeep((UINT)-1);
}

void MemoryViewer::registerClass()
{
  if(!isRegistered) {
    WNDCLASS wc;
    ZeroMemory(&wc, sizeof(wc));
    wc.style = CS_PARENTDC | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
    wc.lpfnWndProc = (WNDPROC)::DefWindowProc;
    wc.hInstance = AfxGetInstanceHandle();
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH )GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = "VbaMemoryViewer";
    AfxRegisterClass(&wc);
    isRegistered = true;
  }
}

void MemoryViewer::setAddressSize(int s)
{
  addressSize = s;
}


u32 MemoryViewer::getCurrentAddress()
{
  return editAddress;
}

int MemoryViewer::getSize()
{
  return dataSize;
}
