/* -*-c-*- -------------- mixvm_command.c :
 * Implementation of the functions declared in mixvm_command.h
 * ------------------------------------------------------------------
 * Copyright (C) 2000, 2001 Free Software Foundation, Inc.
 *  
 * 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 <mixlib/mix.h>

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#include <mixlib/mix.h>

#ifdef HAVE_LIBREADLINE
#  include <readline/readline.h>
#  include <readline/history.h>
#else
  typedef int Function ();
#endif /* HAVE_LIBREADLINE */

#include <mixlib/mix_vm.h>
#include <mixlib/mix_vm_dump.h>
#include <mixlib/mix_eval.h>
#include <mixlib/mix_src_file.h>
#include <mixlib/mix_vm_command.h>
#include "mixvm_command.h"

/* The names of functions that actually do the manipulation. */
#define DEC_FUN(name) \
static int cmd_##name (char *arg)

DEC_FUN (help_);
DEC_FUN (shell_);
DEC_FUN (compile_);
DEC_FUN (edit_);
DEC_FUN (quit_);

/* A structure which contains information on the commands this program
   can understand. */
typedef struct {
  const char *name;		/* User printable name of the function. */
  Function *func;		/* Function to call to do the job. */
  const char *doc;		/* Documentation for this function.  */
  const char *usage;		/* Usage */
} COMMAND;
     
COMMAND commands[] = {
  { "help", cmd_help_, N_("Display this text"), "help [COMMAND]" },
  { "shell", cmd_shell_, N_("Execute shell command"), "shell COMMAND" },
  { "compile", cmd_compile_, N_("Compile a source file"), "compile FILENAME"},
  { "edit", cmd_edit_, N_("Edit a source file"), "edit FILENAME"},
  { "quit", cmd_quit_, N_("Quit the program"), "quit" },
  { (char *)NULL, (Function *)NULL, (char *)NULL }
};

#define LOCAL_COMANDS_NO_  ((sizeof (commands) / sizeof (commands[0])) - 1)
#define MIX_COMMANDS_NO_ MIX_CMD_INVALID+1
#define ALL_COMMANDS_NO_ LOCAL_COMANDS_NO_+MIX_COMMANDS_NO_

static const char *mix_commands_[ALL_COMMANDS_NO_] = {NULL};



#ifdef HAVE_LIBREADLINE
/* readline functions */
static char *
mixvm_cmd_generator_ (char *text, int state);


/* Attempt to complete on the contents of TEXT.  START and END bound the
   region of rl_line_buffer that contains the word to complete.  TEXT is
   the word to complete.  We can use the entire contents of rl_line_buffer
   in case we want to do some simple parsing.  Return the array of matches,
   or NULL if there aren't any. */
static char **
mixvm_cmd_completion_ (char *text, int start, int end)
{
  char **matches;
     
  matches = (char **)NULL;
     
  /* If this word is at the start of the line, then it is a command
     to complete.  Otherwise it is the name of a file in the current
     directory. */
  if (start == 0)
    matches = completion_matches (text, mixvm_cmd_generator_);
  
  return (matches);
}
     
/* Generator function for command completion.  STATE lets us know whether
   to start from scratch; without any state (i.e. STATE == 0), then we
   start at the top of the list. */
static char *
mixvm_cmd_generator_ (char *text, int state)
{
  static int list_index, len;
  const char *name;
     
  /* If this is a new word to complete, initialize now.  This includes
     saving the length of TEXT for efficiency, and initializing the index
     variable to 0. */
  if (!state)
    {
      list_index = 0;
      len = strlen (text);
    }
     
  /* Return the next name which partially matches from the command list. */
  while ((name = mix_commands_[list_index]) != NULL)
    {
      list_index++;
      if (strncmp (name, text, len) == 0)
	return (g_strdup (name));
    }

  /* If no names matched, then return NULL. */
  return ((char *)NULL);
}
#endif /* HAVE_LIBREADLINE */

/* command functions */
static COMMAND *
find_command_ (const char *name)
{
  int i;
     
  for (i = 0; commands[i].name; i++)
    if (strcmp (name, commands[i].name) == 0)
      return (&commands[i]);
     
  return ((COMMAND *)NULL);
}


/* mixvm dispatcher */
static mix_vm_cmd_dispatcher_t *dis_ = NULL;

/* emacs interface */
static void
emacs_output_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg, gpointer data)
{
  /* pek: probably bad that we snag the src w/every emacs_output_,
     however when multiple files are supported then this will
     have to be done each time (but the info will be snagged
     from elsewhere...) */
  const mix_vm_t *vm = mix_vm_cmd_dispatcher_get_vm (dis);
  const mix_src_file_t *src = mix_vm_get_src_file (vm);
  const gchar *path = mix_src_file_get_path (src);
  
  mix_address_t loc = mix_vm_get_prog_count (vm);
  guint lineno = mix_vm_get_address_lineno (vm, loc);

  printf ("\032\032mixvm:%s%s:%d\n", path, MIX_SRC_DEFEXT, lineno);
  return;
}

