/* debugger.c: The debugger widget
   Copyright (c) 2002-2011 Philip Kendall, Darren Salt
   Copyright (c) 2015 Stuart Brady
   Copyright (c) 2016 BogDan Vatra

   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.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

   Author contact information:

   E-mail: philip-fuse@shadowmagic.org.uk

*/

#include "config.h"

#include <stdio.h>
#include <string.h>

#include "libspectrum.h"
  
#include "debugger/debugger.h"
#include "display.h"
#include "keyboard.h"
#include "machine.h"
#include "peripherals/ide/zxcf.h"
#include "peripherals/scld.h"
#include "peripherals/ula.h"
#include "ui/uidisplay.h"
#include "widget.h"
#include "widget_internals.h"
#include "z80/z80.h"
#include "z80/z80_macros.h"

static enum {
  DB_REGISTERS, DB_BYTES, DB_TEXT, DB_DISASM, DB_BREAKPT
} display = DB_REGISTERS;

struct {
  libspectrum_word value;
  size_t page;
  char x, width, column;
} db_editv = {
  0, 0, 255, 0
};

static libspectrum_word debugger_memaddr;
static int breakpt_no = 0, breakpt_show = 0;

/* Various data displays */
static void display_registers( void );
static void display_bytes( void );
static void display_text( void );
static void display_disasm( void );
static void display_breakpts( void );

/* Scrolling for the data displays */
static void scroll( int step );

#define LC(X) ( (X)*8 - DISPLAY_BORDER_ASPECT_WIDTH )
#define LR(Y) ( (Y)*8 - DISPLAY_BORDER_HEIGHT )

static inline const char *format_8_bit( void )
{
  return debugger_output_base == 10 ? "%-3d" : "%02X";
}

static inline const char *format_16_bit( void )
{
  return debugger_output_base == 10 ? "%-5d" : "%04X";
}

int ui_debugger_activate( void )
{
  return widget_do_debugger();
}

int ui_debugger_deactivate( int interruptible GCC_UNUSED )
{
  /* Refresh the Spectrum's display, including the border */
  display_refresh_all();
  return widget_end_all( WIDGET_FINISHED_OK );
}

int ui_debugger_update( void )
{
  return widget_debugger_draw( NULL );
}

int ui_debugger_disassemble( libspectrum_word addr )
{
  debugger_memaddr = addr;
  return 0;
}

/* Debugger update function. The dialog box is created every time it is
   displayed, so no need to do anything here */
void ui_breakpoints_updated( void )
{
}

int widget_debugger_draw( void *data )
{
  static const char state[][8] = {
    "Running", "Halted", "Stepped", "Breakpt"
  };
  int x;
  char pbuf[8];

  widget_rectangle( LC(0), LR(0), 40 * 8, 17 * 8 + 4, 1 );
  widget_rectangle( LC(0), LR(17) + 2, 320, 1, 7 );

  switch ( display ) {
  case DB_REGISTERS: display_registers(); break;
  case DB_BYTES:     display_bytes();	  break;
  case DB_TEXT:      display_text();	  break;
  case DB_DISASM:    display_disasm();	  break;
  case DB_BREAKPT:   display_breakpts();  break;
  }

  widget_printstring( LC(0), LR(15) - 4, 6, state[debugger_mode] );
  widget_printstring( LC(10), LR(15) - 4, 6,
		      "\022S\021ingle step  \022C\021ontinue  Co\022m\021mand" );

  x = LC(-1);
  if( display != DB_REGISTERS )
    x = widget_printstring( x + 8, LR(16), 7, "\022R\021egs" );
  if( display != DB_BYTES )
    x = widget_printstring( x + 8, LR(16), 7, "\022B\021ytes" );
  if( display != DB_TEXT )
    x = widget_printstring( x + 8, LR(16), 7, "\022T\021ext" );
  if( display != DB_DISASM )
    x = widget_printstring( x + 8, LR(16), 7, "\022D\021isasm" );
  if( display != DB_BREAKPT )
    x = widget_printstring( x + 8, LR(16), 7, "Brea\022k\021pts" );

  widget_printstring_right( LC(25) + 4, LR(16), 5, "PC" );
  sprintf( pbuf, "%04X", PC );
  widget_printstring_fixed( LC(26), LR(16), 7, pbuf );

  widget_printstring_right( LR(35) + 4, LR(16), 5, "Bas\022e\021" );
  sprintf( pbuf, "%d", debugger_output_base );
  widget_printstring( LR(36), LR(16), 7, pbuf );

  widget_display_lines( LR(0) / 8, 18 );

  return 0;
}


