/*
 * input.c: does the actual input line stuff... keeps the appropriate stuff
 * on the input line, handles insert/delete of characters/words... the whole
 * ball o wax 
 *
 *
 * Written By Michael Sandrof
 *
 * Copyright(c) 1990 
 *
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 */
#include <stdio.h>
#include <ctype.h>
#include "irc.h"
#include "input.h"
#include "term.h"
#include "alias.h"
#include "vars.h"
#include "ircaux.h"
#include "window.h"
#include "screen.h"
#include "exec.h"

#define WIDTH 10

/* input_prompt: contains the current, unexpanded input prompt */
static	char	*input_prompt = null(char *);

/* input_line: the actual screen line where the input goes */
static	int	input_line;

/* str_start: position in buffer of first visible character in the input line */
static	int	str_start = 0;

/*
 * upper_mark and lower_mark: marks the upper and lower positions in the
 * input buffer which will cause the input display to switch to the next or
 * previous bunch of text 
 */
static	int	lower_mark;
static	int	upper_mark;

/* zone: the amount of editable visible text on the screen */
static	int	zone;

/* cursor: position of the cursor in the input line on the screen */
static	int	cursor = 0;

/* cursor_to_input: move the cursor to the input line, if not there already */
void cursor_to_input()
{
	Screen *old_current_screen;

	old_current_screen = current_screen;
	for (current_screen = screen_list; current_screen;
			current_screen = current_screen->next)
	{
		if (current_screen->alive && is_cursor_in_display())
		{
			term_move_cursor(cursor, input_line);
			cursor_not_in_display();
			term_flush();
		}
	}
	set_current_screen(old_current_screen);
}

/*
 * update_input: does varying amount of updating on the input line depending
 * upon the position of the cursor and the update flag.  If the cursor has
 * move toward one of the edge boundaries on the screen, update_cursor()
 * flips the input line to the next (previous) line of text. The update flag
 * may be: 
 *
 * NO_UPDATE - only do the above bounds checking. 
 *
 * UPDATE_JUST_CURSOR - do bounds checking and position cursor where is should
 * be. 
 *
 * UPDATE_FROM_CURSOR - does all of the above, and makes sure everything from
 * the cursor to the right edge of the screen is current (by redrawing it). 
 *
 * UPDATE_ALL - redraws the entire line 
 */