static int
cmd_help_ (char *arg)
{
  int i;
  int printed = 0;
     
  for (i = 0; commands[i].name; i++)
    {
      if (!*arg || (strcmp (arg, commands[i].name) == 0))
	{
	  printf (_("%s\t\t%s. Usage: %s\n"), commands[i].name, 
		  _(commands[i].doc), commands[i].usage);
	  printed++;
	}
    }

  if (printed > 1) printf ("\n");

  for (i = LOCAL_COMANDS_NO_ + 1 /* skip help cmd */; mix_commands_[i]; i++)
    {
      if (!*arg || (strcmp (arg, mix_commands_[i]) == 0))
	{
	  printf (_("%s\t\t%s. Usage: %s\n"), mix_commands_[i], 
		  mix_vm_command_help (i - LOCAL_COMANDS_NO_),
		  mix_vm_command_usage (i - LOCAL_COMANDS_NO_));
	  printed++;
	}
    }
  
  if (!printed) printf ("Command \'%s\' not found\n", arg);
  
  return TRUE;
  
}

static int
cmd_quit_ (char *arg)
{
  puts ("Quitting ...");
  if ( dis_ ) mix_vm_cmd_dispatcher_delete (dis_);

  /* pek: anything needed here to make the marker disappear??? */
  return FALSE;
}


static int 
cmd_shell_ (char *arg)
{
  system (arg);
  return TRUE;
}

static int
cmd_compile_ (char *arg)
{
  gchar *line;
  
  if ( strlen (arg) == 0 )
    return cmd_help_ ("compile");
  
  line = g_strconcat ("mixasm -g ", arg, NULL);
  if ( system (line) == EXIT_SUCCESS )
    fputs (_("Successful compilation\n"), stderr);
  g_free (line);
  
  return TRUE;
}

static int
cmd_edit_ (char *arg)
{
  static const gchar * envars[] = { "MDK_EDITOR", "X_EDITOR", "EDITOR",
				    "VISUAL" };
  
  static const guint s = sizeof (envars) / sizeof (envars[0]);
  static const gchar *editor = NULL;
  
  if ( strlen (arg) == 0 )
    return cmd_help_ ("edit");
  
  if (!editor)
    {
      int k;
      for (k = 0; k < s; k++)
	if ( (editor = getenv (envars[k])) != NULL ) break;
    }
  if (!editor)
    {
      int k;
      fputs (_("Cannot find editor ("), stderr);
      for (k = 0; k < s; k++)
	fprintf (stderr, "%s ", envars[k]);
      fputs (_("undefined)\n"), stderr);
    }
  else
    {
      gchar *line = g_strconcat (editor, " ", arg, NULL);
      system (line);
      g_free (line);
    }
  
  return TRUE;
}



/* external interface */
void
mixvm_cmd_init (char *arg, gboolean use_emacs)
{
  int k;
  /* get local command names */
  for (k = 0; k < LOCAL_COMANDS_NO_; ++k)
    mix_commands_[k] = commands[k].name;
  /* get external command names */
  for (k = 0; k < MIX_CMD_INVALID; ++k)
    mix_commands_[k + LOCAL_COMANDS_NO_] = mix_vm_command_to_string (k);
  mix_commands_[ALL_COMMANDS_NO_ - 1] = NULL;

#ifdef HAVE_LIBREADLINE
  /* Tell the completer that we want a crack first. */
  rl_attempted_completion_function = (CPPFunction *)mixvm_cmd_completion_;
#endif /* HAVE_LIBREADLINE */

  /* initialise the dispatcher */
  dis_ = mix_vm_cmd_dispatcher_new (stdout, stderr);

  if ( dis_ == NULL)
    g_error (_("Failed initialisation (no memory resources)"));
  
  /* install post hook for emacs interaction */
  if (use_emacs)
    {
      mix_vm_cmd_dispatcher_post_hook (dis_, MIX_CMD_LOAD, emacs_output_, NULL);
      mix_vm_cmd_dispatcher_post_hook (dis_, MIX_CMD_RUN, emacs_output_, NULL);
      mix_vm_cmd_dispatcher_post_hook (dis_, MIX_CMD_NEXT, emacs_output_, NULL);
    }
      
  if (arg)
    mix_vm_cmd_dispatcher_dispatch (dis_, MIX_CMD_LOAD, arg);
}

gboolean
mixvm_cmd_exec (char *line)
{
  int i;
  COMMAND *command;
  char *cmd, *arg;
  mix_vm_command_t mix_cmd;

  if (!line) return cmd_quit_(NULL);
  
  /* strip white  space */
  line = g_strstrip(line);
   
  /* Isolate the command word. */
  i = 0;
  while (line[i] && isspace (line[i]))
    i++;
  cmd = line + i;
     
  while (line[i] && !isspace (line[i]))
    i++;
     
  if (line[i])
    line[i++] = '\0';
     
  if (cmd == NULL || strlen (cmd) == 0)
    return TRUE;
  
  /* Get argument to command, if any. */
  while (isspace (line[i]))
    i++;
     
  arg = line + i;
     
  /* try to find local command */
  command = find_command_ (cmd);
  if (command)
    return ((*(command->func)) (arg));

  /* try to find mix command */
  mix_cmd = mix_vm_command_from_string (cmd);
  
  if (mix_cmd == MIX_CMD_INVALID)
    fprintf (stderr, _("%s: No such command. Try \'help\'\n"), cmd);
  else
    mix_vm_cmd_dispatcher_dispatch (dis_, mix_cmd, arg);
  
  return TRUE;
}