/*
 *	Universal Terminal Interface -- Cursor Movement
 *
 *	(c) 1997--1999 Martin Mares, <mj@ericsson.cz>
 */

#include <stdio.h>

#include "termint.h"

/* Refreshing primitives */

static inline void
refresh_single_char(ulg pos)
{
  cold[pos] = cnew[pos];
  aold[pos] = set_attrs;
  kold[pos] = set_color;
}

static inline void
send_single_char(ulg pos)
{
  send_char(cnew[pos]);
  refresh_single_char(pos);
}

/* Cursor parameters */

ulg set_r, set_c;			/* Coordinates of physical cursor */
ulg lastc_r, lastc_c;			/* Coordinates of logical cursor at last mode change */
byte set_cm;				/* Cursor mode currently active */
byte set_cshape;			/* Cursor shape corresponding to set_cm (internal to set_cursor_shape) */
byte current_cm;			/* Cursor mode to be set */
static int no_goto_refresh;		/* Don't do costless refresh when moving cursor */
static byte cannot_move_with_attr;	/* Attributes we cannot move with */
static byte cannot_tab_with_attr;	/* Attributes we cannot use tabs with */

/* Simple macros */

#define CAN_DO_SIMPLE_REFRESH(pos) ( \
knew[pos] == set_color && \
anew[pos] == set_attrs && \
(!BOOL(ONLY_CLR_RESETS_ATTR) || aold[pos] == set_attrs) && \
(!overstriking || (overstriking == OS_SPACE && anew[pos] == ' '))) \

#define CAN_DO_CHEAP_REFRESH(pos) \
(cnew[pos] != cold[pos] && CAN_DO_SIMPLE_REFRESH(pos) && !no_goto_refresh)

/* Initialization */

void
cursor_init(void)
{
  current_cm = CM_FOLLOW;
  no_goto_refresh = CTRL(NO_GOTO_REFRESH);
  cannot_move_with_attr = BOOL(CAN_MOVE_WITH_ATTR) ? 0xff : 0;
  cannot_tab_with_attr = ATTR_ALTERNATE; /* Ugly! */
  DBG(("Goto refresh: %d, move-no-attr=%02x, tab-no-attr=%02x\n",
       !no_goto_refresh, cannot_move_with_attr, cannot_tab_with_attr));
}

/* Cursor shape setting */

void
set_cursor_shape(void)
{
  if ((set_cm ^ current_cm) & CM_SHAPEMASK)
    {
      char *c;
      int i = current_cm & CM_SHAPEMASK;

      if (i == CM_ENHANCED && STR(CURSOR_ENHANCE))
	;
      else if (i == CM_INVISIBLE && STR(CURSOR_OFF))
	;
      else
	i = CM_NORMAL;
      if (i != set_cshape)
	{
	  switch (i)
	    {
	    case CM_INVISIBLE:
	      c = STR(CURSOR_OFF);
	      break;
	    case CM_ENHANCED:
	      c = STR(CURSOR_ENHANCE);
	      break;
	    default:
	      c = STR(CURSOR_NORMAL);
	    }
	  send(c);
	  set_cshape = i;
	}
    }

  set_cm = current_cm;
}

void
Tsetcursor(int m)
{
  lastc_r = write_r;
  lastc_c = write_c;
  current_cm = m;
}

/* Go to given position (cursor optimizer) */

#define XC(k,l) ( (l)<10 ? cost_##k.c0 : (l)<100 ? cost_##k.c1 : cost_##k.c2 )

static inline int
try_direct(int r, int c)
{
  int p = XC(move_row, r) + XC(move_col, c);
  return p;
}

static inline void
do_skip_right(int cc, int c, int pos)
{
  pos += cc;
  while (cc < c)
    {
      if (CAN_DO_SIMPLE_REFRESH(pos))
	send_single_char(pos);
      else
	send(STR(RIGHT));
      cc++;
      pos++;
    }
}

static inline void
do_skip_left(int c, int new)
{
  while (c > new)
    {
      send(STR(LEFT));
      c--;
    }
}

