/* breakpoints.c: Setting, inserting, and deleting breakpoints in MDB. */

/* Author: Brian J. Fox (bfox@ai.mit.edu) Sun Oct  1 20:30:09 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 "language.h"
#include "mdb.h"
#include "commands.h"
#include "breakpoints.h"


/* An array of breakpoints. */
static MDB_Breakpoint **mdb_breakpoints = (MDB_Breakpoint **)NULL;
static int mdb_breakpoints_slots = 0;
static int mdb_breakpoints_index = 0;

/* The breakpoint used internally, e.g., for doing "next". */
static MDB_Breakpoint *mdb_internal_bp = (MDB_Breakpoint *)NULL;

/* Return the list of our breakpoints. */
MDB_Breakpoint **
mdb_breakpoint_list (void)
{
  return (mdb_breakpoints);
}

/* Return the indicated breakpoint structure. */
MDB_Breakpoint *
mdb_this_breakpoint (int which)
{
  MDB_Breakpoint *bp = (MDB_Breakpoint *)NULL;

  if ((which < mdb_breakpoints_index) && (which > -1))
    bp = mdb_breakpoints[which];
  else if (which == -1)
    bp = mdb_internal_bp;

  return (bp);
}

int
mdb_skip_sexp (char *string)
{
  register int i;
  int quoted = 0;
  int brace_depth = 0;
  char c;

  /* Skip leading whitespace. */
  for (i = 0; string[i] && whitespace (string[i]); i++);

  for (; (c = string[i]) != '\0'; i++)
    {
      switch (c)
	{
	case '"':
	  quoted = !quoted;
	  break;

	case '<':
	  if (!quoted)
	    brace_depth++;
	  break;

	case '>':
	  if (!quoted)
	    brace_depth--;

	  if (!brace_depth)
	    return (i + 1);
	  break;

	case ' ':
	  if (!brace_depth && !quoted)
	    return (i);
	  break;
	}
    }

  return (i);
}

static void
mdb_insert_bp_1 (PAGE *page, int which, MDB_Breakpoint *bp, int position)
{
  int expression_length;
  BPRINTF_BUFFER *insertion;

  if (position == -1)
    return;

  /* The breakpoint surrounds the area to be broken.  We have to parse
     a single sexp forward. */
  insertion = bprintf_create_buffer ();
  bprintf  (insertion, "<*mdb-break* %d ", which);

  /* Now skip until we have read an entire expression. */
  expression_length = mdb_skip_sexp (page->buffer + position);

  /* Insert the breakpoint. */
  bprintf_insert (page, position, "%s", insertion->buffer);
  bprintf_insert (page, position + insertion->bindex + expression_length, ">");

  /* Remember the page. */
  bp->code = page;
}


/* Insert a breakpoint at bp->position. */
static void
mdb_insert_bp_at_pos (PAGE *page, int which, MDB_Breakpoint *bp)
{
  int position = bp->position;

  mdb_insert_bp_1 (page, which, bp, position);
}

/* Insert a single breakpoint. */
static void
mdb_insert_bp_at_line (PAGE *page, int which, MDB_Breakpoint *bp)
{
  int position = mdb_position_of_line (page->buffer, bp->line_number);

  mdb_insert_bp_1 (page, which, bp, position);
}

/* Insert BPS into PAGE (which came from FILE. */
void
mdb_insert_breakpoints (MDB_File *file, PAGE *page, MDB_Breakpoint **bps)
{
  register int i;
  MDB_Breakpoint *bp;

  if (!bps)
    return;

  for (i = 0; (bp = bps[i]) != (MDB_Breakpoint *)NULL; i++)
    if ((bp->file == file) && (bp->type != break_DELETED))
      mdb_insert_bp_at_line (page, i, bp);
}

/* Report the total number of breakpoints in FILE. */
int
mdb_count_breakpoints (MDB_File *file)
{
  register int i;
  int counter = 0;

  for (i = 0; i < mdb_breakpoints_index; i++)
    if ((mdb_breakpoints[i]->file == file) &&
	(mdb_breakpoints[i]->type != break_DELETED))
      counter++;

  return (counter);
}

static int
mdb_what_line (char *string, int pos)
{
  register int i;
  int line = 1;

  for (i = 0; string[i] && i < pos; i++)
    if (string[i] == '\n')
      line++;

  return (line);
}