void	update_input(update)
int	update;
{
	int	old_start;
	static	int	co = 0,
			li = 0;
	char	*ptr;
	int	len,
		free_it = 1,
		cnt,
		max;
	char	*prompt;

	if (dumb)
		return;
	cursor_to_input();
	if (current_screen->promptlist)
		prompt = current_screen->promptlist->prompt;
	else
		prompt = input_prompt;
	if (prompt)
	{
		if (update != NO_UPDATE)
		{
			char	*inp_ptr = null(char *);
			int	args_used;	/* this isn't used here but is
						 * passed to expand_alias()
						 */
			if (is_process(get_target_by_refnum(0)))
			{
				ptr = (char *)get_prompt_by_refnum(0);
				free_it = 0;
			}
			else
				ptr = expand_alias(null(char *), prompt,
					empty_string, &args_used, NULL);
			if (*ptr && (
				(strnicmp(ptr, "Password:", 9) == 0) ||
				(strnicmp(ptr, "Operator Password:",18) == 0) ||
				(strnicmp(ptr, "Server Password:", 16) == 0)))
				term_echo(0);
			else
				term_echo(1);
			len = strlen(ptr);
			if (strncmp(ptr, current_screen->input_buffer, len) ||
					!len)
			{
				malloc_strcpy(&inp_ptr,
					current_screen->input_buffer +
					current_screen->buffer_min_pos);
				strmcpy(current_screen->input_buffer, ptr,
					INPUT_BUFFER_SIZE);
				current_screen->buffer_pos += (len -
					current_screen->buffer_min_pos);
				current_screen->buffer_min_pos = strlen(ptr);
				strmcat(current_screen->input_buffer, inp_ptr,
					INPUT_BUFFER_SIZE);
				new_free(&inp_ptr);
				update = UPDATE_ALL;
			}
			if (free_it) new_free(&ptr);
		}
	}
	else
		term_echo(1);
	if ((li != LI) || (co != CO))
	{
		/* resized?  Keep it simple and reset everything */
		input_line = LI - 1;
		zone = CO - (WIDTH * 2);
		lower_mark = WIDTH;
		upper_mark = CO - WIDTH;
		cursor = current_screen->buffer_min_pos;
		current_screen->buffer_pos = current_screen->buffer_min_pos;
		str_start = 0;
		li = LI;
		co = CO;
	}
	old_start = str_start;
	while ((current_screen->buffer_pos < lower_mark) && lower_mark > WIDTH)
	{
		upper_mark = lower_mark;
		lower_mark -= zone;
		str_start -= zone;
	}
	while (current_screen->buffer_pos >= upper_mark)
	{
		lower_mark = upper_mark;
		upper_mark += zone;
		str_start += zone;
	}
	cursor = current_screen->buffer_pos - str_start;
	if ((old_start != str_start) || (update == UPDATE_ALL))
	{
		term_move_cursor(0, input_line);
		if ((str_start == 0) && (current_screen->buffer_min_pos > 0))
		{
			int	echo;

			echo = term_echo(1);
			if (current_screen->buffer_min_pos > (CO - WIDTH))
				len = CO - WIDTH - 1;
			else
				len = current_screen->buffer_min_pos;
			cnt = term_puts(&(current_screen->input_buffer[
				str_start]), len);
			term_echo(echo);
			cnt += term_puts(&(current_screen->input_buffer[
				str_start + len]), CO - len);
		}
		else
			cnt = term_puts(&(current_screen->input_buffer[
				str_start]), CO);
		if (term_clear_to_eol())
			term_space_erase(cnt);
		term_move_cursor(cursor, input_line);
	}
	else if (update == UPDATE_FROM_CURSOR)
	{
		term_move_cursor(cursor, input_line);
		cnt = cursor;
		max = CO - (current_screen->buffer_pos - str_start);
		if ((len = strlen(&(current_screen->input_buffer[
				current_screen->buffer_pos]))) > max)
			len = max;
		cnt += term_puts(&(current_screen->input_buffer[
			current_screen->buffer_pos]), len);
		if (term_clear_to_eol())
			term_space_erase(cnt);
		term_move_cursor(cursor, input_line);
	}
	else if (update == UPDATE_JUST_CURSOR)
		term_move_cursor(cursor, input_line);
	term_flush();
}

void	refresh_inputline()
{
	update_input(UPDATE_ALL);
}

void	change_input_prompt(direction)
int	direction;
{
	if (!current_screen->promptlist)
	{
		strcpy(current_screen->input_buffer,
				current_screen->saved_input_buffer);
		current_screen->buffer_pos =
				current_screen->saved_buffer_pos;
		current_screen->buffer_min_pos =
				current_screen->saved_min_buffer_pos;
		update_input(UPDATE_ALL);
	}
	else if (direction == -1)
	{
		update_input(UPDATE_ALL);
	}
	else if (!current_screen->promptlist->next)
	{
		strcpy(current_screen->saved_input_buffer,
				current_screen->input_buffer);
		current_screen->saved_buffer_pos =
				current_screen->buffer_pos;
		current_screen->saved_min_buffer_pos =
				current_screen->buffer_min_pos;
		*current_screen->input_buffer = '\0';
		current_screen->buffer_pos =
				current_screen->buffer_min_pos = 0;
		update_input(UPDATE_ALL);
	}
}