void widget_debugger_keyhandler( input_key key )
{
  /* Display mode */
  switch ( key ) {
  case INPUT_KEY_Escape:	/* Close widget */
    widget_end_widget( WIDGET_FINISHED_CANCEL );
    debugger_run();
    break;

  case INPUT_KEY_c:
  case INPUT_KEY_Return:	/* Close widget */
  case INPUT_KEY_KP_Enter:
    widget_end_all( WIDGET_FINISHED_OK );
    debugger_run();
    break;

  case INPUT_KEY_s:		/* Single step & reopen widget */
    debugger_mode = DEBUGGER_MODE_HALTED;
    widget_end_all( WIDGET_FINISHED_OK );
    break;

  case INPUT_KEY_r:		/* Display the registers */
    display = DB_REGISTERS;
    widget_debugger_draw( NULL );
    break;

  case INPUT_KEY_b:		/* Display a memory dump (bytes) */
    display = DB_BYTES;
    widget_debugger_draw( NULL );
    break;

  case INPUT_KEY_t:		/* Display a memory dump (text) */
    display = DB_TEXT;
    widget_debugger_draw( NULL );
    break;

  case INPUT_KEY_d:		/* Display a disassembly */
    display = DB_DISASM;
    widget_debugger_draw( NULL );
    break;

  case INPUT_KEY_k:		/* Display the breakpoints */
    display = DB_BREAKPT;
    widget_debugger_draw( NULL );
    break;

  case INPUT_KEY_e:		/* Switch base */
    debugger_output_base = 26 - debugger_output_base;	/* 10 or 16 */
    widget_debugger_draw( NULL );
    break;

  case INPUT_KEY_m:		/* Enter a command */
    {
      widget_text_t text_data;

      text_data.title = "Debugger command";
      text_data.allow = WIDGET_INPUT_ASCII;
      text_data.max_length = 63;
      text_data.text[0] = 0;
      if( !widget_do_text( &text_data ) )
	debugger_command_evaluate( widget_text_text );
    }
    break;

  case INPUT_KEY_Up:		/* Back one line */
    scroll( -1 );
    break;

  case INPUT_KEY_Down:		/* Back one instruction or four lines */
    scroll( 1 );
    break;

  case INPUT_KEY_Page_Up:	/* Back eight lines */
    scroll( -8 );
    break;

  case INPUT_KEY_Page_Down:	/* Forward eight lines */
    scroll( 8 );
    break;

  case INPUT_KEY_Home:		/* To start of memory */
    debugger_memaddr = 0;
    scroll( 0 );
    break;

  case INPUT_KEY_End:		/* To end of RAM */
    debugger_memaddr = 0;
    scroll( -8 );
    break;

  default:;
  }
}

static void show_register0( int x, int y, const char *label, int value )
{
  char pbuf[8];

  sprintf( pbuf, "%d", value );
  widget_printstring_right( x - 4, y, 5, label );
  widget_printstring_fixed( x, y, 7, pbuf );
}

static void show_register1( int x, int y, const char *label, int value )
{
  char pbuf[8];

  sprintf( pbuf, format_8_bit(), value );
  widget_printstring_right( x - 4, y, 5, label );
  widget_printstring_fixed( x, y, 7, pbuf );
}

static void show_register2( int x, int y, const char *label, int value )
{
  char pbuf[8];

  sprintf( pbuf, format_16_bit(), value );
  widget_printstring_right( x - 4, y, 5, label );
  widget_printstring_fixed( x, y, 7, pbuf );
}

