/* -*-c-*- -------------- mix_vm_command.c :
 * Implementation of the functions declared in mix_vm_command.h
 * ------------------------------------------------------------------
 * $Id: mix_vm_command.c,v 1.29 2004/06/23 10:46:17 jao Exp $
 * ------------------------------------------------------------------
 * Copyright (C) 2001, 2002, 2004 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 "xmix_vm_command.h"
#include "xmix_vm_handlers.h"

#ifdef HAVE_LIBHISTORY
#  include <readline/history.h>
#endif

#ifdef HAVE_LIBREADLINE
#  include <readline/readline.h>
#endif

/* hook execution */
static void
exec_hook_list_ (GSList *list, mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  while (list)
    {
      hook_ *hook = (hook_ *)list->data;
      if (hook)
	(hook->func)(dis, arg, hook->data);
      list = list->next;
    }
}

static void
exec_global_hook_list_ (GSList *list,
			mix_vm_cmd_dispatcher_t *dis,
			mix_vm_command_t cmd, const gchar *arg)
{
  while (list)
    {
      global_hook_ *hook = (global_hook_ *)list->data;
      if (hook)
	(hook->func)(dis, cmd, arg, hook->data);
      list = list->next;
    }
}

/* conversion from/to commands to strings */
const gchar *
mix_vm_command_to_string (mix_vm_command_t cmd)
{
  if (cmd < MIX_CMD_INVALID) return commands_[cmd].name;
  else return NULL;
}

mix_vm_command_t
mix_vm_command_from_string (const gchar *name)
{
  gint cmd = 0;
  while (cmd < MIX_CMD_INVALID && strcmp (name, commands_[cmd].name))
    ++cmd;
  return cmd;
}

/* get help string about a command */
const gchar *
mix_vm_command_help (mix_vm_command_t cmd)
{
  if (cmd < MIX_CMD_INVALID) return commands_[cmd].doc;
  else return NULL;
}

const gchar *
mix_vm_command_usage (mix_vm_command_t cmd)
{
  if (cmd < MIX_CMD_INVALID) return commands_[cmd].usage;
  else return NULL;
}

/* create a new command dispatcher */
static GCompletion *
make_completions_ (void)
{
  GList *cmds = NULL;
  gint k;

  GCompletion *completions = g_completion_new (NULL);
  for (k = 0; k < MIX_CMD_INVALID; ++k)
    cmds = g_list_append (cmds, (gpointer) mix_vm_command_to_string (k));
  g_completion_add_items (completions, cmds);
  return completions;
}

mix_vm_cmd_dispatcher_t *
mix_vm_cmd_dispatcher_new (FILE *out_fd, /* output messages file */
			   FILE *err_fd /* error messages file */)
{
  mix_vm_cmd_dispatcher_t *result = NULL;
  int k;

  /* g_return_val_if_fail (out_fd && err_fd, NULL); */

  result = g_new (mix_vm_cmd_dispatcher_t, 1);
  result->result = TRUE;
  result->out = out_fd;
  result->err = err_fd;
  result->log_msg = TRUE;
  result->uptime = result->laptime = result->progtime = 0;
  result->printtime = TRUE;
  result->trace = FALSE;
  result->program = NULL;
  result->editor = NULL;
  result->assembler = NULL;
  result->eval = mix_eval_new ();
  result->dump = mix_dump_context_new (out_fd,
				       MIX_SHORT_ZERO, MIX_SHORT_ZERO,
				       MIX_DUMP_ALL);
  result->vm = mix_vm_new ();
  result->global_pre = result->global_post = NULL;

  for (k =0; k < MIX_CMD_INVALID; ++k)
    result->pre_hooks[k] = result->post_hooks[k] = NULL;

  result->config = NULL;

  for (k = 0; k < PRNO_; ++k)
    result->preds[k] = mix_predicate_new (k);

  result->mem_preds = g_hash_table_new (NULL, NULL);
  result->commands = g_hash_table_new (g_str_hash, g_str_equal);
  result->completions = make_completions_ ();

  return result;
}