static inline int
skip_right_cost(int x, int c, int pos)
{
  int cost = 0;

  pos += x;
  while (x < c)
    {
      if (CAN_DO_SIMPLE_REFRESH(pos))
	{
	  if (cnew[pos] == cold[pos] || no_goto_refresh) /* Refresh? */
	    cost++;
	}
      else
	cost += cost_right;
      if (cost > 10)
	return 10000;
      x++;
      pos++;
    }
  return cost;
}

static int
try_vrel(int rr, int r, int r0, int r1, int *rem)
{
  int alg, best, cost;

  if (rr == r)
    {
      best = 0;
      alg = 0;
      goto vdone;
    }

  best = XC(set_row, r);
  alg = 1;

  if (r < rr)
    {
      cost = XC(multi_up, rr - r);
      if (cost < best)
	best = cost, alg = 2;
      cost = cost_up * (rr - r);
      if (cost < best)
	best = cost, alg = 4;
    }
  else
    {
      cost = XC(multi_down, r - rr);
      if (cost < best)
	best = cost, alg = 3;
      cost = cost_down * (r - rr);
      if (cost < best)
	best = cost, alg = 5;
    }

vdone:
  TR("\t\tvertical cost=%d, alg=%d\n", best, alg);
  *rem = alg;
  return best;
}

static int
try_tab(int pos, int c)
{
  int n = NEXT_TAB_STOP(c);
  int pos2, cost;

  if (!use_tabs || (set_attrs & cannot_tab_with_attr))
    return 10000;
  cost = 0;
  pos2 = pos + c;
  while (c < n && CAN_DO_CHEAP_REFRESH(pos2)) /* Refresh with zero cost */
    {
      pos2++;
      c++;
    }
  if (c == n)				/* Everything skipped by refresh */
    return cost;
  if (use_tabs == TABS_NORMAL)		/* Not destructive */
    return cost + cost_tab;

  pos += n;				/* Find refreshable spaces at the end */
  while (pos2 < pos && CAN_DO_SIMPLE_REFRESH(pos-1) && cnew[pos-1] == ' ')
    pos--;
  if (pos2 + 4 < pos)			/* Too many to rewrite => reject */
    return 10000;
  while (pos2 < pos)
    {
      if (CAN_DO_SIMPLE_REFRESH(pos2))	/* Needs to be rewritten */
	cost++;
      else
	cost += cost_right;
      pos2++;
      c++;
    }
  if (c < n)
    {
      if (n - c < cost_tab)		/* Use spaces */
	cost += n - c;
      else				/* Use a tab */
	cost += cost_tab;
    }
  return cost;
}

static int
try_hrel(int cc, int c, int c0, int c1, int *rem, ulg pos)
{
  int alg, cost, best, x, y, cost2, lma;

  if (cc == c)
    {
      best = 0;
      alg = 0;
      goto hdone;
    }

  best = XC(set_column, c);
  alg = 1;

  if (cc < c)
    {
      cost = XC(multi_right, c - cc);
      if (cost < best)
	best = cost, alg = 2;

      cost = 0;				/* (TAB|Write)* ((Right|Write)* | Left*) */
      x = cc;
      while (NEXT_TAB_STOP(x) <= c)	/* TABs */
	{
	  cost += try_tab(pos, x);
	  x = NEXT_TAB_STOP(x);
	  if (cost > 10)
	    goto notab;
	}
      cost2 = cost;			/* The variant with Right/Write */
      cost += skip_right_cost(x, c, pos);
      if (cost < best)
	best = cost, alg = 4;

      if (NEXT_TAB_STOP(x) < c1)	/* The variant with TAB/Left */
	{
	  cost2 += try_tab(pos, x);
	  x = NEXT_TAB_STOP(x);
	  cost2 += cost_left * (x - c);
	  if (cost2 < best)
	    best = cost2, alg = 5;
	}
    notab:;
    }
  else					/* Going left */
    {
      cost = XC(multi_left, cc - c);
      if (cost < best)
	best = cost, alg = 3;

      x = cc;				/* BackTab* (Left* | (Right|Write)*) */
      cost = 0;
      lma = NEXT_TAB_STOP(c0);
      if (use_tabs == TABS_NORMAL && STR(BACK_TAB))
	{
	  y = 1;
	  while (x >= lma && PREV_TAB_STOP(x) >= c)
	    {
	      cost += cost_back_tab;
	      x = PREV_TAB_STOP(x);
	    }
	}
      else
	y = 0;
      cost2 = cost + (x - c) * cost_left; /* + Left* */
      if (cost2 < best)
	best = cost2, alg = 6;
      if (y && x >= lma)		/* + BackTab Right* */
	{
	  cost += cost_back_tab;
	  cost += skip_right_cost(PREV_TAB_STOP(x), c, pos);
	  if (cost < best)
	    best = cost, alg = 7;
	}
    }

hdone:
  TR("\t\thorizontal cost=%d, alg=%d\n", best, alg);
  *rem = alg;
  return best;
}

