/*
 * Primitives for displaying the file on the screen.
 */

#ifdef AMIGA
#define REGCMP 1
#define regex regexec
#define regcmp(x,y) regcomp(x)
#include "regexp.h"
#endif

#include "less.h"

#include "position.h"


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

extern int quiet;
extern int top_search;
extern int top_scroll;
extern int back_scroll;
extern int sc_width, sc_height;
extern int sigs;
extern int quit_at_eof;
extern int ac;
extern char *line;
extern char *first_cmd;

/* Prototypes for functions defined in prim.c */

static void eof_bell __PROTO((void));
static void eof_check __PROTO((void));
static void forw __PROTO((register int n,
                          POSITION pos,
                          int force,
                          int only_last));
static void back __PROTO((register int n,
                          POSITION pos,
                          int force,
                          int only_last));
static void prepaint __PROTO((POSITION pos));
static int badmark __PROTO((int c));
#if (!REGCMP) && (!RECOMP)
static int match __PROTO((char *pattern,
                          char *buf));
#endif



/*
 * Sound the bell to indicate he is trying to move past end of file.
 */
#ifdef __STDC__
static void eof_bell (void)
#else
        static void
eof_bell()
#endif
{
        if (quiet == NOT_QUIET)
                bell();
        else
                vbell();
}

/*
 * Check to see if the end of file is currently "displayed".
 */
#ifdef __STDC__
static void eof_check (void)
#else
        static void
eof_check()
#endif
{
        POSITION 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 || pos == ch_length())
                hit_eof++;
}

/*
 * Display n lines, scrolling forward,
 * starting at position pos in the input file.
 * "force" means display the n lines even if we hit end of file.
 * "only_last" means display only the last screenful if n > screen size.
 */
#ifdef __STDC__
static void forw (register int n, POSITION pos, int force, int only_last)
#else
        static void
forw(n, pos, force, only_last)
        register int n;
        POSITION pos;
        int force;
        int only_last;
#endif
{
        int eof = 0;
        int nlines = 0;
        int do_repaint;
        static int first_time = 1;

        /*
         * do_repaint tells us not to display anything till the end,
         * then just repaint the entire screen.
         */
        do_repaint = (only_last && n > sc_height-1);

        if (!do_repaint)
        {
                if (top_scroll && n >= sc_height - 1)
                {
                        /*
                         * Start a new screen.
                         * {{ This is not really desirable if we happen
                         *    to hit eof in the middle of this screen,
                         *    but we don't yet know if that will happen. }}
                         */
                        if (top_scroll == 2)
                                clear();
                        home();
                        force = 1;
                } else
                {
                        lower_left();
                        clear_eol();
                }

                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)
                        {
                                if (top_scroll == 2)
                                        clear();
                                home();
                        } else if (!first_time)
                        {
                                putstr("...skipping...\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.
                         */
                        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 (do_repaint ||
                        (first_time && line == NULL && !top_scroll))
                        continue;
                if (top_scroll == 1)
                        clear_eol();
                put_line();
        }

        if (eof)
                hit_eof++;
        else
                eof_check();
        if (nlines == 0)
                eof_bell();
        else if (do_repaint)
                repaint();
#ifndef AMIGA
        if (first_time && hit_eof && quit_at_eof && ac <= 1)
                quit();
#endif
        first_time = 0;
}

/*
 * Display n lines, scrolling backward.
 */
#ifdef __STDC__
static void back (register int n, POSITION pos, int force, int only_last)
#else
        static void
back(n, pos, force, only_last)
        register int n;
        POSITION pos;
        int force;
        int only_last;
#endif
{
        int nlines = 0;
        int do_repaint;

        do_repaint = (n > get_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;
                }
                /*
                 * Add the position of the previous line to the position table.
                 * Display the line on the screen.
                 */
                add_back_pos(pos);
                nlines++;
                if (!do_repaint)
                {
                        home();
                        add_line();
                        put_line();
                }
        }

        eof_check();
        if (nlines == 0)
                eof_bell();
        else if (do_repaint)
                repaint();
}

