/* Copyright 1986-1996 by Abacus Research and
 * Development, Inc.  All rights reserved.
 */

#if !defined (OMIT_RCSID_STRINGS)
char ROMlib_rcsid_teMisc[] =
	    "$Id: teMisc.c 63 2004-12-24 18:19:43Z ctm $";
#endif

/* Forward declarations in TextEdit.h (DO NOT DELETE THIS LINE) */

/*
 * TODO:  for better compatability we should ignore trailing spaces when doing
 *        calculations.
 */

#include "rsys/common.h"
#include "WindowMgr.h"
#include "ControlMgr.h"
#include "EventMgr.h"
#include "TextEdit.h"
#include "MemoryMgr.h"

#include "rsys/cquick.h"
#include "rsys/tesave.h"
#include "rsys/hook.h"
#include "rsys/text.h"

int16 nextbreak (TEHandle teh, int16 off, int16 len,
		 int16 max_width);

#if ERROR_SUPPORTED_P (ERROR_TEXT_EDIT_SLAM)
void
ROMlib_sledgehammer_te (TEHandle te)
{
  int16 n_lines;
  int16 width;
  int16 length;
  Rect *dest_rect;
  Handle hText;
  char *Text;
  int16 current_lineno;
  int16 *line_starts;
  LHHandle lh_table;
  int te_size;
  int i;

  line_starts = TE_LINE_STARTS (te);
  hText = TE_HTEXT (te);
  Text = (char *) STARH (hText);
  length = TE_LENGTH (te);
  n_lines = TE_N_LINES (te);
  dest_rect = &TE_DEST_RECT (te);
  width = RECT_WIDTH (dest_rect);
  
  if (TE_STYLIZED_P (te))
      {
	TEStyleHandle te_style;
	int lh_table_size, te_style_size;
	int16 n_runs, n_styles;
	int16 *style_count_vec;
	STHandle style_table;
	
	te_style = TE_GET_STYLE (te);
	style_table = TE_STYLE_STYLE_TABLE (te_style);
	
	lh_table = TE_STYLE_LH_TABLE (te_style);
	lh_table_size = GetHandleSize ((Handle) lh_table);
	gui_assert (lh_table_size == (n_lines + 1) * (int) sizeof (LHElement));
	
	te_style_size = GetHandleSize ((Handle) te_style);
	n_runs = TE_STYLE_N_RUNS (te_style);
	gui_assert (te_style_size
		    == ((int) sizeof (TEStyleRec)
			- (int) sizeof TE_STYLE_RUNS ((TEStyleHandle) NULL)
			+ ((n_runs + 1)
			   * (int)
			   sizeof *TE_STYLE_RUNS ((TEStyleHandle) NULL))));
	n_styles = TE_STYLE_N_STYLES (te_style);
	style_count_vec = alloca (n_styles * sizeof *style_count_vec);
	memset (style_count_vec, 0, n_styles * sizeof *style_count_vec);
	for (i = 0; i < n_runs; i ++)
	  {
	    int16 style_index;
	    StyleRun *run;
	    
	    run = TE_STYLE_RUN (te_style, i);
	    style_index = STYLE_RUN_STYLE_INDEX (run);
	    
	    if (!i)
	      gui_assert (!STYLE_RUN_START_CHAR (run));
	    else if (i > 0)
	      {
		StyleRun *prev_run;
		int16 prev_style_index;
		
		prev_run = TE_STYLE_RUN (te_style, i - 1);
		prev_style_index = STYLE_RUN_STYLE_INDEX (prev_run);
		
		gui_assert (prev_style_index != style_index);
		/* guarantee that runs are strictly increasing */
		gui_assert (STYLE_RUN_START_CHAR (prev_run)
			    < STYLE_RUN_START_CHAR (run));
	      }
	    
	    gui_assert (style_index >= 0 && style_index < n_styles);
	    
	    style_count_vec[style_index] ++;
	  }

	{
	  StyleRun *last_run = TE_STYLE_RUN (te_style, n_runs);
	  
	  gui_assert (STYLE_RUN_START_CHAR (last_run) == length + 1
		      && (STYLE_RUN_STYLE_INDEX (last_run) == -1));
	}
	
	/* ### check that styles are not duplicated */
	for (i = 0; i < n_styles; i ++)
	  {
	    STElement *style;
	    
	    style = ST_ELT (style_table, i);
	    
	    gui_assert (style_count_vec[i] > 0
			&& style_count_vec[i] == ST_ELT_COUNT (style));
	  }
      }
  te_size = GetHandleSize ((Handle) te);
  gui_assert ((((int) sizeof (TERec)
		- (int) sizeof TE_LINE_STARTS (te))
	       + (n_lines + 4) * (int) sizeof TE_LINE_STARTS (te)[1])
	      <= te_size);
  
  for (current_lineno = 0;
       current_lineno < n_lines;
       current_lineno ++)
    {
      int16 current_line_start, next_break;
      
      current_line_start = LINE_START (line_starts,
				       current_lineno);
      next_break = nextbreak (te, current_line_start,
			      length, width);
      gui_assert (next_break == LINE_START (line_starts,
					    current_lineno + 1));
    }
}
#endif