static int
try_rel(int cost, int rr, int cc, int r, int c, int r0, int r1, int c0, int c1, int *rem, int limit)
{
  int temp;

  TR("rel: init_cost=%d, %d.%d -> %d.%d\n", cost, rr, cc, r, c);

  if (cost >= 1000)
    return cost;
  if (rr == r && cc == c)
    {
      *rem = 0;
      TR("\t\tcost = %d (already there)\n", cost);
      return cost;
    }

  cost += try_vrel(rr, r, r0, r1, &temp);
  if (cost >= limit)
    {
      TR("\t\tpartial cost = %d (too much, rejected)\n", cost);
      cost = 10000;
    }
  else
    {
      cost += try_hrel(cc, c, c0, c1, rem, r * columns);
      *rem |= (temp << 16);
      TR("\t\tcost = %d rem %d\n", cost, *rem);
    }
  return cost;
}

static void
do_vrel(int rr, int r, int r0, int r1, int rem)
{
  switch (rem)
    {
    case 0:
      break;
    case 1:
      sendp(STR(SET_ROW), r);
      break;
    case 2:
      sendp(STR(MULTI_UP), rr - r);
      break;
    case 3:
      sendp(STR(MULTI_DOWN), r - rr);
      break;
    case 4:
      while (rr > r)
	{
	  send(STR(UP));
	  rr--;
	}
      break;
    case 5:
      while (rr < r)
	{
	  send(STR(DOWN));
	  rr++;
	}
      break;
    default:
      fatal_error("do_vrel confused");
    }
}

static int
do_tab(ulg pos, int c)
{
  int n = NEXT_TAB_STOP(c);
  int pos2;

  if (!use_tabs)
    fatal_error("do_tab called, but no tabs available");
  pos2 = pos + c;
  while (c < n && CAN_DO_CHEAP_REFRESH(pos2))
    {
      send_single_char(pos2);
      c++;
      pos2++;
    }
  if (c == n)
    return n;
  if (use_tabs == TABS_NORMAL)
    {
      sendpad(STR(TAB), INT(PAD_TAB));
      return n;
    }

  pos += n;				/* Destructive tab! */
  while (pos2 < pos && CAN_DO_SIMPLE_REFRESH(pos-1) && cnew[pos-1] == ' ')
    pos--;
  while (pos2 < pos)
    {
      if (CAN_DO_SIMPLE_REFRESH(pos2))
	send_single_char(pos2);
      else if (STR(RIGHT))
	send(STR(RIGHT));
      else
	fatal_error("do_tab: RIGHT ordered, but not defined");
      pos2++;
      c++;
    }
  if (c < n)
    {
      if (n - c >= cost_tab)		/* Use a TAB */
	{
	  sendpad(STR(TAB), INT(PAD_TAB));
	  while (c < n)
	    {
	      refresh_single_char(pos);
	      pos++;
	      c++;
	    }
	}
      else				/* Use spaces */
	while (c < n)
	  {
	    send_single_char(pos);
	    pos++;
	    c++;
	  }
    }
  return n;
}

