/* debug.c - interactive debugger
   Copyright (C) 1996, 1997 Paul Sheer

   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 <config.h>
#include <stdio.h>
#include <my_string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#if HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#include <sys/stat.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
#ifdef HAVE_GRP_H
#    include <grp.h>
#endif
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include "lkeysym.h"
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif

#include "stringtools.h"
#include "coolwidget.h"
#include "edit.h"
#include "editcmddef.h"
#include "loadfile.h"
#include "editoptions.h"
#include "cmdlineopt.h"
#include "shell.h"
#include "widget/pool.h"
#include "find.h"
#include "mad.h"

#define DEBUG_TIME_OUT		15
#define DEBUG_BREAKPOINT_COLOR	18
#define DEBUG_CURRENT_COLOR	6

#define ACTION_PROMPT		1
#define ACTION_MESSAGE		2
#define ACTION_BREAKPOINT	3
#define ACTION_INFO_SOURCE	4
#define ACTION_BACKTRACE	5
#define ACTION_RUNNING		6
#define ACTION_STEP		7
#define ACTION_INFO_PROGRAM	8
#define ACTION_VERSION		9
#define ACTION_CONDITION	10
#define ACTION_QUIT		11
#define ACTION_SHOW		12
#define ACTION_UNTIL		13
#define ACTION_VARIABLE		14
#define ACTION_LASTVAR		15
#define ACTION_WATCH		16

#define GDB_VERSION		"GNU gdb 4.17"

static void debug_read_callback (int fd, fd_set * reading, fd_set * writing, fd_set * error, void *data);
static void debug_write_callback (int fd, fd_set * reading, fd_set * writing, fd_set * error, void *data);

#define MAX_DEBUG_CMD_LEN	MAX_PATH_LEN
#define MAX_QUEUED_COMMANDS	100
#define MAX_VARIABLES		60

typedef struct struct_debug {
    int x, y, r;
    Window w;
    int in;
    int out;
    char *prompt;
    char *get_line;
    char *get_source;
    char *get_backtrace;
    char *query;
    char *progname;
    char *debugger;
    char *args;
    pid_t pid;
    pid_t child;
    struct _command {
	int action;
	char *command;
	char *editor;
	int line;
    } command[MAX_QUEUED_COMMANDS];
    struct _variable {
	char *name;
	char *output;
	int watch;
	int changed;
    } variable[MAX_VARIABLES];
    int last_variable_displayed;
    int n_commands;
    int action;
    POOL *pool;
    int line;
    char *file;
    char *condition;
    int show_output, stop_at_main, show_on_stdout;
    char *xterm_cmd;
    pid_t xterm_pid, ttyname_pid;
    int break_point_line;
    char *break_point_editor;
} Debug;

extern CWidget *edit[50];
extern int current_edit;
extern int last_edit;

static void debug_message (Debug * d, char *heading, char *str, int options)
{
    if (d->pid) {
	CRemoveWatch (d->in, debug_write_callback, WATCH_WRITING);
	CRemoveWatch (d->out, debug_read_callback, WATCH_READING);
    }
    CMessageDialog (0, 20, 20, options, heading, "%s", str);
    if (d->pid) {
	if (d->n_commands && !d->action)
	    CAddWatch (d->in, debug_write_callback, WATCH_WRITING, 0);
	CAddWatch (d->out, debug_read_callback, WATCH_READING, 0);
    }
}

static void debug_error2 (Debug * d, char *par1, char *par2)
{
    char *t;
    if (d->pid) {
	CRemoveWatch (d->in, debug_write_callback, WATCH_WRITING);
	CRemoveWatch (d->out, debug_read_callback, WATCH_READING);
    }
    t = malloc (strlen (par1) + strlen (par2) + 80);
    if (*par2)
	sprintf (t, " %s: %s ", par1, par2);
    else
	sprintf (t, " %s ", par1);
    CErrorDialog (0, 0, 0, _ (" Debug Message "), "%s", t);
    free (t);
    if (d->pid) {
	if (d->n_commands && !d->action)
	    CAddWatch (d->in, debug_write_callback, WATCH_WRITING, 0);
	CAddWatch (d->out, debug_read_callback, WATCH_READING, 0);
    }
}

#define debug_error1(d, x)		debug_error2(d, x, "")

static void xdebug_cursor_bookmark_flush (void)
{
    int i;
    for (i = 0; i < last_edit; i++)
	book_mark_flush (edit[i]->editor, DEBUG_CURRENT_COLOR);
}

static void xdebug_append_command (Debug * d, char *s, int action, char *editor, int line)
{
    if (d->n_commands < MAX_QUEUED_COMMANDS - 2) {
	d->command[d->n_commands].command = (char *) strdup (s);
	d->command[d->n_commands].action = action;
	if (editor)
	    d->command[d->n_commands].editor = (char *) strdup (editor);
	else
	    d->command[d->n_commands].editor = 0;
	d->command[d->n_commands].line = line;
	d->n_commands++;
	if (!d->action)
	    CAddWatch (d->in, debug_write_callback, WATCH_WRITING, 0);
    }
}

static void xdebug_flush_commands (Debug * d)
{
    int i;
    for (i = 0; i < d->n_commands; i++) {
	if (d->command[i].editor)
	    free (d->command[i].editor);
	free (d->command[i].command);
	d->command[i].command = 0;
    }
    d->action = 0;
    d->n_commands = 0;
    d->last_variable_displayed = 0;
    CRemoveWatch (d->in, debug_write_callback, WATCH_WRITING);
}


/* {{{ variables */

static void xdebug_show_variables (Debug * d)
{
    int i;
    char s[1024];
    for (i = 0; d->variable[i].name; i++) {
	sprintf (s, "p %s\n", d->variable[i].name);
	xdebug_append_command (d, s, d->variable[i + 1].name ? ACTION_VARIABLE : ACTION_LASTVAR, 0, 0);
    }
}

static int xdebug_add_variable (Debug * d, char *s)
{
    int i;
    for (i = 0; d->variable[i].name; i++);
    if (i >= MAX_VARIABLES - 1) {
	debug_error1 (d, _ ("Max variables exceeded"));
	return 1;
    }
    d->variable[i].output = 0;
    d->variable[i].name = (char *) strdup (s);
    d->variable[i + 1].name = 0;
    d->variable[i + 1].output = 0;
    return 0;
}