/* input_move_cursor: moves the cursor left or right... got it? */
void	input_move_cursor(dir)
int	dir;
{
	cursor_to_input();
	if (dir)
	{
		if (current_screen->input_buffer[current_screen->buffer_pos])
		{
			current_screen->buffer_pos++;
			if (term_cursor_right())
				term_move_cursor(cursor + 1, input_line);
		}
	}
	else
	{
		if (current_screen->buffer_pos > current_screen->buffer_min_pos)
		{
			current_screen->buffer_pos--;
			if (term_cursor_left())
				term_move_cursor(cursor - 1, input_line);
		}
	}
	update_input(NO_UPDATE);
}

/*
 * input_forward_word: move the input cursor forward one word in the input
 * line 
 */
void input_forward_word()
{
	cursor_to_input();
	while (
	  (isspace(current_screen->input_buffer[current_screen->buffer_pos]) ||
	  ispunct(current_screen->input_buffer[current_screen->buffer_pos])) &&
	  current_screen->input_buffer[current_screen->buffer_pos])
		current_screen->buffer_pos++;
	while
	 (!(ispunct(current_screen->input_buffer[current_screen->buffer_pos]) ||
	 isspace(current_screen->input_buffer[current_screen->buffer_pos])) &&
	 current_screen->input_buffer[current_screen->buffer_pos])
		current_screen->buffer_pos++;
	update_input(UPDATE_JUST_CURSOR);
}

/* input_backward_word: move the cursor left on word in the input line */
void input_backward_word()
{
	cursor_to_input();
	while ((current_screen->buffer_pos > current_screen->buffer_min_pos) &&
	 (isspace(current_screen->input_buffer[current_screen->buffer_pos - 1])
	 ||
	 ispunct(current_screen->input_buffer[current_screen->buffer_pos - 1])))
		current_screen->buffer_pos--;
	while ((current_screen->buffer_pos > current_screen->buffer_min_pos) &&
	 !(ispunct(current_screen->input_buffer[current_screen->buffer_pos - 1])
	 ||
	 isspace(current_screen->input_buffer[current_screen->buffer_pos - 1])))
		current_screen->buffer_pos--;
	update_input(UPDATE_JUST_CURSOR);
}

/* input_delete_character: deletes a character from the input line */
void input_delete_character()
{
	cursor_to_input();
	if (current_screen->input_buffer[current_screen->buffer_pos])
	{
		char	*ptr = null(char *);
		int	pos;

		malloc_strcpy(&ptr,
		  &(current_screen->input_buffer[current_screen->buffer_pos
		  + 1]));
		strcpy(&(current_screen->input_buffer[
			current_screen->buffer_pos]), ptr);
		new_free(&ptr);
		if (term_delete())
			update_input(UPDATE_FROM_CURSOR);
		else
		{
			pos = str_start + CO - 1;
			if (pos < strlen(current_screen->input_buffer))
			{
				term_move_cursor(CO - 1, input_line);
				term_putchar(current_screen->input_buffer[pos]);
				term_move_cursor(cursor, input_line);
			}
			update_input(NO_UPDATE);
		}
	}
}

/* input_backspace: does a backspace in the input buffer */
/*ARGSUSED*/
void input_backspace(key, ptr)
char	*key;
void	(*ptr)();
{
	cursor_to_input();
	if (current_screen->buffer_pos > current_screen->buffer_min_pos)
	{
		char	*ptr = null(char *);
		int	pos;

		malloc_strcpy(&ptr,
		  &(current_screen->input_buffer[current_screen->buffer_pos]));
		strcpy(&(current_screen->input_buffer[current_screen->buffer_pos
			- 1]), ptr);
		new_free(&ptr);
		current_screen->buffer_pos--;
		if (term_cursor_left())
			term_move_cursor(cursor - 1, input_line);
		if (current_screen->input_buffer[current_screen->buffer_pos])
		{
			if (term_delete())
			{
				update_input(UPDATE_FROM_CURSOR);
				return;
			}
			else
			{
				pos = str_start + CO - 1;
				if (pos < strlen(current_screen->input_buffer))
				{
					term_move_cursor(CO - 1, input_line);
				term_putchar(current_screen->input_buffer[pos]);
				}
				update_input(UPDATE_JUST_CURSOR);
			}
		}
		else
		{
			term_putchar(' ');
			if (term_cursor_left())
				term_move_cursor(cursor - 1, input_line);
			update_input(NO_UPDATE);
		}
	}
}