static void
do_hrel(int cc, int c, int c0, int c1, int rem, ulg pos)
{
  switch (rem)
    {
    case 0:
      break;
    case 1:
      sendp(STR(SET_COLUMN), c);
      break;
    case 2:
      sendp(STR(MULTI_RIGHT), c - cc);
      break;
    case 3:
      sendp(STR(MULTI_LEFT), cc - c);
      break;
    case 4:
    case 5:
      while (NEXT_TAB_STOP(cc) <= c)	/* TABs */
	cc = do_tab(pos, cc);
      if (rem == 4)			/* Right/Write */
	do_skip_right(cc, c, pos);
      else				/* TAB/Left */
	{
	  cc = do_tab(pos, cc);
	  do_skip_left(cc, c);
	}
      break;
    case 6:				/* BACKTABs and then Left/Right/Write */
    case 7:
      if (use_tabs == TABS_NORMAL && STR(BACK_TAB))
	{
	  int lma = NEXT_TAB_STOP(c0);
	  while (cc >= lma && PREV_TAB_STOP(cc) >= c)
	    {
	      send(STR(BACK_TAB));
	      cc = PREV_TAB_STOP(cc);
	    }
	}
      if (rem == 6)			/* And left */
	do_skip_left(cc, c);
      else				/* + BackTab Right */
	{
	  send(STR(BACK_TAB));
	  do_skip_right(PREV_TAB_STOP(cc), c, pos);
	}
      break;
    default:
      fatal_error("do_hrel: confused");
    }
}

static void
do_rel(int rr, int cc, int r, int c, int r0, int r1, int c0, int c1, int rem)
{
  TR(": %d.%d -> %d.%d, valg=%d, halg=%d\n", rr, cc, r, c, rem >> 16, rem & 0xffff);
  do_vrel(rr, r, r0, r1, rem >> 16);
  do_hrel(cc, c, c0, c1, rem & 0xffff, r * columns);
}

int
goto_cost(int r, int c, int *prem, int *palg)
{
  int restricted, cost, best, alg;
  int r0, c0, r1, c1, rr, cc, iw, rem, remm;

  remm = 0;
  r0 = win_r0;
  r1 = win_r1;
  c0 = win_c0;
  c1 = win_c1;
  iw = in_window;
  if (iw && r < r0 || r > r1 || c < c0 || c > c1)
    {
      r0 = 0;
      r1 = rows - 1;
      c0 = 0;
      c1 = columns - 1;
    }

  rr = set_r;
  cc = set_c;
  restricted = (cc == c1);		/* Dangerous situation (auto-margins!) */

  TR("Evaluating %d.%d -> %d.%d, bounding box [%d,%d]..[%d,%d], restrict=%d, in_window=%d\n",
      rr, cc, r, c, r0, c0, r1, c1, restricted, iw);

  best = try_direct(r, c);
  TR("\tdirect: cost=%d\n", best);
  alg = 0;
  if (CTRL(NO_CURSOR_OPT) || set_r == ~0) /* This trick is used in Tredraw */
    goto direct;

  if (!restricted)
    {
      TR("\t");
      cost = try_rel(0, rr, cc, r, c, r0, r1, c0, c1, &rem, best);
      if (cost < best)
	best = cost, alg = 1, remm = rem;

      if (!iw)
	{
	  if (rr || cc)
	    {
	      TR("\thome and ");
	      cost = try_rel(cost_home, 0, 0, r, c, r0, r1, c0, c1, &rem, best);
	      if (cost < best)
		best = cost, alg = 2, remm = rem;
	    }

	  if (cc || rr != r1)
	    {
	      TR("\tlower left and ");
	      cost = try_rel(cost_lower_left, r1, 0, r, c, r0, r1, c0, c1, &rem, best);
	      if (cost < best)
		best = cost, alg = 3, remm = rem;
	    }
	}
    }

  if (cc && !c0)
    {
      TR("\tCR ");
      cost = try_rel(cost_cr, rr, 0, r, c, r0, r1, c0, c1, &rem, best);
      if (cost < best)
	best = cost, alg = 4, remm = rem;
    }

  if (!iw)
    {
      if (cc && rr != r1)
	{
	  TR("\tnewline and ");
	  cost = try_rel(cost_nl, rr+1, 0, r, c, r0, r1, c0, c1, &rem, best);
	  if (cost < best)
	    best = cost, alg = 7, remm = rem;
	}

      if (BOOL(LEFT_WORKS_IN_COL_ZERO) && rr)
	{
	  TR("\tprev-line-end and ");
	  cost = try_rel((cc ? cost_cr : 0) + cost_left, rr-1, c1, r, c, r0, r1, c0, c1, &rem, best);
	  if (cost < best)
	    best = cost, alg = 5 + !cc, remm = rem;
	}
    }

direct:
  if (prem)
    *prem = remm;
  if (palg)
    *palg = alg;
  TR("\tOptimal cost=%d for alg=%d with rem=%x\n", best, alg, remm);
  return best;
}