mix_vm_cmd_dispatcher_t *
mix_vm_cmd_dispatcher_new_with_config (FILE *out, FILE *err,
				       mix_config_t *config)
{
  mix_vm_cmd_dispatcher_t *result = mix_vm_cmd_dispatcher_new (out, err);
  if (result != NULL && (result->config = config) != NULL)
    {
      gint hsize = 0;
      const gchar *val = mix_config_get (result->config, TRACING_KEY_);
      if (val) cmd_strace_ (result, val);
      val = mix_config_get (result->config, EDITOR_KEY_);
      if (val) mix_vm_cmd_dispatcher_set_editor (result, val);
      val = mix_config_get (result->config, ASM_KEY_);
      if (val) mix_vm_cmd_dispatcher_set_assembler (result, val);
      val = mix_config_get (result->config, TIMING_KEY_);
      if (val) cmd_stime_ (result, val);
      val = mix_config_get_devices_dir (result->config);
      if (!val || !mix_stat_dir (val, "devices"))
	{
	  gchar *dirname =
            g_path_get_dirname (mix_config_get_filename (config));
	  cmd_sddir_ (result, dirname);
	  g_free (dirname);
	}
      else
	mix_device_set_dir (val);
      val = mix_config_get (result->config, LOGGING_KEY_);
      if (val) cmd_slog_ (result, val);
#ifdef HAVE_LIBHISTORY
      val = mix_config_get_history_file (result->config);
      hsize = mix_config_get_history_size (result->config);
      using_history ();
      stifle_history (hsize);
      if (val)
	{
	  read_history ((char *)val);
	  history_set_pos (history_base + history_length - 1);
	}
#endif
    }
  return result;
}


/* delete (does not close the fds in the constructor) */
static gboolean
del_pred_ (gpointer key, gpointer val, gpointer data)
{
  if (val) mix_predicate_delete ((mix_predicate_t *)val);
  return TRUE;
}

static void
del_hook_ (gpointer data, gpointer ignored)
{
  if (data) g_free (data);
}

static void
del_hook_list_ (GSList *s)
{
  if (s)
    {
      g_slist_foreach (s, del_hook_, NULL);
      g_slist_free (s);
    }
}

void
mix_vm_cmd_dispatcher_delete (mix_vm_cmd_dispatcher_t *dis)
{
  const gchar *hfile = NULL;
  gint k;

  g_return_if_fail (dis != NULL);
  mix_eval_delete (dis->eval);
  mix_dump_context_delete (dis->dump);
  mix_vm_delete (dis->vm);
  if (dis->editor) g_free (dis->editor);
  if (dis->editor) g_free (dis->assembler);
#ifdef HAVE_LIBHISTORY
  if (dis->config && (hfile = mix_config_get_history_file
		      (dis->config)))
    write_history ((char *)hfile);
#endif
  for (k = 0; k < PRNO_; ++k) mix_predicate_delete (dis->preds[k]);
  g_hash_table_foreach_remove (dis->mem_preds, del_pred_, NULL);
  g_hash_table_destroy (dis->mem_preds);
  g_hash_table_destroy (dis->commands);
  g_completion_free (dis->completions);
  for (k = 0; k < MIX_CMD_INVALID; ++k)
    {
      del_hook_list_ (dis->pre_hooks[k]);
      del_hook_list_ (dis->post_hooks[k]);
    }
  del_hook_list_ (dis->global_pre);
  del_hook_list_ (dis->global_post);
  g_free (dis);
}

/* register new commands for a dispatcher */
void
mix_vm_cmd_dispatcher_register_new (mix_vm_cmd_dispatcher_t *dis,
				    mix_vm_command_info_t *cmd)
{
  GList *list = NULL;
  g_return_if_fail (dis != NULL);
  g_return_if_fail (cmd != NULL);
  g_hash_table_insert (dis->commands, (gpointer)cmd->name, (gpointer)cmd);
  list = g_list_append (list, (gpointer)cmd->name);
  g_completion_add_items (dis->completions, list);
}

const GList *
mix_vm_cmd_dispatcher_complete (const mix_vm_cmd_dispatcher_t *dis,
				const gchar *cmd, gchar **prefix)
{
  char *cp;
  GList *result;

  g_return_val_if_fail (dis != NULL, NULL);
  g_return_val_if_fail (cmd != NULL, NULL);

  cp = g_strdup (cmd);
  result = g_completion_complete (dis->completions, cp, prefix);
  g_free (cp);
  return result;
}

/* set/get out/error streams */
FILE *
mix_vm_cmd_dispatcher_get_out_stream (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis, NULL);
  return dis->out;
}

FILE *
mix_vm_cmd_dispatcher_get_err_stream (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis, NULL);
  return dis->err;
}

FILE * /* old output stream */
mix_vm_cmd_dispatcher_set_out_stream (mix_vm_cmd_dispatcher_t *dis, FILE *out)
{
  FILE *old = NULL;
  g_return_val_if_fail (dis != NULL, old);
  old = dis->out;
  dis->out = out;
  dis->dump->channel = out;
  return old;
}

FILE * /* old error stream */
mix_vm_cmd_dispatcher_set_error_stream (mix_vm_cmd_dispatcher_t *dis,
					FILE *err)
{
  FILE *old = NULL;
  g_return_val_if_fail (dis != NULL, old);
  old = dis->err;
  dis->err = err;
  return old;
}