static void xdebug_remove_variable (Debug * d, int i)
{
    if (d->variable[i].name) {
	free (d->variable[i].name);
	if (d->variable[i].output)
	    free (d->variable[i].output);
    }
    memcpy (&d->variable[i], &d->variable[i + 1], (MAX_VARIABLES - i) * sizeof (struct _variable));
}

static char **xdebug_get_line (void *data, int line, int *numfields, int *tagged)
{
    static char *fields[3];
    Debug *d = (Debug *) data;
    *numfields = 3;
    if (!d->variable[line].name || !d->variable[line].output)
	return 0;
    *tagged = (d->variable[line].watch != 0);
    fields[0] = d->variable[line].name;
    fields[1] = d->variable[line].changed ? "\bX" : " ";
    fields[2] = d->variable[line].output;
    fields[3] = 0;
    return fields;
}

static int xdebug_varlist_callback (CWidget * w, XEvent * x, CEvent * c)
{
    Debug *d;
    d = (Debug *) w->hook;
    if (c->command == CK_Delete || c->command == CK_BackSpace) {
	xdebug_remove_variable (d, w->cursor);
	CRedrawFieldedTextbox ("debug_vars", 1);
    }
    if (c->command == CK_Toggle_Insert && !d->action && !d->n_commands) {
	char s[MAX_DEBUG_CMD_LEN];
	if (d->variable[w->cursor].watch) {
	    sprintf (s, "delete %d\n", d->variable[w->cursor].watch);
	    xdebug_append_command (d, s, ACTION_WATCH, 0, w->cursor);
	} else {
	    sprintf (s, "watch %s\n", d->variable[w->cursor].name);
	    xdebug_append_command (d, s, ACTION_WATCH, 0, w->cursor);
	}
    }
    return 0;
}

static void xdebug_display_variable (Debug * d, char *message, int action)
{
    char *p;
    int i;
    if (*message == '$') {
	p = message;
	message = strstr (message, "= ");
	if (message)
	    message += 2;
	else
	    message = p;
    }
    for (p = message, i = 0; *p && i < 255; p++, i++) {
	if (isspace (*p))
	    *p = ' ';
	else if (!isprint ((unsigned char) *p))
	    *p = '?';
    }
    *p = '\0';
    if (d->variable[d->last_variable_displayed].output) {
	d->variable[d->last_variable_displayed].changed = strcmp (message, d->variable[d->last_variable_displayed].output);
	free (d->variable[d->last_variable_displayed].output);
    }
    d->variable[d->last_variable_displayed++].output = (char *) strdup (message);
    if (action == ACTION_LASTVAR) {
	d->last_variable_displayed = 0;
	if (CIdent ("debug_vars")) {
	    CRedrawFieldedTextbox ("debug_vars", 1);
	} else {
	    Window win;
	    CWidget *w;
	    int x, y;
	    win = CDrawMainWindow ("debug_vars.win", _ ("Variables"));
	    CGetHintPos (&x, &y);
	    w = CDrawFieldedTextbox ("debug_vars", win, x, y, FONT_MEAN_WIDTH * 50 + 7,
		FONT_PIX_PER_LINE * 15 + 6, 0, 0, xdebug_get_line, 0, d);
	    w->hook = d;
	    CSetMovement ("debug_vars", POSITION_WIDTH | POSITION_HEIGHT);
	    CAddCallback ("debug_vars", xdebug_varlist_callback);
	    CSetMovement ("debug_vars.vsc", POSITION_HEIGHT | POSITION_RIGHT);
	    CSetMovement ("debug_vars.hsc", POSITION_WIDTH | POSITION_BOTTOM);
	    CSetSizeHintPos ("debug_vars.win");
	    CMapDialog ("debug_vars.win");
	    CSetWindowResizable ("debug_vars.win", FONT_MEAN_WIDTH * 20, FONT_PIX_PER_LINE * 10, 1600, 1200);
	    CSetToolHint ("debug_vars", _ ("Use Del to delete from this list. Use Ins to\nmark variable as a watchpoint. This will cause\ndebugger to break if the variable changes."));
	}
    }
}

/* }}} variables */

/* returns 1 on error */
static void xdebug_break_point_special (Debug * d, char *cmd, char *editor, int line, int action)
{
    CWidget *w;
    if ((w = CIdent (editor))) {
	char s[MAX_DEBUG_CMD_LEN];
	sprintf (s, "%s %s:%d\n", cmd, w->editor->filename, line);
	xdebug_append_command (d, s, action, editor, line);
    }
}

static void xdebug_break_point (Debug * d, char *cmd, char *editor, int line)
{
    xdebug_break_point_special (d, cmd, editor, line, ACTION_BREAKPOINT);
}

static void xdebug_set_break_points (Debug * d)
{
    int i;
    for (i = 0; i < last_edit; i++) {
	struct _book_mark *b;
	if (!edit[i]->editor->book_mark)
	    continue;
	for (b = edit[i]->editor->book_mark; b->prev; b = b->prev);	/* rewind */
	for (; b; b = b->next)
	    if (b->line >= 0 && b->c == DEBUG_BREAKPOINT_COLOR)
		xdebug_break_point (d, "b", edit[i]->ident, b->line + 1);
    }
}

int goto_error (char *message, int raise_wm_window);

static int xdebug_set_current (char *file, int line)
{
    char s[MAX_DEBUG_CMD_LEN];
    sprintf (s, "%s:%d", file, line);
    return goto_error (s, 0);
}

static int xdebug_goto_line (Debug * d, char *file, int line)
{
    xdebug_cursor_bookmark_flush ();
    if (!xdebug_set_current (file, line)) {
	book_mark_insert (edit[current_edit]->editor, edit[current_edit]->editor->curs_line, DEBUG_CURRENT_COLOR);
	edit_render_keypress (edit[current_edit]->editor);
	return 0;
    }
    edit_render_keypress (edit[current_edit]->editor);
    return 1;
}

void goto_file_dialog (char *heading, char *tool_hint, char *text);

