/* lpecomm.c
 *
 * Copyright (c) 1999 Chris Smith
 *
 * This file is distributed under the GPL, version 2 or at your option any
 * later version.  See COPYING for details.
 */

#include "options.h"
#include <slang.h>
#include <stdlib.h>
#include "lpecomm.h"
#include "cfg.h"

/* Calculate the next tab stop position.  Returns the number of spaces needed
 * to reach that position.  This is separated into a separate function just to
 * make code look nicer.
 */
int
next_mult (int now, int spacing)
{
    return spacing - (now - ((now / spacing) * spacing));
}

/* Make sure that the current cursor position is visible, scrolling the screen
 * as necessary is both the horizontal and vertical directions.  Returns
 * non-zero if scrolling positions were updated (and hence the screen needs to
 * be redrawn) or zero if the current scrolling was okay.
 */
int
check_scrolling (buffer * buf)
{
    int ret = 0;

    while (buf->linenum < buf->scrollnum)
    {
	int i;
	for (i = 0;
	     i <= cfg_get_global_int_with_default ("scroll_granularity",
						   DEF_SCROLL_GRAN); i++)
	{
	    if (buf->scrollpos->prev != NULL)
	    {
		buf->scrollpos = buf->scrollpos->prev;
		buf->scrollnum--;
	    }
	}

	ret = 1;
    }
    while (buf->linenum >= buf->scrollnum + SLtt_Screen_Rows - 1)
    {
	int i;
	for (i = 0;
	     i <= cfg_get_global_int_with_default ("scroll_granularity",
						   DEF_SCROLL_GRAN); i++)
	{
	    if (buf->scrollpos->next != NULL)
	    {
		buf->scrollpos = buf->scrollpos->next;
		buf->scrollnum++;
	    }
	}

	ret = 1;
    }

    if (buf->scr_col < buf->scrollcol)
    {
	buf->scrollcol =
	    buf->scr_col -
	    cfg_get_global_int_with_default ("scroll_granularity",
					     DEF_SCROLL_GRAN);
	if (buf->scrollcol < 0)
	    buf->scrollcol = 0;
	ret = 1;
    }
    if (buf->scr_col >= buf->scrollcol + SLtt_Screen_Cols)
    {
	buf->scrollcol =
	    buf->scr_col - SLtt_Screen_Cols + 1 +
	    cfg_get_global_int_with_default ("scroll_granularity",
					     DEF_SCROLL_GRAN);
	if (buf->scrollcol >= LINELEN (buf->pos))
	    buf->scrollcol = LINELEN (buf->pos) - 1;
	ret = 1;
    }

    return ret;
}

/* Adjust the column after switching to a new line.  Tries to move the cursor
 * to the preferred column.  If that fails, moves it to the last column in the
 * current line.  This provides the sticky column behavior that makes editors
 * a lot nicer to use.
 */
void
check_col (buffer * buf)
{
    int txt_i = 0, scr_i = 0;

    while ((scr_i < buf->preferred_col)
	   && (buf->pos.line->txt[txt_i] != '\0'))
    {
	if (buf->pos.line->txt[txt_i] == '\t')
	    scr_i += next_mult (scr_i,
				cfg_get_global_int_with_default
				("hard_tab_width", DEF_HARD_TAB_WIDTH));
//                      DEF_HARD_TAB_WIDTH );
	else if (is_control (buf->pos.line->txt[txt_i]))
	    scr_i += 2;
	else
	    scr_i++;

	txt_i++;
    }

    buf->pos.col = txt_i;
    buf->scr_col = scr_i;
}

/* Recalculate the screen column from the current cursor position.  Needed
 * because of the possibility of multi-column characters in the text.
 */
void
set_scr_col (buffer * buf)
{
    int i;

    buf->scr_col = 0;
    for (i = 0; i < buf->pos.col; i++)
    {
	if (buf->pos.line->txt[i] == '\t')
	    buf->scr_col += next_mult (buf->scr_col,
				       cfg_get_global_int_with_default
				       ("hard_tab_width",
					DEF_HARD_TAB_WIDTH));
//              DEF_HARD_TAB_WIDTH );
	else if (is_control (buf->pos.line->txt[i]))
	    buf->scr_col += 2;
	else
	    buf->scr_col++;
    }
}

int
is_control (char c)
{
    unsigned char ch = c;

    if (ch < 9)
	return 1;
    if ((ch > 10) && (ch < 32))
	return 1;
    if ((ch > 126) && (ch < 160))
	return 1;
    return 0;
}

/* Insert a character in the buffer.  Returns 0 on success, or -1 if it runs
 * out of memory.
 */
int
add_char (int c, buffer * buf)
{
    int ch, insc;
    char *p;

    if (buf->pos.line->txt_len == LINELEN (buf->pos) + 1)
    {
	/* need to allocate more space for the insertion */
	char *nbuf;

	nbuf = (char *) realloc (buf->pos.line->txt,
				 buf->pos.line->txt_len +
				 cfg_get_global_int_with_default
				 ("realloc_granularity",
				  DEF_REALLOC_GRAN));
	if (nbuf == NULL)
	    return -1;

	buf->pos.line->txt = nbuf;
	buf->pos.line->txt_len +=
	    cfg_get_global_int_with_default ("realloc_granularity",
					     DEF_REALLOC_GRAN);
    }

    p = buf->pos.line->txt + buf->pos.col;
    insc = c;
    while (*p != '\0')
    {
	ch = *p;
	*p = insc;
	insc = ch;
	p++;
    }

    *p = insc;
    *(p + 1) = '\0';

    buf->pos.col++;

    if (c == '\t')
	buf->scr_col += next_mult (buf->scr_col,
				   cfg_get_global_int_with_default
				   ("hard_tab_width", DEF_HARD_TAB_WIDTH));
//              DEF_HARD_TAB_WIDTH );
    else if (is_control (c))
	buf->scr_col += 2;
    else
	buf->scr_col++;

    buf->preferred_col = buf->scr_col;

    return 0;
}

/* Delete the character preceding the cursor */
void
del_char (buffer * buf)
{
    char *p;
    buf->pos.col--;

    if (buf->pos.line->txt[buf->pos.col] == '\t')
	set_scr_col (buf);
    else if (is_control (buf->pos.line->txt[buf->pos.col]))
	buf->scr_col -= 2;
    else
	buf->scr_col--;

    p = buf->pos.line->txt + buf->pos.col;
    do
    {
	*p = *(p + 1);
	p++;
    }
    while (*p != '\0');

    buf->preferred_col = buf->scr_col;
}

/* The default implementation of auto-indentation.  It just adds spaces to the
 * new line to match the indent of the previous line.  If hard tabs are enabled
 * for the current buffer, it fills the indent with tabs as much as possible;
 * if not, it uses spaces.  This implementation of auto-indent is used when
 * dynamic modes are disabled, if no mode is found, or if the mode does not
 * implement a replacement for auto-indentation.
 */
void
def_indent (buffer * buf, char ch)
{
    char *p;

    if (ch != '\n')
	return;

    for (p = buf->pos.line->prev->txt; (*p == ' ') || (*p == '\t'); p++)
	add_char (*p, buf);

    /* kill the contents of the previous line if there were whitespaces *
     * only.  This way, autoindent doesn't leave a bunch of random whitespace
     * * when it's not necessary. */
    if (*p == '\0')
	*(buf->pos.line->prev->txt) = 0;
}
