//	bfe2 - memory watchpoints
//	Copyright (c) 1999-2003 Brand Huntsman and Lee Salzman
//


#include "common.h"
#include "functions.h"


//////////////////////////////////////////////////////////////////////////

// global
s_watchpoint watchpoints[MAX_WATCHPOINTS];

// local
GtkWidget *wp_add_button, *wp_remove_button, *wp_removeall_button, *wp_address_text;
GtkCList *wp_list;
int selected_wp;
uint nr_watchpoints;
s_bgroup *wp_type;

#define WP_COLUM_TITLES 4
gchar *wp_column_titles[] = { "Last Stop", "Type", "Watchpoint", "Value" };

char *wp_titles[] = { " Read ", " Write " };

//////////////////////////////////////////////////////////////////////////


void read_watchpoints( ){
	char type[6], c, value[LEN_NUMBER];
	uint n;

	for(n = 0; n < MAX_WATCHPOINTS; n++) watchpoints[n].enabled = 0;
	nr_watchpoints = 0;

	fprintf(writepipe, "watch\n");

	for(n = 0; n < MAX_WATCHPOINTS; n++){
		c = fgetc(readpipe);
		for(; (c != EOF) && (c != 'w') && (c != 'r') && (c != '<'); c = fgetc(readpipe));
		if(c == EOF) return;
		if((ungetc(c, readpipe) == EOF) || (c == '<')) break;

		if(fscanf(readpipe, "%s %x (%s", type, &watchpoints[n].offset, value) != 3) break;
		if(!strcmp(type, "read")) watchpoints[n].type = WP_READ;
		else if(!strcmp(type, "write")) watchpoints[n].type = WP_WRITE;
		else break;
		value[strlen(value)-1] = '\0';
		if(strlen(value) == 4) watchpoints[n].value_size = 2;
		else if(strlen(value) == 8) watchpoints[n].value_size = 4;
		else break;
		sscanf(value, "%x", &watchpoints[n].value);

		watchpoints[n].enabled = 1;
		nr_watchpoints++;
	}

	prompt_read();
	return;
}


void add_watchpoint( GtkWidget *widget, gpointer data ){
	const char *address;

	address = gtk_entry_get_text(GTK_ENTRY(wp_address_text));

	switch(wp_type->current){
	case WP_READ:
		fprintf(writepipe, "watch read %s\n", address);
		break;
	case WP_WRITE:
		fprintf(writepipe, "watch write %s\n", address);
		break;
	}
	prompt_read();
	if((int)data) watchpointsUpdate(WP_UPDATE);
	gtk_entry_set_text(GTK_ENTRY(wp_address_text), "");
}


void remove_watchpoint( GtkWidget *widget, gpointer data ){
	switch(watchpoints[selected_wp].type){
	case WP_READ:
		fprintf(writepipe, "unwatch read 0x%x\n", watchpoints[selected_wp].offset);
		break;
	case WP_WRITE:
		fprintf(writepipe, "unwatch write 0x%x\n", watchpoints[selected_wp].offset);
		break;
	}
	prompt_read();
	watchpointsUpdate(WP_UPDATE);
}


void removeall_watchpoints( GtkWidget *widget, gpointer data ){
	fprintf(writepipe, "unwatch\n");
	prompt_read();
	watchpointsUpdate(WP_UPDATE);
}


void wp_selected( GtkWidget *widget, gint row, gint column, GdkEventButton *event, gpointer data ){
	selected_wp = row;
	gtk_widget_set_sensitive(wp_remove_button, TRUE);
}


void wp_unselected( GtkWidget *widget, gint row, gint column, GdkEventButton *event, gpointer data ){
	selected_wp = -1;
	gtk_widget_set_sensitive(wp_remove_button, FALSE);
}


//////////////////////////////////////////////////////////////////////////