A2 (PUBLIC, void, SetWordBreak, ProcPtr, wb, TEHandle, teh)
{
  TE_SLAM (teh);
  HxX (teh, wordBreak) = RM(wb);
  TE_SLAM (teh);
}

/*
 * TODO: make callback routines conform to IMI-395
 */

/*
 * NOTE: on the 68k, SetClikLoop is provided by glue, and the glue doesn't
 *       correspond to the implementation of SetClikLoop below, because it
 *       has to address the goofy calling conventions that are used there.
 *       So this routine is only useful on the PPC.
 */

A2(PUBLIC, void, SetClikLoop, ProcPtr, cp, TEHandle, teh)
{
  TE_SLAM (teh);
  HxX(teh, clikLoop) = RM(cp);
  TE_SLAM (teh);
}

A1(PUBLIC, INTEGER, ROMlib_wordb, char *, p)			/* INTERNAL */
{
  return (U(*p) <= 0x20);
}

#define MYWORDB(p)  (U(p) <= 0x20)

int16
nextbreak (TEHandle teh, int16 off, int16 len,
	   int16 max_width)
{
  char *sp, *ep;
  char *minsp, *maxsp;
  int16 width = 0;
  Handle hText;
  char *Text;
  INTEGER curpos;
  SignedByte hText_flags;
  int16 retval;

  /* ### warn if the width is less than that of any char? */
  hText = TE_HTEXT (teh);
  hText_flags = HGetState (hText);
  HLock (hText);
  Text = (char *) STARH (hText);
  sp = Text + off;
  ep = Text + len;
  if (off > len)
    warning_unexpected ("off > len");
  minsp = sp; 
  if (Hx(teh, crOnly) < 0)
    {
      while (sp != ep && *sp++ != '\r')
	;
      retval = sp - Text;	/* includes newline */
    }
  else
    {
      if (TE_STYLIZED_P (teh))
	{
	  curpos = off;
	  while (width <= max_width && curpos != len
		 && Text[curpos] != '\r')
	    width += ROMlib_StyleTextWidth (STARH (teh), curpos++, 1);
	  sp = Text + curpos;
	}
      else
	{
	  /* Only the size face and font need to be saved so this is
             overkill. */
	    TESAVE (teh);
	    PORT_TX_SIZE_X (thePort) = TE_TX_SIZE_X (teh);
	    PORT_TX_FACE (thePort) = TE_TX_FACE (teh);
	    PORT_TX_FONT_X (thePort) = TE_TX_FONT_X (teh);
	    while (width <= max_width && sp != ep && *sp != '\r')
		width += CharWidth(*sp++);
	    TERESTORE ();
	}
      if (width > max_width)
	{
	  maxsp = --sp;
	  if (*sp == ' ')
	    {
	      while (sp != ep && *sp == ' ')
		sp++;
	      retval = sp - Text;
	    }
	  else
	    {
	      while (sp > minsp && !MYWORDB (*--sp))
		;
	      if (sp == minsp)
		retval = maxsp - Text;   /* i.e. no wordbreaks */
	      else
		retval = ++sp - Text;    /* include the break on the line */
	    }
	}
      else
	{
	  if (sp == ep)
	    retval = len;        	/* can't go no further */
	  else
	    retval = ++sp - Text;	/* skip over the newline */
	}
    }
  HSetState (hText, hText_flags);
  if (retval == off && off < len) /* always advance at least one character */
    ++retval;
  return retval;
}

