// $Id: Command.C,v 1.16 1998/06/02 13:03:00 zeller Exp $ -*- C++ -*-
// DDD interface to GDB commands

// Copyright (C) 1996-1997 Technische Universitaet Braunschweig, Germany.
// Written by Andreas Zeller <zeller@ips.cs.tu-bs.de>.
// 
// This file is part of DDD.
// 
// DDD 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.
// 
// DDD 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 DDD -- see the file COPYING.
// If not, write to the Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// 
// DDD is the data display debugger.
// For details, see the DDD World-Wide-Web page, 
// `http://www.cs.tu-bs.de/softech/ddd/',
// or send a mail to the DDD developers <ddd@ips.cs.tu-bs.de>.

char Command_rcsid[] = 
    "$Id: Command.C,v 1.16 1998/06/02 13:03:00 zeller Exp $";

#ifdef __GNUG__
#pragma implementation
#pragma implementation "Queue.h"
#endif

#include "Command.h"
#include "comm-manag.h"
#include "status.h"
#include "exectty.h"
#include "cmdtty.h"
#include "findParent.h"
#include "history.h"
#include "ddd.h"
#include "cook.h"
#include "Queue.h"
#include "windows.h"
#include "GDBAgent.h"
#include "TimeOut.h"
#include "AppData.h"
#include "disp-read.h"

#include <ctype.h>
#include <string.h>
#include <Xm/Xm.h>
#include <Xm/Text.h>
#include <X11/StringDefs.h>

// ANSI C++ doesn't like the XtIsRealized() macro
#ifdef XtIsRealized
#undef XtIsRealized
#endif

#define LOG_QUEUE 0

// Origin of last command
static Widget gdb_last_origin;

// True if a user command was processed
static bool had_user_command = false;


//-----------------------------------------------------------------------------
// Auto command management
//-----------------------------------------------------------------------------

// Strip auto-command prefix
void strip_auto_command_prefix(string& cmd)
{
    // Neither of these ever changes, so make it static
    static string echo = gdb->echo_command(app_data.auto_command_prefix);

    int i;
    while ((i = cmd.index(echo)) >= 0)
    {
	cmd.at(i, echo.length()) = "";
	int nl = cmd.index("\\n", i);
	if (nl >= 0)
	    cmd.at(nl, 2) = "";
    }
}

// Add it
void add_auto_command_prefix(string& cmd)
{
    cmd = gdb->echo_command(app_data.auto_command_prefix + cmd + "\n");
}


//-----------------------------------------------------------------------------
// GDB command management
//-----------------------------------------------------------------------------

static void ClearOriginCB(Widget w, XtPointer, XtPointer)
{
    if (gdb_last_origin == w)
	gdb_last_origin = 0;
}

// Translate frequently used commands
void translate_command(string& command)
{
    switch(gdb->type())
    {
    case XDB:
	// Although we have defined these as aliases, we translate
	// them anyway.  This way, DDD can find the documentation
	// (which won't work for aliases).
	if (command == "run")
	    command = "r";
	else if (command == "cont")
	    command = "c";
	else if (command == "next")
	    command = "S";
	else if (command == "step")
	    command = "s";
	else if (command == "quit")
	    command = "q";
	break;

    case JDB:
	// JDB uses `step up' instead of `finish'.
	if (command == "finish")
	    command = "step up";

	// Typing ^C at JDB kills it, which is not what the user
	// expects.  Simply suspend all threads instead.
	if (command == '\003')
	    command = "suspend";

	break;

    case GDB:
    case DBX:
	break;
    }

    // When recording graph commands, realize them as auto commands instead
    if (gdb->recording() && is_graph_cmd(command))
	add_auto_command_prefix(command);
}

// Process command C; do it right now.
void _gdb_command(const Command& c)
{
    string cmd = c.command;

    if (gdb->isReadyWithPrompt())
    {
	if (c.verbose)
	{
	    set_status("");
	    had_user_command = true;
	}

	if (cmd.length() == 1 && iscntrl(cmd[0]))
	{
	    promptPosition = messagePosition = XmTextGetLastPosition(gdb_w);
	}
	else if (cmd.length() > 0 && c.echo)
	{
	    add_to_history(cmd);
	}

	if (!gdb->recording())
	    handle_running_commands(cmd, c.origin);

	if (cmd.length() == 0)
	{
	    _gdb_out(gdb->prompt());
	    return;
	}
    }

    gdb_keyboard_command = private_gdb_input;

    if (gdb_last_origin != 0)
    {
	XtRemoveCallback(gdb_last_origin, XtNdestroyCallback, 
			 ClearOriginCB, 0);
    }

    gdb_last_origin = find_shell(gdb_keyboard_command ? gdb_w : c.origin);

    if (gdb_last_origin != 0)
    {
	XtAddCallback(gdb_last_origin, XtNdestroyCallback, 
		      ClearOriginCB, 0);
    }

    translate_command(cmd);
    send_gdb_command(cmd, c.origin, c.callback, c.data, 
		     c.echo, c.verbose, c.prompt, c.check);
    messagePosition = XmTextGetLastPosition(gdb_w);
}