/*
 * Display n more lines, forward.
 * Start just after the line currently displayed at the bottom of the screen.
 */
#ifdef __STDC__
void forward (int n, int only_last)
#else
        public void
forward(n, only_last)
        int n;
        int only_last;
#endif
{
        POSITION pos;

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

/*
 * Display n more lines, backward.
 * Start just before the line currently displayed at the top of the screen.
 */
#ifdef __STDC__
void backward (int n, int only_last)
#else
        public void
backward(n, only_last)
        int n;
        int only_last;
#endif
{
        POSITION 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);
}

/*
 * Repaint the screen, starting from a specified position.
 */
#ifdef __STDC__
static void prepaint (POSITION pos)
#else
        static void
prepaint(pos)
        POSITION pos;
#endif
{
        hit_eof = 0;
        forw(sc_height-1, pos, 1, 0);
}

/*
 * Repaint the screen.
 */
#ifdef __STDC__
void repaint (void)
#else
        public void
repaint()
#endif
{
        /*
         * Start at the line currently at the top of the screen
         * and redisplay the screen.
         */
#ifndef AMIGA
        /* screen might have been resized */
        POSITION savepos;

        savepos = position(TOP);
        pos_clear();
        add_forw_pos(savepos);
        prepaint(savepos);
#else
		extern int file;

		if ( file >= 0 )
	        prepaint(position(TOP));
	    else
	    	clear();
#endif
}

/*
 * 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.
 */
#ifdef __STDC__
void jump_forw (void)
#else
        public void
jump_forw()
#endif
{
        POSITION pos;

        if (ch_end_seek())
        {
                error("Cannot seek to end of file");
                return;
        }
        lastmark();
        pos = ch_tell();
        clear();
        pos_clear();
        add_back_pos(pos);
        back(sc_height - 1, pos, 0, 0);
}

/*
 * Jump to line n in the file.
 */
#ifdef __STDC__
void jump_back (register int n)
#else
        public void
jump_back(n)
        register int n;
#endif
{
        register int c;
        int nlines;

        /*
         * This is done the slow way, by starting at the beginning
         * of the file and counting newlines.
         */
        if (ch_seek((POSITION)0))
        {
                /*
                 * Probably a pipe with beginning of file no longer buffered.
                 * If he wants to go to line 1, we do the best we can,
                 * by going to the first line which is still buffered.
                 */
                if (n <= 1 && ch_beg_seek() == 0)
                        jump_loc(ch_tell());
                error("Cannot get to beginning of file");
                return;
        }

        /*
         * Start counting lines.
         */
        for (nlines = 1;  nlines < n;  nlines++)
        {
                while ((c = ch_forw_get()) != '\n')
                        if (c == EOF)
                        {
                                char message[40];
                                sprintf(message, "File has only %ld lines",
                                        nlines-1);
                                error(message);
                                return;
                        }
        }

        jump_loc(ch_tell());
}

/*
 * Jump to a specified percentage into the file.
 * This is a poor compensation for not being able to
 * quickly jump to a specific line number.
 */
#ifdef __STDC__
void jump_percent (int percent)
#else
        public void
jump_percent(percent)
        int percent;
#endif
{
        POSITION pos, len;
        register int c;

        /*
         * Determine the position in the file
         * (the specified percentage of the file's length).
         */
        if ((len = ch_length()) == NULL_POSITION)
        {
                error("Don't know length of file");
                return;
        }
        pos = (percent * len) / 100;

        /*
         * Back up to the beginning of the line.
         */
        if (ch_seek(pos) == 0)
        {
                while ((c = ch_back_get()) != '\n' && c != EOF)
                        ;
                if (c == '\n')
                        (void) ch_forw_get();
                pos = ch_tell();
        }
        jump_loc(pos);
}

/*
 * Jump to a specified position in the file.
 */
#ifdef __STDC__
void jump_loc (POSITION pos)
#else
        public void
jump_loc(pos)
        POSITION pos;
