#include <ctype.h>

#include "libiberty.h"
#include "cg_arcs.h"
#include "sym_ids.h"

struct sym_id
  {
    struct sym_id *next;
    char *spec;			/* parsing modifies this */
    Table_Id which_table;
    bool has_right;
    struct match
      {
	int prev_index;		/* index of prev match */
	Sym *prev_match;	/* previous match */
	Sym *first_match;	/* chain of all matches */
	Sym sym;
      }
    left, right;
  }
 *id_list;

Sym_Table syms[NUM_TABLES];

#ifdef DEBUG
const char *table_name[] =
{
  "INCL_GRAPH", "EXCL_GRAPH",
  "INCL_ARCS", "EXCL_ARCS",
  "INCL_FLAT", "EXCL_FLAT",
  "INCL_TIME", "EXCL_TIME",
  "INCL_ANNO", "EXCL_ANNO",
  "INCL_EXEC", "EXCL_EXEC"
};
#endif /* DEBUG */

/*
 * This is the table in which we keep all the syms that match
 * the right half of an arc id.  It is NOT sorted according
 * to the addresses, because it is accessed only through
 * the left half's CHILDREN pointers (so it's crucial not
 * to reorder this table once pointers into it exist).
 */
static Sym_Table right_ids;

static Source_File non_existent_file =
{
  0, "<non-existent-file>"
};


void
DEFUN (sym_id_add, (spec, which_table),
       const char *spec AND Table_Id which_table)
{
  struct sym_id *id;
  int len = strlen (spec);

  id = (struct sym_id *) xmalloc (sizeof (*id) + len + 1);
  memset (id, 0, sizeof (*id));

  id->spec = (char *) id + sizeof (*id);
  strcpy (id->spec, spec);
  id->which_table = which_table;

  id->next = id_list;
  id_list = id;
}


/*
 * A spec has the syntax FILENAME:(FUNCNAME|LINENUM).  As a convenience
 * to the user, a spec without a colon is interpreted as:
 *
 *      (i)   a FILENAME if it contains a dot
 *      (ii)  a FUNCNAME if it starts with a non-digit character
 *      (iii) a LINENUM if it starts with a digit
 *
 * A FUNCNAME containing a dot can be specified by :FUNCNAME, a
 * FILENAME not containing a dot can be specified by FILENAME:.
 */
static void
DEFUN (parse_spec, (spec, sym), char *spec AND Sym * sym)
{
  char *colon;

  sym_init (sym);
  colon = strrchr (spec, ':');
  if (colon)
    {
      *colon = '\0';
      if (colon > spec)
	{
	  sym->file = source_file_lookup_name (spec);
	  if (!sym->file)
	    {
	      sym->file = &non_existent_file;
	    }
	}
      spec = colon + 1;
      if (strlen (spec))
	{
	  if (isdigit ((unsigned char) spec[0]))
	    {
	      sym->line_num = atoi (spec);
	    }
	  else
	    {
	      sym->name = spec;
	    }
	}
    }
  else if (strlen (spec))
    {
      /* no colon: spec is a filename if it contains a dot: */
      if (strchr (spec, '.'))
	{
	  sym->file = source_file_lookup_name (spec);
	  if (!sym->file)
	    {
	      sym->file = &non_existent_file;
	    }
	}
      else if (isdigit ((unsigned char) *spec))
	{
	  sym->line_num = atoi (spec);
	}
      else if (strlen (spec))
	{
	  sym->name = spec;
	}
    }
}


/*
 * A symbol id has the syntax SPEC[/SPEC], where SPEC is is defined
 * by parse_spec().
 */
static void
DEFUN (parse_id, (id), struct sym_id *id)
{
  char *slash;

  DBG (IDDEBUG, printf ("[parse_id] %s -> ", id->spec));

  slash = strchr (id->spec, '/');
  if (slash)
    {
      parse_spec (slash + 1, &id->right.sym);
      *slash = '\0';
      id->has_right = TRUE;
    }
  parse_spec (id->spec, &id->left.sym);

#ifdef DEBUG
  if (debug_level & IDDEBUG)
    {
      printf ("%s:", id->left.sym.file ? id->left.sym.file->name : "*");
      if (id->left.sym.name)
	{
	  printf ("%s", id->left.sym.name);
	}
      else if (id->left.sym.line_num)
	{
	  printf ("%d", id->left.sym.line_num);
	}
      else
	{
	  printf ("*");
	}
      if (id->has_right)
	{
	  printf ("/%s:",
		  id->right.sym.file ? id->right.sym.file->name : "*");
	  if (id->right.sym.name)
	    {
	      printf ("%s", id->right.sym.name);
	    }
	  else if (id->right.sym.line_num)
	    {
	      printf ("%d", id->right.sym.line_num);
	    }
	  else
	    {
	      printf ("*");
	    }
	}
      printf ("\n");
    }
#endif
}


/*
 * Return TRUE iff PATTERN matches SYM.
 */