void watchpointsInit( GtkWidget *vbox ){
	GtkWidget *hbox, *vbox2;

	hbox = new_hbox(vbox, TRUE);

	wp_list = new_list(hbox, WP_COLUM_TITLES, wp_column_titles);
	gtk_clist_column_titles_show(wp_list);
	gtk_clist_set_column_justification(wp_list, 0, GTK_JUSTIFY_CENTER);
	gtk_clist_set_column_justification(wp_list, 1, GTK_JUSTIFY_CENTER);
	gtk_clist_set_column_justification(wp_list, 2, GTK_JUSTIFY_CENTER);
	gtk_clist_set_column_justification(wp_list, 3, GTK_JUSTIFY_LEFT);

	// setup selection handler
	gtk_signal_connect(GTK_OBJECT(wp_list), "select_row", GTK_SIGNAL_FUNC(wp_selected), NULL);
	gtk_signal_connect(GTK_OBJECT(wp_list), "unselect_row", GTK_SIGNAL_FUNC(wp_unselected), NULL);

	vbox2 = new_vbox(hbox, FALSE);
	hbox = new_hbox(vbox2, FALSE);

	// remove button
	wp_remove_button = new_button(hbox, TRUE, " Remove ");
	gtk_signal_connect(GTK_OBJECT(wp_remove_button), "clicked", GTK_SIGNAL_FUNC(remove_watchpoint), NULL);

	// remove all button
	wp_removeall_button = new_button(hbox, TRUE, " Remove All ");
	gtk_signal_connect(GTK_OBJECT(wp_removeall_button), "clicked", GTK_SIGNAL_FUNC(removeall_watchpoints), NULL);

	new_separator(vbox2, FALSE, HORIZONTAL);

	hbox = new_hbox(vbox2, FALSE);

	wp_type = new_button_group(hbox, TRUE, HORIZONTAL, 2, wp_titles); 

	new_label(vbox2, FALSE, "Physical address", FALSE);

	// address text field
	wp_address_text = new_text_entry(vbox2, FALSE, 145);
	gtk_signal_connect(GTK_OBJECT(wp_address_text), "activate", GTK_SIGNAL_FUNC(add_watchpoint), (gpointer)1);

	// add button
	wp_add_button = new_button(vbox2, FALSE, " Add ");
	gtk_signal_connect(GTK_OBJECT(wp_add_button), "clicked", GTK_SIGNAL_FUNC(add_watchpoint), (gpointer)1);

	// message label
	new_label(vbox2, FALSE, "Bochs 2.0 does not support", FALSE);
	new_label(vbox2, FALSE, "remove, it might be supported", TRUE);
	new_label(vbox2, FALSE, "in newer versions.", TRUE);
	new_label(vbox2, FALSE, "Remove All does work.", TRUE);

	selected_wp = -1;
	nr_watchpoints = 0;
}


void watchpointsUpdate( e_wp_mode mode ){
	const char *r_type = "Read", *w_type = "Write";
	char address[LEN_ADDRESS], value[LEN_NUMBER], *row[WP_COLUM_TITLES];
	uint n;

	if(bochs_offline) return;

	if(mode == WP_UPDATE) read_watchpoints();

	gtk_clist_freeze(wp_list);
	gtk_clist_clear(wp_list);
	selected_wp = -1;
	gtk_widget_set_sensitive(wp_remove_button, FALSE);
	if(nr_watchpoints) gtk_widget_set_sensitive(wp_removeall_button, TRUE);
	else gtk_widget_set_sensitive(wp_removeall_button, FALSE);

	row[0] = NULL;
	// row[1] is set below
	row[2] = address;
	row[3] = value;

	for(n = 0; n < MAX_WATCHPOINTS && watchpoints[n].enabled; n++){
		switch(watchpoints[n].type){
		case WP_READ:
			row[1] = (char *)r_type;
			snprintf(address, LEN_ADDRESS, "%.8X", watchpoints[n].offset);
			break;
		case WP_WRITE:
			row[1] = (char *)w_type;
			snprintf(address, LEN_ADDRESS, "%.8X", watchpoints[n].offset);
			break;
		}
		switch(watchpoints[n].value_size){
		case 2:
			snprintf(value, LEN_NUMBER, "%.4X", watchpoints[n].value);
			break;
		case 4:
			snprintf(value, LEN_NUMBER, "%.8X", watchpoints[n].value);
			break;
		}
		gtk_clist_append(wp_list, row);
	}
	gtk_clist_thaw(wp_list);
}


void watchpointsReload( ){
	char address[LEN_ADDRESS];
	uint x, old_wp_type;

	if(reload_watchpoints){
		old_wp_type = wp_type->current;

		for(x = 0; (x < MAX_WATCHPOINTS) && watchpoints[x].enabled; x++){
			snprintf(address, LEN_ADDRESS, "0x%.8X", watchpoints[x].offset);
			wp_type->current = watchpoints[x].type;
			gtk_entry_set_text(GTK_ENTRY(wp_address_text), address);
			add_watchpoint(wp_address_text, (gpointer)0);
		}

		wp_type->current = old_wp_type;
	}

	watchpointsUpdate(WP_UPDATE);
}


void watchpointsEnablePage( gboolean v ){
	gtk_widget_set_sensitive(GTK_WIDGET(wp_list), v);
	gtk_widget_set_sensitive(wp_add_button, v);
	if(selected_wp != -1) gtk_widget_set_sensitive(wp_remove_button, v);
	else gtk_widget_set_sensitive(wp_remove_button, FALSE);
	if(nr_watchpoints) gtk_widget_set_sensitive(wp_removeall_button, v);
	else gtk_widget_set_sensitive(wp_removeall_button, FALSE);
	gtk_widget_set_sensitive(wp_address_text, v);
}


void watchpointsSelect( uint wp ){
	gtk_clist_set_text(wp_list, wp, 0, "X");
	gtk_clist_select_row(wp_list, wp, 0);
}
