/*	Copyright (c) 1988 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

/*
 *      Copyright (c) 1997, by Sun Microsystems, Inc.
 *      All rights reserved.
 */

#pragma ident	"@(#)chg_data.c	1.5	97/09/17 SMI" /* SVr4.0 1.5 */

/*LINTLIBRARY*/

#include <sys/types.h>
#include <stdlib.h>
#include "utility.h"

#define	AT_BOTTOM(f)	(Y(f) == Ymax(f) - 1)		/* last line	*/
#define	AT_END(f)	(Y(f) == Ymax(f) - 1 && X(f) == Xmax(f) - 1)
							/* last char */
#define	AT_BEGINNING(f)	(Y(f) == 0 && X(f) == 0)	/* first char	*/

static int
room_for_line(FORM *f)
{
	char * v;

	_sync_buffer(f);
	v = LineBuf(C(f), Ymax(f) - 1);
	return (v == _data_end(v, Xmax(f)));	/* check for empty line */
}

static int
room_for_char(FORM *f)
{
	WINDOW * w = W(f);
	int c;

	(void) wmove(w, Y(f), Xmax(f) - 1);
	c = (int) (winch(w) & A_CHARTEXT);
	(void) wmove(w, Y(f), X(f));
	return (c == Pad(C(f)));	/* check for empty char */
}

static int
extra_padding(char *str, int nstr)		/* used for word wrapping */
{
	int c = *(str + nstr - 1);

	if (c == '"' || c == '\'')
		c = *(str + nstr - 2);

	return ((c == '.' || c == '?' || c == '!' || c == ':') ? 2 : 1);

}

BOOLEAN
_grow_field(FIELD *c, int chunks)
{
	/* This function handles the growth of dymanically growable fields */
	/* Returns TRUE if successful, FALSE otherwise	*/

	FORM		*f = c->form;
	WINDOW		*w = W(f);
	BOOLEAN		current = Status(f, POSTED) && c == C(f);
	char		*old_buf;
	char		*new_buf;
	char		*save;
	int		old_len = BufSize(c);
	int		grow;
	int		lcv;
	int		max = c->maxgrow;
	int		i;

	if (current && Status(f, WIN_CHG)) {
		_win_to_buf(w, c);
		Clr(f, WIN_CHG);
		Set(f, BUF_CHG);
	}

	if (OneRow(c)) {
		grow = chunks * c->cols;

		if (max)
			grow = MIN(max - c->dcols, grow);

		c->dcols += grow;

		if (c->dcols == max)
			Clr(c, GROWABLE);
	} else {
		grow = chunks * (c->rows + c->nrow);

		if (max)
			grow = MIN(max - c->drows, grow);

		c->drows += grow;
		grow *= c->cols;

		if (c->drows == max)
			Clr(c, GROWABLE);
	}

	save = old_buf = Buf(c);
	new_buf = Buf(c) = malloc(TotalBuf(c));

	if (!new_buf)
		return (FALSE);

	lcv = c->nbuf + 1;

	for (i = 0; i < lcv; i++) {
		(void) memcpy(new_buf, old_buf, old_len);
		(void) memset(new_buf + old_len, ' ', grow);
		old_buf += old_len + 1;
		new_buf += old_len + grow;
		*new_buf++ = '\0';
	}

	free(save);	/* delete old buffer */

	if (current) {
		(void) delwin(w);
		W(f) = w = newwin(c->drows, c->dcols, 0, 0);

		if (!w)
			return (FALSE);

		wbkgdset(w, Pad(c) | Back(c));
		(void) wattrset(w, Fore(c));
		(void) werase(w);
		_buf_to_win(c, w);
		(void) untouchwin(w);
		(void) wmove(w, Y(f), X(f));
	}

	if (c->link != c) {
		FIELD	*p = c->link;

		while (p != c) {
			Buf(p) = Buf(c);
			p->drows = c->drows;
			p->dcols = c->dcols;
			/* _sync_field(p) */
			p = p->link;
		}
	}

	return (TRUE);
}