static Debug debug_session;

static void debug_finish (unsigned long x);

void debug_callback (void)
{
    static int animation = 0;
    static int refresh = 0;

    if ((animation++) % 50) {
	if (debug_session.action) {
	    CSetColor (color_widget (4));
	    XFillArc (CDisplay, debug_session.w, CGC, debug_session.x, debug_session.y, debug_session.r * 2, debug_session.r * 2, (animation * 487) % (360 * 64) - 180 * 64, 90 * 64);
	    XFillArc (CDisplay, debug_session.w, CGC, debug_session.x, debug_session.y, debug_session.r * 2, debug_session.r * 2, (animation * 487 + 180 * 64) % (360 * 64) - 180 * 64, 90 * 64);
	    CSetColor (color_widget (12));
	    XFillArc (CDisplay, debug_session.w, CGC, debug_session.x, debug_session.y, debug_session.r * 2, debug_session.r * 2, (animation * 487 + 90 * 64) % (360 * 64) - 180 * 64, 90 * 64);
	    XFillArc (CDisplay, debug_session.w, CGC, debug_session.x, debug_session.y, debug_session.r * 2, debug_session.r * 2, (animation * 487 + 270 * 64) % (360 * 64) - 180 * 64, 90 * 64);
	    refresh = 1;
	} else if (refresh) {
	    refresh = 0;
	    CSetColor (COLOR_FLAT);
	    XFillArc (CDisplay, debug_session.w, CGC, debug_session.x, debug_session.y, debug_session.r * 2, debug_session.r * 2, 0, 360 * 64);
	    CSendExpose (debug_session.w, 0, 0, 1000, 1000);
	}
    }
}

static char *from_start (char *s, char *p, int n)
{
    p = strstr (s, p);
    if (!p)
	return 0;
    if ((unsigned long) p > (unsigned long) s + n)
	return 0;
    return p;
}

static char *from_end (char *s, char *p, int n)
{
    int i, l, k;
    if ((l = strlen (s)) >= (k = strlen (p)) + n)
	for (i = l - k; i >= l - k - n; i--)
	    if (!memcmp (s + i, p, k))
		return s + i;
    return 0;
}

static void xdebug_check_watch_point (Debug * d, char *m)
{
    char *p, *q, *s, *r;
    CWidget *w;
    p = strstr (m, "\nHardware watchpoint ");
    if (!p)
	return;
    w = CIdent ("debug_vars");
    if (w) {
	CSetTextboxPos (w, TEXT_SET_CURSOR_LINE, atoi (p + 12));
	CRedrawFieldedTextbox ("debug_vars", 1);
    }
    r = strchr ((char *) pool_start (d->pool), ':');
    if (!r)
	return;
    r += 2;
    p = strchr (r, '\n');
    if (!p)
	return;
    *p++ = '\0';
    p = strchr (p, '=');
    if (!p)
	return;
    p += 2;
    q = strchr (p, '\n');
    if (!q)
	return;
    *q++ = '\0';
    q = strchr (q, '=');
    if (!q)
	return;
    q += 2;
    *(strchr (q, '\n')) = '\0';
    s = malloc (strlen (p) + strlen (q) + 100);
    strcpy (s, _ ("Variable: "));
    strcat (s, r);
    strcat (s, "\n\n");
    strcat (s, p);
    strcat (s, "\n     --->     \n");
    strcat (s, q);
    debug_message (d, _ (" Watchpoint "), s, TEXT_CENTRED);
    free (s);
}