static void
tc_goto(int r, int c)
{
  int r0, r1, c0, c1, rr, cc, rem, alg;

  r0 = win_r0;
  r1 = win_r1;
  c0 = win_c0;
  c1 = win_c1;

  goto_cost(r, c, &rem, &alg);

  rr = set_r;
  cc = set_c;

  TR("\tBest: ");
  switch (alg)
    {
    case 0:
      TR("Direct move\n");
      sendp(STR(MOVE), r, c);
      break;
    case 1:
      TR("Relative move");
      do_rel(rr, cc, r, c, r0, r1, c0, c1, rem);
      break;
    case 2:
      TR("Home and then relative");
      send(STR(HOME));
      do_rel(0, 0, r, c, r0, r1, c0, c1, rem);
      break;
    case 3:
      TR("Lower left and then relative");
      send(STR(LOWER_LEFT));
      do_rel(r1, 0, r, c, r0, r1, c0, c1, rem);
      break;
    case 4:
      TR("CR and then relative");
      sendpad(STR(CR), INT(PAD_CR));
      do_rel(rr, 0, r, c, r0, r1, c0, c1, rem);
      break;
    case 5:
      sendpad(STR(CR), INT(PAD_CR));
    case 6:
      TR("%sCursor to end of prev line and then relative", alg == 5 ? "CR + " : "");
      send(STR(LEFT));
      do_rel(rr-1, c1, r, c, r0, r1, c0, c1, rem);
      break;
    case 7:
      TR("NL and then relative");
      send(STR(NL));
      do_rel(rr+1, 0, r, c, r0, r1, c0, c1, rem);
      break;
    default:
      fatal_error("cgoto confused");
    }
}

void
cgoto(int r, int c)
{
  if (set_r != r || set_c != c)
    TR("cgoto(%d,%d)\n", r, c);

  if (r >= rows || c >= columns)
    fatal_error("cgoto(%d,%d) [from %d,%d] out of screen", r, c, set_r, set_c);

  if (set_cm != current_cm)
    set_cursor_shape();
  if (set_r == r && set_c == c)
    return;
  if (ins_mode && !BOOL(CAN_MOVE_IN_INSERT))
    set_ins_mode(0);
  if (set_attrs & cannot_move_with_attr)
    send_attrs(0, set_color);

  if (in_window && r < win_r0 || r > win_r1 || c < win_c0 || c > win_c1)
    reset_window();

  if (use_vcsa)
    vcsa_goto(r, c);
  else
    tc_goto(r, c);

  set_r = r;
  set_c = c;
}

/* In-refresh horizontal to-the-right cursor movement optimizations */

#define CAN_OVERWRITE(pos) (allow_owr && (cnew[pos] != cold[pos] || knew[pos] != kold[pos] || anew[pos] != aold[pos]))

static int
try_refresh_tab(int c, int pos, int allow_owr)
{
  int k, n;

  if (!use_tabs || (set_attrs & cannot_tab_with_attr))
    return 10000;
  if (use_tabs == TABS_NORMAL)
    return cost_tab;

  k = NEXT_TAB_STOP(c);
  n = cost_tab;
  pos += c;
  while (c < k && CAN_DO_SIMPLE_REFRESH(pos))
    {
      n++;
      c++;
      pos++;
    }
  while (c < k)
    {
      if (!CAN_OVERWRITE(pos) && (cnew[pos] != ' ' || !CAN_DO_SIMPLE_REFRESH(pos)))
	return 10000;
      pos++;
      c++;
    }
  return n;
}

