/* mdbline.c: MDB's interface to the readline library. */

/* Author: Brian J. Fox (bfox@ai.mit.edu) Mon Oct  2 08:07:30 1995.

   This file is part of <Meta-HTML>(tm), a system for the rapid deployment
   of Internet and Intranet applications via the use of the Meta-HTML
   language.

   Copyright (c) 1995, 1996, Brian J. Fox (bfox@ai.mit.edu).
   Copyright (c) 1996, Universal Access Inc. (http://www.ua.com).

   Meta-HTML is free software; you can redistribute it and/or modify
   it under the terms of the UAI Free Software License as published
   by Universal Access Inc.; either version 1, 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
   UAI Free Software License for more details.

   You should have received a copy of the UAI Free Software License
   along with this program; if you have not, you may obtain one by
   writing to:

   Universal Access Inc.
   129 El Paseo Court
   Santa Barbara, CA
   93101  */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <bprintf/bprintf.h>
#include <xmalloc/xmalloc.h>
#include "pages.h"
#include "session_data.h"
#include "parser.h"
#include "mdb.h"
#include "commands.h"

#if !defined (__FUNCTION_DEF)
#  define __FUNCTION_DEF
typedef char **CPPFunction (char *, int, int);
typedef char *CPFunction (char *, int);
#endif

extern char *rl_terminal_name;
extern FILE *rl_instream;
extern FILE *rl_outstream;
extern char *rl_special_prefixes;
extern char *rl_readline_name;
extern char *rl_completer_quote_characters;
extern char *rl_completer_word_break_characters;
extern char *rl_line_buffer;
extern char **completion_matches (char *, CPFunction *);
extern CPPFunction *rl_attempted_completion_function;
typedef int Function (void);
extern int rl_insert_close (int count, int key);
extern int rl_bind_key (int key, Function *);
;
/* Helper functions for Readline. */
static char **attempt_mdb_completion (char *text, int start, int end);
static char *mdb_command_completion_function (char *text, int state);
static char *pagefunc_completion_function (char *text, int state);
static char *symbol_completion_function (char *text, int state);

/* Variables used here but defined in other files. */
extern int rl_filename_completion_desired;

/* Non-zero once mdb_initalize_readline () has been called. */
int mdb_readline_initialized = 0;

/* Called once from mdb.c. */
void
mdb_initialize_readline (void)
{
  if (mdb_readline_initialized)
    return;

  rl_terminal_name = getenv ("TERM");
  rl_instream = stdin;
  rl_outstream = stderr;
  rl_special_prefixes = "<";

  /* Allow conditional parsing of `~/.inputrc'. */
  rl_readline_name = "MDB";

  /* Tell the completer that we want a crack first. */
  rl_attempted_completion_function = (CPPFunction *)attempt_mdb_completion;

  rl_completer_quote_characters = "'\"";

  /* Need to modify this from the default; `$', `{', `\', and ``' are not
     word break characters. */
  rl_completer_word_break_characters = " \t\n\"'@><=;|&(";

  rl_bind_key ('>', (Function *)rl_insert_close);

  mdb_readline_initialized = 1;
}

/* Do some completion on TEXT.  The indices of TEXT in RL_LINE_BUFFER are
   at START and END.  Return an array of matches, or NULL if none. */
static char **
attempt_mdb_completion (char *text, int start, int end)
{
  char **matches = (char **)NULL;
  int in_command_position = 0;
  int tindex;

  /* Determine if this could be a command word.  It is if it appears at
     the start of the line disregarding preceding whitespace. */
  tindex = start - 1;

  while ((tindex > -1) && (whitespace (rl_line_buffer[tindex])))
    tindex--;

  if (tindex < 0)
    in_command_position++;

  /* If the word starts with '<' then we want to complete over the
     possible function calls in Meta-HTML. */
  if (*text == '<')
    matches = completion_matches (text, pagefunc_completion_function);
  else
    {
      /* If this word is in a command position, then complete over possible
	 MDB command names. */
      if (in_command_position)
	matches = completion_matches (text, mdb_command_completion_function);
      else
	matches = completion_matches (text, symbol_completion_function);
    }

  return (matches);
}

/* Find possible matches out of our command table. */
static char *
mdb_command_completion_function (char *hint_text, int state)
{
  static int local_index = 0;
  static char *hint = (char *)NULL;
  static int hint_len = 0;

  /* We have to map over the possibilities for command words.  If we have
     no state, then make one just for that purpose. */
  if (!state)
    {
      if (hint)
	free (hint);

      hint = strdup (hint_text);
      hint_len = strlen (hint);
      local_index = 0;
    }

  /* Try all of the command names. */
  while (mdb_command_table[local_index].name)
    {
      register char *command_name;

      command_name = mdb_command_table[local_index++].name;

      if (strncasecmp (command_name, hint, hint_len) == 0)
	return (strdup (command_name));
    }

  return ((char *)NULL);
}

static void **
concat_arrays (void **first, void **second)
{
  if (second)
    {
      register int i1, i2;

      for (i1 = 0; first[i1]; i1++);
      for (i2 = 0; second[i2]; i2++);

      first = (void **)xrealloc (first, ((2 + i1 + i2) * sizeof (void *)));

      for (i2 = 0; second[i2]; i2++)
	first[i1++] = second[i2];

      first[i1] = (void *)NULL;
    }

  return (first);
}

