/*************************************************************************
 ***                        prim.c                       (JJB TEMPLAR) ***
 *** Date modifications begun: 7/8/89.                                 ***
 *** Last modified: 27/8/89.                                           ***
 *************************************************************************/
/*** Primitives for displaying the file on the screen.                 ***
 *************************************************************************/

#include "less.h"
#include "position.h"
#include "screen.h"

int     hit_eof;    /* Keeps track of how many times we hit end of file */

extern int  quiet;
extern int  top_scroll;
extern int  back_scroll;
extern int  sc_height;
extern char *line;
extern int  page_break;

#define eof_bell()  bell()

void    prepaint(LONG);

static void eof_check() /*===============================================*/
{                       /* Check if EOF displayed.                       */
LONG    pos;

  /* If the bottom line is empty, we are at EOF.
     If the bottom line ends at the file length, we must be just at EOF. */
    pos = position(BOTTOM_PLUS_ONE);
    if (((pos == NULL_POSITION) && !page_break) || (pos == ch_length()))
        hit_eof++;
}

static void forw(n, pos, force, only_last) /*============================*/
register int n;     /* Display n lines, scrolling forward.    */
LONG    pos;        /* Starting from position pos in the file. */
int     force;      /* Display lines even if hit eof. */
int     only_last;  /* Display only last screenful if n > screen size. */
{
int     eof = 0;
int     nlines = 0;
int     repaint_flag,top_flag = 0;

    repaint_flag = (only_last && (n > sc_height-1));

    if (!repaint_flag) {
        if (top_scroll && (n >= sc_height - 1)) {
            clear();        /* From top of screen */
            home();
            force = 1;
            top_flag = 1;   /* Don't precede top line with '\n' */
        }
        else {              /* Or from bottom left */
            lower_left();
        }

        if (pos != position(BOTTOM_PLUS_ONE)) {
          /* This is not contiguous with what is currently displayed.
             Clear the screen image (position table) and start a new screen. */
            pos_clear();
            add_forw_pos(pos);
            force = 1;
            if (top_scroll) {
                clear();
                home();
                top_flag = 1;
            }
            else puts("[0;33;40m...skipping...[m\n");
	}
    }

    while (--n >= 0) {
      /* Read the next line of input. */
        pos = forw_line(pos);
        if (pos == NULL_POSITION) {
          /* End of file: stop here unless the top line
             is still empty, or "force" is true. */
            if (!page_break) eof = 1;
            if (!force && (position(TOP) != NULL_POSITION)) break;
            line = NULL;
        }
      /* Add the position of the next line to the position table.
         Display the current line on the screen. */
        add_forw_pos(pos);
        nlines++;
        if (!repaint_flag) {
            if (top_flag) top_flag = 0;
            else putc('\n');
            put_line();
        }
    }

    if (eof) hit_eof++;
    else eof_check();
    if (!nlines) eof_bell();
    else if (repaint_flag) repaint();
    page_break = 0;
}

static void back(n, pos, force, only_last) /*============================*/
register int n;                            /* As above, but backward     */
LONG    pos;
int     force;
int     only_last;
{
int nlines = 0;
int repaint_flag;
int tempn = n;

    repaint_flag = ((n > back_scroll) || (only_last && (n > sc_height-1)));
    hit_eof = 0;
    while (--n >= 0) {
      /* Get the previous line of input. */
        pos = back_line(pos);
        if (pos == NULL_POSITION) {
          /* Beginning of file: stop here unless "force" is true. */
            if (!force) break;
            line = NULL;
        }
        if (page_break && (n == tempn - 1)) page_break = 0;
      /* Add the position of the previous line to the position table.
         Display the line on the screen. */
        add_back_pos(pos);
        nlines++;
        if (!repaint_flag && !page_break) {
            home();
            add_line();
            put_line();     putc('\n');
        }
    }

    if (page_break) {
        page_break = 0;
        pos = forw_line(pos);
        page_break = 0;
        set_top_pos(pos);       /* Patched together with a nappy pin and */
        repaint(pos);           /* chewing gum... Very wierd this. */
    }
    else {
        eof_check();
        if (!nlines) eof_bell();
        else if (repaint_flag) repaint();
    }
}