static int
insert_str(FORM *f, int y, int off, int nstr)	/* used for word wrapping */
{
	WINDOW *	w	= W(f);
	FIELD *		c	= C(f);
	char *		vbeg	= LineBuf(c, y);
	char *		v	= _data_end(vbeg, Xmax(f));
	int		x	= (int) (v - vbeg);
	int		n	= Xmax(f) - x;
	int		pad	= extra_padding(Buf(c) + off, nstr);
	int		siz	= nstr + 1 + pad;
	int		ret 	= E_REQUEST_DENIED;

	if (n >= siz) {	/* check for fit on this line */
		(void) wmove(w, y, 0);
		(void) winsnstr(w, Buf(c) + off, nstr);
		(void) wmove(w, y, nstr);
		(void) winsnstr(w, "  ", pad);
	} else {		/* wrap */
		if (y == Ymax(f) - 1 && Status(c, GROWABLE)) {
			if (!_grow_field(c, 1))
				return (E_SYSTEM_ERROR);

			vbeg = LineBuf(c, y);	/* grow changes buffer */
			w = W(f);		/* grow changes window */
		}

		v = _data_beg(vbeg + Xmax(f) - siz, siz);
		v = _whsp_end(vbeg, (int) (v - vbeg));
		x = (int) (v - vbeg);
		n = Xmax(f) - x - n;

		if (y < Ymax(f) - 1 && (ret =
		    insert_str(f, y+1, (int) (v - Buf(c)), n)) == E_OK) {
			(void) wmove(w, y, x);
			(void) wclrtoeol(w);
			(void) wmove(w, y, 0);
			(void) winsnstr(w, Buf(c) + off, nstr);
			(void) wmove(w, y, nstr);
			(void) winsnstr(w, "  ", pad);
		} else
			return (ret);	/* no room for wrap */
	}
	return (E_OK);
}

static int
wrap_ok(FORM *f)		/* used for word wrapping */
{
/*
 * when this routine is called a char has already been added/inserted
 * on the screen at Y(f), X(f).  this routine checks to see if the current
 * line needs wrapping and if so attempts the wrap.  if unsuccessful
 * it deletes the char at Y(f), X(f) and returns FALSE.
 */
	FIELD		*c = C(f);
	BOOLEAN		at_bottom = AT_BOTTOM(f);
	int		ret = E_REQUEST_DENIED;

	if (Opt(c, O_WRAP) && !OneRow(c) && !room_for_char(f) &&
	    (!at_bottom || Status(c, GROWABLE))) {
		WINDOW *w;
		char *vbeg;
		char *v;
		int x, n;

		if (at_bottom && !_grow_field(c, 1))
			return (E_SYSTEM_ERROR);

		vbeg = LineBuf(c, Y(f));
		w = W(f);

		_win_to_buf(w, c);	/* sync buffer without changing flags */

		v = _whsp_end(vbeg, Xmax(f));
		x = (int) (v - vbeg);
		n = Xmax(f) - x;

		if (x && (ret = insert_str(f, Y(f)+1, (int) (v - Buf(c)), n)) ==
		    E_OK) {
			w = W(f);	/* window may change in insert_str */
			(void) wmove(w, Y(f), x);
			(void) wclrtoeol(w);

			if (X(f) >= x) {
				++Y(f);
				X(f) = X(f) - x;
			}
		} else {	/* error condition */
			if (ret == E_SYSTEM_ERROR)
				return (E_SYSTEM_ERROR);

			(void) wmove(w, Y(f), X(f));
			(void) wdelch(w);	/* delete the char */
			_win_to_buf(w, c);	/* restore buffer  */
			return (E_REQUEST_DENIED);
		}
	}
	return (E_OK);
}