static int
do_refresh_tab(int pos, int c)
{
  int n = NEXT_TAB_STOP(c);

  if (!use_tabs)
    fatal_error("do_refresh_tab, but no tabs");
  if (use_tabs == TABS_NORMAL)
    {
      sendpad(STR(TAB), INT(PAD_TAB));
      return n;
    }
  pos += c;
  while (c < n && CAN_DO_SIMPLE_REFRESH(pos))
    {
      send_single_char(pos);
      c++;
      pos++;
    }
  if (c < n)
    {
      sendpad(STR(TAB), INT(PAD_TAB));
      while (c < n)
	{
	  cold[pos] = ' ';
	  aold[pos] = set_attrs;
	  kold[pos] = set_color;
	  c++;
	  pos++;
	}
    }
  return n;
}

static inline int
refresh_skip_right_cost(int c, int new, int pos, int allow_owr)
{
  int cost = 0;

  pos += c;
  while (c < new)
    {
      if (CAN_OVERWRITE(pos) || CAN_DO_SIMPLE_REFRESH(pos))
	cost++;
      else
	cost += cost_right;
      if (cost > 10)
	return 10000;
      c++;
      pos++;
    }
  return cost;
}

static inline void
refresh_do_skip_right(int c, int new, int pos, int allow_owr)
{
  pos += c;
  while (c < new)
    {
      if (CAN_OVERWRITE(pos) || CAN_DO_SIMPLE_REFRESH(pos))
	send_single_char(pos);
      else
	send(STR(RIGHT));
      c++;
      pos++;
    }
}

static int
refresh_move_right_cost0(int c, int by, int pos, int allow_owr, int *rem, int cost_base)
{
  int new = c + by;
  int alg;
  ulg best, cost2, cost;

  if (!by)
    {
      alg = best = 0;
      goto rmcend;
    }

  best = try_direct(set_r, new);
  alg = 5;

  cost = XC(set_column, new);
  if (cost < best)
    best = cost, alg = 1;

  cost = XC(multi_right, by);
  if (cost < best)
    best = cost, alg = 2;

  cost = 0;				/* (TAB|Write)* ... */
  while (NEXT_TAB_STOP(c) <= new)
    {
      cost += try_refresh_tab(c, pos, allow_owr);
      c = NEXT_TAB_STOP(c);
      if (cost > 10)
	goto rmcend;
    }
  cost2 = cost;				/* ... (Right|Write)* */
  cost += refresh_skip_right_cost(c, new, pos, allow_owr);
  if (cost < best)
    best = cost, alg = 3;

  if (NEXT_TAB_STOP(c) < win_c1)	/* ... Tab Left* */
    {
      cost2 += try_refresh_tab(c, pos, allow_owr);
      c = NEXT_TAB_STOP(c);
      cost2 += cost_left * (c - new);
      if (cost2 < best)
	best = cost2, alg = 4;
    }

rmcend:					/* Selected */
  if (rem)
    *rem = alg;

  best += cost_base;
  TR("cost = %d, alg = %d\n", best, alg);

  return best;
}

int
refresh_move_right_cost(int c, int by, int pos, int allow_owr, int *rem)
{
  if (!by)
    return 0;

  TR("refresh_move_right_cost %d -> %d (owr=%d)\n\t", c, c+by, allow_owr);

  return refresh_move_right_cost0(c, by, pos, allow_owr, rem, 0);
}

