/* This file is part of cqual.
   Copyright (C) 2000-2001 The Regents of the University of California.

cqual 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, or (at your option)
any later version.

cqual 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 cqual; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA. */

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include "analyze.h"
#include "buffer.h"
#include "color.h"
#include "cqual.h"
#include "pam.h"
#include "util.h"
#include "qtype.h"

/* Size of buffer for receiving PAM commands */
#define BUF_MAX 1024

typedef void (*click_func)(const char *click, const char *file,
			   const char *name, void *arg);

typedef struct click
{
  click_func func;            /* Function to execute */
  void *arg;                  /* Extra argument to pass */
} click;

typedef struct click_list
{
  struct click_list *next;
  const char *name;           /* Name for this click */
  click c;                    /* Function to execute */
} *click_list;

typedef struct overlay_list
{
  struct overlay_list *next;
  const char *name;
  unsigned long start, end;
  qtype qt;
} *overlay_list;

typedef struct buffer_click_list
{
  struct buffer_click_list *next;
  const char *buffer;           /* Name of buffer */
  click_list clicks;
} *buffer_click_list;

typedef struct file_list
{
  const char *filename;
  bool loaded;
  const char *link;
  overlay_list overlays;
  struct file_list *next;
} *file_list;

struct error_list
{
  const char *func;
  location loc;
  severity sev;
  const char *message;
  const char *link;
  struct error_list *next;
};

/**************************************************************************
 *                                                                        *
 * Global Variables and Prototypes                                        *
 *                                                                        *
 **************************************************************************/

static region pam_region = NULL;
static buffer_click_list buffer_clicks = NULL;
static struct file_list *pam_files = NULL;
static struct error_list *pam_errors = NULL;
static unsigned int next_link = 0;
static growbuf pam_vprintf_scratch_buf = NULL;

/* Reset any side-effected stuff */
void init_pam(void)
{
  pam_vprintf_scratch_buf = NULL;
  if (pam_region)
    deleteregion(pam_region);
  pam_region = newregion();
  buffer_clicks = NULL;
  pam_files = NULL;
  pam_errors = NULL;
  next_link = 0;
  pam_vprintf_scratch_buf = growbuf_new(pam_region, 256);
}


/*
 * Some prototypes
 */

file_list pam_find_file(const char *name);
void pam_jump_file(const char *click_name, const char *file,
		   const char *name, location *loc);
const char *get_next_link(void);

/**************************************************************************
 *                                                                        *
 * Low-level PAM communication                                            *
 *                                                                        *
 **************************************************************************/

void pam_clear_buffer(const char *);      /* Erase the buffer contents */
void pam_change_buffer(const char *);     /* Switch to the buffer */
void pam_set_lower_buffer(const char *);   /* ? */
void pam_set_upper_buffer(const char *);     /* ? */
void pam_close_buffer(const char *);      /* Close (delete?) the buffer */
void pam_split_buffer(const char *);      /* ? */
void pam_goto_char_buffer(const char *, unsigned long);
                                          /* Go to character pos in a buffer */

void pam_clear_file(const char *);        /* See above */
void pam_change_file(const char *);
void pam_set_lower_file(const char *);
void pam_set_upper_file(const char *);
void pam_close_file(const char *);
void pam_split_file(const char *);
void pam_goto_char_file(const char *, unsigned long);


void pam_debug(const char *, ...);        /* Print a debugging message */
int pam_message(const char *, ...);      /* Print a text message */
void pam_default_path(const char *);      /* Set the default path */

void pam_blobs_begin(void);               /* Begin printing marked-up text */
void pam_blobs_end(void);                 /* End printing marked-up text */
void pam_overlays_begin(void);            /* Begin marking-up file */
void pam_overlays_end(void);              /* End marking-up file */

void pam_markup_file(const char *f);      /* Start marking up file f */
void pam_markup_buffer(const char *b);    /* Start marking up buffer b */


/* Add some text to a mark-up */
int pam_add_text(const char *, ...);

/* Add a hyperlink to a markup.  ``text'' is displayed marked up with
   color.  ``name'' is the internal name of the hyperlink. */
void pam_add_link(const char *name, const char *color, const char *text);

/* Add some marked-up text -- no link */
void pam_add_marked_text(const char *color, const char *text);

/* Add a hyperlink on top of some text.  The text to add a link to
   ranges from positions start-end in the current markup buffer, and
   is marked up with color.  ``name'' is the internal name of the
   hyperlink. */
void pam_add_overlink(const char *name, const char *color, int start, int end);

/* Add a color on top of some text -- no link */
void pam_add_overmark(const char *color, int start, int end);

/* Definitions */

#define pam_printf printf
#define pam_vprintf vprintf
#define pam_putc(c) putc(c, stdout)
#define pam_getc()  getc(stdin)

#define pam_window_buffer "buffer"
#define pam_window_file "file"
#define pam_click "click"
#define pam_shift_click "shift-click"

