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

#if !defined (OMIT_RCSID_STRINGS)
char ROMlib_rcsid_teEdit[] =
	    "$Id: teEdit.c 88 2005-05-25 03:59:37Z ctm $";
#endif

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

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

#include "rsys/cquick.h"
#include "rsys/mman.h"
#include "rsys/tesave.h"
#include "rsys/arrowkeys.h"
#include "rsys/screen.h"
#include "rsys/notmac.h"
#include "rsys/osevent.h"

void
tedoinserttext (TEHandle te, int16 hlen, int16 len,
		int16 start, Ptr ptr)
{
  Handle hText;
  char *Text;

  hText = TE_HTEXT (te);
  SetHandleSize (hText, hlen + len);
       
  Text = (char *) STARH (hText);
  
  memmove (&Text[start + len], &Text[start], hlen - start);
  memmove (&Text[start], ptr, len);
  
  HASSIGN_3
    (te,
     selEnd, CW (TE_SEL_END (te) + len),
     selStart, CW (TE_SEL_START (te) + len),
     teLength, CW (TE_LENGTH (te) + len));
}

void
ROMlib_teremovestyleinfo (TEStyleHandle te_style,
			  int16 start, int16 end)
{
  StyleRun *runs, *start_run, *prev_run;
  int16 start_run_index, end_run_index;
  int16 current_run_index;
  int16 shift;
  int16 n_runs;
  
  if (start == end)
    return;
  start_run_index = make_style_run_at (te_style, start);
  end_run_index = make_style_run_at (te_style, end);
  
  runs = TE_STYLE_RUNS (te_style);
  n_runs = TE_STYLE_N_RUNS (te_style);

  start_run = &runs[start_run_index];
  prev_run = &runs[start_run_index - 1];
  if (start_run_index
      && (RUN_STYLE_INDEX (start_run) != RUN_STYLE_INDEX (prev_run)))
    {
      StScrpHandle null_scrap;
      ScrpSTElement *scrap_elt;
      STElement *st_elt;
      
      null_scrap = TE_STYLE_NULL_SCRAP (te_style);
      
      SCRAP_N_STYLES_X (null_scrap) = CWC (1);
      SetHandleSize ((Handle) null_scrap, SCRAP_SIZE_FOR_N_STYLES (1));
      scrap_elt = SCRAP_ST_ELT (null_scrap, 0);
      st_elt = ST_ELT (TE_STYLE_STYLE_TABLE (te_style),
		       RUN_STYLE_INDEX (start_run));
      
      generic_elt_copy (SCRAP_ELT_TO_GENERIC_ELT (scrap_elt),
			ST_ELT_TO_GENERIC_ELT (st_elt));
      SCRAP_ELT_START_CHAR_X (scrap_elt) = CLC (0);
    }
  
  shift = end - start;
  for (current_run_index = start_run_index;
       current_run_index < end_run_index;
       current_run_index ++)
    {
      StyleRun *current_run;
      
      current_run = &runs[current_run_index];
      release_style_index (te_style, STYLE_RUN_STYLE_INDEX (current_run));
    }
  for (; current_run_index <= n_runs; current_run_index ++)
    {
      StyleRun *current_run;
      
      current_run = &runs[current_run_index];
      STYLE_RUN_START_CHAR_X (current_run)
	= CW (STYLE_RUN_START_CHAR (current_run) - shift);
    }
  
  memmove (&runs[start_run_index], &runs[end_run_index],
	   (n_runs - end_run_index + 1) * sizeof *runs);
  n_runs -= end_run_index - start_run_index;
  TE_STYLE_N_RUNS_X (te_style) = CW (n_runs);
  SetHandleSize ((Handle) te_style,
		 TE_STYLE_SIZE_FOR_N_RUNS (n_runs));
  
  te_style_combine_runs (te_style);
  stabilize_style_info (te_style);
}