static void debug_read_callback (int fd, fd_set * reading, fd_set * writing, fd_set * error, void *data)
{
    Debug *d;
    char *p;
    char buf[1025];
    int c, action, n;
    d = &debug_session;
    do {
	c = read (d->out, buf, 1024);
    } while (c < 0 && errno == EINTR);
    if (c <= 0)
	return;
    buf[c] = '\0';
    if (from_end (buf, d->query, 1)) {
	p = strchr (buf, '\n');
	if (!p)
	    buf[0] = '\0';
	else
	    p[1] = '\0';
	while (write (d->in, "y\n", 2) < 0 && errno == EINTR);
    }
    if (!d->pool)
	d->pool = pool_init ();
    pool_printf (d->pool, "%s", buf);
    if (d->show_on_stdout) {
	printf ("%s", buf);
	fflush (stdout);
    }
    if (!d->action)
	goto fin_read;
    p = from_end ((char *) pool_start (d->pool), d->prompt, 0);
    if (!p)
	return;			/* we have not found a prompt, so the command must not have completed */
    action = d->action;
    d->action = 0;
    *p = '\0';
    switch (action) {
    case ACTION_PROMPT:	/* just waiting for the prompt to return, then command is completed */
	pool_free (d->pool);
	d->pool = 0;
	break;
    case ACTION_QUIT:
    case ACTION_MESSAGE:	/* There was message from this command, display it */
	if (strspn ((char *) pool_start (d->pool), "\n\t \r") != strlen ((char *) pool_start (d->pool)))
	    debug_error1 (d, (char *) pool_start (d->pool));
	pool_free (d->pool);
	d->pool = 0;
	if (action == ACTION_QUIT)
	    debug_finish (0L);
	break;
    case ACTION_VERSION:
	if (!strstr ((char *) pool_start (d->pool), GDB_VERSION))
	    debug_error1 (d, catstrs (GDB_VERSION, _ (" expected. You seem to have a different debugger \n or version. If things don't work properly, this is why."), 0));
	if (from_end ((char *) pool_start (d->pool), "no debugging symbols found", 6)) {
	    debug_error1 (d, _ ("The debugger returned \"no debugging symbols found\" implying that \n the executable has been stripped, or was not compiled with \n the \"-g\" option for debugging"));
	    debug_finish (0L);
	    break;
	}
	pool_free (d->pool);
	d->pool = 0;
	break;
    case ACTION_WATCH:
	p = strstr ((char *) pool_start (d->pool), "watchpoint");
	if (p) {
	    d->variable[d->break_point_line].watch = atoi (p + 11);
	} else {
	    d->variable[d->break_point_line].watch = 0;
	}
	CRedrawFieldedTextbox ("debug_vars", 1);
	pool_free (d->pool);
	d->pool = 0;
	break;
    case ACTION_VARIABLE:
    case ACTION_LASTVAR:
	xdebug_display_variable (d, (char *) pool_start (d->pool), action);
	pool_free (d->pool);
	d->pool = 0;
	break;
    case ACTION_CONDITION:
    case ACTION_BREAKPOINT:
	if (from_start ((char *) pool_start (d->pool), "No symbol table is loaded", 2)) {
	    debug_error1 (d, _ ("The debugger returned \"No symbol table is loaded\" \n implying that the executable does not \n exist or is badly formatted"));
	    debug_finish (0L);
	    break;
	}
	if (from_start ((char *) pool_start (d->pool), "Function \"main\" not defined", 2) ||
	    from_start ((char *) pool_start (d->pool), "No source file specified", 2)) {
	    debug_error1 (d, _ ("The debugger returned a message implying that \n the executable was not compiled with \n the \"-g\" option for debugging"));
	    debug_finish (0L);
	    break;
	}
	p = strstr ((char *) pool_start (d->pool), "Breakpoint ");
	if (p) {
	    p += 11;
	    n = atoi (p);
	    p += strspn (p, "0123456789");
	    p = from_start (p, " at ", 0);
	}
	if (p && action == ACTION_CONDITION) {
	    char *q;
	    q = malloc (strlen (d->condition) + 30);
	    sprintf (q, "condition %d %s\n", n, d->condition);
	    free (d->condition);
	    d->condition = 0;
	    xdebug_append_command (&debug_session, q, ACTION_MESSAGE, 0, 0);
	    free (q);
	} {
	    CWidget *w;
	    w = CIdent (d->break_point_editor);
	    if (p) {
		if (w) {
		    book_mark_insert (w->editor, d->break_point_line - 1, DEBUG_BREAKPOINT_COLOR);
		    w->editor->force |= REDRAW_COMPLETELY;
		}
	    } else {
		p = from_start ((char *) pool_start (d->pool), "Deleted breakpoint", 2);
		if (!p)
		    p = from_start ((char *) pool_start (d->pool), "No breakpoint ", 2);
		if (!p)
		    p = from_start ((char *) pool_start (d->pool), "No source file named ", 2);
		if (p && w) {
		    while (!book_mark_clear (w->editor, d->break_point_line - 1, DEBUG_BREAKPOINT_COLOR));
		    w->editor->force |= REDRAW_COMPLETELY;
		}
	    }
	}
	if (!p) {
	    debug_error1 (d, (char *) pool_start (d->pool));
	    xdebug_flush_commands (d);
	}
	if (p)
	    xdebug_show_variables (d);
	pool_free (d->pool);
	d->pool = 0;
	break;
    case ACTION_INFO_SOURCE:
	if (d->file) {
	    free (d->file);
	    d->file = 0;
	}
	p = strstr ((char *) pool_start (d->pool), "Located in ");
	if (p) {
	    char *q;
	    q = p + 11;
	    p = strchr (q, '\n');
	    if (p) {
		*p = '\0';
		d->file = (char *) strdup (q);
	    }
	}
	p = strstr ((char *) pool_start (d->pool), "No current source file");
	if (p) {
	    xdebug_cursor_bookmark_flush ();
	} else {
	    if (d->file && d->line)	/* if we can't get the file number, issue a backtrace */
		xdebug_goto_line (d, d->file, d->line);
	    else
		xdebug_append_command (d, d->get_backtrace, ACTION_BACKTRACE, 0, 0);
	}
	pool_free (d->pool);
	d->pool = 0;
	break;
    case ACTION_INFO_PROGRAM:
	p = strstr ((char *) pool_start (d->pool), "Pid");
	if (!p)
	    p = strstr ((char *) pool_start (d->pool), "Thread");
	if (p) {
	    p += strcspn (p, "0123456789");
	    d->child = atoi (p);
	}
	pool_free (d->pool);
	d->pool = 0;
	break;
    case ACTION_BACKTRACE:
    case ACTION_SHOW:
	p = strstr ((char *) pool_start (d->pool), "No stack");
	if ((unsigned long) p == (unsigned long) pool_start (d->pool)) {
	    debug_error1 (d, _ (" There is no stack "));
	    pool_free (d->pool);
	} else {
	    goto_file_dialog (action == ACTION_BACKTRACE ? _ (" Backtrace ") : _ (" Output "), 0, (char *) pool_break (d->pool));
	}
	d->pool = 0;
	break;
    case ACTION_STEP:
    case ACTION_RUNNING:
    case ACTION_UNTIL:
	for (p = (char *) pool_start (d->pool); p; p++) {
	    if (!*p)
		break;
	    if (*p >= '0' && *p <= '9')
		d->line = atoi (p);
	    p = strchr (p, '\n');
	    if (!p)
		break;
	}
	xdebug_check_watch_point (d, (char *) pool_start (d->pool));
	p = from_end ((char *) pool_start (d->pool), "\nProgram exited normally", 3);
	if (!p)
	    p = from_end ((char *) pool_start (d->pool), "\nProgram exited with code", 7);
	if (p) {
	    xdebug_cursor_bookmark_flush ();
	    d->child = 0;
	}
	if (!p) {		/* in all other circumstances, we must get the file and line number */
	    if (d->line)	/* got the line, now be sure of the filename */
		xdebug_append_command (d, d->get_source, ACTION_INFO_SOURCE, 0, 0);
	    else		/* don't have the line, so show a backtrace */
		xdebug_append_command (d, d->get_backtrace, ACTION_BACKTRACE, 0, 0);
	    p = strstr ((char *) pool_start (d->pool), "\nProgram received signal");
	}
	if (!p) {		/* in all other circumstances, we must get the file and line number */
	    p = from_start ((char *) pool_start (d->pool), "The program is not being run", 0);
	    if (p) {
		xdebug_cursor_bookmark_flush ();
		d->child = 0;
		if (action == ACTION_UNTIL)
		    xdebug_break_point (d, "clear", d->break_point_editor, d->break_point_line + 1);
	    }
	}
	if (p) {
	    debug_error1 (d, p);
	} else if (action == ACTION_UNTIL) {
	    xdebug_break_point (d, "clear", d->break_point_editor, d->break_point_line + 1);
	}
	pool_free (d->pool);
	d->pool = 0;
	xdebug_show_variables (d);
	break;
    default:
	break;
    }
  fin_read:
    if (d->n_commands && !d->action)
	CAddWatch (d->in, debug_write_callback, WATCH_WRITING, 0);
}

