/* Copyright (c) 1990,1991,1992 Chris and John Downey */
#ifndef lint
static char *sccsid = "@(#)windows.c	2.3 (Chris & John Downey) 11/6/92";
#endif

/***

* program name:
    xvi
* function:
    PD version of UNIX "vi" editor, with extensions.
* module name:
    windows.c
* module function:
    Window handling functions.
* history:
    STEVIE - ST Editor for VI Enthusiasts, Version 3.10
    Originally by Tim Thompson (twitch!tjt)
    Extensive modifications by Tony Andrews (onecom!wldrdg!tony)
    Heavily modified by Chris & John Downey

***/

#include "xvi.h"

#undef	min
#define	min(a, b)	(((a) < (b)) ? (a) : (b))

static	int	nwindows = 0;

static	Xviwin	*new_window P((Xviwin *, Xviwin *));
static	bool_t	setup_window P((Xviwin *));

Xviwin *
init_window(vs)
VirtScr	*vs;
{
    Xviwin	*newwin;

    newwin = new_window((Xviwin *) NULL, (Xviwin *) NULL);
    if (newwin == NULL) {
	return(NULL);
    }

    newwin->w_vs = vs;
    newwin->w_nrows = (*vs->v_rows)(vs);
    newwin->w_ncols = (*vs->v_cols)(vs);

    /*
     * Initialise screen stuff.
     */
    init_screen(newwin);

    newwin->w_winpos = 0;
    newwin->w_cmdline = Rows - 1;

    return(newwin);
}

/*
 * Split the given window in half, placing a new empty
 * window in the bottom section and resizing the old one
 * in the top half.
 */
Xviwin *
split_window(oldwin)
Xviwin	*oldwin;
{
    Xviwin	*newwin;

    /*
     * Make sure there are enough rows for the new window.
     * This does not obey the minrows parameter, because
     * the point is to have enough space to actually display
     * a window, not just to have a zero-size one.
     */
    if (oldwin->w_nrows < (MINROWS * 2))
	return(NULL);

    newwin = new_window(oldwin, oldwin->w_next);
    if (newwin == NULL) {
	return(NULL);
    }

    newwin->w_vs = oldwin->w_vs;

    /*
     * Calculate size and position of new and old windows.
     */
    newwin->w_nrows = oldwin->w_nrows / 2;
    newwin->w_cmdline = oldwin->w_cmdline;
    newwin->w_winpos = (newwin->w_cmdline - newwin->w_nrows) + 1;

    oldwin->w_nrows -= newwin->w_nrows;
    oldwin->w_cmdline = newwin->w_winpos - 1;

    newwin->w_ncols = oldwin->w_ncols;

    return(newwin);
}

/*
 * Delete the given window.
 */
void
free_window(window)
Xviwin	*window;
{
    if (window == NULL || nwindows < 1)
	return;

    if (window->w_next != NULL) {
	window->w_next->w_last = window->w_last;
    }
    if (window->w_last != NULL) {
	window->w_last->w_next = window->w_next;
    }
    nwindows -= 1;

    window->w_buffer->b_nwindows -= 1;

    free((char *) window->w_cursor);
    flexdelete(&window->w_statusline);
    free((char *) window);
}

/*
 * Allocate a new window.
 */
static Xviwin *
new_window(last, next)
Xviwin	*last, *next;
{
    Xviwin	*newwin;

    newwin = (Xviwin *) malloc(sizeof(Xviwin));
    if (newwin == NULL) {
	return(NULL);
    }

    if (setup_window(newwin) == FALSE) {
	free((char *) newwin);
	return(NULL);
    }

    /*
     * Link the window into the list.
     */
    if (last != NULL) {
	last->w_next = newwin;
    }
    if (next != NULL) {
	next->w_last = newwin;
    }
    newwin->w_last = last;
    newwin->w_next = next;
    nwindows += 1;

    return(newwin);
}

/*
 * Set up and allocate data structures for the given window,
 * assumed to contain a valid pointer to a buffer.
 *
 * This routine should be called after setup_buffer().
 */
static bool_t
setup_window(w)
Xviwin	*w;
{
    /*
     * Allocate space for the status line.
     */
    flexnew(&w->w_statusline);

    /*
     * Allocate a Posn structure for the cursor.
     */
    w->w_cursor = (Posn *) malloc(sizeof(Posn));
    if (w->w_cursor == NULL) {
	return(FALSE);
    }

    return(TRUE);
}

void
map_window_onto_buffer(w, b)
Xviwin	*w;
Buffer	*b;
{
    /*
     * Connect the two together.
     */
    w->w_buffer = b;
    b->b_nwindows += 1;

    /*
     * Put the cursor and the screen in the right place.
     */
    move_cursor(w, b->b_file, 0);
    w->w_topline = b->b_file;
    w->w_botline = b->b_lastline;

    /*
     * Miscellany.
     */
    w->w_row = w->w_col = 0;
    w->w_virtcol = 0;
    w->w_curswant = 0;
    w->w_set_want_col = FALSE;
    w->w_curs_new = TRUE;
}

/*
 * Unmap the given window from its buffer.
 * We don't need to do much here, on the assumption that the
 * calling code is going to do a map_window_onto_buffer()
 * immediately afterwards; the vital thing is to decrement
 * the window reference count.
 */
void
unmap_window(w)
Xviwin	*w;
{
    w->w_buffer->b_nwindows -= 1;

    w->w_cursor->p_line = NULL;
    w->w_topline = NULL;
    w->w_botline = NULL;
}

/*
 * Given a window, find the "next" one in the list.
 */