/*
 * input_beginning_of_line: moves the input cursor to the first character in
 * the input buffer 
 */
void input_beginning_of_line()
{
	cursor_to_input();
	current_screen->buffer_pos = current_screen->buffer_min_pos;
	update_input(UPDATE_JUST_CURSOR);
}

/*
 * input_end_of_line: moves the input cursor to the last character in the
 * input buffer 
 */
void input_end_of_line()
{
	cursor_to_input();
	current_screen->buffer_pos = strlen(current_screen->input_buffer);
	update_input(UPDATE_JUST_CURSOR);
}

/*
 * input_delete_previous_word: deletes from the cursor backwards to the next
 * space character. 
 */
void input_delete_previous_word()
{
	int	old_pos;
	char	c;

	cursor_to_input();
	old_pos = current_screen->buffer_pos;
	while ((current_screen->buffer_pos > current_screen->buffer_min_pos) &&
	 (isspace(current_screen->input_buffer[current_screen->buffer_pos - 1])
	 ||
	 ispunct(current_screen->input_buffer[current_screen->buffer_pos - 1])))
		current_screen->buffer_pos--;
	while ((current_screen->buffer_pos > current_screen->buffer_min_pos) &&
	 !(ispunct(current_screen->input_buffer[current_screen->buffer_pos - 1])
	 ||
	 isspace(current_screen->input_buffer[current_screen->buffer_pos - 1])))
		current_screen->buffer_pos--;
	c = current_screen->input_buffer[old_pos];
	current_screen->input_buffer[old_pos] = null(char);
	malloc_strcpy(&cut_buffer,
		&(current_screen->input_buffer[current_screen->buffer_pos]));
	current_screen->input_buffer[old_pos] = c;
	strcpy(&(current_screen->input_buffer[current_screen->buffer_pos]),
		&(current_screen->input_buffer[old_pos]));
	update_input(UPDATE_FROM_CURSOR);
}

/*
 * input_delete_next_word: deletes from the cursor to the end of the next
 * word 
 */
void input_delete_next_word()
{
	int	pos;
	char	*ptr = null(char *),
		c;

	cursor_to_input();
	pos = current_screen->buffer_pos;
	while ((isspace(current_screen->input_buffer[pos]) ||
	    ispunct(current_screen->input_buffer[pos])) &&
	    current_screen->input_buffer[pos])
		pos++;
	while (!(ispunct(current_screen->input_buffer[pos]) ||
	    isspace(current_screen->input_buffer[pos])) &&
	    current_screen->input_buffer[pos])
		pos++;
	c = current_screen->input_buffer[pos];
	current_screen->input_buffer[pos] = null(char);
	malloc_strcpy(&cut_buffer,
		&(current_screen->input_buffer[current_screen->buffer_pos]));
	current_screen->input_buffer[pos] = c;
	malloc_strcpy(&ptr, &(current_screen->input_buffer[pos]));
	strcpy(&(current_screen->input_buffer[current_screen->buffer_pos]),			ptr);
	new_free(&ptr);
	update_input(UPDATE_FROM_CURSOR);
}

/*
 * input_add_character: adds the character c to the input buffer, repecting
 * the current overwrite/insert mode status, etc 
 */