#endif
{
        register int nline;
        POSITION 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.
         * {{ This can be expensive if he has specified a very
         *    large back_scroll count.  Perhaps we should put
         *    some sanity limit on the loop count here. }}
         */
        tpos = position(TOP);
        if (tpos != NULL_POSITION && pos < tpos)
        {
                int bs = get_back_scroll();
                for (nline = 1;  nline <= bs;  nline++)
                {
                        tpos = back_line(tpos);
                        if (tpos == NULL_POSITION)
                                break;
                        if (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.
         * Remember where we were; clear and paint the screen.
         */
        if (ch_seek(pos))
        {
                error("Cannot seek to that position");
                return;
        }
        lastmark();
        prepaint(pos);
}

/*
 * The table of marks.
 * A mark is simply a position in the file.
 */
#define NMARKS          (27)            /* 26 for a-z plus one for quote */
#define LASTMARK        (NMARKS-1)      /* For quote */
static POSITION marks[NMARKS];

/*
 * Initialize the mark table to show no marks are set.
 */
#ifdef __STDC__
void init_mark (void)
#else
        public void
init_mark()
#endif
{
        int i;

        for (i = 0;  i < NMARKS;  i++)
                marks[i] = NULL_POSITION;
}

/*
 * See if a mark letter is valid (between a and z).
 */
#ifdef __STDC__
static int badmark (int c)
#else
        static int
badmark(c)
        int c;
#endif
{
        if (c < 'a' || c > 'z')
        {
                error("Choose a letter between 'a' and 'z'");
                return (1);
        }
        return (0);
}

/*
 * Set a mark.
 */
#ifdef __STDC__
void setmark (int c)
#else
        public void
setmark(c)
        int c;
#endif
{
        if (badmark(c))
                return;
        marks[c-'a'] = position(TOP);
}

#ifdef __STDC__
void lastmark (void)
#else
        public void
lastmark()
#endif
{
        marks[LASTMARK] = position(TOP);
}

/*
 * Go to a previously set mark.
 */
#ifdef __STDC__
void gomark (int c)
#else
        public void
gomark(c)
        int c;
#endif
{
        POSITION pos;

        if (c == '\'')
                pos = marks[LASTMARK];
        else if (badmark(c))
                return;
        else
                pos = marks[c-'a'];

        if (pos == NULL_POSITION)
                error("mark not set");
        else
                jump_loc(pos);
}

/*
 * Get the backwards scroll limit.
 * Must call this function instead of just using the value of
 * back_scroll, because the default case depends on sc_height and
 * top_scroll, as well as back_scroll.
 */
#ifdef __STDC__
int get_back_scroll (void)
#else
        public int
get_back_scroll()
#endif
{
        if (back_scroll >= 0)
#ifdef AMIGA
                return (back_scroll < sc_height? back_scroll: sc_height - 1);
#else
                return (back_scroll);
#endif
        if (top_scroll)
                return (sc_height - 2);
        return (sc_height - 1);
}

/*
 * Search for the n-th occurence of a specified pattern,
 * either forward (direction == '/'), or backwards (direction == '?').
 */
#ifdef __STDC__
void search (int direction, char *pattern, register int n)
#else
        public void
search(direction, pattern, n)
        int direction;
        char *pattern;
        register int n;
#endif
{
        register int search_forward = (direction == '/');
        POSITION pos, linepos;

#if RECOMP
        char *re_comp();
        char *errmsg;

        /*
         * (re_comp handles a null pattern internally,
         *  so there is no need to check for a null pattern here.)
         */
        if ((errmsg = re_comp(pattern)) != NULL)
        {
                error(errmsg);
                return;
        }
#else
#if REGCMP
#ifdef AMIGA
        static regexp *cpattern = NULL;
#else
        char *regcmp();
        static char *cpattern = NULL;
#endif

        if (pattern == NULL || *pattern == '\0')
        {
                /*
                 * A null pattern means use the previous pattern.
                 * The compiled previous pattern is in cpattern, so just use it.
                 */
                if (cpattern == NULL)
                {
                        error("No previous regular expression");
                        return;
                }
        } else
        {
                /*
                 * Otherwise compile the given pattern.
                 */
#ifdef AMIGA
                regexp *s;
#else
                char *s;
#endif
                if ((s = regcmp(pattern, 0)) == NULL)
                {
#ifdef AMIGA
                        /* regexp had already displayed a more specific
                           error message
                         */
#else
                        error("Invalid pattern");
#endif
                        return;
                }
                if (cpattern != NULL)
                        free((char *)cpattern);

                cpattern = s;
        }
#else
        static char lpbuf[100];
        static char *last_pattern = NULL;

        if (pattern == NULL || *pattern == '\0')
        {
                /*
                 * Null pattern means use the previous pattern.
                 */
                if (last_pattern == NULL)
                {
                        error("No previous regular expression");
                        return;
                }
                pattern = last_pattern;
        } else
        {
                strcpy(lpbuf, pattern);
                last_pattern = lpbuf;
        }
#endif
#endif

        /*
         * Figure out where to start the search.
         */

        if (position(TOP) == NULL_POSITION)
        {
                /*
                 * Nothing is currently displayed.
                 * Start at the beginning of the file.
                 * (This case is mainly for first_cmd searches,
                 * for example, "+/xyz" on the command line.)
                 */
                pos = (POSITION)0;
        } else if (!search_forward)
        {
                /*
                 * Backward search: start just before the top line
                 * displayed on the screen.
                 */
                pos = position(TOP);
        } else if (top_search)
        {
                /*
                 * Forward search and "start from top".
                 * Start at the second line displayed on the screen.
                 */
                pos = position(TOP_PLUS_ONE);
        } else
        {
                /*
                 * Forward search but don't "start from top".
                 * Start just after the bottom line displayed on the screen.
                 */
                pos = position(BOTTOM_PLUS_ONE);
        }

        if (pos == NULL_POSITION)
        {
                /*
                 * Can't find anyplace to start searching from.
                 */
                error("Nothing to search");
                return;
        }

        for (;;)
        {
                /*
                 * Get lines until we find a matching one or
                 * until we hit end-of-file (or beginning-of-file
                 * if we're going backwards).
                 */
#ifdef AMIGA
                if (chk_sigs())
#else
                if (sigs)
#endif
                        /*
                         * A signal aborts the search.
                         */
                        return;

                if (search_forward)
                {
                        /*
                         * Read the next line, and save the
                         * starting position of that line in linepos.
                         */
                        linepos = pos;
                        pos = forw_raw_line(pos);
                } else
                {
                        /*
                         * Read the previous line and save the
                         * starting position of that line in linepos.
                         */
                        pos = back_raw_line(pos);
                        linepos = pos;
                }

                if (pos == NULL_POSITION)
                {
                        /*
                         * We hit EOF/BOF without a match.
                         */
                        error("Pattern not found");
                        return;
                }

                /*
                 * Test the next line to see if we have a match.
                 * This is done in a variety of ways, depending
                 * on what pattern matching functions are available.
                 */
#if REGCMP
                if ( (regex(cpattern, line) != NULL)
#else
#if RECOMP
                if ( (re_exec(line) == 1)
#else
                if ( (match(pattern, line))
#endif
#endif
                                && (--n <= 0) )
                        /*
                         * Found the matching line.
                         */
                        break;
        }

        jump_loc(linepos);
}


#if (!REGCMP) && (!RECOMP)
/*
 * We have neither regcmp() nor re_comp().
 * We use this function to do simple pattern matching.
 * It supports no metacharacters like *, etc.
 */
        static int
match(pattern, buf)
        char *pattern, *buf;
{
        register char *pp, *lp;

        for ( ;  *buf != '\0';  buf++)
        {
                for (pp = pattern, lp = buf;  *pp == *lp;  pp++, lp++)
                        if (*pp == '\0' || *lp == '\0')
                                break;
                if (*pp == '\0')
                        return (1);
        }
        return (0);
}
#endif

