/* SLang Scrolling Window Routines */
/* Copyright (c) 1996, 1999, 2001 John E. Davis
 * This file is part of the S-Lang library.
 *
 * You may distribute under the terms of either the GNU General Public
 * License or the Perl Artistic License.
 */

#include "slinclud.h"

#include "slang.h"
#include "_slang.h"

static void find_window_bottom (SLscroll_Window_Type *win)
{
   unsigned int nrows;
   unsigned int hidden_mask;
   SLscroll_Type *bot, *cline, *last_bot;
   unsigned int row;

   nrows = win->nrows;
   hidden_mask = win->hidden_mask;
   cline = win->current_line;

   win->window_row = row = 0;
   last_bot = bot = win->top_window_line;

   while (row < nrows)
     {
	if (bot == cline)
	  win->window_row = row;

	last_bot = bot;

	if (bot == NULL)
	  break;

	bot = bot->next;

	if (hidden_mask)
	  {
	     while ((bot != NULL) && (bot->flags & hidden_mask))
	       bot = bot->next;
	  }

	row++;
     }

   win->bot_window_line = last_bot;
}

static int find_top_to_recenter (SLscroll_Window_Type *win)
{
   unsigned int nrows;
   unsigned int hidden_mask;
   SLscroll_Type *prev, *last_prev, *cline;

   nrows = win->nrows;
   cline = win->current_line;
   hidden_mask = win->hidden_mask;

   nrows = nrows / 2;

   last_prev = prev = cline;

   while (nrows && (prev != NULL))
     {
	nrows--;
	last_prev = prev;
	do
	  {
	     prev = prev->prev;
	  }
	while (hidden_mask
	       && (prev != NULL)
	       && (prev->flags & hidden_mask));
     }

   if (prev == NULL) prev = last_prev;

   win->top_window_line = prev;
   find_window_bottom (win);

   return 0;
}

#define HAS_BORDER_CODE 1
int SLscroll_find_top (SLscroll_Window_Type *win)
{
   unsigned int i;
   SLscroll_Type *cline, *prev, *next;
   SLscroll_Type *top_window_line;
   unsigned int nrows;
   unsigned int hidden_mask;
   int scroll_mode;
   unsigned int border;

   cline = win->current_line;
   nrows = win->nrows;
   scroll_mode = win->cannot_scroll;
   border = win->border;
   if (scroll_mode == 2)
     border = 0;

   if ((cline == NULL) || (nrows <= 1))
     {
	win->top_window_line = cline;
	find_window_bottom (win);
	return 0;
     }

   hidden_mask = win->hidden_mask;

   /* Note: top_window_line might be a bogus pointer.  This means that I cannot
    * access it unless it really corresponds to a pointer in the buffer.
    */
   top_window_line = win->top_window_line;

   if (top_window_line == NULL)
     return find_top_to_recenter (win);

   /* Chances are that the current line is visible in the window.  This means
    * that the top window line should be above it.
    */
   prev = cline;

   i = 0;

   while ((i < nrows) && (prev != NULL))
     {
	if (prev == top_window_line)
	  {
	     SLscroll_Type *twl = top_window_line;
	     int dir = 0;

	     if (i < border) dir = -1; else if (i + border >= nrows) dir = 1;

	     if (dir) while (border)
	       {
		  if (dir < 0) twl = twl->prev;
		  else twl = twl->next;

		  if (twl == NULL)
		    {
		       twl = top_window_line;
		       break;
		    }
		  if ((hidden_mask == 0)
		      || (0 == (twl->flags & hidden_mask)))
		    border--;
	       }

	     win->top_window_line = twl;
	     find_window_bottom (win);
	     return 0;
	  }

	do
	  {
	     prev = prev->prev;
	  }
	while (hidden_mask
	       && (prev != NULL)
	       && (prev->flags & hidden_mask));
	i++;
     }

   /* Now check the borders of the window.  Perhaps the current line lies
    * outsider the border by a line.  Only do this if terminal can scroll.
    */

   if (scroll_mode == 1)
     return find_top_to_recenter (win);
   else if (scroll_mode == -1)
     scroll_mode = 0;

   next = cline->next;
   while (hidden_mask
	  && (next != NULL)
	  && (next->flags & hidden_mask))
     next = next->next;

   if ((next != NULL)
       && (next == top_window_line))
     {
	/* The current line is one line above the window.  This means user
	 * has moved up past the top of the window.  If scroll_mode is set
	 * to scroll by pages, we need to do a page up.
	 */

	win->top_window_line = cline;
	find_window_bottom (win);

	if (scroll_mode) return SLscroll_pageup (win);

	return 0;
     }

   prev = cline->prev;

   while (hidden_mask
	  && (prev != NULL)
	  && (prev->flags & hidden_mask))
     prev = prev->prev;

   if ((prev == NULL)
       || (prev != win->bot_window_line))
     return find_top_to_recenter (win);

   /* It looks like cline is below window by one line.  See what line should
    * be at top to scroll it into view.  Only do this unless we are scrolling
    * by pages.
    */
   if (scroll_mode)
     {
	win->top_window_line = cline;
	find_window_bottom (win);
	return 0;
     }

   i = 2;
   while ((i < nrows) && (prev != NULL))
     {
	do
	  {
	     prev = prev->prev;
	  }
	while (hidden_mask
	       && (prev != NULL)
	       && (prev->flags & hidden_mask));
	i++;
     }

   if (prev != NULL)
     {
	win->top_window_line = prev;
	find_window_bottom (win);
	return 0;
     }

   return find_top_to_recenter (win);
}