/*ARGSUSED*/
void input_add_character(c, unused)
char	c;
char	*unused;
{
	int	display_flag = NO_UPDATE;

	cursor_to_input();
	if (current_screen->buffer_pos < INPUT_BUFFER_SIZE)
	{
		if (get_int_var(INSERT_MODE_VAR))
		{
			if (current_screen->input_buffer[
					current_screen->buffer_pos])
			{
				char	*ptr = null(char *);

				malloc_strcpy(&ptr, &(
					current_screen->input_buffer[
					current_screen->buffer_pos]));
				current_screen->input_buffer[
					current_screen->buffer_pos] = c;
				current_screen->input_buffer[
					current_screen->buffer_pos + 1] =
					null(char);
				strmcat(current_screen->input_buffer, ptr,
					INPUT_BUFFER_SIZE);
				new_free(&ptr);
				if (term_insert(c))
				{
					term_putchar(c);
					if (current_screen->input_buffer[
						current_screen->buffer_pos + 1])
					    display_flag = UPDATE_FROM_CURSOR;
					else
					    display_flag = NO_UPDATE;
				}
			}
			else
			{
				current_screen->input_buffer[
					current_screen->buffer_pos] = c;
				current_screen->input_buffer[
					current_screen->buffer_pos + 1] =
					null(char);
				term_putchar(c);
			}
		}
		else
		{
			if (current_screen->input_buffer[
				    current_screen->buffer_pos] == null(char))
				current_screen->input_buffer[
				   current_screen->buffer_pos + 1] = null(char);
			current_screen->input_buffer[current_screen->buffer_pos]
				= c;
			term_putchar(c);
		}
		current_screen->buffer_pos++;
		update_input(display_flag);
	}
}

/*
 * set_input: sets the input buffer to the given string, discarding whatever
 * was in the input buffer before 
 */
void set_input(str)
char	*str;
{
	strmcpy(current_screen->input_buffer + current_screen->buffer_min_pos,
		str, INPUT_BUFFER_SIZE - current_screen->buffer_min_pos);
	current_screen->buffer_pos = strlen(current_screen->input_buffer);
}

/*
 * get_input: returns a pointer to the input buffer.  Changing this will
 * actually change the input buffer.  This is a bad way to change the input
 * buffer tho, cause no bounds checking won't be done 
 */
char	*get_input()
{
	return (&(current_screen->input_buffer[current_screen->buffer_min_pos]));
}

/* input_clear_to_eol: erases from the cursor to the end of the input buffer */
void input_clear_to_eol()
{
	cursor_to_input();
	malloc_strcpy(&cut_buffer,
		&(current_screen->input_buffer[current_screen->buffer_pos]));
	current_screen->input_buffer[current_screen->buffer_pos] = null(char);
	if (term_clear_to_eol())
	{
		term_space_erase(cursor);
		term_move_cursor(cursor, input_line);
	}
	update_input(NO_UPDATE);
}

/*
 * input_clear_to_bol: clears from the cursor to the beginning of the input
 * buffer 
 */
void input_clear_to_bol()
{
	char	*ptr = null(char *);

	cursor_to_input();
	malloc_strcpy(&cut_buffer,
	    &(current_screen->input_buffer[current_screen->buffer_min_pos]));
	cut_buffer[current_screen->buffer_pos -
		current_screen->buffer_min_pos] = null(char);
	malloc_strcpy(&ptr,
		&(current_screen->input_buffer[current_screen->buffer_pos]));
	current_screen->input_buffer[current_screen->buffer_min_pos] =
		null(char);
	strmcat(current_screen->input_buffer, ptr, INPUT_BUFFER_SIZE);
	new_free(&ptr);
	current_screen->buffer_pos = current_screen->buffer_min_pos;
	term_move_cursor(current_screen->buffer_min_pos, input_line);
	if (term_clear_to_eol())
	{
		term_space_erase(current_screen->buffer_min_pos);
		term_move_cursor(current_screen->buffer_min_pos, input_line);
	}
	update_input(UPDATE_FROM_CURSOR);
}

/*
 * input_clear_line: clears entire input line
 */
void input_clear_line()
{
	cursor_to_input();
	malloc_strcpy(&cut_buffer, current_screen->input_buffer +
		current_screen->buffer_min_pos);
	current_screen->input_buffer[current_screen->buffer_min_pos] =
		null(char);
	current_screen->buffer_pos = current_screen->buffer_min_pos;
	term_move_cursor(current_screen->buffer_min_pos, input_line);
	if (term_clear_to_eol())
	{
		term_space_erase(current_screen->buffer_min_pos);
		term_move_cursor(current_screen->buffer_min_pos, input_line);
	}
	update_input(NO_UPDATE);
}