static void debug_write_callback (int fd, fd_set * reading, fd_set * writing, fd_set * error, void *data)
{
    Debug *d;
    int c;
    d = &debug_session;
    if (d->show_on_stdout) {
	printf ("%s", d->command[0].command);
	fflush (stdout);
    }
    if (d->break_point_editor)
	free (d->break_point_editor);
    d->break_point_editor = 0;
    d->break_point_line = 0;
/* no backtrace if no program running, all other commands are ok */
    if (!(!d->child && (d->command[0].action == ACTION_INFO_SOURCE || d->command[0].action == ACTION_BACKTRACE))) {
	do {
	    c = write (d->in, d->command[0].command, strlen (d->command[0].command));
	} while (c < 0 && errno == EINTR);
	d->action = d->command[0].action;
	d->break_point_editor = d->command[0].editor;
	d->break_point_line = d->command[0].line;
    }
    free (d->command[0].command);
    if (d->action == ACTION_RUNNING || d->action == ACTION_STEP || d->action == ACTION_UNTIL)
	xdebug_cursor_bookmark_flush ();
    memmove (&d->command[0], &d->command[1], d->n_commands * sizeof (struct _command));
    d->command[d->n_commands].action = 0;
    d->command[d->n_commands].command = 0;
    d->n_commands--;
    CRemoveWatch (d->in, debug_write_callback, WATCH_WRITING);
}

static int xdebug_run_program (Debug * d)
{
    char *arg[10];
    int i = 0;
    struct stat s;
    xdebug_cursor_bookmark_flush ();
    if (!ttyname (0))
	d->show_output = 1;
    if (stat (d->progname, &s)) {
	debug_error2 (d, _ ("Error trying to stat executable: check that the filename and \n current directory are correct"), strerror (errno));
	return 1;
    } else {
	if (!(s.st_mode & S_IXUSR)) {
	    debug_error2 (d, _ ("Your program does not seem to have execute permissions"), d->progname);
	    return 1;
	}
    }
    arg[i++] = d->debugger;
    if (d->show_output) {
	int tries, p, l, success = 0;
	char tty[100], *f;
	if (!d->xterm_cmd)
	    d->xterm_cmd = (char *) strdup ("xterm");
	f = (char *) tempnam (0, "cetty");
	mknod (f, S_IFIFO | S_IRUSR | S_IWUSR, 0);
	if (!(d->xterm_pid = fork ())) {
	    execlp ("/bin/sh", "/bin/sh", "-c", catstrs (d->xterm_cmd, " -T ", d->progname, " -e ttyname_stop ", f, 0), 0);
	    exit (1);
	}
	do {
	    p = open (f, O_RDONLY);
	} while (p < 0 && errno == EINTR);
	if (p >= 0)
	    for (tries = 0; tries < 6; tries++) {
		if ((l = read (p, tty, 80)) > 5) {
		    char *r;
		    tty[l] = '\0';
		    r = strchr (tty, ',');
		    if (r) {
			*r++ = '\0';
			if (atoi (r))
			    d->ttyname_pid = atoi (r);
			success = 1;
			arg[i++] = catstrs ("-tty=", tty, 0);
		    }
		    break;
		}
		CSleep (1.0);
	    }
	close (p);
	unlink (f);
	free (f);
	if (!success) {
	    kill (debug_session.xterm_pid, SIGTERM);
	    debug_session.xterm_pid = 0;
	    debug_error1 (d, _ ("Could not get xterm pseudo terminal"));
	    return 1;
	}
    }
    arg[i++] = d->progname;
    arg[i] = 0;
    d->pid = triple_pipe_open (&d->in, &d->out, 0, 1, d->debugger, arg);
    d->child = 0;
    if (d->pid <= 0) {
	d->pid = 0;
	kill (debug_session.ttyname_pid, SIGTERM);
	kill (debug_session.xterm_pid, SIGTERM);
	debug_session.xterm_pid = debug_session.ttyname_pid = 0;
	debug_error2 (d, _ ("Error executing gdb"), strerror (errno));
	xdebug_flush_commands (d);
	return 1;
    }
    d->action = ACTION_VERSION;
    CAddWatch (d->out, debug_read_callback, WATCH_READING, 0);
    return 0;
}

static void xdebug_set_args (Debug * d, char *args)
{
    char *s;
    s = malloc (strlen (args) + 20);
    sprintf (s, "set args %s\n", args ? args : "");
    xdebug_append_command (d, s, ACTION_PROMPT, 0, 0);
    free (s);
}

static void xdebug_run (Debug * d)
{
    char s[80];
    xdebug_append_command (d, "b main\n", ACTION_BREAKPOINT, 0, 0);
    xdebug_append_command (d, "r\n", ACTION_RUNNING, 0, 0);
    xdebug_append_command (d, "clear\n", ACTION_BREAKPOINT, 0, 0);
/* programs get this signal when they ioctl the terminal given to them by gdb */
/* this prevents them quiting when using the `show output' option */
    sprintf (s, "call signal (%d, (void *) %lu)\n", SIGTTOU, (unsigned long) SIG_IGN);
    xdebug_append_command (d, s, ACTION_PROMPT, 0, 0);
    xdebug_append_command (d, "info program\n", ACTION_INFO_PROGRAM, 0, 0);
    if (!d->stop_at_main)
	xdebug_append_command (d, "c\n", ACTION_RUNNING, 0, 0);
}

static void debug_start (void)
{
    if (xdebug_run_program (&debug_session))
	return;
    xdebug_set_args (&debug_session, debug_session.args);
/*    xdebug_set_watch_points (&debug_session);	*/
    xdebug_set_break_points (&debug_session);
    xdebug_run (&debug_session);
}

static int debug_set_info (unsigned long x);