void    forward(n, only_last) /*=========================================*/
int     n;                    /* Display n more lines, forward.          */
int     only_last;
{
LONG    pos;

    pos = position(BOTTOM_PLUS_ONE);
    if (pos == NULL_POSITION) {
        eof_bell();
        hit_eof++;
        return;
    }
    forw(n, pos, 0, only_last);
}

void    backward(n, only_last) /*========================================*/
int     n;                     /* Display n more lines, backward.        */
int     only_last;
{
LONG    pos;

    pos = position(TOP);
    if (pos == NULL_POSITION) {
      /* This will almost never happen,
         because the top line is almost never empty. */
        eof_bell();
        return;
    }
    back(n, pos, 0, only_last);
}

static void prepaint(pos) /*=============================================*/
LONG    pos;              /* Repaint, starting from a specified pos.     */
{
    hit_eof = 0;
    forw(sc_height-1, pos, 0, 0);
}

void    repaint() /*=====================================================*/
{  /* Start at line currently at the top of screen and redisplay screen. */
register int    temp;
    temp = top_scroll;
    top_scroll = 1;
    prepaint(position(TOP));
    top_scroll = temp;
}

void    jump_forw() /*===================================================*/
{
LONG    pos;

/* Jump to the end of the file. It is more convenient to paint the screen
 * backward, from the end of the file toward the beginning. */
    end_seek();
    pos = ch_tell();
    clear();
    pos_clear();
    add_back_pos(pos);
    back(sc_height - 1, pos, 0, 0);
}

void    jump_back(n) /*==================================================*/
register int n;      /* Jump to line n in file.                          */
{
register int c;

  /* This is done the slow way, by starting at the beginning
     of the file and counting newlines. */
    if (ch_seek(0)) {
        /* Probably a pipe with beginning of file no longer buffered. */
        error("Cannot get to beginning of file",0);
        return;
    }

  /* Start counting lines. */
    while (--n > 0) {
        while ((c = ch_forw_get()) != '\n')
            if (c == EOF) {
                error("File is not that long",0);
                    /* {{ Maybe tell him how long it is? }} */
                return;
            }
    }

  /* Finally found the place to start.
     Clear and redisplay the screen from there.
     {{ We *could* figure out if the new position is close enough to just
        scroll there without clearing the screen, but it's not worth it. }} */
    prepaint(ch_tell());
}

void    jump_percent() /*================================================*/
{                      /* Jump a %age into the file.                     */
static int  percent;
LONG    pos,len;

    if (!(r_int("Percentage",&percent,3))) return;      /* 2 digits max */
    if (percent < 0) percent = 0;

    if ((len = ch_length()) == NULL_POSITION) {
        error("Don't know length of file",0);
        return;
    }
    pos = (percent * len) / 100;
    jump_loc(pos);
    setbar(0);
}

void    jump_loc(pos) /*=================================================*/
LONG    pos;
{
register int c;
register int nline;
LONG    tpos;

  /* See if the desired line is BEFORE the currently displayed screen.
     If so, see if it is close enough to scroll backwards to it. */
    tpos = position(TOP);
    if (pos < tpos) {
        for (nline = 1;  nline <= back_scroll;  nline++) {
            tpos = back_line(tpos);
            if (tpos == NULL_POSITION || tpos <= pos) {
                back(nline, position(TOP), 1, 0);
                return;
            }
        }
    }
    else if ((nline = onscreen(pos)) >= 0) {
      /* The line is currently displayed. Just scroll there. */
        forw(nline, position(BOTTOM_PLUS_ONE), 1, 0);
        return;
    }

  /* Line is not on screen. Back up to the beginning of the current line. */
    if (ch_seek(pos)) {
        error("Cannot seek to that position",0);
        return;
    }
    while ((c = ch_back_get()) != '\n' && c != EOF);
    if (c == '\n') ch_forw_get();

  /* Clear and paint the screen. */
    prepaint(ch_tell());
}