/* Print out a string, adding escapes where necessary */
int pam_print_escaped_string(const char *s)
{
  int count = 0;

  pam_putc('\"');
  while (*s)
  {
    switch (*s)
    {
    case '\\': pam_printf("\\\\"); break;
    case '\"': pam_printf("\\\""); break;
    case '\n': pam_printf("\\n"); break;
    default:
      if (isprint(*s))
	pam_putc(*s);
      else
	fail("Error: 0x%x not printable\n", *s);
    }
    s++;
    count++;
  }
  pam_putc('\"');
  return count;
}

int pam_vprintf_escaped(const char *fmt, va_list args)
{
  growbuf_reset(pam_vprintf_scratch_buf);
  gvprintf(pam_vprintf_scratch_buf, fmt, args);
  return pam_print_escaped_string(growbuf_contents(pam_vprintf_scratch_buf));
}

void pam_printf_escaped(const char *fmt, ...)
{
  va_list args;
  va_start(args, fmt);
  pam_vprintf_escaped(fmt, args);
}

typedef enum { pam_clear_window, pam_change_window, pam_set_lower_window,
	       pam_set_upper_window, pam_close_window, pam_split_window
} pam_op_kind;

void pam_window_op(pam_op_kind op, const char *wname, const char *kind)
{
  pam_putc('(');
  switch (op)
    {
    case pam_clear_window: pam_printf("pam-clear-window"); break;
    case pam_change_window: pam_printf("pam-change-window"); break;
    case pam_set_lower_window: pam_printf("pam-set-window-down"); break;
    case pam_set_upper_window: pam_printf("pam-set-window-up"); break;
    case pam_close_window: pam_printf("pam-close-window"); break;
    case pam_split_window: pam_printf("pam-split-window"); break;
    default:
      fail("Unexpected window op %d\n", op);
    }
  pam_printf(" (");
  pam_print_escaped_string(wname);
  pam_putc(' ');
  pam_print_escaped_string(kind);
  pam_printf("))\n");
}