/* return the `run' index that contains the current character */
int16 te_char_to_run_index (TEStyleHandle te_style, int16 sel)
{
  StyleRun *current_run;
  int16 n_runs;
  int16 high, low, current;
  int16 retval;
  int16 current_elt;

  n_runs = TE_STYLE_N_RUNS (te_style);
  if (n_runs <= 1)
    return 0;
  
  low = 0;
  high = n_runs;
  current = (low + high) / 2;
  
  while (low < high
	 && (current_run = TE_STYLE_RUN (te_style, current),
	     current_elt = STYLE_RUN_START_CHAR (current_run)) != sel)
    {
      if (current_elt < sel)
	low = current + 1;
      else
	high = current - 1;
      current = (high + low) / 2;
    }
  if (STYLE_RUN_START_CHAR (TE_STYLE_RUN (te_style, current)) > sel)
    retval = current - 1;
  else
    retval = current;
  
  return retval;
}

/* return the line number that contains character index `sel' */
int16 te_char_to_lineno (TEPtr te, int16 sel)
{
  int16 n_lines;
  int16 *line_starts;
  int16 high, low, current;
  int16 retval;
  int16 current_elt;
  
  n_lines = TEP_N_LINES (te);
  if (n_lines <= 1)
    return 0;
  line_starts = TEP_LINE_STARTS (te);
  
  high = n_lines;
  low = 0;
  current = (high + low) / 2;

  while (low < high
	 && (current_elt = CW (line_starts[current])) != sel)
    {
      if (current_elt < sel)
	low = current + 1;
      else
	high = current - 1;
      current = (high + low) / 2;
    }
  if (CW (line_starts[current]) > sel
      || current == n_lines)
    retval = current - 1;
  else
    retval = current;
  
  return retval;
}

int16
calclhtab (TEHandle teh)
{
  int16 n_lines;
  StyleRun *current_run;
  int16 *linestarts;
  int16 first_changed;
  int16 orig_height = -1, orig_ascent = -1;
  LHHandle lh_table;
  LHPtr lh;
  TEStyleHandle sth;
  int clear_lh_p;
  
  n_lines = TE_N_LINES (teh);
  
  sth = TE_GET_STYLE (teh);
  lh_table = TE_STYLE_LH_TABLE (sth);
  SetHandleSize ((Handle) lh_table,
		 sizeof (LHElement) * (n_lines + 1));
  lh = STARH (lh_table);
  first_changed = -1;
  clear_lh_p = TRUE;
  for (current_run = TE_STYLE_RUNS (sth), linestarts = TE_LINE_STARTS (teh);
       linestarts <= (TE_LINE_STARTS (teh) + n_lines);)
    {
      STPtr current_style;
      
      current_style = ST_ELT (TE_STYLE_STYLE_TABLE (sth),
			      STYLE_RUN_STYLE_INDEX (current_run));
      if (clear_lh_p)
	{
	  clear_lh_p = FALSE;
	  if (first_changed == -1)
	    {
	      orig_height = LH_HEIGHT (lh);
	      orig_ascent = LH_ASCENT (lh);
	    }
	  LH_HEIGHT_X (lh) = CWC (0);
	  LH_ASCENT_X (lh) = CWC (0);
	}
      
      if (ST_ELT_HEIGHT (current_style) > LH_HEIGHT (lh))
	LH_HEIGHT_X (lh) = ST_ELT_HEIGHT_X (current_style);
      if (ST_ELT_ASCENT (current_style) > LH_ASCENT (lh))
	LH_ASCENT_X (lh) = ST_ELT_ASCENT_X (current_style);

      if (CW (*linestarts) == TE_LENGTH (teh))
	break;
      
      if (STYLE_RUN_START_CHAR (current_run + 1) > CW (linestarts[1]))
	{
	  if (first_changed == -1
	      && (   LH_HEIGHT (lh) != orig_height
		  || LH_ASCENT (lh) != orig_ascent))
	    first_changed = lh - STARH (lh_table);
	  linestarts ++;
	  lh ++;
	  clear_lh_p = TRUE;
	}
      else if (STYLE_RUN_START_CHAR (current_run + 1) < CW (linestarts[1]))
	current_run ++;
      else
	{
	  if (first_changed == -1
	      && (   LH_HEIGHT (lh) != orig_height
		  || LH_ASCENT (lh) != orig_ascent))
	    first_changed = lh - STARH (lh_table);
	  linestarts ++;
	  lh ++;
	  clear_lh_p = TRUE;
	  current_run ++;
	}
    }
  
  return first_changed;
}