int
_new_line(FORM *f)
{
/*
		overloaded operation

	if at beginning of field
		move to next field

	else if in OVERLAY mode
		if on last line of field
			clear to eol and move to next field
		else
			clear to eol and move to beginning of next line

	else if in INSERT mode
		if on last line of field
			move to next field
		else
			move text from cursor to eol to new line
*/
	BOOLEAN		at_bottom = AT_BOTTOM(f);
	FIELD *		c = C(f);

	if (Opt(f, O_NL_OVERLOAD) && AT_BEGINNING(f))
		return (_field_navigation(_next_field, f));

	if (!Opt(c, O_EDIT))
		return (E_REQUEST_DENIED);

	if (Status(f, OVERLAY)) {		/* OVERLAY mode	*/
		if (at_bottom && (!Status(c, GROWABLE) || OneRow(c))) {
			if (Opt(f, O_NL_OVERLOAD)) {
				(void) wclrtoeol(W(f));
				Set(f, WIN_CHG);
				return (_field_navigation(_next_field, f));
			} else
				return (E_REQUEST_DENIED);
		}

		if (at_bottom && !_grow_field(c, 1))
			return (E_SYSTEM_ERROR);

		(void) wclrtoeol(W(f));
		++Y(f); X(f) = 0;
	} else {		/* INSERT mode	*/
		BOOLEAN		room;

		if (at_bottom && (!Status(c, GROWABLE) || OneRow(c))) {
			if (Opt(f, O_NL_OVERLOAD))
				return (_field_navigation(_next_field, f));
			else
				return (E_REQUEST_DENIED);
		}

		room = !at_bottom && room_for_line(f);

		if (room || Status(c, GROWABLE)) {
			WINDOW	*w;
			char *v;
			char *vend;

			if (!room && !_grow_field(c, 1))
				return (E_SYSTEM_ERROR);

			w = W(f);
			v = LineBuf(c, Y(f)) + X(f);
			vend = _data_end(v, Xmax(f) - X(f));

			(void) wclrtoeol(w);
			++Y(f); X(f) = 0;
			(void) wmove(w, Y(f), X(f));
			(void) winsertln(w);
			(void) waddnstr(w, v, (int) (vend - v));
		} else
			return (E_REQUEST_DENIED);
	}
	Set(f, WIN_CHG);
	return (E_OK);
}

/* _ins_char - insert blank char with error on overflow */
int
_ins_char(FORM *f)
{
	FIELD	*c = C(f);
	BOOLEAN	room = room_for_char(f);

	if (CheckChar(c, ' ') && (room || (OneRow(c) &&
	    Status(c, GROWABLE)))) {
		if (!room && !_grow_field(c, 1))
			return (E_SYSTEM_ERROR);

		(void) winsch(W(f), ' ');

		return (wrap_ok(f));
	}
	return (E_REQUEST_DENIED);
}

/* _ins_line -  insert blank line with error on overflow */
int
_ins_line(FORM *f)
{
	BOOLEAN		room = !AT_BOTTOM(f) && room_for_line(f);
	FIELD		*c = C(f);

	if (CheckChar(c, ' ') && !OneRow(c) && (room || Status(c, GROWABLE))) {
		if (!room && !_grow_field(c, 1))
			return (E_SYSTEM_ERROR);

		X(f) = 0;
		(void) winsertln(W(f));
		return (E_OK);
	}
	return (E_REQUEST_DENIED);
}

/* _del_char - delete char at cursor */
int
_del_char(FORM *f)
{
	(void) wdelch(W(f));
	return (E_OK);
}

int
_del_prev(FORM *f)
{
/*
		overloaded operation

	if at beginning of field
		move to previous field

	else if in OVERLAY mode
		if at beginning of line
			error
		else
			delete previous char

	else if in INSERT mode
		if at beginning of line
			if current line can fit on preceding
				join current line with preceding line
			else
				error
		else
			delete previous char
*/
	WINDOW *	w = W(f);
	FIELD *		c = C(f);

	if (AT_BEGINNING(f)) {
		if (Opt(f, O_BS_OVERLOAD))
			return (_field_navigation(_prev_field, f));
		else
			return (E_REQUEST_DENIED);
	}
	if (!Opt(c, O_EDIT))
		return (E_REQUEST_DENIED);

	if (--X(f) < 0) {
		++X(f);

		if (Status(f, OVERLAY))	/* OVERLAY mode	*/
			return (E_REQUEST_DENIED);
		else {			/* INSERT mode	*/
			char *	p = LineBuf(c, Y(f) - 1);
			char *	v = LineBuf(c, Y(f));
			char *	pend;
			char *	vend;

			_sync_buffer(f);
			pend = _data_end(p, Xmax(f));
			vend = _data_end(v, Xmax(f));

			if ((vend - v) > (Xmax(f) - (pend - p)))
				return (E_REQUEST_DENIED);
			else {
				(void) wdeleteln(w);
				_adjust_cursor(f, pend);
				(void) wmove(w, Y(f), X(f));
				(void) waddnstr(w, v, (int) (vend - v));
			}
		}
	} else {
		(void) wmove(w, Y(f), X(f));
		(void) wdelch(w);
	}
	Set(f, WIN_CHG);
	return (E_OK);
}