/* You can only call this with an active breakpoint that has already been
   run through pf_break_handler ().  If you don't understand this, don't
   call this function. */
void
mdb_set_next_breakpoint (MDB_Breakpoint *bp)
{
  MDB_Breakpoint *new;
  int pos;

  new = (MDB_Breakpoint *)xmalloc (sizeof (MDB_Breakpoint));
  new->type = break_INTERNAL;
  new->file = bp->file;
  new->code = bp->code;

  /* Get the line number in the execution page of this breakpoint. */
  pos = bp->position + mdb_skip_sexp (bp->code->buffer + bp->position);
  new->position = pos;
  new->line_number = mdb_what_line (bp->code->buffer, pos);

  /* Get the line number in the original file of this breakpoint. */
  pos = mdb_position_of_line (bp->file->contents->buffer, bp->line_number);
  pos += mdb_skip_sexp (bp->file->contents->buffer + pos);

  while ((pos < bp->file->contents->bindex) &&
	 (whitespace (bp->file->contents->buffer[pos])))
    pos++;

  if (pos != bp->file->contents->bindex)
    {
      /* Set this breakpoint in the page. */
      mdb_insert_bp_at_pos (new->code, -1, new);

      new->line_number = mdb_what_line (bp->file->contents->buffer, pos);

      /* Remeber this breakpoint... */
      mdb_internal_bp = new;
    }
  else
    free (new);
}

/* Return a string describing the current state of breakpoints. */
char *
mdb_breakpoint_info (void)
{
  register int i;
  BPRINTF_BUFFER *listing = (BPRINTF_BUFFER *)NULL;
  char *result;

  if (mdb_breakpoints_index != 0)
    {
      for (i = 0; i < mdb_breakpoints_index; i++)
	{
	  if (mdb_breakpoints[i]->type != break_DELETED)
	    {
	      if (listing == (BPRINTF_BUFFER *)NULL)
		{
		  listing = bprintf_create_buffer ();
		  bprintf (listing, "Num         In File      \tAt Line\n");
		}

	      bprintf (listing, "%3d  %20s\t%5d\n", i + 1,
		       mdb_breakpoints[i]->file->nameonly,
		       mdb_breakpoints[i]->line_number);
	    }
	}
    }

  if (listing == (BPRINTF_BUFFER *)NULL)
    result = strdup ("There are no breakpoints.");
  else
    {
      result = listing->buffer;
      free (listing);
    }
  return (result);
}

/* Locate the breakpoint structure for FILE, LINE and TYPE. */
MDB_Breakpoint *
mdb_find_breakpoint (MDB_File *file, int line, int type)
{
  register int i;
  MDB_Breakpoint *result;

  result = (MDB_Breakpoint *)NULL;

  for (i = 0; i < mdb_breakpoints_index; i++)
    if ((mdb_breakpoints[i]->file == file) &&
	(mdb_breakpoints[i]->type == type) &&
	(mdb_breakpoints[i]->line_number == line))
      {
	result = mdb_breakpoints[i];
	break;
      }

  return (result);
}

/* Add a breakpoint to the list of breakpoints. */
void
mdb_add_breakpoint (MDB_File *file, int at_line, int type)
{
  MDB_Breakpoint *bp = (MDB_Breakpoint *)NULL;

  if (type != break_CONDITION)
    bp = mdb_find_breakpoint (file, at_line, type);

  if (!bp)
    {
      bp = (MDB_Breakpoint *)xmalloc (sizeof (MDB_Breakpoint));
      bp->type = type;
      bp->line_number = at_line;
      bp->file = file;
      bp->code = (PAGE *)NULL;
      bp->position = -1;

      if (mdb_breakpoints_index + 2 > mdb_breakpoints_slots)
	mdb_breakpoints = (MDB_Breakpoint **)xrealloc
	  (mdb_breakpoints, ((mdb_breakpoints_slots += 10) * sizeof (char *)));
      mdb_breakpoints[mdb_breakpoints_index++] = bp;
      mdb_breakpoints[mdb_breakpoints_index] = (MDB_Breakpoint *)NULL;
    }
}

int
mdb_position_of_line (char *string, int which)
{
  if (string)
    {
      register int i;
      int line = 1;

      if (which == 1)
	return (0);

      for (i = 0; string[i]; i++)
	{
	  if (string[i] == '\n')
	    {
	      line++;

	      if (line == which)
		return (i + 1);
	    }
	}
    }

  return (-1);
}