/* guarantee that handle `te' has allocated enough space for at least
   `n_lines' worth of `line_starts' */
void
te_guarantee_line_starts_allocation (TEHandle te, int n_lines)
{
  int te_size;
  int min_te_size;
  
  te_size = GetHandleSize ((Handle) te);
  
  min_te_size = ((sizeof (TERec)
		  - sizeof TE_LINE_STARTS (te))
		 + (n_lines + 4) * sizeof *TE_LINE_STARTS (te));
  if (te_size < min_te_size)
    SetHandleSize ((Handle) te, min_te_size);
}

void
te_set_line_starts_allocation (TEHandle te, int n_lines)
{
  SetHandleSize ((Handle) te,
		 ((sizeof (TERec)
		   - sizeof TE_LINE_STARTS (te))
		  + (n_lines + 4) * sizeof *TE_LINE_STARTS (te)));
}

/* recalculate the various text edit tables associated with `te' after
   `nadded' characters are to be added (or deleted) at `sel' */
void
ROMlib_caltext (TEHandle te,
		int16 sel, int16 n_added,
		int16 *first_changed_out, int16 *last_changed_out)
{
  int16 n_lines;
  int16 *line_starts;
  int16 first_lineno;
  int16 t;
  int16 width;
  int16 length;
  int16 first_changed = -1, last_changed = -1;
  
  width = RECT_WIDTH (&TE_DEST_RECT (te));
  
  if (width <= 0)
    {
      warning_unexpected ("width <= 0");
      return;
    }

  n_lines = TE_N_LINES (te);
  line_starts  = TE_LINE_STARTS (te);
  length = TE_LENGTH (te);
  
  first_lineno = TE_CHAR_TO_LINENO (te, sel);
  
  /* advance each line after the current line forward by `n_added' in
     the optimistic case, where the insertion/deletion changes no
     lines breaks, this is all we will do */
  for (t = first_lineno + 1; t <= n_lines; t ++)
    {
      int line_start = LINE_START (line_starts, t);
      int new_line_start = line_start + n_added;
      
      LINE_START_X (line_starts, t) = CW (new_line_start);
    }
  
  /* starting from the first line, recompute all the end lines.  we
     can stop when we have pasted `sel + n_added', and the end of
     lines are unchanging

     note, as we loop, we may create more lines than there were
     previuosly.  make sure we keep `te' allocated to an appropriate
     length */
  {
    /* break point for the current line, also the start point for
       the next line */
    int16 current_line_break;
    int16 current_lineno;

    /* start with the previous line, since deltion on the current line
       can cause the previous line's break to change */
    for (current_lineno = MAX (first_lineno - 1, 0);; current_lineno ++)
      {
	int16 current_line_start;
	int16 orig_current_line_break;
	
	/* compute the new break for the current line */
	current_line_start = LINE_START (line_starts, current_lineno);
	current_line_break = nextbreak (te, current_line_start,
					length, width);
	
	te_guarantee_line_starts_allocation (te, current_lineno + 1);
	/* pull `line_starts' back out, since the `te' handle may have
	   been relocated */
	line_starts  = TE_LINE_STARTS (te);
	orig_current_line_break = LINE_START (line_starts, current_lineno + 1);
	LINE_START_X (line_starts, current_lineno + 1) = CW (current_line_break);
	
	if (first_changed == -1
	    && orig_current_line_break != current_line_break)
	  first_changed = LINE_START (line_starts, current_lineno);
	
	if (current_line_break == length)
	  {
	    if (length == 0)
	      n_lines = 0;
	    else
	      n_lines = current_lineno + 1;
	    
	    TE_N_LINES_X (te) = CW (n_lines);
	    te_set_line_starts_allocation (te, n_lines);
	    line_starts  = TE_LINE_STARTS (te);
	    LINE_START_X (line_starts, n_lines + 1) = CWC (0);
	    
	    last_changed = length;
	    break;
	  }
	else if (current_line_break == orig_current_line_break
		 && current_line_break > MAX (sel, sel + n_added))
	  {
	    last_changed = LINE_START (line_starts + 1, current_lineno) - 1;
	    break;
	  }
      }
  }
  
  {
    int lh_first_changed = -1;
    
    if (TE_STYLIZED_P (te)
	&& TE_TX_SIZE_X (te) == CWC (-1))
      lh_first_changed = calclhtab (te);
    
    if (lh_first_changed != -1)
      {
	if (first_changed == -1)
	  first_changed = lh_first_changed;
	else
	  first_changed = MIN (lh_first_changed, first_changed);
	last_changed = length;
      }
  }

  if (first_changed == -1)
    first_changed = sel;
  
  if (last_changed == -1
      || last_changed < first_changed)
    last_changed = first_changed + n_added;
  
  if (first_changed_out)
    *first_changed_out = first_changed;
  if (last_changed_out)
    *last_changed_out = last_changed;
}