Xviwin *
next_window(window)
Xviwin	*window;
{
    if (window == NULL) {
	return(NULL);
    } else if (window->w_next != NULL) {
	return(window->w_next);
    } else {
	Xviwin	*tmp;

	/*
	 * No next window; go to start of list.
	 */
	for (tmp = window; tmp->w_last != NULL; tmp = tmp->w_last)
	    ;
	return(tmp);
    }
}

/*
 * Find the next window onto the buffer with the given filename,
 * starting at the current one; if there isn't one, or if it is
 * too small to move into, return NULL.
 */
Xviwin *
find_window(window, filename)
Xviwin	*window;
char	*filename;
{
    Xviwin	*wp;
    char	*f;

    if (window != NULL && filename != NULL) {
	wp = window;
	do {
	    f = wp->w_buffer->b_filename;
	    if (f != NULL && strcmp(filename, f) == 0) {
		return(wp);
	    }
	    wp = next_window(wp);
	} while (wp != window);
    }

    return(NULL);
}

/*
 * Grow or shrink the given buffer window by "nlines" lines.
 * We prefer to move the bottom of the window, and will only
 * move the top when there is no room for manoeuvre below
 * the current one - i.e. any windows are at minimum size.
 */
void
resize_window(window, nlines)
Xviwin	*window;
int	nlines;
{
    unsigned	savecho;

    if (nlines == 0 || nwindows == 1) {
	/*
	 * Nothing to do.
	 */
	return;
    }

    savecho = echo;

    if (nlines < 0) {
	int	spare;		/* num spare lines in this window */

	nlines = - nlines;

	/*
	 * The current window must always contain 2 rows,
	 * so that the cursor has somewhere to go.
	 */
	spare = window->w_nrows - MINROWS;

	/*
	 * If the window is already as small as it
	 * can get, don't bother to do anything.
	 */
	if (spare <= 0)
	    return;

	/*
	 * Don't allow any screen updating until we've
	 * finished moving things around.
	 */
	echo &= ~e_CHARUPDATE;

	/*
	 * First shrink the current window up from the bottom.
	 *
	 * move_sline()'s return value should be negative or 0
	 * in this case.
	 */
	nlines += move_sline(window, - min(spare, nlines));

	/*
	 * If that wasn't enough, grow the window above us
	 * by the appropriate number of lines.
	 */
	if (nlines > 0) {
	    (void) move_sline(window->w_last, nlines);
	}
    } else {
	/*
	 * Don't allow any screen updating until we've
	 * finished moving things around.
	 */
	echo &= ~e_CHARUPDATE;

	/*
	 * Expand window.
	 */
	nlines -= move_sline(window, nlines);
	if (nlines > 0) {
	    (void) move_sline(window->w_last, -nlines);
	}
    }

    /*
     * Update screen. Note that status lines have
     * already been updated by move_sline().
     *
     * This still needs a lot more optimization.
     */
    echo = savecho;
    update_all();
}

/*
 * Adjust the boundary between two adjacent windows by moving the status line
 * up or down, updating parameters for both windows as appropriate.
 *
 * Note that this can shrink the window to size 0.
 */
int
move_sline(wp, nlines)
Xviwin	*wp;		/* window whose status line we have to move */
int	nlines;		/*
			 * number of lines to move (negative for
			 * upward moves, positive for downwards)
			 */
{
    Xviwin	*nextwin;

    if (wp == NULL || (nextwin = wp->w_next) == NULL) {
	return(0);
    }

    if (nlines < 0) {		/* move upwards */
	int	amount;
	int	spare;

	amount = -nlines;
	spare = wp->w_nrows - Pn(P_minrows);

	if (amount > spare && wp->w_last != NULL) {
	    /*
	     * Not enough space: call move_sline() recursively
	     * for previous line; note that the second parameter
	     * should be negative.
	     */
	    (void) move_sline(wp->w_last, spare - amount);
	    spare = wp->w_nrows - Pn(P_minrows);
	}
	if (amount > spare)
	    amount = spare;
	if (amount != 0) {
	    wp->w_nrows -= amount;
	    wp->w_cmdline -= amount;
	    nextwin->w_winpos -= amount;
	    nextwin->w_nrows += amount;
	    (void) shiftdown(nextwin, (unsigned) amount);
	    if (wp->w_nrows > 0) {
		show_file_info(wp, TRUE);
	    }
	}
	nlines = -amount;	/* return value */
    } else {			/* move downwards */
	int	spare;

	spare = nextwin->w_nrows - Pn(P_minrows);

	if (nlines > spare) {
	    /*
	     * Not enough space: call move_sline()
	     * recursively for next line.
	     */
	    (void) move_sline(nextwin, nlines - spare);
	    spare = nextwin->w_nrows - Pn(P_minrows);
	}
	if (nlines > spare)
	    nlines = spare;
	if (nlines != 0) {
	    wp->w_nrows += nlines;
	    wp->w_cmdline += nlines;
	    nextwin->w_winpos += nlines;
	    nextwin->w_nrows -= nlines;
	    (void) shiftup(nextwin, (unsigned) nlines);
	    if (wp->w_nrows > 0) {
		show_file_info(wp, TRUE);
	    }
	}
    }
    return(nlines);
}

/*
 * Update all windows associated with the given buffer.
 */
void
update_buffer(buffer)
Buffer	*buffer;
{
    Xviwin	*w;
	    
    w = curwin;		/* as good a place as any to start */
    do {
	if (w->w_buffer == buffer) {
	    update_window(w);
	}
	w = next_window(w);
    } while (w != curwin);
}

bool_t
can_split()
{
    return(nwindows < Pn(P_autosplit));
}