static bool
DEFUN (match, (pattern, sym), Sym * pattern AND Sym * sym)
{
  return (pattern->file ? pattern->file == sym->file : TRUE)
    && (pattern->line_num ? pattern->line_num == sym->line_num : TRUE)
    && (pattern->name ? strcmp (pattern->name, sym->name) == 0 : TRUE);
}


static void
DEFUN (extend_match, (m, sym, tab, second_pass),
     struct match *m AND Sym * sym AND Sym_Table * tab AND bool second_pass)
{
  if (m->prev_match != sym - 1)
    {
      /* discontinuity: add new match to table: */
      if (second_pass)
	{
	  tab->base[tab->len] = *sym;
	  m->prev_index = tab->len;

	  /* link match into match's chain: */
	  tab->base[tab->len].next = m->first_match;
	  m->first_match = &tab->base[tab->len];
	}
      ++tab->len;
    }

  /* extend match to include this symbol: */
  if (second_pass)
    {
      tab->base[m->prev_index].end_addr = sym->end_addr;
    }
  m->prev_match = sym;
}


/*
 * Go through sym_id list produced by option processing and fill
 * in the various symbol tables indicating what symbols should
 * be displayed or suppressed for the various kinds of outputs.
 *
 * This can potentially produce huge tables and in particulars
 * tons of arcs, but this happens only if the user makes silly
 * requests---you get what you ask for!
 */
void
DEFUN_VOID (sym_id_parse)
{
  Sym *sym, *left, *right;
  struct sym_id *id;
  Sym_Table *tab;

  /*
   * Convert symbol ids into Syms, so we can deal with them more easily:
   */
  for (id = id_list; id; id = id->next)
    {
      parse_id (id);
    }

  /* first determine size of each table: */

  for (sym = symtab.base; sym < symtab.limit; ++sym)
    {
      for (id = id_list; id; id = id->next)
	{
	  if (match (&id->left.sym, sym))
	    {
	      extend_match (&id->left, sym, &syms[id->which_table], FALSE);
	    }
	  if (id->has_right && match (&id->right.sym, sym))
	    {
	      extend_match (&id->right, sym, &right_ids, FALSE);
	    }
	}
    }

  /* create tables of appropriate size and reset lengths: */

  for (tab = syms; tab < &syms[NUM_TABLES]; ++tab)
    {
      if (tab->len)
	{
	  tab->base = (Sym *) xmalloc (tab->len * sizeof (Sym));
	  tab->limit = tab->base + tab->len;
	  tab->len = 0;
	}
    }
  if (right_ids.len)
    {
      right_ids.base = (Sym *) xmalloc (right_ids.len * sizeof (Sym));
      right_ids.limit = right_ids.base + right_ids.len;
      right_ids.len = 0;
    }

  /* make a second pass through symtab, creating syms as necessary: */

  for (sym = symtab.base; sym < symtab.limit; ++sym)
    {
      for (id = id_list; id; id = id->next)
	{
	  if (match (&id->left.sym, sym))
	    {
	      extend_match (&id->left, sym, &syms[id->which_table], TRUE);
	    }
	  if (id->has_right && match (&id->right.sym, sym))
	    {
	      extend_match (&id->right, sym, &right_ids, TRUE);
	    }
	}
    }

  /* go through ids creating arcs as needed: */

  for (id = id_list; id; id = id->next)
    {
      if (id->has_right)
	{
	  for (left = id->left.first_match; left; left = left->next)
	    {
	      for (right = id->right.first_match; right; right = right->next)
		{
		  DBG (IDDEBUG,
		       printf (
				"[sym_id_parse]: arc %s:%s(%lx-%lx) -> %s:%s(%lx-%lx) to %s\n",
				left->file ? left->file->name : "*",
				left->name ? left->name : "*", left->addr,
				left->end_addr,
				right->file ? right->file->name : "*",
				right->name ? right->name : "*", right->addr,
				right->end_addr,
				table_name[id->which_table]));
		  arc_add (left, right, (unsigned long) 0);
		}
	    }
	}
    }

  /* finally, we can sort the tables and we're done: */

  for (tab = &syms[0]; tab < &syms[NUM_TABLES]; ++tab)
    {
      DBG (IDDEBUG, printf ("[sym_id_parse] syms[%s]:\n",
			    table_name[tab - &syms[0]]));
      symtab_finalize (tab);
    }
}


/*
 * Symbol tables storing the FROM symbols of arcs do not necessarily
 * have distinct address ranges.  For example, somebody might request
 * -k /_mcount to suppress any arcs into _mcount, while at the same
 * time requesting -k a/b.  Fortunately, those symbol tables don't get
 * very big (the user has to type them!), so a linear search is probably
 * tolerable.
 */
bool
DEFUN (sym_id_arc_is_present, (symtab, from, to),
       Sym_Table * symtab AND Sym * from AND Sym * to)
{
  Sym *sym;

  for (sym = symtab->base; sym < symtab->limit; ++sym)
    {
      if (from->addr >= sym->addr && from->addr <= sym->end_addr
	  && arc_lookup (sym, to))
	{
	  return TRUE;
	}
    }
  return FALSE;
}