static void debug_attach (unsigned long x)
{
    char *s;
    char t[80];
    long p;
    if (debug_session.pid) {
	debug_error1 (&debug_session, _ ("Debugger is current running, use Kill or Detach first"));
	return;
    }
    if (debug_session.action) {
	debug_error1 (&debug_session, _ ("Program is currently running, use Control-C first"));
	return;
    }
    xdebug_cursor_bookmark_flush ();
    xdebug_flush_commands (&debug_session);
    if (!debug_session.progname)
	if (debug_set_info (0))
	    return;
    s = CInputDialog ("debugattach", 0, 0, 0, 30 * FONT_MEAN_WIDTH, TEXTINPUT_LAST_INPUT, _ (" Debug "), _ (" Enter process id to attach to : "));
    if (!s)
	return;
    p = atol (s);
    free (s);
    if (p <= 0)
	return;
    debug_session.show_output = 0;
    if (xdebug_run_program (&debug_session))
	return;
    xdebug_set_break_points (&debug_session);
    sprintf (t, "attach %ld\n", p);
    xdebug_append_command (&debug_session, t, ACTION_PROMPT, 0, 0);
    xdebug_append_command (&debug_session, "info program\n", ACTION_INFO_PROGRAM, 0, 0);
    xdebug_append_command (&debug_session, "c\n", ACTION_RUNNING, 0, 0);
}

void debug_detach (unsigned long x)
{
    if (!debug_session.pid) {
	debug_error1 (&debug_session, _ ("Debugger is not running"));
	return;
    }
    if (debug_session.action) {
	debug_error1 (&debug_session, _ ("Program is currently running, use Control-C first"));
	return;
    }
    xdebug_append_command (&debug_session, "detach\n", ACTION_QUIT, 0, 0);
    debug_session.child = 0;
}

/* resturns 1 on cancel */
static int debug_set_info (unsigned long x)
{
    char *inputs[10] =
    {
	gettext_noop (""),
	gettext_noop (""),
	"xterm -fn 8x13bold -fg white -bg black",
	0
    };
    char *input_labels[10] =
    {
	gettext_noop ("Program name"),
	gettext_noop ("Enter program arguments"),
	gettext_noop ("Terminal"),
	0
    };
    char *check_labels[10] =
    {
	gettext_noop ("Show program output"),
	gettext_noop ("Stop at main() function"),
	gettext_noop ("Show on stdout"),
	0
    };
    char *check_tool_hints[10] =
    {
	gettext_noop ("This will open the xterm for the program given under \"Terminal\".\nIf you started Cooledit without a controlling tty, this must be set"),
	gettext_noop ("This will cause the debugger to break at the first\nline of code in the program when issuing a `Start'."),
	gettext_noop ("This will cause all debugger commands to show at\nthe terminal where you started cooledit."),
	0
    };
    char *input_names[10] =
    {
	gettext_noop ("debugprogname"),
	gettext_noop ("debugargs"),
	gettext_noop ("debugxterm"),
	0
    };
    char *input_tool_hint[10] =
    {
	gettext_noop ("The executable that you would like to debug - this must be compiled with the -g option"),
	gettext_noop ("Arguments to be passed to the executable - can be empty"),
	gettext_noop ("The programs stdout etc. will be redirected to this program for viewing"),
	0
    };
    int *checks_values_result[10];
    char **inputs_result[10];
    int r;
    inputs_result[0] = &inputs[0];
    inputs_result[1] = &inputs[1];
    inputs_result[2] = &inputs[2];
    inputs_result[3] = 0;
    if (!ttyname (0))
	debug_session.show_output = 1;
    checks_values_result[0] = &debug_session.show_output;
    checks_values_result[1] = &debug_session.stop_at_main;
    checks_values_result[2] = &debug_session.show_on_stdout;
    checks_values_result[3] = 0;
    r = CInputsWithOptions (0, 0, 0, _ (" Debug : Program Data "), inputs_result, input_labels, input_names, input_tool_hint, checks_values_result, check_labels, check_tool_hints, INPUTS_WITH_OPTIONS_BROWSE_LOAD_1, 60);
    if (r)
	return 1;
    if (debug_session.progname)
	free (debug_session.progname);
    debug_session.progname = inputs[0];
    if (debug_session.args)
	free (debug_session.args);
    debug_session.args = inputs[1];
    if (debug_session.xterm_cmd)
	free (debug_session.xterm_cmd);
    debug_session.xterm_cmd = inputs[2];
    if (debug_session.pid)
	debug_error1 (&debug_session, _ (" The debugger is currently running. You will have \n to kill the debugger for these changes to take effect. "));
    return 0;
}

/* ACTION_STEP is the same as ACTION_RUNNING except that if a user hits it twice in a row,
   it won't be prone to give "Program is currently running..." messages. */
static void debug_step (unsigned long c)
{
    char *cmd;
    cmd = (char *) c;
    if (!debug_session.pid) {
	debug_error1 (&debug_session, _ ("Debugger is not running"));
	return;
    }
    if (debug_session.action) {
	debug_error1 (&debug_session, _ ("Program is currently running, use Control-C first"));
	return;
    }
    xdebug_append_command (&debug_session, cmd, ACTION_STEP, 0, 0);
}

static void debug_command (unsigned long c)
{
    char *cmd;
    cmd = (char *) c;
    if (!debug_session.pid) {
	debug_error1 (&debug_session, _ ("Debugger is not running"));
	return;
    }
    if (debug_session.action) {
	debug_error1 (&debug_session, _ ("Program is currently running, use Control-C first"));
	return;
    }
    xdebug_append_command (&debug_session, cmd, ACTION_RUNNING, 0, 0);
}

static void debug_show_output (unsigned long c)
{
    char s[MAX_DEBUG_CMD_LEN];
    char *cmd;
    cmd = (char *) c;
    if (!debug_session.pid) {
	debug_error1 (&debug_session, _ ("Debugger is not running"));
	return;
    }
    if (debug_session.action) {
	debug_error1 (&debug_session, _ ("Program is currently running, use Control-C first"));
	return;
    }
    if (!cmd) {
	cmd = CInputDialog ("debugruncmd", 0, 0, 0, 50 * FONT_MEAN_WIDTH, TEXTINPUT_LAST_INPUT, _ (" Debug "), _ (" Enter debugger command : "));
	if (cmd) {
	    sprintf (s, "%s\n", cmd);
	    free (cmd);
	    cmd = s;
	}
    }
    if (!cmd)
	return;
    xdebug_append_command (&debug_session, cmd, ACTION_SHOW, 0, 0);
}