static void display_registers( void )
{
  int source, page_num, writable, contended;
  libspectrum_word offset;
  size_t block;
  char pbuf[16];
  int i, capabilities;

  show_register2( LC(3),  LR(0), "AF",   AF );
  show_register2( LC(12), LR(0), "AF'",  AF_ );
  show_register2( LC(20), LR(0), "SP",   SP );
  show_register2( LC(29), LR(0), "PC",   PC );
  show_register1( LC(36), LR(0), "R",    ( R & 0x7F ) | ( R7 & 0x80 ) );

  show_register2( LC(3),  LR(1), "BC",   BC );
  show_register2( LC(12), LR(1), "BC'",  BC_ );
  show_register2( LC(20), LR(1), "IX",   IX );
  show_register2( LC(29), LR(1), "IY",   IY );
  show_register1( LC(36), LR(1), "I",    I );

  show_register2( LC(3),  LR(2), "DE",   DE );
  show_register2( LC(12), LR(2), "DE'",  DE_ );
  show_register0( LC(20), LR(2), "IM",   IM );
  show_register0( LC(29), LR(2), "IFF1", IFF1 );
  show_register0( LC(36), LR(2), "IFF2", IFF2 );

  show_register0( LC(36), LR(2), "IFF2", IFF2 );
  show_register2( LC(3),  LR(3), "HL",   HL );
  show_register2( LC(12), LR(3), "HL'",  HL_ );
  widget_printstring_fixed( LC(18), LR(3), 5, "SZ5H3PNC" );
  show_register0( LC(36), LR(3), "HALTED", z80.halted );
  show_register1( LC(36), LR(4), "ULA",  ula_last_byte() );

  sprintf( pbuf, "%d", tstates );
  widget_printstring_right( LC(12) - 4, LR(4), 5, "Tstates" );
  widget_printstring_fixed( LC(12), LR(4), 7, pbuf );
  for( i = 0; i < 8; ++i )
    pbuf[i] = ( F & ( 0x80 >> i ) ) ? '1' : '0';
  pbuf[8] = 0;
  widget_printstring_fixed( LC(18), LR(4), 7, pbuf );

  capabilities = libspectrum_machine_capabilities( machine_current->machine );

  if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_AY )
    show_register1( LC(29), LR(4), "AY",
		    machine_current->ay.current_register );

  if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_128_MEMORY )
    show_register1( LC(6), LR(5), "128Mem",
		    machine_current->ram.last_byte );

  if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_128_MEMORY )
    show_register1( LC(15), LR(5), "+3Mem",
		    machine_current->ram.last_byte2 );

  if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_VIDEO ||
      capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_MEMORY ||
      capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_SE_MEMORY )
    show_register1( LC(24), LR(5), "TmxDec", scld_last_dec.byte );

  if( capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_TIMEX_MEMORY ||
      capabilities & LIBSPECTRUM_MACHINE_CAPABILITY_SE_MEMORY )
    show_register1( LC(33), LR(5), "TmxHSR", scld_last_hsr );

  if( settings_current.zxcf_active )
    show_register1( LC(6), LR(5), "ZXCF", zxcf_last_memctl() );

  source = page_num = writable = contended = -1;
  offset = 0;
  i = 0;

  for( block = 0; block < MEMORY_PAGES_IN_64K; block++ ) {
    memory_page *page = &memory_map_read[block];

    if( page->source != source ||
      page->page_num != page_num ||
      page->offset != offset ||
      page->writable != writable ||
      page->contended != contended ) {

      int x = LC(5 + 20 * ( i & 1 ) ), y = LR(6 + ( i / 2 ) );

      sprintf( pbuf, format_16_bit(), (unsigned)block * MEMORY_PAGE_SIZE );
      widget_printstring_right( x, y, 5, pbuf );
      snprintf( pbuf, sizeof( pbuf ), "%s %d",
                memory_source_description( memory_map_read[block].source ),
                memory_map_read[block].page_num );
      x = widget_printstring( x + 4, y, 7, pbuf ) + 4;
      if( memory_map_read[block].writable )
        x = widget_printstring( x, y, 4, "w" );
      if( memory_map_read[block].contended )
        x = widget_printstring( x, y, 4, "c" );

      i++;

      source = page->source;
      page_num = page->page_num;
      writable = page->writable;
      contended = page->contended;
      offset = page->offset;
    }

    /* We expect the next page to have an increased offset */
    offset += MEMORY_PAGE_SIZE;
  }
}


static void display_bytes( void )
{
  int x, y;
  char pbuf[36];

  for( y = 0; y < 8; ++y ) {
    libspectrum_word addr = debugger_memaddr + y * 8;

    sprintf( pbuf, format_16_bit(), addr );
    widget_printstring_fixed( LC(1), LR(y), 7, pbuf );
    widget_printstring( LC(6), LR(y), 5, ":" );

    for( x = 0; x < 8; ++x ) {
      sprintf( pbuf + x * 4, format_8_bit(),
	       readbyte_internal( addr + x ) );
      if( x < 7 )
	strcat( pbuf, "  " );
    }
    widget_printstring_fixed( LC(7), LR(y), 7, pbuf );
  }
}


static void display_text( void )
{
  int x, y;
  char pbuf[8];

  for( y = 0; y < 8; ++y ) {
    libspectrum_word addr = debugger_memaddr + y * 32;

    sprintf( pbuf, format_16_bit(), addr );
    widget_printstring_fixed( LC(1), LR(y), 7, pbuf );
    widget_printstring( LC(6), LR(y), 5, ":" );

    for( x = 0; x < 32; ++x )
      widget_printchar_fixed( LC(x + 8), LR(y), 7,
			      readbyte_internal( addr + x ) );
  }
}