P1 (PUBLIC pascal trap, void, TECalText, TEHandle, te)
{
  /* don't do this check because people call this caltext when the TE
     has been frobbed and is in a wacky state */
  /* TE_SLAM (te); */
  TE_LENGTH_X (te) = CW (GetHandleSize (TE_HTEXT (te)));
  ROMlib_caltext (te, 0, 32767, NULL, NULL);
  TE_SLAM (te);
}

P3 (PUBLIC pascal trap, int16, TEFeatureFlag,
    int16, feature, int16, action, TEHandle, te)
{
  switch (feature)
    {
    case teFAutoScroll:
      if (action == teBitTest)
	return ((TE_FLAGS (te) & TEAUTOVIEWBIT)
		? teBitSet
		: teBitClear);
      TEAutoView (action == teBitSet, te);
      break;
      
    case teFOutlineHilite:
      /* #### implement outline hilite */
      
    case teFTextBuffering:
    case teFInlineInput:
    case teFUseTextServices:
      warning_unimplemented ("unable to handle te feature flag `%d'",
			     feature);
      return teBitClear;
    }
  
  return action;
}

int16
ROMlib_call_TEDoText (TEPtr tp, int16 first, int16 last, int16 what)
{
  register int16 myd0;

  if ((a0trap INTEGERRET (*) (void)) MR (TEDoText) == (void *) P_ROMlib_dotext)
    myd0 = C_ROMlib_dotext (tp, first, last, what);
  else
    {
      ROMlib_hook (te_dotextnumber);
      {
	int32 saved2, saved3, saved4, saved7, savea2, savea3;

	saved2 = EM_D2;
	saved3 = EM_D3;
	saved4 = EM_D4;
	saved7 = EM_D7;
	savea2 = EM_A2;
	savea3 = EM_A3;
	EM_A3 = (LONGINT) (long) US_TO_SYN68K(tp);
	EM_D3 = (LONGINT) first;
	EM_D4 = (LONGINT) last;
	EM_D7 = (LONGINT) what;
	EM_A0 = (LONGINT) (long) CL ((long) TEDoText);
	CALL_EMULATOR (EM_A0);
	myd0 = EM_D0;
	EM_D2 = saved2;
	EM_D3 = saved3;
	EM_D4 = saved4;
	EM_D7 = saved7;
	EM_A2 = savea2;
	EM_A3 = savea3;
      }
    }
  return myd0;
}