static void
tereplaceselection (TEHandle teh, int16 start, int16 stop, int16 len,
		    Ptr ptr, int16 nchar, int16 hlen)
{
  Handle hText;
  /* number of characters after teh selection to relocate */
  int16 n_to_move;
  
  hText = TE_HTEXT (teh);
  n_to_move = hlen - stop;
  if (nchar > 0)
    SetHandleSize (TE_HTEXT (teh), hlen + nchar);
  if (nchar != 0)
    {
      BlockMove (STARH (hText) + stop,
		 STARH (hText) + start + len,
		 n_to_move);
    }
  if (nchar < 0)
    SetHandleSize (hText, hlen + nchar);
  BlockMove (ptr, STARH (hText) + start, len);
  
  TE_SEL_END_X (teh) = TE_SEL_START_X (teh)
    = CW (TE_SEL_START (teh) + len);
  TE_LENGTH_X (teh) = CW (hlen + nchar);
  TE_CARET_STATE_X (teh) = CWC (-1);	/* will be highlit below */
}

void
te_style_insert_runs (TEStyleHandle te_style,
		      int16 start, int len,
		      StyleRun *new_runs, int16 n_new_runs)
{
  STHandle style_table;
  StyleRun *runs;
  int16 start_run_index;
  int16 run_i, n_runs;
  
  start_run_index = te_char_to_run_index (te_style, start);
  runs = TE_STYLE_RUNS (te_style);
  n_runs = TE_STYLE_N_RUNS (te_style);
  style_table = TE_STYLE_STYLE_TABLE (te_style);
  for (run_i = start_run_index + 1; run_i <= n_runs; run_i ++)
    {
      StyleRun *run = &runs[run_i];
      
      STYLE_RUN_START_CHAR_X (run)
	= CW (STYLE_RUN_START_CHAR (run) + len);
    }
  
  for (run_i = 0; run_i < n_new_runs; run_i ++)
    {
      int16 style_index, new_style_index;
      StyleRun *new_run, *next_new_run, *run;
      int16 run_start, run_end, run_index;
      
      new_run = &new_runs[run_i];
      next_new_run = &new_runs[run_i + 1];
      
      /* compute `run'.  since we are going to change the style for
	 this run, make sure that `run' contains only the range to be
	 changed */
      run_start = STYLE_RUN_START_CHAR (new_run) + start;
      run_index = make_style_run_at (te_style, run_start);
      
      run_end = STYLE_RUN_START_CHAR (next_new_run) + start;
      make_style_run_at (te_style, run_end);
      
      run = TE_STYLE_RUN (te_style, run_index);
      
      new_style_index = STYLE_RUN_STYLE_INDEX (new_run);
      style_index = STYLE_RUN_STYLE_INDEX (run);
      
      if (style_index != new_style_index)
	{
	  STElement *new_style;
	  
	  new_style = ST_ELT (style_table, new_style_index);
	  
	  ST_ELT_COUNT_X (new_style) = CW (ST_ELT_COUNT (new_style) + 1);
	  release_style_index (te_style, style_index);
	  
	  STYLE_RUN_STYLE_INDEX_X (run) = CW (new_style_index);
	}
    }
  stabilize_style_info (te_style);
  te_style_combine_runs (te_style);
}