/*
 * input_transpose_characters: swaps the positions of the two characters
 * before the cursor position 
 */
void input_transpose_characters()
{
	cursor_to_input();
	if (current_screen->buffer_pos > current_screen->buffer_min_pos &
		current_screen->input_buffer[current_screen->buffer_pos])
	{
		char	c1,
			c2;

		c1 = current_screen->input_buffer[current_screen->buffer_pos];
		c2 = current_screen->input_buffer[current_screen->buffer_pos] =
		    	current_screen->input_buffer[current_screen->buffer_pos - 1];
		current_screen->input_buffer[current_screen->buffer_pos - 1]
			= c1;
		if (term_cursor_left())
			term_move_cursor(cursor - 1, input_line);
		term_putchar(c1);
		term_putchar(c2);
		if (term_cursor_left())
			term_move_cursor(cursor - 1, input_line);
		update_input(NO_UPDATE);
	}
}

/* init_input: initialized the input buffer by clearing it out */
void init_input()
{
	*current_screen->input_buffer = null(char);
	current_screen->buffer_pos = current_screen->buffer_min_pos;
}

/*
 * input_yank_cut_buffer: takes the contents of the cut buffer and inserts it
 * into the input line 
 */
void input_yank_cut_buffer()
{
	char	*ptr = null(char *);

	if (cut_buffer)
	{
		malloc_strcpy(&ptr,
		   &(current_screen->input_buffer[current_screen->buffer_pos]));
		current_screen->input_buffer[current_screen->buffer_pos] =
			null(char);
		strmcat(current_screen->input_buffer, cut_buffer,
			INPUT_BUFFER_SIZE);
		strmcat(current_screen->input_buffer, ptr, INPUT_BUFFER_SIZE);
		new_free(&ptr);
		update_input(UPDATE_FROM_CURSOR);
		current_screen->buffer_pos += strlen(cut_buffer);
		if (current_screen->buffer_pos > INPUT_BUFFER_SIZE)
			current_screen->buffer_pos = INPUT_BUFFER_SIZE;
		update_input(UPDATE_JUST_CURSOR);
	}
}

/* get_input_prompt: returns the current input_prompt */
char	*get_input_prompt()
{ 
	return (input_prompt); 
}

/*
 * set_input_prompt: sets a prompt that will be displayed in the input
 * buffer.  This prompt cannot be backspaced over, etc.  It's a prompt.
 * Setting the prompt to null uses no prompt 
 */
void set_input_prompt(prompt)
char	*prompt;
{
	if (prompt)
	{
		if (input_prompt && !strcmp (prompt, input_prompt)) return;
		malloc_strcpy(&input_prompt, prompt);
	}
	else
	{
		*current_screen->input_buffer = null(char);
		current_screen->buffer_min_pos = 0;
		current_screen->buffer_pos = 0;
		new_free(&input_prompt);
	}
	update_input(UPDATE_ALL);
}

/*
 * input_pause: prompt the user with a message then waits for a single
 * keystroke before continuing.  The key hit is returned. 
 *
 * This function is NEVER to be called, once the inital irc_io()
 * loop has been called from main().  Perhaps it would be better
 * to just put this code at the only place it is called, and not
 * have the function.  If you want an input_pause, use add_wait_prompt()
 * with WAIT_PROMPT_KEY.
 */
char	input_pause(msg)
char	*msg;
{
	char	*ptr = null(char *);
	char	c;

	if (dumb)
	{
		puts(msg);
		fflush(stdout);
		if (use_input)
			irc_io(&c, NULL, -1, 1);
	}
	else
	{
		malloc_strcpy(&ptr, get_input());
		set_input(msg);
		update_input(UPDATE_ALL);
		irc_io(&c, NULL, -1, 1);
		set_input(ptr);
		update_input(UPDATE_ALL);
		new_free(&ptr);
	}
	return (c);
}