void
refresh_do_move_right(int c, int by, int pos, int allow_owr, int alg)
{
  int new = c + by;

  if (!by)
    return;

  switch (alg)
    {
    case 0:
      break;
    case 1:
      sendp(STR(SET_COLUMN), new);
      break;
    case 2:
      sendp(STR(MULTI_RIGHT), by);
      break;
    case 3:
    case 4:
      while (NEXT_TAB_STOP(c) <= new)	/* TABs */
	c = do_refresh_tab(pos, c);
      if (alg == 3)			/* Right/Write */
	refresh_do_skip_right(c, new, pos, allow_owr);
      else				/* TAB/Left */
	{
	  c = do_refresh_tab(pos, c);
	  do_skip_left(c, new);
	}
      break;
    case 5:
      sendp(STR(MOVE), set_r, new);
      break;
    default:
      fatal_error("refresh_do_move_right confused");
    }

  set_c = new;
}

int
refresh_move_left_cost0(int c, int by, int pos, int allow_owr, int *rem)
{
  int new = c - by;
  int alg, lma;
  ulg cost, best, cost2, x, y;

  if (!by)
    {
      alg = best = 0;
      goto lmcend;
    }

  best = try_direct(set_r, new);
  alg = 5;

  cost = XC(set_column, new);
  if (cost < best)
    best = cost, alg = 1;

  cost = XC(multi_left, by);
  if (cost < best)
    best = cost, alg = 2;

  x = c;				/* BackTab* (Left* | (Right|Write)*) */
  cost = 0;
  lma = NEXT_TAB_STOP(win_c0);
  if (use_tabs == TABS_NORMAL && STR(BACK_TAB))
    {
      y = 1;
      while (x >= lma && PREV_TAB_STOP(x) >= new)
	{
	  cost += cost_back_tab;
	  x = PREV_TAB_STOP(x);
	}
    }
  else
    y = 0;
  cost2 = cost + (x - new) * cost_left;	/* + Left* */
  if (cost2 < best)
    best = cost2, alg = 3;
  if (y && x >= lma)			/* + BackTab Right* */
    {
      cost += cost_back_tab;
      x = PREV_TAB_STOP(x);
      cost += refresh_skip_right_cost(x, new, pos, allow_owr);
      if (cost < best)
	best = cost, alg = 4;
    }

lmcend:					/* Selected */
  if (rem)
    *rem = alg;

  TR("cost = %d, alg = %d\n", best, alg);

  return best;
}

int
refresh_move_left_cost(int c, int by, int pos, int allow_owr, int *rem)
{
  int alg1, cost0, cost1;

  if (!by)
    return 0;

  TR("refresh_move_left_cost %d -> %d (owr=%d)\n\t", c, c-by, allow_owr);

  cost0 = refresh_move_left_cost0(c, by, pos, allow_owr, rem);
  if (!win_c0 && c)
    {
      TR("\tWith CR: ");
      cost1 = refresh_move_right_cost0(0, c - by, pos, allow_owr, &alg1, cost_cr);
      if (cost1 < cost0)
	{
	  if (rem)
	    *rem = alg1 + 100;
	  return cost1;
	}
    }
  return cost0;
}

void
refresh_do_move_left(int c, int by, int pos, int allow_owr, int alg)
{
  int new = c - by;

  if (!by)
    return;

  if (alg >= 100)
    {
      sendpad(STR(CR), INT(PAD_CR));
      refresh_do_move_right(0, new, pos, allow_owr, alg - 100);
      return;
    }

  switch (alg)
    {
    case 0:
      break;
    case 1:
      sendp(STR(SET_COLUMN), new);
      break;
    case 2:
      sendp(STR(MULTI_LEFT), by);
      break;
    case 3:
    case 4:
      if (use_tabs == TABS_NORMAL && STR(BACK_TAB))
	{
	  int lma = NEXT_TAB_STOP(win_c0);
	  while (c >= lma && PREV_TAB_STOP(c) >= new)
	    {
	      send(STR(BACK_TAB));
	      c = PREV_TAB_STOP(c);
	    }
	}
      if (alg == 3)
	do_skip_left(c, new);
      else
	{
	  send(STR(BACK_TAB));
	  refresh_do_skip_right(PREV_TAB_STOP(c), new, pos, allow_owr);
	}
      break;
    case 5:
      sendp(STR(MOVE), set_r, new);
      break;
    default:
      fatal_error("refresh_do_move_left confused");
    }

  set_c = new;
}