void
ROMlib_teinsertstyleinfo (TEHandle te,
			  int16 start, int16 len, StScrpHandle scrap)
{
  StScrpHandle null_scrap;
  TEStyleHandle te_style;
  int16 scrap_n_styles;
  int cleanup_scrap_p = FALSE;
  StyleRun *new_runs;
  int i;
  
  te_style = TE_GET_STYLE (te);
  
  if (!scrap)
    {
      null_scrap = TE_STYLE_NULL_SCRAP (te_style);
      
      if (SCRAP_N_STYLES (null_scrap))
	scrap = null_scrap;
      else
	{
	  int16 start_run_index;
	  StyleRun *start_run;
	  STElement *start_st_elt;
	  ScrpSTElement *scrap_elt;
	  STHandle style_table;
	  
	  /* allocate a temp scrap */
	  /* enough for a single scrap style element */
	  scrap = (StScrpHandle) NewHandle (sizeof (StScrpRec));
	  cleanup_scrap_p = TRUE;
	  
	  SCRAP_N_STYLES_X (scrap) = CWC (1);
	  scrap_elt = SCRAP_ST_ELT (scrap, 0);
	  
	  start_run_index = te_char_to_run_index (te_style, (start
							     ? start - 1
							     : start));
	  start_run = TE_STYLE_RUN (te_style, start_run_index);
	  style_table = TE_STYLE_STYLE_TABLE (te_style);
	  start_st_elt = ST_ELT (style_table, STYLE_RUN_STYLE_INDEX (start_run));

	  generic_elt_copy (SCRAP_ELT_TO_GENERIC_ELT (scrap_elt),
			    ST_ELT_TO_GENERIC_ELT (start_st_elt));
	  SCRAP_ELT_START_CHAR_X (scrap_elt) = CLC (0);
	}
    }

  scrap_n_styles = SCRAP_N_STYLES (scrap);
  new_runs = alloca ((scrap_n_styles + 1) * sizeof *new_runs);
  for (i = 0; i < scrap_n_styles; i ++)
    {
      ScrpSTElement *scrap_elt;
      StyleRun *new_run;
      int16 style_index;
      
      new_run = &new_runs[i];
      scrap_elt = SCRAP_ST_ELT (scrap, i);
      
      style_index = get_style_index (te_style, SCRAP_ELT_TO_ATTR (scrap_elt),
				     FALSE);
      
      /* must swap here, the elt start char is a `int32' */
      STYLE_RUN_START_CHAR_X (new_run) = CW (SCRAP_ELT_START_CHAR (scrap_elt));
      STYLE_RUN_STYLE_INDEX_X (new_run) = CW (style_index);
    }
  {
    StyleRun *new_run;
    
    new_run = &new_runs[scrap_n_styles];
    STYLE_RUN_START_CHAR_X (new_run) = CW (len);
    STYLE_RUN_STYLE_INDEX_X (new_run) = CWC (-1);
  }
  
  te_style_insert_runs (te_style, start, len, new_runs, scrap_n_styles);
  
  if (cleanup_scrap_p)
    DisposHandle ((Handle) scrap);
}

PUBLIC int ROMlib_forward_del_p = FALSE;