/* Find possible matches for a Meta-HTML function call. */
static char *
pagefunc_completion_function (char *hint_text, int state)
{
  static int local_index = 0;
  static char *hint = (char *)NULL;
  static int hint_len = 0;
  static char *prefix = (char *)NULL;
  static Symbol **symbols = (Symbol **)NULL;

  if (!state)
    {
      if (symbols) { free (symbols); symbols = (Symbol **)NULL; }
      if (prefix)  { free (prefix); prefix = (char *)NULL; }
      if (hint) free (hint);
      hint = strdup (hint_text);
      hint_len = strlen (hint);
      local_index = 0;

      /* Skip any number of open braces and slash characters. */
      {
	register int i;

	for (i = 0; hint[i]; i++)
	  if ((hint[i] != '<') && (hint[i] != '/'))
	    break;

	if (i)
	  {
	    prefix = (char *)xmalloc (1 + i);
	    strncpy (prefix, hint, i);
	    prefix[i] = '\0';

	    memmove (hint, hint + i, hint_len - (i - 1));
	    hint_len -= i;
	  }
      }

      symbols = symbols_of_package (mhtml_function_package);
      if (mhtml_user_keywords)
	{
	  Symbol **uks = symbols_of_package (mhtml_user_keywords);
	  symbols = (Symbol **)
	    concat_arrays ((void **)symbols, (void **)uks);
	  if (uks) free (uks);
	}
    }

  while (symbols && symbols[local_index])
    {
      Symbol *sym = symbols[local_index++];

      if (strncasecmp (sym->name, hint, hint_len) == 0)
	{
	  if (prefix)
	    {
	      char *result;

	      result = (char *)xmalloc (1 + strlen (prefix) + sym->name_len);
	      sprintf (result, "%s%s", prefix, sym->name);
	      return (result);
	    }
	  else
	    return (strdup (sym->name));
	}
    }

  return ((char *)NULL);
}

/* Find possible matches for a Meta-HTML symbol. */
static char *
symbol_completion_function (char *hint_text, int state)
{
  static int local_index = 0;
  static char *hint = (char *)NULL;
  static int hint_len = 0;
  static char *packname = (char *)NULL;
  static char **names = (char **)NULL;

  if (!state)
    {
      /* Check to see if we have seen a "::" yet.  If so, limit the
	 search to those symbols in that package.  Otherwise, the
	 possibilites are the symbols in the default package, and all
	 of the package names, including "default". */
      register int i;
      Symbol **symbols;
      char *dots = strstr (hint_text, "::");

      if (packname) { free (packname); packname = (char *)NULL; }

      if (dots)
	{
	  if (dots == hint_text)
	    packname = strdup ("default");
	  else
	    {
	      packname = (char *)xmalloc (dots - hint_text);
	      strncpy (packname, hint_text, (dots - hint_text));
	      packname[dots - hint_text] = '\0';
	    }
	}

      if (hint) { free (hint); hint = (char *)NULL; }
      if (names)
	{
	  for (i = 0; names[i]; i++)
	    free (names[i]);
	  free (names);
	  names = (char **)NULL;
	}
	
      hint = strdup (hint_text);
      hint_len = strlen (hint);
      local_index = 0;

      /* Get the symbol names that we need.  If there is a specific
	 package name, then get all of those names now. */
      if (packname)
	{
	  Package *package = symbol_lookup_package (packname);

	  /* If the package couldn't be found, there are no possible
	     completions here.  Give up immediately. */
	  if (!package)
	    {
	      free (packname);
	      packname = (char *)NULL;
	      return (char *)NULL;
	    }

	  /* The package could be found.  Add all of these symbols to
	     our array. */
	  symbols = symbols_of_package (package);

	  for (i = 0; symbols && symbols[i]; i++);

	  names = (char **)xmalloc ((i + 2) * sizeof (char *));
	  for (i = 0; symbols && symbols[i]; i++)
	    {
	      char *addname = (char *)
		xmalloc (4 + package->name_len + symbols[i]->name_len);
	      sprintf (addname, "%s::%s", packname, symbols[i]->name);
	      names[i] = addname;
	    }

	  names[i] = (char *)NULL;
	  free (symbols);
	}
      else
	{
	  /* No particular package name was specified, so get all of the
	     packages, and all of the symbols in those packages. */
	  register int pi;
	  char **temp;

	  for (pi = 0; AllPackages && AllPackages[pi]; pi++);
	  names = (char **)xmalloc ((1 + pi) * sizeof (Package *));
	  for (pi = 0; AllPackages && AllPackages[pi]; pi++)
	    names[pi] = strdup (AllPackages[pi]->name);
	  names[pi] = (char *)NULL;

	  /* Now, loop over each package, adding their symbols to the
	     existing NAMES. */
	  for (pi = 0; AllPackages && AllPackages[pi]; pi++)
	    {
	      symbols = symbols_of_package (AllPackages[pi]);

	      for (i = 0; symbols && symbols[i]; i++);

	      temp = (char **)xmalloc ((1 + i) * sizeof (char *));

	      /* Optimize for the default package. */
	      if (strcasecmp (AllPackages[pi]->name, "default") == 0)
		{
		  for (i = 0; symbols && symbols[i]; i++)
		    temp[i] = strdup (symbols[i]->name);
		}
	      else
		{
		  char *addname;

		  for (i = 0; symbols && symbols[i]; i++)
		    {
		      addname = (char *)xmalloc
			(4 + AllPackages[pi]->name_len + symbols[i]->name_len);

		      sprintf (addname, "%s::%s",
			       AllPackages[pi]->name, symbols[i]->name);
		    temp[i] = addname;
		    }
		}
	      temp[i] = (char *)NULL;

	      names = (char **)concat_arrays ((void **)names, (void **)temp);
	    }
	}
    }

  while (names && names[local_index])
    {
      char *result = names[local_index++];

      if (strncasecmp (result, hint, hint_len) == 0)
	return (strdup (result));
    }

  return ((char *)NULL);
}