/* set editor and compiler templates */
void
mix_vm_cmd_dispatcher_set_editor (mix_vm_cmd_dispatcher_t *dis,
				  const gchar *edit_tplt)
{
  g_return_if_fail (dis != NULL);
  if (dis->editor) g_free (dis->editor);
  dis->editor = (edit_tplt) ? g_strdup (edit_tplt) : NULL;
  if (dis->config && dis->editor)
    mix_config_update (dis->config, EDITOR_KEY_, dis->editor);
}

void
mix_vm_cmd_dispatcher_set_assembler (mix_vm_cmd_dispatcher_t *dis,
				     const gchar *asm_tplt)
{
  g_return_if_fail (dis != NULL);
  if (dis->assembler) g_free (dis->assembler);
  dis->assembler = (asm_tplt) ? g_strdup (asm_tplt) : NULL;
  if (dis->config && dis->assembler)
    mix_config_update (dis->config, ASM_KEY_, dis->assembler);
}

const gchar *
mix_vm_cmd_dispatcher_get_editor (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis != NULL, NULL);
  return dis->editor;
}

const gchar *
mix_vm_cmd_dispatcher_get_assembler (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis != NULL, NULL);
  return dis->assembler;
}

const gchar *
mix_vm_cmd_dispatcher_get_src_file_path (const mix_vm_cmd_dispatcher_t *dis)
{
  static gchar *PATH = NULL;

  const mix_vm_t *vm = mix_vm_cmd_dispatcher_get_vm (dis);
  const mix_src_file_t *f  = mix_vm_get_src_file (vm);

  if (PATH)
    {
      g_free (PATH);
      PATH = NULL;
    }

  if (f)
    PATH = mix_file_complete_name (mix_src_file_get_path (f), MIX_SRC_DEFEXT);

  return PATH;
}

const gchar *
mix_vm_cmd_dispatcher_get_program_path (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis != NULL, NULL);
  return dis->program;
}

/* install hooks */
void
mix_vm_cmd_dispatcher_pre_hook (mix_vm_cmd_dispatcher_t *dis,
				mix_vm_command_t cmd,
				mix_vm_cmd_hook_t hook, gpointer data)
{
  hook_ *phook;

  g_return_if_fail (dis != NULL);
  g_return_if_fail (cmd < MIX_CMD_INVALID);

  phook = g_new (hook_, 1);
  phook->func = hook;
  phook->data = data;

  dis->pre_hooks[cmd] = g_slist_append (dis->pre_hooks[cmd], phook);
}

void
mix_vm_cmd_dispatcher_post_hook (mix_vm_cmd_dispatcher_t *dis,
				 mix_vm_command_t cmd,
				 mix_vm_cmd_hook_t hook, gpointer data)
{
  hook_ *phook;

  g_return_if_fail (dis != NULL);
  g_return_if_fail (cmd < MIX_CMD_INVALID);

  phook = g_new (hook_, 1);
  phook->func = hook;
  phook->data = data;

  dis->post_hooks[cmd] = g_slist_append (dis->post_hooks[cmd], phook);
}

void
mix_vm_cmd_dispatcher_global_pre_hook (mix_vm_cmd_dispatcher_t *dis,
				       mix_vm_cmd_global_hook_t hook,
				       gpointer data)
{
  global_hook_ *phook;

  g_return_if_fail (dis != NULL);

  phook = g_new (global_hook_, 1);
  phook->func = hook;
  phook->data = data;

  dis->global_pre = g_slist_append (dis->global_pre, phook);
}

void
mix_vm_cmd_dispatcher_global_post_hook (mix_vm_cmd_dispatcher_t *dis,
					mix_vm_cmd_global_hook_t hook,
					gpointer data)
{
  global_hook_ *phook;

  g_return_if_fail (dis != NULL);

  phook = g_new (global_hook_, 1);
  phook->func = hook;
  phook->data = data;

  dis->global_post = g_slist_append (dis->global_post, phook);
}