/* _del_line - delete current line */
int
_del_line(FORM *f)
{
	X(f) = 0;
	(void) wdeleteln(W(f));
	return (E_OK);
}

/* _del_word - delete word under cursor plus trailing blanks */
int
_del_word(FORM *f)
{
	FIELD *		c = C(f);
	WINDOW *	w = W(f);
	char *		y = LineBuf(c, Y(f));
	char *		t = y + Xmax(f);
	char *		v = y + X(f);
	char *		x = v;

	_sync_buffer(f);

	if (*v == ' ')
		return (E_REQUEST_DENIED);

	_adjust_cursor(f, _whsp_end(y, X(f)));
	(void) wmove(w, Y(f), X(f));
	(void) wclrtoeol(w);

	v = _whsp_beg(v, (int) (t - v));
	v = _data_beg(v, (int) (t - v));

	if (v != x && *v != ' ')
		(void) waddnstr(w, v, (int) (_data_end(v, (int) (t - v)) - v));

	return (E_OK);
}

/* _clr_eol - clear to end of line */
int
_clr_eol(FORM *f)
{
	(void) wclrtoeol(W(f));
	return (E_OK);
}

/* _clr_eof - clear to end of field */
int
_clr_eof(FORM *f)
{
	(void) wclrtobot(W(f));
	return (E_OK);
}

/* _clr_field - clear entire field */
int
_clr_field(FORM *f)
{
	X(f) = 0; Y(f) = 0;
	(void) werase(W(f));
	return (E_OK);
}

/* _ovl_mode - go into overlay mode */
int
_ovl_mode(FORM *f)
{
	Set(f, OVERLAY);
	return (E_OK);
}

/* _ins_mode - go into insert mode */
int
_ins_mode(FORM *f)
{
	Clr(f, OVERLAY);
	return (E_OK);
}

/* _validation - apply validation function associated with field type */
int
_validation(FORM *f)
{
	return (_validate(f) ? E_OK : E_INVALID_FIELD);
}

/* _next_choice - apply next choice function associated with field type */
int
_next_choice(FORM *f)
{
	_sync_buffer(f);
	return (NextChoice(C(f)) ? E_OK : E_REQUEST_DENIED);
}

/* _prev_choice - apply previous choice function associated with field type */
int
_prev_choice(FORM *f)
{
	_sync_buffer(f);
	return (PrevChoice(C(f)) ? E_OK : E_REQUEST_DENIED);
}

/*
 * _data_entry - enter printable ascii char ch
 * in current field at cursor position
 */
int
_data_entry(FORM *f, int ch)
{
	FIELD *		c = C(f);	/* current field	*/
	WINDOW *	w = W(f);	/* field window		*/
	BOOLEAN		at_end;
	int		ret;

	if (!Opt(c, O_EDIT))
		return (E_REQUEST_DENIED);

	if (AT_BEGINNING(f) && Opt(c, O_BLANK) && ! Status(f, BUF_CHG) &&
	    !Status(f, WIN_CHG))
		(void) werase(w);

	if (Status(f, OVERLAY))	/* OVERLAY mode	*/
		(void) waddch(w, (chtype) ch);
	else {				/* INSERT mode	*/
		BOOLEAN	room = room_for_char(f);

		if (room || (OneRow(c) && Status(c, GROWABLE))) {
			if (!room && !_grow_field(c, 1))
				return (E_SYSTEM_ERROR);

			(void) winsch(w, (chtype) ch);
		} else
			return (E_REQUEST_DENIED);
	}

	if ((ret = wrap_ok(f)) != E_OK)
		return (ret);

	Set(f, WIN_CHG);

	at_end = AT_END(f);

	if (at_end && !Status(c, GROWABLE) && Opt(c, O_AUTOSKIP))
		return (_field_navigation(_next_field, f));

	if (at_end && Status(c, GROWABLE) && !_grow_field(c, 1))
		return (E_SYSTEM_ERROR);

	(void) _next_char(f);
	return (E_OK);
}