static void debug_break (unsigned long x)
{
    if (!debug_session.pid) {
	debug_error1 (&debug_session, _ ("Debugger is not running"));
	return;
    }
    if (!debug_session.action)	/* already stopped */
	return;
    if (debug_session.action == ACTION_RUNNING || debug_session.action == ACTION_STEP || debug_session.action == ACTION_UNTIL) {
	xdebug_flush_commands (&debug_session);
	if (debug_session.child) {
	    kill (debug_session.child, SIGINT);
	    debug_session.action = ACTION_RUNNING;
	}
    }
}

static void debug_finish (unsigned long x)
{
    xdebug_cursor_bookmark_flush ();
    xdebug_flush_commands (&debug_session);
    if (debug_session.pool)
	pool_free (debug_session.pool);
    debug_session.pool = 0;
    if (debug_session.pid) {
	kill (debug_session.pid, SIGTERM);
	debug_session.pid = 0;
	CRemoveWatch (debug_session.in, debug_write_callback, WATCH_WRITING);
	CRemoveWatch (debug_session.out, debug_read_callback, WATCH_READING);
	close (debug_session.in);
	close (debug_session.out);
	if (debug_session.ttyname_pid) {
	    kill (debug_session.ttyname_pid, SIGTERM);
	    debug_session.ttyname_pid = 0;
	}
	if (debug_session.xterm_pid) {
	    kill (debug_session.xterm_pid, SIGTERM);
	    debug_session.xterm_pid = 0;
	}
	if (debug_session.child) {
	    kill (debug_session.child, SIGKILL);
	    debug_session.child = 0;
	}
    }
}

void debug_check (pid_t p)
{
    if (p == debug_session.xterm_pid)
	debug_finish (0);
}

static void debug_restart (unsigned long x)
{
    if (debug_session.action) {
	debug_error1 (&debug_session, _ ("Program is currently running, use Control-C first"));
	return;
    }
    xdebug_cursor_bookmark_flush ();
    xdebug_flush_commands (&debug_session);
    if (!debug_session.progname)
	if (debug_set_info (0))
	    return;
    if (!debug_session.pid) {
	debug_start ();
    } else {
	debug_session.child = 0;
	xdebug_append_command (&debug_session, "k\n", ACTION_PROMPT, 0, 0);
	xdebug_run (&debug_session);
    }
}

static void debug_clear_breakpoints (unsigned long x)
{
    int i;
    if (debug_session.action) {
	debug_error1 (&debug_session, _ ("Program is currently running, use Control-C first"));
	return;
    }
    if (debug_session.pid)
	xdebug_append_command (&debug_session, "d\n", ACTION_PROMPT, 0, 0);
    for (i = 0; i < last_edit; i++)
	book_mark_flush (edit[i]->editor, DEBUG_BREAKPOINT_COLOR);
}

static void debug_conditional (unsigned long x)
{
    if (!debug_session.pid) {
	debug_error1 (&debug_session, _ ("Debugger is not running"));
	return;
    }
    if (debug_session.action) {
	debug_error1 (&debug_session, _ ("Program is currently running, use Control-C first"));
	return;
    }
    if (debug_session.condition)
	free (debug_session.condition);
    debug_session.condition = CInputDialog ("debugcondition", 0, 0, 0, 50 * FONT_MEAN_WIDTH, TEXTINPUT_LAST_INPUT, _ (" Breakpoint "), _ (" Enter condition on which to break : "));
    if (debug_session.condition)
	xdebug_break_point_special (&debug_session, "b", edit[current_edit]->ident, edit[current_edit]->editor->curs_line + 1, ACTION_CONDITION);
}

static void debug_break_point (unsigned long x)
{
    char *cmd = "b";
    if (debug_session.action) {
	debug_error1 (&debug_session, _ ("Program is currently running, use Control-C first"));
	return;
    }
    if (book_mark_query_color (edit[current_edit]->editor, edit[current_edit]->editor->curs_line, DEBUG_BREAKPOINT_COLOR))
	cmd = "clear";
    if (debug_session.pid)
	xdebug_break_point (&debug_session, cmd, edit[current_edit]->ident, edit[current_edit]->editor->curs_line + 1);
    else {
	if (!strcmp (cmd, "clear"))
	    while (!book_mark_clear (edit[current_edit]->editor, edit[current_edit]->editor->curs_line, DEBUG_BREAKPOINT_COLOR));
	else
	    book_mark_insert (edit[current_edit]->editor, edit[current_edit]->editor->curs_line, DEBUG_BREAKPOINT_COLOR);
	edit[current_edit]->editor->force |= REDRAW_COMPLETELY;
    }
}

static void debug_add_var (unsigned int x)
{
    char *s;
    if (!debug_session.pid) {
	debug_error1 (&debug_session, _ ("Debugger is not running"));
	return;
    }
    if (debug_session.action) {
	debug_error1 (&debug_session, _ ("Program is currently running, use Control-C first"));
	return;
    }
    s = CInputDialog ("debugvariab", 0, 0, 0, 30 * FONT_MEAN_WIDTH, TEXTINPUT_LAST_INPUT, _ (" Debug "), _ (" Enter variable to display : "));
    if (!s)
	return;
    if (xdebug_add_variable (&debug_session, s)) {
	free (s);
	return;
    }
    free (s);
    xdebug_show_variables (&debug_session);
}

static void debug_until (unsigned int x)
{
    if (!debug_session.pid) {
	debug_error1 (&debug_session, _ ("Debugger is not running"));
	return;
    }
    if (debug_session.action) {
	debug_error1 (&debug_session, _ ("Program is currently running, use Control-C first"));
	return;
    }
    xdebug_break_point (&debug_session, "b", edit[current_edit]->ident, edit[current_edit]->editor->curs_line + 1);
    xdebug_append_command (&debug_session, "c\n", ACTION_UNTIL, edit[current_edit]->ident, edit[current_edit]->editor->curs_line);
}