bool userInteractionSeen()
{
    return had_user_command;
}


//-----------------------------------------------------------------------------
// Command queue
//-----------------------------------------------------------------------------

void Command::add_destroy_callback()
{
    if (origin != 0)
	XtAddCallback(origin, XtNdestroyCallback, clear_origin, 
		      (XtPointer)this);
}

void Command::remove_destroy_callback()
{
    if (origin != 0)
	XtRemoveCallback(origin, XtNdestroyCallback, clear_origin,
			 (XtPointer)this);
}

void Command::clear_origin(Widget w, XtPointer client_data, XtPointer)
{
    (void) w;			// Use it

    // The widget is being destroyed.  Remove all references.
    Command *command = (Command *)client_data;
    assert(w == command->origin);
    command->origin = 0;
}

typedef Queue<Command> CommandQueue;
typedef QueueIter<Command> CommandQueueIter;

static CommandQueue commandQueue;

#if LOG_QUEUE
static ostream& operator<<(ostream& os, const CommandQueue& queue)
{
    os << "[";
    bool first = true;
    for (CommandQueueIter i = queue; i.ok(); i = i.next())
    {
	if (first)
	    first = false;
	else
	    os << ", ";

	os << quote(i().command) << "<" << i().priority << ">";
    }
    return os << "]";
}
#endif

void clearCommandQueue()
{
    CommandQueue oldCommandQueue(commandQueue);
    static CommandQueue emptyQueue;
    commandQueue = emptyQueue;

    while (!oldCommandQueue.isEmpty())
    {
	const Command& cmd = oldCommandQueue.first();
	if (cmd.callback != 0)
	{
	    // We're deleting a command with associated callback.
	    // Call callback with NO_GDB_ANSWER such that it can clean
	    // up the associated data.  Commands added by the callback
	    // will be added to the command queue.
	    cmd.callback(NO_GDB_ANSWER, cmd.data);
	}
	oldCommandQueue -= cmd;
    }

#if LOG_QUEUE
    clog << "Command queue: " << commandQueue << "\n";
#endif
}

bool emptyCommandQueue()
{
    return commandQueue.isEmpty();
}

void gdb_command(const Command& c)
{
    if (c.command.length() == 1 && iscntrl(c.command[0]) 
	|| c.command == "no" || c.command == "yes")
    {
	// User interaction -- execute immediately
	_gdb_command(c);

	if (c.command != "yes")
	{
	    // Probably some canceling command - clear remaining commands
	    clearCommandQueue();
	}
	return;
    }

    if (gdb->isReadyWithPrompt() && emptyCommandQueue())
    {
	_gdb_command(c);
    }
    else
    {
	// Enqueue before first command with lower priority.  This
	// ensures that user commands are placed at the end.
	CommandQueueIter i(commandQueue);
	CommandQueueIter pos(commandQueue);

	while (i.ok() && c.priority <= i().priority)
	{
	    pos = i; i = i.next();
	}

	if (!i.ok())
	{
	    assert(!pos.ok() || pos().priority >= c.priority);

	    // End of queue reached
	    commandQueue.enqueue_at_end(c);
	}
	else if (pos().priority >= c.priority)
	{
	    assert(pos().priority >= c.priority && c.priority > i().priority);

	    // Enqueue after POS
	    commandQueue.enqueue_after(c, pos);
	}
	else
	{
	    CommandQueueIter start(commandQueue);
	    (void) start;	// Use it
	    assert(!start.ok() || start().priority < c.priority);

	    // Higher priority than first element
	    commandQueue.enqueue_at_start(c);
	}

#if LOG_QUEUE
	clog << "Command queue: " << commandQueue << "\n";
#endif
    }
}

void processCommandQueue(XtPointer, XtIntervalId *)
{
    if (emptyCommandQueue())
	return;

    if (!gdb->isReadyWithPrompt())
    {
	// Try again later...
	XtAppAddTimeOut(XtWidgetToApplicationContext(gdb_w), 
			200, processCommandQueue, XtPointer(0));
	return;
    }

    const Command& c = commandQueue.first();
    Command cmd(c);
    commandQueue.dequeue(c);

    _gdb_command(cmd);

    gdb_keyboard_command = false;

#if LOG_QUEUE
    clog << "Command queue: " << commandQueue << "\n";
#endif
}

// Wait for command queue to drain
void syncCommandQueue()
{
    while (!(emptyCommandQueue() && gdb->isReadyWithPrompt()))
    {
	if (gdb->isReadyWithPrompt())
	    processCommandQueue();

	XtAppProcessEvent(XtWidgetToApplicationContext(command_shell),
			  XtIMTimer | XtIMAlternateInput);
    }
}

// Shell finder
Widget find_shell(Widget w)
{
    if (w == 0)
	w = gdb_last_origin;
    if (w == 0)
	return command_shell;

    Widget parent = findTopLevelShellParent(w);
    if (parent == 0 || !XtIsRealized(parent))
	return command_shell;

    XWindowAttributes xwa;
    XGetWindowAttributes(XtDisplay(parent), XtWindow(parent), &xwa);
    if (xwa.map_state != IsViewable)
	return command_shell;

    return parent;
}