void
ROMlib_tedoitall (TEHandle teh, Ptr ptr,      /* INTERNAL */
		  int16 len, boolean_t insert, StScrpHandle styleh)
{
  INTEGER start, stop, sellen, hlen, nchar;
  int16 calstart, calend;
  Point oldend, newend;
  Rect eraser;
  TEStyleHandle te_style;
  INTEGER oldlh, newlh;
  LHHandle lht;
  
  TESAVE (teh);
  TRAPBEGIN ();
  
#if !defined (LETGCCWAIL)
  te_style = NULL;
  newlh = 0;
#endif /* LETGCCWAIL */
  
  if (TE_STYLIZED_P (teh))
    te_style = TE_GET_STYLE (teh);
  
  if (TE_STYLIZED_P (teh)
      && TE_LINE_HEIGHT (teh) == -1)
    {
      lht = TE_STYLE_LH_TABLE (te_style);
      oldlh = CW ((STARH(lht) + Hx(teh, nLines) - 1)->lhHeight);
    }
  else
    {
      oldlh = newlh = TE_LINE_HEIGHT (teh);
      lht = NULL;
    }

#if 1
  /* If start and stop are both -1 on a Mac, text is inserted at the end.
     My guess is they're using unsigned numbers, so this is a hacky
     approximation until we can run a lot of tests to figure out exactly
     what is going on.  PhysTCL points this out. */

  if (TE_SEL_START (teh) < 0)
    TE_SEL_START_X (teh) = CWC (32767);
  if (TE_SEL_END (teh) < 0)
    TE_SEL_END_X (teh) = CWC (32767);
#endif
  
  start = TE_SEL_START (teh);
  stop  = TE_SEL_END (teh);
  hlen = TE_LENGTH (teh);

  if (start < 0)
    {
      warning_unexpected ("start = %d", start);
      TE_SEL_START_X (teh) = CWC (0);
      start = 0;
    }
  
  if (stop < 0)
    {
      warning_unexpected ("stop = %d", stop);
      TE_SEL_END_X (teh) = CWC (0);
      stop = 0;
    }

  if (hlen < 0)
    {
      warning_unexpected ("nlen = %d", hlen);
      TE_LENGTH_X (teh) = CWC (0);
      hlen = 0;
    }

  if (start > hlen)
    {
      warning_unexpected ("start = %d, hlen = %d", start, hlen);
      TE_SEL_START_X (teh) = CW (hlen);
      start = hlen;
    }

  if (stop > hlen)
    {
      warning_unexpected ("stop = %d, hlen = %d", stop, hlen);
      TE_SEL_END_X (teh) = CW (hlen);
      stop = hlen;
    }

  if (start > stop)
    {
      warning_unexpected ("start = %d, stop = %d", start, stop);
      TE_SEL_START_X (teh) = CW (stop);
      start = stop;
    }

  TE_CHAR_TO_POINT (teh, hlen, &oldend);
  
  if (TE_CARET_STATE (teh) != 255)
    /* turn off any highliting */
    ROMlib_togglelite (teh);
  if (insert)
    {
      nchar = len;
      tedoinserttext (teh, hlen, len, start, ptr);
    }
  else
    {
      if (ptr && (*ptr == '\010' || *ptr == '\177'))
	{
	  ptr++;
	  len--;
	  if (start > 0 && start == stop)
	    {
	      if (*ptr == '\010' || !ROMlib_forward_del_p)
		{
		  HxX(teh, selStart) = CW(Hx(teh, selStart) - 1);
		  start = Hx(teh, selStart);
		}
	      else
		{
		  if (stop < hlen)
		    {
		      HxX(teh, selEnd) = CW(Hx(teh, selEnd) + 1);
		      stop = Hx(teh, selEnd);
		    }
		}
	    }
	}
      sellen = stop - start;
      nchar = len - sellen;
      if (TE_STYLIZED_P (teh))
	ROMlib_teremovestyleinfo (te_style, start, stop);
      tereplaceselection(teh, start, stop, len, ptr, nchar, hlen);
    }
  if (TE_STYLIZED_P (teh) && len != 0)
    ROMlib_teinsertstyleinfo (teh, start, len, styleh);
  ROMlib_caltext (teh, start, nchar, &calstart, &calend);
  TE_CHAR_TO_POINT (teh, TE_LENGTH (teh), &newend);
  if (TE_STYLIZED_P (teh) && TE_LINE_HEIGHT (teh) == -1)
    newlh = CW ((STARH (lht) + TE_N_LINES (teh) - 1)->lhHeight);
  
  if (oldend.v > newend.v)
    {
      eraser.top  = CW(newend.v);
      eraser.left = CW(newend.h);
      eraser.bottom = CW(newend.v + newlh);
      eraser.right = HxX(teh, viewRect.right);
      SectRect(&HxX(teh, viewRect), &eraser, &eraser);
      EraseRect(&eraser);
      eraser.top = eraser.bottom;
      eraser.left = HxX(teh, viewRect.left);
      if (eraser.top != CW(oldend.v))
	{
	  eraser.bottom = CW(oldend.v);
	  eraser.right  = HxX(teh, viewRect.right);
	  SectRect(&HxX(teh, viewRect), &eraser, &eraser);
	  EraseRect(&eraser);
	  eraser.top = CW(oldend.v);
	}
      eraser.bottom = CW(oldend.v + oldlh);
      eraser.right = CW(oldend.h);
      SectRect(&HxX(teh, viewRect), &eraser, &eraser);
      EraseRect(&eraser);
    }
  else if (oldend.v == newend.v && oldend.h > newend.h)
    {
      eraser.top    = CW(oldend.v);
      eraser.left   = CW(newend.h);
      eraser.bottom = CW(oldend.v + oldlh);
      eraser.right  = CW(oldend.h);
      SectRect(&HxX(teh, viewRect), &eraser, &eraser);
      EraseRect(&eraser);
    }
  if (TE_STYLIZED_P (teh))
    SCRAP_N_STYLES_X (TE_STYLE_NULL_SCRAP (te_style)) = CWC (0);
  
  TE_DO_TEXT (teh, calstart, calend, teDraw);
  
  if (TE_SEL_START_X (teh) != TE_SEL_END_X (teh))
    /* turn on any highliting */
    ROMlib_togglelite (teh);
  else
    TE_CARET_STATE_X (teh) = CWC (255);
  
  TRAPEND ();
  TERESTORE ();
}