/* dispatch a command */
gboolean /* TRUE if success, FALSE otherwise */
mix_vm_cmd_dispatcher_dispatch (mix_vm_cmd_dispatcher_t *dis,
				mix_vm_command_t cmd, const gchar *arg)
{
  g_return_val_if_fail (dis != NULL, FALSE);

  if (dis->global_pre)
    exec_global_hook_list_ (dis->global_pre, dis, cmd, arg);

  if (cmd < MIX_CMD_INVALID)
    {
      if (dis->pre_hooks[cmd])
	exec_hook_list_ (dis->pre_hooks[cmd], dis, arg);

      fflush (dis->out);
      fflush (dis->err);

      dis->result = (commands_[cmd].func)(dis, arg);

      fflush (dis->out);
      fflush (dis->err);

      if (dis->post_hooks[cmd])
	exec_hook_list_ (dis->post_hooks[cmd], dis, arg);

      fflush (dis->out);
      fflush (dis->err);
    }
  else
    {
      fprintf (dis->err, _("Unknown command. Try: help\n"));
    }

  if (dis->global_post)
    exec_global_hook_list_ (dis->global_post, dis, cmd, arg);

  fflush (dis->out);
  fflush (dis->err);
  return dis->result;
}

/* dispatch a command in text format */
gboolean
mix_vm_cmd_dispatcher_dispatch_text (mix_vm_cmd_dispatcher_t *dis,
				     const gchar *text)
{
  gchar *cp, *arg = "";
  int k = 0;

  g_return_val_if_fail (dis != NULL, FALSE);
  g_return_val_if_fail (text != NULL, FALSE);

  cp = g_strdup (text);
  while (cp[k] && !isspace (cp[k])) ++k;
  if (cp[k])
    {
      cp[k] = '\0'; ++k;
      while (cp[k] && isspace (cp[k])) ++k;
      arg = cp + k;
    }

  (void) mix_vm_cmd_dispatcher_dispatch_split_text (dis, cp, arg);

  g_free (cp);

  return dis->result;
}

/* dispatch a command in text format, with command and arg split */
gboolean
mix_vm_cmd_dispatcher_dispatch_split_text (mix_vm_cmd_dispatcher_t *dis,
					   const gchar *command,
					   const gchar *arg)
{
  mix_vm_command_info_t *info;

  g_return_val_if_fail (dis, FALSE);

  if (!command) return FALSE;
  if (!arg) arg = "";

  info = (mix_vm_command_info_t *)g_hash_table_lookup (dis->commands, command);

  if (info)
    {
      if (dis->global_pre)
	exec_global_hook_list_ (dis->global_pre, dis, MIX_CMD_LOCAL, arg);

      fflush (dis->out);
      fflush (dis->err);

      dis->result = info->func (dis, arg);

      fflush (dis->out);
      fflush (dis->err);

      if (dis->global_post)
	exec_global_hook_list_ (dis->global_post, dis, MIX_CMD_LOCAL, arg);

      fflush (dis->out);
      fflush (dis->err);
    }
  else
    dis->result =
      mix_vm_cmd_dispatcher_dispatch (dis,
				      mix_vm_command_from_string (command),
				      arg);
  return dis->result;
}


/* get the last dispatch's result */
gboolean
mix_vm_cmd_dispatcher_get_last_result (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis != NULL, FALSE);
  return dis->result;
}

/* get total uptime */
mix_time_t
mix_vm_cmd_dispatcher_get_uptime (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis != NULL, 0);
  return dis->uptime;
}

/* get program total time */
mix_time_t
mix_vm_cmd_dispatcher_get_progtime (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis != NULL, 0);
  return dis->progtime;
}

/* get time lapse */
mix_time_t
mix_vm_cmd_dispatcher_get_laptime (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis != NULL, 0);
  return dis->laptime;
}

/* toggle time printing */
void
mix_vm_cmd_dispatcher_print_time (mix_vm_cmd_dispatcher_t * dis, gboolean print)
{
  g_return_if_fail (dis != NULL);
  dis->printtime = print;
}

/* get the mix vm */
const mix_vm_t *
mix_vm_cmd_dispatcher_get_vm (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis != NULL, NULL);
  return dis->vm;
}

/* src file info */
gulong
mix_vm_cmd_dispatcher_get_src_file_lineno (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis != NULL, 0);
  return mix_vm_get_break_lineno (dis->vm);
}

const gchar *
mix_vm_cmd_dispatcher_get_src_file_line (const mix_vm_cmd_dispatcher_t *dis,
					 gulong line, gboolean cr)
{
  const mix_src_file_t *file;
  g_return_val_if_fail (dis != NULL, NULL);

  file = mix_vm_get_src_file (dis->vm);

  if (line == 0 || file == NULL) return cr? "" : "\n";

  if (cr)
    return mix_src_file_get_line (file, line);
  else
    {
      enum {BUFF_SIZE = 256};
      static gchar BUFFER[BUFF_SIZE];
      int len =
	g_snprintf (BUFFER, BUFF_SIZE, "%s", mix_src_file_get_line (file, line));
      if (len > 0 && BUFFER[len - 1] == '\n') BUFFER[len - 1] = '\0';
      return BUFFER;
    }
}