#define mkpam_buffer_op(op) void pam_ ## op ## _buffer(const char *wname) \
    { pam_window_op(pam_ ## op ## _window, wname, pam_window_buffer); }

#define mkpam_file_op(op) void pam_ ## op ## _file(const char *wname) \
    { pam_window_op(pam_ ## op ## _window, wname, pam_window_file); }

mkpam_buffer_op(clear);
mkpam_buffer_op(change);
mkpam_buffer_op(set_lower);
mkpam_buffer_op(set_upper);
mkpam_buffer_op(close);
mkpam_buffer_op(split);
mkpam_file_op(clear);
mkpam_file_op(change);
mkpam_file_op(set_lower);
mkpam_file_op(set_upper);
mkpam_file_op(close);
mkpam_file_op(split);

void pam_goto_char_buffer(const char *buffer, unsigned long pos)
{
  pam_printf("(pam-goto-char (");
  pam_print_escaped_string(buffer);
  pam_putc(' ');
  pam_print_escaped_string(pam_window_file);
  pam_printf(") %ld)\n", pos);
}

void pam_goto_char_file(const char *buffer, unsigned long pos)
{
  pam_printf("(pam-goto-char (");
  pam_print_escaped_string(buffer);
  pam_putc(' ');
  pam_print_escaped_string(pam_window_buffer);
  pam_printf(") %ld)\n", pos);
}

void pam_debug(const char *str, ...)
{
  va_list args;

  va_start(args, str);
  pam_printf("(pam-debug ");
  pam_vprintf_escaped(str, args);
  pam_printf(")\n");
}

int pam_message(const char *str, ...)
{
  va_list args;
  int count = 0;

  va_start(args, str);
  count += pam_printf("(pam-message ");
  count += pam_vprintf_escaped(str, args);
  count += pam_printf(")\n");
  return count;
}

void pam_default_path(const char *path)
{
  pam_printf("(pam-default-path ");
  pam_print_escaped_string(path);
  pam_printf(")\n");
}

void pam_blobs_begin(void) { pam_printf("(pam-blobs"); }
void pam_blobs_end(void)   { pam_printf(")\n"); }

void pam_overlays_begin(void) { pam_printf("(pam-overlays"); }
void pam_overlays_end(void)   { pam_printf(")\n"); }

void pam_markup_file(const char *name)
{
  pam_printf(" (file (");
  pam_print_escaped_string(name);
  pam_printf(" ");
  pam_print_escaped_string(pam_window_file);
  pam_printf("))");
}

void pam_markup_buffer(const char *name)
{
  pam_printf(" (file (");
  pam_print_escaped_string(name);
  pam_printf(" ");
  pam_print_escaped_string(pam_window_buffer);
  pam_printf("))");
}

int pam_add_text(const char *text, ...)
{
  int result;
  va_list args;

  va_start(args, text);
  pam_printf(" (text ");
  result = pam_vprintf_escaped(text, args);
  pam_printf(")");
  return result;
}

/*
void pam_print_color(pam_color c)
{
  switch (c)
    {
    case pam_color_1: pam_printf("(pam-color-1)"); break;
    case pam_color_2: pam_printf("(pam-color-2)"); break;
    case pam_color_3: pam_printf("(pam-color-3)"); break;
    case pam_color_4: pam_printf("(pam-color-4)"); break;
    case pam_color_5: pam_printf("(pam-color-5)"); break;
    case pam_color_6: pam_printf("(pam-color-6)"); break;
    case pam_color_7: pam_printf("(pam-color-7)"); break;
    case pam_color_8: pam_printf("(pam-color-8)"); break;
    case pam_color_mouse: pam_printf("(pam-color-mouse)"); break;
    default:
      fail("Unexpected markup color %d\n", c);
    }
}
*/

void pam_add_markup(int start, int end, const char *color, const char *text)
{
  pam_printf(" (markup (%d %d) (%s) ", start, end, color);
  pam_print_escaped_string(text);
  pam_printf(")");
}

void pam_add_hyper(const char *name, int start, int end, const char *color,
		   const char *text)
{
  pam_printf(" (hyper ");
  pam_print_escaped_string(name);
  pam_printf(" (%d %d) (%s) ", start, end, color);
  pam_print_escaped_string(text);
  pam_printf(")");
}

/* Add a hyperlink to a markup.  ``text'' is displayed marked up with
   color.  ``name'' is the internal name of the hyperlink. */
void pam_add_link(const char *name, const char *color, const char *text)
{
  pam_add_hyper(name, -1, -1, color, text);
}

/* Add some marked-up text -- no link */
void pam_add_marked_text(const char *color, const char *text)
{
  pam_add_markup(-1, -1, color, text);
}

/* Add a hyperlink on top of some text.  The text to add a link to
   ranges from positions start-end in the current markup buffer, and
   is marked up with color.  ``name'' is the internal name of the
   hyperlink. */
void pam_add_overlink(const char *name, const char *color, int start, int end)
{
  pam_add_hyper(name, start, end, color, "");
}

/* Add a color on top of some text -- no link */
void pam_add_overmark(const char *color, int start, int end)
{
  pam_add_markup(start, end, color, "");
}

/**************************************************************************
 *                                                                        *
 * PAM environments                                                       *
 *                                                                        *
 * Functions for remembering what to do when the user clicks on stuff.    *
 *                                                                        *
 **************************************************************************/

#define pam_any_click ""

/* Return the clicks for a buffer, or NULL if the buffer does not
   exist. */
click_list *pam_get_buffer_clicks(const char *bname)
{
  buffer_click_list bcl = buffer_clicks;
  while (bcl)
    if (!strcmp(bcl->buffer, bname))
      break;
    else
      bcl = bcl->next;
  if (bcl)
    return &bcl->clicks;
  else
    return NULL;
}

/* Add a list of buffer clicks, overriding the clicks for the buffer
   if it's already present. */
void pam_add_buffer_clicks(const char *bname, click_list cl)
{
  buffer_click_list bcl;

  bcl = ralloc(pam_region, struct buffer_click_list);
  bcl->next = buffer_clicks;
  bcl->buffer = bname;
  bcl->clicks = cl;
  buffer_clicks = bcl;
}

/* Add a dispatch to the click dispatch table for buffer bname on
   click cname.  If the click was already there, override it
   with a new click. */
void pam_add_buffer_click(const char *bname, const char *cname,
			  click_func func, void *arg)
{
  click_list cl, *old_cl;

  /* Make a new click */
  cl = ralloc(pam_region, struct click_list);
  cl->next = NULL;
  cl->name = cname;
  cl->c.func = func;
  cl->c.arg = arg;

  old_cl = pam_get_buffer_clicks(bname);
  if (!old_cl)
    pam_add_buffer_clicks(bname, cl);
  else
    {
      cl->next = *old_cl;
      *old_cl = cl;
    }
}

/* Remove a dispatch from the click dispatch table for buffer bname on
   click cname.  Aborts if click wasn't already there. */
void pam_remove_buffer_click(const char *bname, const char *cname)
{
  click_list *cl = pam_get_buffer_clicks(bname);

  if (!cl)
    fail("No buffer clicks for ``%s''\n", bname);

  while (*cl && strcmp((*cl)->name, cname))
    cl = &(*cl)->next;

  if (*cl)
    *cl = (*cl)->next;
  else
    fail("No click ``%s'' for buffer ``%s''\n", cname, bname);
}

/* Look up the click for a particular buffer.  Return NULL if
   no click exists. */
click *pam_get_buffer_click(const char *bname, const char *cname)
{
  click_list *cl, cur;

  cl = pam_get_buffer_clicks(bname);
  if (!cl)
    return NULL; /* No buffer exists */
  cur = *cl;
  while (cur)
    if (!strcmp(cur->name, cname) || !strcmp(cur->name, pam_any_click))
      return &cur->c;
    else
      cur = cur->next;
  return NULL;
}

/* Change the effect of clicks on cname in buffer bname. Aborts if
   click wasn't already there. */
void pam_change_buffer_click(const char *bname, const char *cname,
			     click_func func, void *arg)
{
  click *c = pam_get_buffer_click(bname, cname);

  if (!c)
    fail("No click ``%s'' for buffer ``%s''\n", cname, bname);

  c->func = func;
  c->arg = arg;
}

/**************************************************************************
 *                                                                        *
 * About                                                                  *
 *                                                                        *
 **************************************************************************/

#define pam_about_buffer "*About*"
#define pam_about_click "abt"

/* (Re)create the about buffer */
void pam_create_about_buffer(void)
{
  pam_clear_buffer(pam_about_buffer);

  pam_blobs_begin();
  pam_markup_buffer(pam_about_buffer);
  pam_add_text(
"Copyright (c) 2000 The Regents of the University of California.\n"
"All rights reserved.\n"
"\n"
"Permission to use, copy, modify, and distribute this software for any\n"
"purpose, without fee, and without written agreement is hereby granted,\n"
"provided that the above copyright notice and the following two\n"
"paragraphs appear in all copies of this software.\n"
"\n"
"IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR\n"
"DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT\n"
"OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY\n"
"OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
"\n"
"THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,\n"
"INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY\n"
"AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS\n"
"ON AN \"AS IS\" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION\n"
"TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS\n");
  pam_blobs_end();
}

/* Bring up an about window */
void pam_about_click_func(const char *click, const char *file,
			  const char *name, void *arg)
{
  pam_close_buffer(pam_about_buffer);
  pam_set_upper_buffer(pam_about_buffer);
  pam_change_buffer(pam_about_buffer);
  pam_create_about_buffer();
}

/**************************************************************************
 *                                                                        *
 * Hotspots                                                               *
 *                                                                        *
 **************************************************************************/

void pam_qual_click_func(const char *click, const char *file,
			 const char *name, void *arg);
int pam_add_qual_link(qual q, const char *link_name);

#define pam_hotspots_buffer "*Hotspots*"
#define pam_hotspots_click "hotspots"

void pam_create_hotspots_buffer(void)
{
  int i;

  pam_clear_buffer(pam_hotspots_buffer);
  pam_blobs_begin();
  pam_markup_buffer(pam_hotspots_buffer);
  pam_add_text("Potential qualifier variable hotspots:\n\n");
  for (i = 0; i < num_hotspots && hotspots[i] != NULL; i++)
    {
      qual q;

      q = hotspots[i];
      pam_add_qual_link(q, ptr_to_ascii(q));
      pam_add_text(" (%d)\n", error_count_qual(q));
    }
  pam_blobs_end();
  pam_add_buffer_click(pam_hotspots_buffer,
		       pam_any_click,
		       pam_qual_click_func,
		       NULL);
}

/* Bring up the hotspots window */
void pam_hotspots_click_func(const char *click, const char *file,
			     const char *name, void *arg)
{
  pam_close_buffer(pam_hotspots_buffer);
  pam_set_lower_buffer(pam_hotspots_buffer);
  pam_change_buffer(pam_hotspots_buffer);
  pam_create_hotspots_buffer();
}

/* Return TRUE iff there are hotspots */
bool exist_hotspots(void)
{
  return (hotspots && hotspots[0]);
}

/**************************************************************************
 *                                                                        *
 * Undefined/Unannotated Globals                                          *
 *                                                                        *
 * These functions are parameterized by whether to list undefined globals *
 * or unannotated varargs functions.                                      *
 *                                                                        *
 **************************************************************************/

#define pam_undefineds_buffer "*Undefined Globals*"
#define pam_undefineds_click "undef"
#define pam_varargs_buffer "*Non-Polymorphic Varargs Functions*"
#define pam_varargs_click "varargs"

/* Which kind of global to deal with */
typedef enum { undefined_globals, varargs_globals } kind_globals;

typedef bool (*ddecl_pred)(data_declaration ddecl);

typedef struct pred_info {
  ddecl_pred pred;
  const char *buffer;
} *pred_info;

/* Make a hyperlink to an undefined global name */
void pam_markup_global(data_declaration ddecl, void *arg)
{
  pred_info pi;

  pi = (pred_info) arg;
  if (pi->pred(ddecl))
    {
      pam_add_link(ddecl->name, pam_color_1, ddecl->name);
      pam_add_text("\n");
      pam_add_buffer_click(pi->buffer,
			   ddecl->name,
			   (click_func) pam_jump_file,
			   location_declaration(ddecl->ast));
    }
}

/* (Re)create the buffer listing k-kinded globals */
void pam_create_globals_buffer(kind_globals k)
{
  struct pred_info pi;
  const char *title;

  switch (k)
    {
    case undefined_globals:
      title = "Undefined Globals";
      pi.pred = is_undefined_global;
      pi.buffer = pam_undefineds_buffer;
      break;
    case varargs_globals:
      title = "Non-Polymorphic Varargs Functions";
      pi.pred = is_unannotated_global;
      pi.buffer = pam_varargs_buffer;
      break;
    default:
      fail("Unexpected kind_globals %x\n", k);
    }
  pam_clear_buffer(pi.buffer);
  pam_blobs_begin();
  pam_markup_buffer(pi.buffer);
  pam_add_text("%s:\n\n", title);
  traverse_globals(pam_markup_global, &pi);
  pam_blobs_end();
}

/* Bring up the undefined globals window */
void pam_undefineds_click_func(const char *click, const char *file,
			       const char *name, void *arg)
{
  pam_close_buffer(pam_undefineds_buffer);
  pam_set_lower_buffer(pam_undefineds_buffer);
  pam_change_buffer(pam_undefineds_buffer);
  pam_create_globals_buffer(undefined_globals);
}
 
/* Bring up the unannotated varargs window */
void pam_varargs_click_func(const char *click, const char *file,
			    const char *name, void *arg)
{
  pam_close_buffer(pam_varargs_buffer);
  pam_set_lower_buffer(pam_varargs_buffer);
  pam_change_buffer(pam_varargs_buffer);
  pam_create_globals_buffer(varargs_globals);
}

typedef struct pred_info2 {
  ddecl_pred pred;
  bool flag;
} *pred_info2;
 
/* arg is a pred_info.  Set arg->flag to TRUE if arg->pred is true of
   ddecl.  Otherwise leave arg->flag alone. */
void set_if_pred(data_declaration ddecl, void *arg)
{
  pred_info2 pi;

  pi = (pred_info2) arg;
  if (pi->pred(ddecl))
    pi->flag = TRUE;
}

/* Return TRUE iff there is some global satisfying pred */
bool exist_globals(ddecl_pred pred)
{
  struct pred_info2 pi;

  pi.pred = pred;
  pi.flag = FALSE;
  traverse_globals(set_if_pred, &pi);
  return pi.flag;
}

/* Return TRUE iff there is some undefined but used global */
bool exist_undefineds(void)
{
  return exist_globals(is_undefined_global);
}

/* Return TRUE iff there is some unannotated (global) varargs function */
bool exist_varargs(void)
{
  return exist_globals(is_unannotated_global);
}

/**************************************************************************
 *                                                                        *
 * Qualified Types                                                        *
 *                                                                        *
 **************************************************************************/

#define pam_results_buffer "*Qualifier Results*"
#define pam_type_buffer "*Types*"

void pam_qtype_click_func(const char *click, const char *file,
			  const char *name, void *arg);
void pam_qual_click_func(const char *click, const char *file,
			 const char *name, void *arg);

/* Add a hyperlink for a qualifier labeled with link_name. */
int pam_add_qual_link(qual q, const char *link_name)
{
  const char *name = name_qual(q);
  const char *color = color_qual(q);
  if (!color)
    color = pam_color_noqual;
  if (variable_qual(q))
    pam_add_link(link_name, color, name);
  else
    {
      assert(constant_qual(q));
      pam_add_marked_text(color, name);
    }
  return strlen(name);
}

/* Called indirectly by pam_click_dispatch to print out a qualifier.
   Prints the qualifier, and also adds the qual to our list of
   overlays. */
int pam_quals_printf(printf_func pf, qual q)
{
  assert(pf == pam_add_text);
  return pam_add_qual_link(q, ptr_to_ascii(q));
}

/* Show a qualified type */
void pam_qtype_click_func(const char *click, const char *file,
			  const char *name, void *arg)
{
  void *addr;
  /* pam_message("Generic click to ptr %s in file %s", name, file); */
  /* It's a dispatch in a file with overlays */
  
  if (sscanf(name, "%p", &addr) == 0)
    pam_message("Invalid click on ``%s'' in file ``%s''", name, file);
  else
    {
      overlay_list overlay = (overlay_list) addr;
      pam_close_buffer(pam_type_buffer);
      pam_set_lower_buffer(pam_type_buffer);
      pam_change_buffer(pam_type_buffer);
      pam_clear_buffer(pam_type_buffer);
      pam_change_buffer_click(pam_type_buffer, pam_any_click,
			      pam_qual_click_func, NULL);
      pam_blobs_begin();
      pam_markup_buffer(pam_type_buffer);
      /*
      pam_add_text("(static) %s: ", overlay->name);
      */
      pam_add_text("%s: ", overlay->name);
      print_qtype_qf(pam_add_text,
		     pam_quals_printf, overlay->qt);
      pam_add_text("\n");
      /*
      pam_add_text("(flow-sensitive) %s: ", overlay->name);
      print_qtype_qf(pam_add_text,
		     pam_quals_printf, print_aloc, overlay->dqt,overlay->s);
      pam_add_text("\n");
      */
      pam_blobs_end();
    }
}

/**************************************************************************
 *                                                                        *
 * Qualifiers                                                             *
 *                                                                        *
 **************************************************************************/

static bool printed_edge = FALSE;
static bool printed_left = FALSE;

void pam_print_edge(qual left, qual right, location *loc, void *arg)
{
  char *s;
  static int left_size;

  printed_edge = TRUE;
  if (!printed_left)
    {
      left_size = pam_add_qual_link(left, s = ptr_to_ascii(left));
      left_size += pam_add_text(" %s ", s);
      printed_left = TRUE;
    }
  else
    pam_add_text("%*s", left_size, "");
  if (loc)
    {
      const char *name;

      name = get_next_link();
      pam_add_link(name, pam_color_8, "<=");
      pam_add_buffer_click(pam_type_buffer,
			   name,
			   (click_func) pam_jump_file,
			   loc);
      pam_add_text(" ");
    }
  else
    pam_add_text("<= ");
  pam_add_qual_link(right, s = ptr_to_ascii(right));
  pam_add_text(" %s\n", s);
}

void pam_qual_mouse_2_click_func(const char *file, const char *name, void *arg)
{
  void *addr;
  qual_set_scanner qss;
  qual q, b;

  assert(!arg);
  if (sscanf(name, "%p", &addr) == 0)
    {
      pam_message("Invalid click on ``%s'' in buffer ``%s''", name, file);
      return;
    }
  q = (qual) addr;
  /*  print_qual_graph(q, "qual.dot", TRUE);*/
  pam_close_buffer(pam_type_buffer);
  pam_set_lower_buffer(pam_type_buffer);
  pam_change_buffer(pam_type_buffer);
  pam_clear_buffer(pam_type_buffer);
  pam_blobs_begin();
  pam_markup_buffer(pam_type_buffer);

  /* Print the qualifier bounds */
  pam_add_text("%s:", name_qual(q));
  scan_qual_set(b, qss, lbc_qual(q))
    if (constant_qual(b) &&
	(sign_qual(b) == sign_pos || sign_qual(b) == sign_eq))
      {
	pam_add_text(" ");
	pam_add_qual_link(b, "");
      }

  scan_qual_set(b, qss, ubc_qual(q))
    if (constant_qual(b) && sign_qual(b) == sign_neg)
      {
	pam_add_text(" ");
	pam_add_qual_link(b, "");
      }
  pam_add_text("\n\n");

  /* Print the path(s) to the bounds */
  printed_edge = FALSE;
  printed_left = FALSE;
  /*traverse_qual_graph_edges(q, TRUE, pam_print_edge, NULL);*/
  //  pam_remove_buffer_clicks(pam_type_buffer);
  traverse_shortest_path_edges(q, NULL, pam_print_edge, NULL);
  if (!printed_edge)
    pam_add_text("No qualifiers\n");
  pam_blobs_end();
}

/* Jump to the location defining a qualifier. */
void pam_qual_shift_mouse_2_click_func(const char *file, const char *name,
				       void *arg)
{
  void *addr;
  qual q;
  location *loc;

  /* They clicked on a qualifier in a qtype */
  if (sscanf(name, "%p", &addr) == 0)
    {
      pam_message("Invalid click on ``%s'' in buffer ``%s''", name, file);
      return;
    }
  q = (qual) addr;
  loc = location_qual(q);
  pam_jump_file(pam_click, file, name, loc);
}

/* Handle click on a qualifier. */
void pam_qual_click_func(const char *click, const char *file,
			 const char *name, void *arg)
{
  if (!strcmp(click, pam_click))
    pam_qual_mouse_2_click_func(file, name, arg);
  else if (!strcmp(click, pam_shift_click))
    pam_qual_shift_mouse_2_click_func(file, name, arg);
  else
    fail("Unexpected click ``%s''\n", click);
}

/**************************************************************************
 *                                                                        *
 * Files                                                                  *
 *                                                                        *
 **************************************************************************/

/* Load in file and mark it up. */
void pam_load_file(const char *click_name, const char *file_containing_link,
		   const char *name, char *file)
{
  file_list file_info;
  overlay_list overlay;
  int numOverlays = 0;

  pam_close_file(file);
  pam_set_upper_file(file);
  pam_change_file(file);


  file_info = pam_find_file(file);
  if (!file_info)
    {
      pam_message("Asked to add overlays to unknown file ``%s''", file);
      return;
    }

  if (file_info->loaded)
    /* Assume already loaded info */
    return;

  pam_message("Adding overlays to ``%s''", file);

  overlay = file_info->overlays;

  pam_overlays_begin();
  pam_markup_file(file);
  while (overlay)
    {
      const char *color;

      color = color_qtype(overlay->qt);
      if (!color)
	color = pam_color_noqual;
      pam_add_overlink(ptr_to_ascii(overlay), color, overlay->start,
		       overlay->end);
      numOverlays++;
      if (numOverlays > 1000)
	{
	  pam_overlays_end();
	  pam_overlays_begin();
	  pam_markup_file(file);
	  numOverlays = 0;
	}
      overlay = overlay->next;
    }
  pam_overlays_end();
  pam_message("done.");
  file_info->loaded = TRUE;
}

/* Load in file, mark it up, and jump to location */
void pam_jump_file(const char *click_name, const char *file,
		   const char *name, location *loc)
{
  pam_load_file(pam_click, file, name, loc->filename);

  /* Emacs:  first character in the file is at position 1 */
  pam_goto_char_buffer(loc->filename, loc->filepos + 1);
}

/* Return the name of the next free link */
const char *get_next_link(void)
{
  return inttostr(pam_region, next_link++);
}

/* Internal function: Given a file name, return a pointer to its
   file_list, or NULL if no such list exists. */
file_list pam_find_file(const char *name)
{
  file_list files = pam_files;

  while (files)
    {
      if (!strcmp(files->filename, name))
	break;
      files = files->next;
    }
  return files;
}

/* Add a file to the list of analyzed files, and add it to the
   buffer click table. */
void pam_add_file(const char *name)
{
  file_list cur, fl = ralloc(pam_region, struct file_list);

  fl->filename = rstrdup(pam_region, name);
  fl->loaded = FALSE;
  fl->link = get_next_link();
  fl->next = NULL;
  fl->overlays = NULL;

  /* Append to end of list */
  cur = pam_files;
  if (!cur)
    pam_files = fl;
  else
    {
      while (cur->next)
	cur = cur->next;
      cur->next = fl;
    }
  pam_add_buffer_click(pam_results_buffer, fl->link,
		       (click_func) pam_load_file, (void *) fl->filename);
}

/* Add an error message, and add it to the buffer click table */
void pam_add_error(const char *func, location loc, severity sev,
		   const char *fmt, va_list args)
{
  struct error_list *cur, *el = ralloc(pam_region, struct error_list);

  el->func = func;
  el->loc = loc;
  el->sev = sev;
  el->message = rvsprintf(pam_region, fmt, args);
  el->link = get_next_link();
  el->next = NULL;

  /* Append to end of list */
  cur = pam_errors;
  if (!cur)
    pam_errors = el;
  else
    {
      while (cur->next)
	cur = cur->next;
      cur->next = el;
    }
  pam_add_buffer_click(pam_results_buffer, el->link,
		       (click_func) pam_jump_file, (void *) &el->loc);
}

/* Add an overlay to a file.  Adds the file if it doesn't already
   exist. */
void pam_add_overlay_file(location loc, const char *idname, qtype qt)
{
  struct file_list *files;
  overlay_list overlay;

  files = pam_find_file(loc.filename);
  if (!files)
    {
      pam_add_file(loc.filename);
      files = pam_find_file(loc.filename);
    }

  /*  printf("Adding overlay %s to %s\n", idname, files->filename);*/
  overlay = ralloc(pam_region, struct overlay_list);
  overlay->next = files->overlays;
  overlay->name = idname;
  overlay->start = loc.filepos + 1; /* Emacs off-by-one error */
  overlay->end = loc.filepos + strlen(idname) + 1;
  overlay->qt = qt;
  files->overlays = overlay;
}

/**************************************************************************
 *                                                                        *
 * Initial (Summary) Window                                               *
 *                                                                        *
 **************************************************************************/

/* Print out a list of files.  Assumes we're already in a blob. */
void pam_print_files(void)
{
  struct file_list *fl = pam_files;

  if (fl)
    pam_add_text("Analyzed files:\n\n");
  while (fl)
    {
      pam_add_text("        ");
      pam_add_link(fl->link, pam_color_2, fl->filename);
      pam_add_text("\n");
      fl = fl->next;
    }
}

/* Print out a list of errors.  Assumes we're already in a blob. */
void pam_print_errors(void)
{
  struct error_list *el = pam_errors;
  region temp_region = newregion();
  char *s;

  if (el)
    pam_add_text("The following errors were detected:\n\n");
  while (el)
    {
      char *color;

      switch (el->sev)
	{
	case sev_err:
	  color = pam_color_error;
	  break;
	case sev_warn:
	  color = pam_color_warning;
	  break;
	case sev_inf:
	  color = pam_color_info;
	  break;
	default:
	  fail("Unexpected severity %d\n", el->sev);
	}

      if (strcmp(el->loc.filename, "<dummy>"))
	{
	  /* If filename is not dummy, add link to location */
	  s = rsprintf(temp_region, "%s:%ld", el->loc.filename,
		       el->loc.lineno);
	  pam_add_link(el->link, color, s);
	}
      else
	{
	  pam_add_marked_text(color, "top level");
	}
      pam_add_text("\n");
      pam_add_text(el->message);
      pam_add_text("\n\n");
      el = el->next;
    }
  deleteregion(temp_region);
}

void pam_create_initial_window(void)
{
  pam_clear_buffer(pam_results_buffer);
  pam_blobs_begin();
  pam_markup_buffer(pam_results_buffer);
  pam_add_text("\nQualifier inference\n\n");


  pam_add_link(pam_about_click, pam_color_6, "About");
  pam_add_text("                                 ");
  if (exist_undefineds())
    pam_add_link(pam_undefineds_click, pam_color_6, "Undefined Globals");
  else
    pam_add_text("Undefined Globals (none)");
  pam_add_text("\n");

  if (exist_hotspots())
    {
      pam_add_link(pam_hotspots_click, pam_color_6,
		   "Hotspots");
      pam_add_text("                              ");
    }
  else
    pam_add_text("Hotspots (none)                       ");
  if (exist_varargs())
    pam_add_link(pam_varargs_click, pam_color_6,
		 "Non-Polymorphic Varargs Functions");
  else
    pam_add_text("Non-Polymorphic Varargs Functions (none)");

  pam_add_text("\n");
  if (qerrors)
    pam_add_text("\nQualifier inference failed.\n\n");
  else
    pam_add_text("\nQualifier inference succesful.\n\n");
  pam_print_files();
  pam_add_text("\n");
  pam_print_errors();
  pam_blobs_end();
}

/**************************************************************************
 *                                                                        *
 * Top-level PAM loop                                                     *
 *                                                                        *
 * Enter the PAM browsing mode, handling clicks, looping until PAM exits  *
 *                                                                        *
 **************************************************************************/

/* Add customized functions for clicks */
void pam_install_generic_buffer_clicks(void)
{
  pam_add_buffer_click(pam_results_buffer, pam_about_click,
		       pam_about_click_func, NULL);
  pam_add_buffer_click(pam_results_buffer, pam_undefineds_click,
		       pam_undefineds_click_func, NULL);
  pam_add_buffer_click(pam_results_buffer, pam_varargs_click,
		       pam_varargs_click_func, NULL);
  pam_add_buffer_click(pam_results_buffer, pam_hotspots_click,
		       pam_hotspots_click_func, NULL);
  pam_add_buffer_click(pam_type_buffer, pam_any_click, pam_qual_click_func,
		       NULL);
}

/* Handle ``click'' in PAM window ``file,'' on type ``type,'' on
   hyperlink ``name'' */
void pam_click_dispatch(char *click_name, char *name, char *file, char *type)
{
  if (!strcmp(type, pam_window_buffer))
    {
      /* It's a dispatch in a custom window.  Search through our table. */
      click *c = pam_get_buffer_click(file, name);
      if (!c)
	pam_message("Don't know what to do for buffer click on ``%s'' in "
		    "``%s''",
		    name, file);
      else
	c->func(click_name, file, name, c->arg);
    }
  else if (!strcmp(type, pam_window_file))
    {
      pam_message("Unexpected PAM window type ``%s''", type);
    }
  else
      pam_qtype_click_func(click_name, file, name, NULL);
}

/* Reads at most size-1 characters from the standard input, storing
   the result in buf.  Stops reading characters after detecting an \n
   or an EOF (not included in buf), or when the buffer is full.
   Always null-terminates buf.  Returns the number of characters
   stored in the buffer, including \0. */
int get_line(char *buf, int size)
{
  int n = 0;
  /*  static FILE *foo = 0;*/

  assert(size >= 1);   /* Must be space for \0 */
  while (n < size - 1) /* Leave space for terminating \0 */
    {
      char c = pam_getc();
      if (c == EOF || c == '\n')
	break;
      buf[n++] = c;
    }
  buf[n++] = '\0';
  /* if (!foo)
       foo = fopen("clicks", "w");
     fprintf(foo, "%s\n", buf);
     fflush(foo);*/
  return n;
}

/* Enter PAM mode, and loop on PAM events */
void enter_pam_mode(void)
{
  pam_install_generic_buffer_clicks();

  /* Set up the results buffer */
  pam_printf("\n\n///\n");  /* Mysterious message to indicate analysis is
			       over */
  pam_close_buffer(pam_results_buffer);
  pam_change_buffer(pam_results_buffer);
  pam_create_initial_window();
  if (qerrors)
    pam_message("Qualifier inference failed.");
  else
    pam_message("Qualifier inference successful.");

  /* Go into a loop, accepting commands from PAM */
  while (1)
    {
      static char buf[BUF_MAX];
      int bufSize;
      char *cmd;

      bufSize = 0; /* Number of characters actually in buffer */
      cmd = buf + bufSize;
      if ((bufSize += get_line(cmd, BUF_MAX-bufSize)) == BUF_MAX)
	fail("PAM sent a command that was too big\n");
      if (!strcmp(buf, pam_click) || !strcmp(buf, pam_shift_click))
	{
	  char *name, *file, *type;

	  /* Read name */
	  name = buf + bufSize;;
	  if ((bufSize += get_line(name, BUF_MAX-bufSize)) == BUF_MAX)
	    fail("PAM sent a command that was too big\n");

	  /* Read file */
	  file = buf + bufSize;
	  if ((bufSize += get_line(file, BUF_MAX-bufSize)) == BUF_MAX)
	    fail("PAM sent a command that was too big\n");

	  /* Read type */
	  type = buf + bufSize;
	  if ((bufSize += get_line(type, BUF_MAX-bufSize)) == BUF_MAX)
	    fail("PAM sent a command that was too big\n");

	  /* pam_message("Click! name=%s, file=%s, type=%s", name, file,
	     type); */
	  pam_click_dispatch(cmd, name, file, type);
	}
      else if (!strcmp(buf, "quit"))
	{
	  pam_message("Analysis program exiting.");
	  break;
	}
      else if (!strcmp(buf, "file"))
	fail("Unimplemented: PAM file\n");
      else
	pam_message("Unrecognized command ``%s'' returned from PAM", buf);
    }
}

/*
 * Extra stuff
 *
 * Print summary of analysis results
 */

void print_results(void)
{
  file_list file;
  overlay_list overlay;
  int i;

  if (hotspots)
    {
      printf("Potential hotspots:\n\n");
      for (i = 0; i < num_hotspots && hotspots[i]; i++)
	printf("%s (%d)\n", name_qual(hotspots[i]),
	       error_count_qual(hotspots[i]));
      printf("\n");
    }

  for (file = pam_files; file; file = file->next)
    {
      printf("File %s:\n", file->filename);
      for (overlay = file->overlays; overlay; overlay = overlay->next)
	{
	  printf("%s: ", overlay->name);
	  print_qtype_qf(printf, print_qual, overlay->qt);
	  printf("\n");
	}
      printf("\n");
    }
}