static void doarrow (TEHandle te, CHAR thec)
{
  int16 sel_start, sel_end;
  int16 length;
  int16 lineno;
  Point pt;
  TEPtr tep;
  SignedByte te_flags;
  Byte c;
  
  TESAVE (te);
  TRAPBEGIN ();
  te_flags = HGetState ((Handle) te);
  HLock ((Handle) te);
  tep = STARH (te);
  
  c = thec;
  sel_start = TEP_SEL_START (tep);
  sel_end = TEP_SEL_END (tep);
  length = TEP_LENGTH (tep);
  
  if  (TEP_CARET_STATE_X (tep) != CWC (caret_invis))
    {
      ROMlib_togglelite (te);
      TEP_CARET_STATE_X (tep) = CWC (caret_invis);
    }
  
  switch(c)
    {
    case ASCIILEFTARROW:
      if (sel_start)
	sel_start --;
      break;
    case ASCIIRIGHTARROW:
      sel_start = sel_end;
      if (sel_start < length)
	sel_start ++;
      break;
    case ASCIIUPARROW:
    case ASCIIDOWNARROW:
      {
	int16 offset;

	TEP_CHAR_TO_POINT (tep, sel_start, &pt);
	if (TEP_LINE_HEIGHT (tep) != -1)
	  offset = TEP_LINE_HEIGHT (tep);
	else if (c == ASCIIUPARROW)
	  offset = 1;
	else
	  {
	    lineno = TEP_CHAR_TO_LINENO (tep, sel_start); 
	    offset = TEP_HEIGHT_FOR_LINE (tep, lineno);
	  }
	if (c == ASCIIUPARROW)
	  pt.v -= offset;
	else
	  pt.v += offset;
	TEP_SEL_POINT (tep).h = CW (pt.h);
	TEP_SEL_POINT (tep).v = CW (pt.v);
	sel_start = TEP_DO_TEXT (tep, 0, length, teFind);
	break;
      }
    }
  
  TEP_SEL_START_X (tep) = CW (sel_start);
  TEP_SEL_END_X (tep) = CW (sel_start);
  if (TEP_CARET_STATE (tep))
    TEP_CARET_STATE_X (tep) = CWC (caret_vis);
  ROMlib_togglelite (te);
  HSetState ((Handle) te, te_flags);
  
  TRAPEND ();
  TERESTORE ();
}

P2(PUBLIC pascal trap, void, TEKey, CHAR, thec, TEHandle, te)
{
  Byte c;
  
  TE_SLAM (te);
  
  c = thec;
  switch (c)
    {
    case ASCIILEFTARROW:	/* <-- left*/
    case ASCIIRIGHTARROW:	/* --> right*/
    case ASCIIUPARROW:		/* ^ up*/
    case ASCIIDOWNARROW:	/* v down*/
      doarrow (te, c);
      if (TE_STYLIZED_P (te))
	{
	  TEStyleHandle te_style;

	  te_style = TE_GET_STYLE (te);
	  SCRAP_N_STYLES_X (TE_STYLE_NULL_SCRAP (te_style)) = CWC (0);
	}
      break;
    case NUMPAD_ENTER:
      c = '\r';
      /* FALL THROUGH */
    default:
      ROMlib_tedoitall (te, (Ptr) &c, 1, FALSE, NULL);
      break;
    }
  ROMlib_recompute_caret (te);
  TE_SLAM (te);
}