void xdebug_init_animation (void)
{
    CWidget *w;
    w = CIdent ("menu.debugmenu");
    debug_session.r = w->height / 2;
    debug_session.x = w->width / 2 - debug_session.r;
    debug_session.y = w->height / 2 - debug_session.r;
    debug_session.w = w->winid;
}

void debug_init (void)
{
    memset (&debug_session, 0, sizeof (debug_session));
    debug_session.prompt = "(gdb) ";
    debug_session.get_line = "info line\n";
    debug_session.get_source = "info source\n";
    debug_session.get_backtrace = "backtrace\n";
    debug_session.query = "( y or n )";
    debug_session.debugger = "gdb";
    debug_session.stop_at_main = 1;
    xdebug_init_animation ();
}

void debug_shut (void)
{
    int i;
    debug_finish (0);
    CDestroyWidget ("debug_vars.win");
    if (debug_session.progname)
	free (debug_session.progname);
    if (debug_session.args)
	free (debug_session.args);
    if (debug_session.xterm_cmd)
	free (debug_session.xterm_cmd);
    if (debug_session.file)
	free (debug_session.file);
    if (debug_session.condition)
	free (debug_session.condition);
    if (debug_session.break_point_editor)
	free (debug_session.break_point_editor);
    for (i = 0; debug_session.variable[i].name && debug_session.variable[i].output; i++) {
	free (debug_session.variable[i].name);
	free (debug_session.variable[i].output);
    }
    memset (&debug_session, 0, sizeof (debug_session));
}

void debug_menus (Window parent, Window main_window, int x, int y)
{
    CDrawMenuButton ("menu.debugmenu", parent, main_window, x, y, AUTO_SIZE, 16,
			 _ (" Debug "),
			 _ ("Start\tM-F5"), '~', debug_restart, 0L,
			 _ ("Stop\tC-c"), '~', debug_break, 0L,
			 _ ("Attach"), '~', debug_attach, 0L,
			 _ ("Detach"), '~', debug_detach, 0L,
			 _ ("Toggle breakpoint\tM-F2"), '~', debug_break_point, 0L,
			 _ ("Insert conditional breakpoint"), '~', debug_conditional, 0L,
			 _ ("Clear all breakpoints"), '~', debug_clear_breakpoints, 0L,
			 _ ("Next\tM-F9"), '~', debug_step, (unsigned long) "n\n",
			 _ ("Step\tM-F8"), '~', debug_step, (unsigned long) "s\n",
			 _ ("Back trace"), '~', debug_show_output, (unsigned long) "backtrace\n",
			 _ ("Continue\tM-F4"), '~', debug_command, (unsigned long) "c\n",
			 _ ("Continue until cursor\tM-F3"), '~', debug_until, 0L,
			 _ ("Kill debugger"), '~', debug_finish, 0L,
			 _ ("Enter command...\tM-F1"), '~', debug_show_output, 0L,
			 _ ("Display variable..."), '~', debug_add_var, 0L,
			 _ ("Set info..."), '~', debug_set_info, 0L
	);
    CSetToolHint ("menu.debugmenu", _ ("Interactive graphical debugger"));
}

/* hack: c is just to check if different keys are pressed - any unique
   thing can go in there. */
static int time_diff (unsigned long c)
{
    static unsigned long c_last = 0;
    int r = 0;
    static struct timeval tv =
    {0, 0};
    struct timeval tv_last;
    tv_last = tv;
    gettimeofday (&tv, 0);
    if ((double) ((double) tv.tv_usec / 1000000.0 + (double) tv.tv_sec) - \
     ((double) tv_last.tv_usec / 1000000.0 + (double) tv_last.tv_sec) < \
	(double) 0.5 && c == c_last)
	r = 1;
    c_last = c;
    return r;
}

void debug_key_command (int command)
{
    if (time_diff (command))
	if (debug_session.action) {
	    return;
	}
    if (command == CK_Debug_Stop) {
	debug_break (0);
	return;
    }
    if (debug_session.n_commands)
	return;
    switch (command) {
    case CK_Debug_Start:
	debug_restart (0);
	break;
    case CK_Debug_Toggle_Break:
	debug_break_point (0);
	break;
    case CK_Debug_Clear:
	debug_clear_breakpoints (0);
	break;
    case CK_Debug_Next:
	debug_step ((unsigned long) "n\n");
	break;
    case CK_Debug_Step:
	debug_step ((unsigned long) "s\n");
	break;
    case CK_Debug_Back_Trace:
	debug_show_output ((unsigned long) "backtrace\n");
	break;
    case CK_Debug_Continue:
	debug_command ((unsigned long) "c\n");
	break;
    case CK_Debug_Enter_Command:
	debug_show_output (0L);
	break;
    case CK_Debug_Until_Curser:
	debug_until (0L);
    }
}



#if 0
    if (!ttyname (0)) {
	int success = 0, tty_fd;
	char *a1, *a2, t[] = "/dev/ttyXX", p[] = "/dev/ptyXX";
	struct group *g;
	int gid;
	if ((g = getgrnam ("tty")) != NULL)
	    gid = g->gr_gid;
	else
	    gid = getgid ();
	for (a1 = "pqrstuvwxyzabcde"; *a1; a1++) {
	    p[8] = t[8] = *a1;
	    for (a2 = "0123456789abcdef"; *a2; a2++) {
		p[9] = t[9] = *a2;
		tty_fd = open (p, O_RDWR, 0);
		if (tty_fd < 0 && errno == ENOENT)
		    goto break_out;
		if (tty_fd < 0)
		    continue;
		close (0);
		close (1);
		close (2);
		dup (0);
		dup (1);
		dup (2);
		chown (t, getuid (), gid);
		chmod (t, S_IRUSR | S_IWUSR | S_IWGRP);
		success = 1;
		goto break_out;
	    }
	}
      break_out:
	if (!success) {
	    debug_error2 (d, _ ("Cooledit is not attached to a terminal, run cooledit from a terminal to use gdb\nor set to display in an xterm from `Set data...'"), strerror (errno));
	    return 1;
	}
    }
#endif