int SLscroll_find_line_num (SLscroll_Window_Type *win)
{
   SLscroll_Type *cline, *l;
   unsigned int n;
   unsigned int hidden_mask;

   if (win == NULL) return -1;

   hidden_mask = win->hidden_mask;
   cline = win->current_line;

   n = 1;

   l = win->lines;
   while (l != cline)
     {
	if ((hidden_mask == 0)
	    || (0 == (l->flags & hidden_mask)))
	  n++;

	l = l->next;
     }

   win->line_num = n;
   n--;

   while (l != NULL)
     {
	if ((hidden_mask == 0)
	    || (0 == (l->flags & hidden_mask)))
	  n++;
	l = l->next;
     }
   win->num_lines = n;

   return 0;
}

unsigned int SLscroll_next_n (SLscroll_Window_Type *win, unsigned int n)
{
   unsigned int i;
   unsigned int hidden_mask;
   SLscroll_Type *l, *cline;

   if ((win == NULL)
       || (NULL == (cline = win->current_line)))
     return 0;

   hidden_mask = win->hidden_mask;
   l = cline;
   i = 0;
   while (i < n)
     {
	l = l->next;
	while (hidden_mask
	       && (l != NULL) && (l->flags & hidden_mask))
	  l = l->next;

	if (l == NULL)
	  break;

	i++;
	cline = l;
     }

   win->current_line = cline;
   win->line_num += i;
   return i;
}

unsigned int SLscroll_prev_n (SLscroll_Window_Type *win, unsigned int n)
{
   unsigned int i;
   unsigned int hidden_mask;
   SLscroll_Type *l, *cline;

   if ((win == NULL)
       || (NULL == (cline = win->current_line)))
     return 0;

   hidden_mask = win->hidden_mask;
   l = cline;
   i = 0;
   while (i < n)
     {
	l = l->prev;
	while (hidden_mask
	       && (l != NULL) && (l->flags & hidden_mask))
	  l = l->prev;

	if (l == NULL)
	  break;

	i++;
	cline = l;
     }

   win->current_line = cline;
   win->line_num -= i;
   return i;
}

int SLscroll_pageup (SLscroll_Window_Type *win)
{
   SLscroll_Type *l, *top;
   unsigned int nrows, hidden_mask;
   unsigned int n;

   if (win == NULL)
     return -1;

   (void) SLscroll_find_top (win);

   nrows = win->nrows;

   if ((NULL != (top = win->top_window_line))
       && (nrows > 2))
     {
	n = 0;
	hidden_mask = win->hidden_mask;
	l = win->current_line;
	while ((l != NULL) && (l != top))
	  {
	     l = l->prev;
	     if ((hidden_mask == 0)
		 || ((l != NULL) && (0 == (l->flags & hidden_mask))))
	       n++;
	  }

	if (l != NULL)
	  {
	     unsigned int save_line_num;
	     int ret = 0;

	     win->current_line = l;
	     win->line_num -= n;

	     /* Compute a new top/bottom header */
	     save_line_num = win->line_num;

	     if ((0 == SLscroll_prev_n (win, nrows - 1))
		 && (n == 0))
	       ret = -1;

	     win->top_window_line = win->current_line;
	     win->current_line = l;
	     win->line_num = save_line_num;

	     find_window_bottom (win);
	     return ret;
	  }
     }

   if (nrows < 2) nrows++;
   if (0 == SLscroll_prev_n (win, nrows - 1))
     return -1;
   return 0;
}

int SLscroll_pagedown (SLscroll_Window_Type *win)
{
   SLscroll_Type *l, *bot;
   unsigned int nrows, hidden_mask;
   unsigned int n;

   if (win == NULL)
     return -1;

   (void) SLscroll_find_top (win);

   nrows = win->nrows;

   if ((NULL != (bot = win->bot_window_line))
       && (nrows > 2))
     {
	n = 0;
	hidden_mask = win->hidden_mask;
	l = win->current_line;
	while ((l != NULL) && (l != bot))
	  {
	     l = l->next;
	     if ((hidden_mask == 0)
		 || ((l != NULL) && (0 == (l->flags & hidden_mask))))
	       n++;
	  }

	if (l != NULL)
	  {
	     win->current_line = l;
	     win->top_window_line = l;
	     win->line_num += n;

	     find_window_bottom (win);

	     if (n || (bot != win->bot_window_line))
	       return 0;

	     return -1;
	  }
     }

   if (nrows < 2) nrows++;
   if (0 == SLscroll_next_n (win, nrows - 1))
     return -1;
   return 0;
}