P1 (PUBLIC pascal trap, void, TECopy, TEHandle, te)
{
  Handle hText;
  SignedByte hText_flags;
  char *Text;
  int16 len;
  int16 start, end;
  
  start = TE_SEL_START (te);
  end = TE_SEL_END (te);
  
  len = end - start;
  
  hText = TE_HTEXT (te);
  hText_flags = HGetState (hText);
  HLock (hText);
  Text = (char *) STARH (hText);
  
  PtrToXHand ((Ptr) &Text[start], MR (TEScrpHandle), len);
  if (TE_STYLIZED_P (te))
    {
      TEStyleHandle te_style;
      SignedByte te_style_flags;
      STHandle style_table;
      StScrpHandle scrap;
      int16 start_run_index, end_run_index, current_run_index;
      int16 n_scrap_styles;
      StyleRun *runs;
      
      ZeroScrap ();
      PutScrap (len, TICK ("TEXT"), (Ptr) &Text[start]);
      
      te_style = TE_GET_STYLE (te);
      te_style_flags = HGetState ((Handle) te_style);
      HLock ((Handle) te_style);

      style_table = TE_STYLE_STYLE_TABLE (te_style);
      runs = TE_STYLE_RUNS (te_style);
      
      n_scrap_styles = 0;
      scrap = (StScrpHandle) NewHandle (SCRAP_SIZE_FOR_N_STYLES
					(n_scrap_styles));
      
      start_run_index = te_char_to_run_index (te_style, start);
      /* ### boundary case, do we put an extra run into the scrap? */
      end_run_index = te_char_to_run_index (te_style, end);
      
      for (current_run_index = start_run_index;
	   current_run_index <= end_run_index;
	   current_run_index ++)
	{
	  StyleRun *current_run;
	  STElement *style;
	  ScrpSTElement *scrap_elt;
	  int16 run_start;
	  
	  n_scrap_styles ++;
	  SetHandleSize ((Handle) scrap,
			 SCRAP_SIZE_FOR_N_STYLES (n_scrap_styles));
	  
	  current_run = &runs[current_run_index];
	  style = ST_ELT (style_table, RUN_STYLE_INDEX (current_run));
	  scrap_elt = SCRAP_ST_ELT (scrap, n_scrap_styles - 1);
	  
	  generic_elt_copy (SCRAP_ELT_TO_GENERIC_ELT (scrap_elt),
			    ST_ELT_TO_GENERIC_ELT (style));
	  run_start = RUN_START_CHAR (current_run);
	  SCRAP_ELT_START_CHAR_X (scrap_elt) = (run_start < start
						? CLC (0)
						: CL (run_start - start));
	}
      SCRAP_N_STYLES_X (scrap) = CW (n_scrap_styles);
      
      LOCK_HANDLE_EXCURSION_1
	(scrap,
	 {
	   PutScrap (SCRAP_SIZE_FOR_N_STYLES (n_scrap_styles),
		     TICK ("styl"), (Ptr) STARH (scrap));
	 });
      DisposHandle ((Handle) scrap);
      
      HSetState ((Handle) te_style, te_style_flags);
    }
  TEScrpLength = CW (len);
#if defined(X) || defined(NEXTSTEP) || defined(SDL)
  /* ### should this lock `TEScrpHandle'? */
  PutScrapX (TICK("TEXT"), CW (TEScrpLength),
	     (char *) STARH (MR (TEScrpHandle)), CW(ScrapCount));
#endif /* defined(X) */

  HSetState (hText, hText_flags);
}

P1(PUBLIC pascal trap, void, TECut, TEHandle, teh)
{
  TECopy (teh);
  ROMlib_tedoitall (teh, NULL, 0, FALSE, NULL);
}

P1(PUBLIC pascal trap, void, TEPaste, TEHandle, teh)
{
#if defined(X) || defined(NEXTSTEP) || defined(SDL)
  Size s;
  
  s = GetScrapX (TICK("TEXT"), (char **) MR (TEScrpHandle));
  if (s >= 0)
    TEScrpLength = CW (s);
#endif /* defined(X) */
  LOCK_HANDLE_EXCURSION_1
    (MR (TEScrpHandle),
     {
       ROMlib_tedoitall (teh, STARH (MR (TEScrpHandle)), CW (TEScrpLength),
			 FALSE, NULL);
     });
}

P1 (PUBLIC pascal trap, void, TEDelete, TEHandle, teh)
{
  ROMlib_tedoitall (teh, NULL, 0, FALSE, NULL);
}

P3 (PUBLIC pascal trap, void, TEInsert, Ptr, p, LONGINT, ln, TEHandle, teh)
{
  ROMlib_tedoitall(teh, p, ln, TRUE, NULL);
}