static void display_disasm( void )
{
  int y;
  char pbuf[40];
  libspectrum_word addr = debugger_memaddr;

  for( y = 0; y < 8; ++y ) {
    size_t length;
    char *spc;

    sprintf( pbuf, format_16_bit(), addr );
    widget_printstring_fixed( LC(1), LR(y), 7, pbuf );
    widget_printstring( LC(6), LR(y), 5, ":" );

    debugger_disassemble( pbuf, sizeof( pbuf ), &length, addr );
    addr += length;
    spc = strchr( pbuf, ' ' );
    if( spc )
      *spc = 0;
    widget_printstring( LC(8), LR(y), 7, pbuf );
    if( spc ) {
      spc += 1 + strspn( spc + 1, " " );
      widget_printstring( LC(12) + 4, LR(y), 7, spc );
    }
  }
}


static void display_breakpts( void )
{
  GSList *ptr;
  int i = -breakpt_show;
  char pbuf[80], fmt[20];

  if( i )
    widget_up_arrow( LC(0), LR(0), 7 );

  for( ptr = debugger_breakpoints; i < 8 && ptr; ptr = ptr->next, ++i ) {
    const debugger_breakpoint *bp = ptr->data;

    if( i < 0 )
      continue;

    sprintf( pbuf, "%lu", ( unsigned long )bp->id );
    widget_printstring( LC(1), LR(i), 5, pbuf );
    widget_printstring( LC(6), LR(i), 7,
			debugger_breakpoint_type_abbr[bp->type] );

    switch ( bp->type ) {
    case DEBUGGER_BREAKPOINT_TYPE_EXECUTE:
    case DEBUGGER_BREAKPOINT_TYPE_READ:
    case DEBUGGER_BREAKPOINT_TYPE_WRITE:
      if( bp->value.address.source == memory_source_any )
	sprintf( pbuf, format_16_bit(), bp->value.address.offset );
      else {
        snprintf( fmt, sizeof( fmt ), "%%s:%s:%s", format_16_bit(),
                  format_16_bit() );
        snprintf( pbuf, sizeof( pbuf ), fmt,
                  memory_source_description( bp->value.address.source ),
                  bp->value.address.page, bp->value.address.offset );
      }
      break;

    case DEBUGGER_BREAKPOINT_TYPE_PORT_READ:
    case DEBUGGER_BREAKPOINT_TYPE_PORT_WRITE:
      sprintf( fmt, "%s:%s", format_16_bit(), format_16_bit() );
      sprintf( pbuf, fmt, bp->value.port.mask, bp->value.port.port );
      break;

    case DEBUGGER_BREAKPOINT_TYPE_TIME:
      sprintf( pbuf, "%5d", bp->value.time.tstates );
      break;

    case DEBUGGER_BREAKPOINT_TYPE_EVENT:
      sprintf( pbuf, "%s:%s", bp->value.event.type, bp->value.event.detail );
      break;
    }
    widget_printstring( LC(10), LR(i), 6, pbuf );

    sprintf( pbuf, "%lu", ( unsigned long )bp->ignore );
    widget_printstring( LC(18) + 4, LR(i), 7, pbuf );

    sprintf( pbuf, "%s", debugger_breakpoint_life_abbr[bp->life] );
    widget_printstring( LC(23), LR(i), 7, pbuf );

    if( bp->condition ) {
      debugger_expression_deparse( pbuf, sizeof( pbuf ), bp->condition );
      widget_printstring( LC(28) + 4, LR(i), 6, pbuf );
    }
  }

  if( !i )
    widget_printstring( LC(1), LR(0), 5, "(No breakpoints)" );
  else if( ptr )
    widget_down_arrow( LC(0), LC(7), 7 );
}


static void scroll( int step )
{
  switch ( display ) {
  case DB_BYTES:
    debugger_memaddr += 8 * step;
    break;

  case DB_TEXT:
    debugger_memaddr += 32 * step;
    break;

  case DB_DISASM:
    if( step > 0 )
      for( ; step; --step ) {
	size_t length;

	debugger_disassemble( NULL, 0, &length, debugger_memaddr );
	debugger_memaddr += length;
    } else
      for( ; step; ++step ) {
	/* For details, see ui/gtk/debugger.c:move_disassembly() */
	size_t i, longest = 1;

	for( i = 1; i <= 8; ++i ) {
	  size_t length;

	  debugger_disassemble( NULL, 0, &length, debugger_memaddr );
	  if( length == i )
	    longest = i;
	}
	debugger_memaddr -= longest;
      }
    break;

  case DB_BREAKPT:
    {
      int length = g_slist_length( debugger_breakpoints );

      breakpt_no += step;
      if( breakpt_no >= length )
	breakpt_no = length - 1;
      if( breakpt_no < 0 )
	breakpt_no = 0;
      if( breakpt_no < breakpt_show )
	breakpt_show = breakpt_no;
      else if( breakpt_no > breakpt_show + 7 )
	breakpt_show = breakpt_no - 7;
    }
    break;

  default:
    return;
  }

  widget_debugger_draw( NULL );
}
