/*
 * 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 
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "irc.h"

#include "expr.h"
#include "commands.h"
#include "exec.h"
#include "history.h"
#include "hook.h"
#include "input.h"
#include "ircaux.h"
#include "keys.h"
#include "screen.h"
#include "server.h"
#include "ircterm.h"
#include "list.h"
#include "vars.h"
#include "misc.h"
#include "screen.h"
#include "output.h"
#include "dcc.h"
#include "whowas.h"
#include "window.h"
#include "status.h"
#include "hash.h"
#include "fset.h"
#include "tcommand.h"
#include "parse.h"		/* last_split_server */

static char new_nick[NICKNAME_LEN + 1] = "";
static char *input_lastmsg = NULL;

NickTab *tabkey_array = NULL;

static const int WIDTH = 10;

/* input_prompt: contains the current, unexpanded input prompt */
static char *input_prompt = NULL;
static int old_ansi = 0;

/* 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;

enum I_STATE {
    STATE_NORMAL = 0,
    STATE_COMPLETE,
    STATE_TABKEY,
    STATE_TABKEYNEXT,
    STATE_CNICK,
    STATE_CNICKNEXT
} in_completion = STATE_NORMAL;

/* These are sanity macros.  The file was completely unreadable before 
 * i put these in here.  I make no apologies for them.
 */
#define INPUT_BUFFER current_screen->input_buffer
#define MIN_POS current_screen->buffer_min_pos
#define THIS_POS current_screen->buffer_pos
#define THIS_CHAR INPUT_BUFFER[THIS_POS]
#define MIN_CHAR INPUT_BUFFER[MIN_POS]
#define PREV_CHAR INPUT_BUFFER[THIS_POS-1]
#define NEXT_CHAR INPUT_BUFFER[THIS_POS+1]
#define ADD_TO_INPUT(x) strmcat(INPUT_BUFFER, (x), INPUT_BUFFER_SIZE-1);
#define INPUT_VISIBLE INPUT_BUFFER[str_start]

/*
 * 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;

static int safe_puts(char *str, int len)
{
    int i = 0;

    while (*str && i < len)
	term_putchar(*str++), i++;
    return i;
}

/* cursor_to_input: move the cursor to the input line, if not there already */
void cursor_to_input(void)
{
    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(NULL)) {
	    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(int update)
{
    int old_start;
    static int co = 0, li = 0;
    char *ptr;
    int len, free_it = 1, cnt, ansi_count, max;

    char *prompt;

    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;
	    int args_used;

	    if (is_process(get_target_by_refnum(0))) {
		ptr = (char *) get_prompt_by_refnum(0);
		free_it = 0;
	    } else if (!get_int_var(DISPLAY_ANSI_VAR))
		ptr = expand_alias(stripansicodes(prompt), empty_str, &args_used, NULL);
	    else
		ptr = expand_alias(prompt, empty_str, &args_used, NULL);
	    if (*ptr && ((my_strnicmp(ptr, "Password:", 9) == 0) || (my_strnicmp(ptr, "Operator Password:", 18) == 0) ||
			 (my_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, INPUT_BUFFER + MIN_POS);
		strmcpy(INPUT_BUFFER, ptr, INPUT_BUFFER_SIZE);
		THIS_POS += (len - MIN_POS);
		MIN_POS = strlen(ptr);
		ADD_TO_INPUT(inp_ptr);
		new_free(&inp_ptr);
		update = UPDATE_ALL;
	    }

	    if (free_it)
		new_free(&ptr);
	}
    } else
	term_echo(1);

    if ((li != term_rows) || (co != term_cols)) {
	/* resized? Keep it simple and reset everything */
	input_line = term_rows - 1;
	zone = term_cols - (WIDTH * 2) + 4;
	lower_mark = WIDTH;
	upper_mark = term_cols - WIDTH;
	cursor = current_screen->buffer_min_pos;
	current_screen->buffer_pos = current_screen->buffer_min_pos;
	str_start = 0;
	li = term_rows;
	co = term_cols;
    }
    old_start = str_start;
    ansi_count = count_ansi(current_screen->input_buffer, zone);
    if (old_ansi != ansi_count || current_screen->buffer_pos - ansi_count > zone) {
	lower_mark = WIDTH;
	upper_mark = term_cols - WIDTH;
	str_start = 0;
    }
    ansi_count = count_ansi(&(current_screen->input_buffer[str_start]), zone);

    while ((current_screen->buffer_pos - ansi_count < lower_mark) && lower_mark > WIDTH) {
	upper_mark = lower_mark - ansi_count;
	lower_mark -= (zone + ansi_count);
	str_start -= (zone + ansi_count);
	if (str_start < zone) {
	    str_start = 0;
	    ansi_count = count_ansi(&(current_screen->input_buffer[str_start]), zone);
	    lower_mark -= ansi_count;
	    upper_mark -= ansi_count;
	}
    }
    while (current_screen->buffer_pos - ansi_count >= upper_mark) {
	lower_mark = upper_mark + ansi_count;
	upper_mark += zone + ansi_count;
	str_start += zone + ansi_count;
	if (ansi_count)
	    ansi_count = 0;
    }

    /* we need to count ansi characters again, this time in the part of the string we are gonna display in a few moments */
    ansi_count = count_ansi(&(current_screen->input_buffer[str_start]), zone);
    old_ansi = count_ansi(current_screen->input_buffer, zone);
    /* we need to substract number of ansi characters from cursor position since those are not visible, otherwise we'd display cursor
     * in wrong place */
    cursor = current_screen->buffer_pos - str_start - ansi_count;
    if ((old_start != str_start) || (update == UPDATE_ALL)) {
	term_move_cursor(0, input_line);
	if ((str_start == 0) && (MIN_POS > 0)) {
	    int echo;

	    echo = term_echo(1);
	    if (MIN_POS > (term_cols - WIDTH))
		len = term_cols - WIDTH - 1 /* + ansi_count */ ;
	    else
		len = MIN_POS;
	    cnt = /* term_puts */ safe_puts(&(INPUT_BUFFER[str_start]), len);
	    term_echo(echo);
	    cnt += /* term_puts */ safe_puts(&(current_screen->input_buffer[
									       str_start + len]), term_cols - len + ansi_count);
	} else
	    cnt = /* term_puts */ safe_puts(&(INPUT_BUFFER[str_start]), term_cols);
	term_clear_to_eol();
	term_move_cursor(cursor, input_line);
    } else if (update == UPDATE_FROM_CURSOR) {
	term_move_cursor(cursor, input_line);
	cnt = cursor;
	max = term_cols - (current_screen->buffer_pos - str_start) + ansi_count;
	if ((len = strlen(&(THIS_CHAR))) > max)
	    len = max;
	cnt += /* term_puts */ safe_puts(&(THIS_CHAR), len);
	term_clear_to_eol();
	term_move_cursor(cursor, input_line);
    } else if (update == UPDATE_JUST_CURSOR)
	term_move_cursor(cursor, input_line);
    term_flush();
}

void change_input_prompt(int direction)
{
    if (!current_screen->promptlist) {
	strcpy(INPUT_BUFFER, current_screen->saved_input_buffer);
	THIS_POS = current_screen->saved_buffer_pos;
	MIN_POS = current_screen->saved_min_buffer_pos;
	*current_screen->saved_input_buffer = '\0';
	current_screen->saved_buffer_pos = 0;
	current_screen->saved_min_buffer_pos = 0;
	update_input(UPDATE_ALL);
    }

    else if (direction == -1)
	update_input(UPDATE_ALL);

    else if (!current_screen->promptlist->next) {
	strcpy(current_screen->saved_input_buffer, INPUT_BUFFER);
	current_screen->saved_buffer_pos = THIS_POS;
	current_screen->saved_min_buffer_pos = MIN_POS;
	*INPUT_BUFFER = '\0';
	THIS_POS = MIN_POS = 0;
	update_input(UPDATE_ALL);
    }
}

/* input_move_cursor: moves the cursor left or right... got it? */
void input_move_cursor(int dir)
{
    cursor_to_input();
    if (dir) {
	if (THIS_CHAR) {
	    THIS_POS++;
	    term_cursor_right();
	}
    } else {
	if (THIS_POS > MIN_POS) {
	    THIS_POS--;
	    term_cursor_left();
	}
    }
    update_input(NO_UPDATE);
}

/*
 * set_input: sets the input buffer to the given string, discarding whatever
 * was in the input buffer before 
 */
void set_input(char *str)
{
    strmcpy(INPUT_BUFFER + MIN_POS, str, INPUT_BUFFER_SIZE - MIN_POS);
    THIS_POS = strlen(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(void)
{
    return (&(MIN_CHAR));
}

/* init_input: initialized the input buffer by clearing it out */
void init_input(void)
{
    *INPUT_BUFFER = (char) 0;
    THIS_POS = MIN_POS;
}

/* get_input_prompt: returns the current input_prompt */
char *get_input_prompt(void)
{
    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(Window * win, char *prompt, int unused)
{
    if (prompt) {
	if (input_prompt && !strcmp(prompt, input_prompt))
	    return;
	malloc_strcpy(&input_prompt, convert_output_format(prompt, NULL, NULL));
    } else {
	if (!input_prompt)
	    return;
	malloc_strcpy(&input_prompt, empty_str);
    }
    update_input(UPDATE_ALL);
}

/* 
 * Why did i put these in this file?  I dunno.  But i do know that the ones 
 * in edit.c didnt have to be here, and i knew the ones that were here DID 
 * have to be here, so i just moved them all to here, so that they would all
 * be in the same place.  Easy enough. (jfn, june 1995)
 */

/*
 * input_forward_word: move the input cursor forward one word in the input
 * line 
 */
void input_forward_word(char unused, char *not_used)
{
    cursor_to_input();

    while ((my_isspace(THIS_CHAR) || ispunct(THIS_CHAR)) && (THIS_CHAR))
	THIS_POS++;
    while (!(ispunct(THIS_CHAR) || my_isspace(THIS_CHAR)) && (THIS_CHAR))
	THIS_POS++;
    update_input(UPDATE_JUST_CURSOR);
}

/* input_backward_word: move the cursor left on word in the input line */
void input_backward_word(char unused, char *not_used)
{
    cursor_to_input();
    while ((THIS_POS > MIN_POS) && (my_isspace(PREV_CHAR) || ispunct(PREV_CHAR)))
	THIS_POS--;
    while ((THIS_POS > MIN_POS) && !(ispunct(PREV_CHAR) || my_isspace(PREV_CHAR)))
	THIS_POS--;

    update_input(UPDATE_JUST_CURSOR);
}

/* input_delete_character: deletes a character from the input line */
void input_delete_character(char unused, char *not_used)
{
    cursor_to_input();
    if (THIS_CHAR) {
	char *ptr = NULL;
	int pos;

	malloc_strcpy(&ptr, &(NEXT_CHAR));
	strcpy(&(THIS_CHAR), ptr);
	new_free(&ptr);
	if (term_delete())
	    update_input(UPDATE_FROM_CURSOR);
	else {
	    pos = str_start + term_cols - 1;
	    pos += count_ansi(&(current_screen->input_buffer[str_start]), zone);
	    if (pos < strlen(INPUT_BUFFER)) {
		term_move_cursor(term_cols - 1, input_line);
		term_putchar(INPUT_BUFFER[pos]);
		term_move_cursor(cursor, input_line);
	    }
	    update_input(NO_UPDATE);
	}
    }
    in_completion = STATE_NORMAL;
}

/* input_backspace: does a backspace in the input buffer */
void input_backspace(char key, char *blah)
{
    cursor_to_input();
    if (THIS_POS > MIN_POS) {
	char *ptr = NULL;
	int pos;

	malloc_strcpy(&ptr, &(THIS_CHAR));
	strcpy(&(PREV_CHAR), ptr);
	new_free(&ptr);
	THIS_POS--;
	term_cursor_left();
	if (THIS_CHAR) {
	    if (term_delete())
		update_input(UPDATE_FROM_CURSOR);
	    {
		pos = str_start + term_cols - 1;
		pos += count_ansi(&(current_screen->input_buffer[str_start]), zone);
		if (pos < strlen(INPUT_BUFFER)) {
		    term_move_cursor(term_cols - 1, input_line);
		    term_putchar(INPUT_BUFFER[pos]);
		}
		update_input(UPDATE_JUST_CURSOR);
	    }
	} else {
	    term_putchar(' ');
	    term_cursor_left();
	    update_input(NO_UPDATE);
	}
    }
    in_completion = STATE_NORMAL;
    *new_nick = 0;
}

/*
 * input_beginning_of_line: moves the input cursor to the first character in
 * the input buffer 
 */
void input_beginning_of_line(char unused, char *not_used)
{
    cursor_to_input();
    THIS_POS = MIN_POS;
    update_input(UPDATE_JUST_CURSOR);
}

/*
 * input_beginning_of_line: moves the input cursor to the first character in
 * the input buffer 
 */
void new_input_beginning_of_line(char unused, char *not_used)
{
    cursor_to_input();
    THIS_POS = MIN_POS;
    update_input(UPDATE_JUST_CURSOR);
    extended_handled = 1;
}

/*
 * input_end_of_line: moves the input cursor to the last character in the
 * input buffer 
 */
void input_end_of_line(char unused, char *not_used)
{
    cursor_to_input();
    THIS_POS = strlen(INPUT_BUFFER);
    update_input(UPDATE_JUST_CURSOR);
}

void input_delete_to_previous_space(char key, char *blah)
{
    int old_pos;
    char c;

    cursor_to_input();
    old_pos = THIS_POS;
    c = THIS_CHAR;

    while (!my_isspace(THIS_CHAR) && THIS_POS >= MIN_POS)
	THIS_POS--;

    if (THIS_POS < old_pos) {
	strcpy(&(NEXT_CHAR), &(INPUT_BUFFER[old_pos]));
	THIS_POS++;
    }

    update_input(UPDATE_FROM_CURSOR);
}

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

    cursor_to_input();
    old_pos = THIS_POS;
    while ((THIS_POS > MIN_POS) && (my_isspace(PREV_CHAR) || ispunct(PREV_CHAR)))
	THIS_POS--;
    while ((THIS_POS > MIN_POS) && !(ispunct(PREV_CHAR) || my_isspace(PREV_CHAR)))
	THIS_POS--;
    c = INPUT_BUFFER[old_pos];
    INPUT_BUFFER[old_pos] = (char) 0;
    malloc_strcpy(&cut_buffer, &THIS_CHAR);
    INPUT_BUFFER[old_pos] = c;
    strcpy(&(THIS_CHAR), &(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(char unused, char *not_used)
{
    int pos;
    char *ptr = NULL, c;

    cursor_to_input();
    pos = THIS_POS;
    while ((my_isspace(INPUT_BUFFER[pos]) || ispunct(INPUT_BUFFER[pos])) && INPUT_BUFFER[pos])
	pos++;
    while (!(ispunct(INPUT_BUFFER[pos]) || my_isspace(INPUT_BUFFER[pos])) && INPUT_BUFFER[pos])
	pos++;
    c = INPUT_BUFFER[pos];
    INPUT_BUFFER[pos] = (char) 0;
    malloc_strcpy(&cut_buffer, &(THIS_CHAR));
    INPUT_BUFFER[pos] = c;
    malloc_strcpy(&ptr, &(INPUT_BUFFER[pos]));
    strcpy(&(THIS_CHAR), 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 
 */
void input_add_character(char c, char *unused)
{
    int display_flag = NO_UPDATE;

    cursor_to_input();
    if (THIS_POS < INPUT_BUFFER_SIZE) {
	if (get_int_var(INSERT_MODE_VAR)) {
	    if (THIS_CHAR) {
		char *ptr = NULL;

		ptr = strdup(&(THIS_CHAR));

		THIS_CHAR = c;
		NEXT_CHAR = 0;
		ADD_TO_INPUT(ptr);
		free(ptr);
		if (term_insert(c)) {
		    term_putchar(c);
		    if (NEXT_CHAR)
			display_flag = UPDATE_FROM_CURSOR;
		    else
			display_flag = NO_UPDATE;
		}
	    } else {
		THIS_CHAR = c;
		NEXT_CHAR = 0;
		term_putchar(c);
	    }
	} else {
	    if (THIS_CHAR == 0)
		NEXT_CHAR = 0;
	    THIS_CHAR = c;
	    term_putchar(c);
	}
	THIS_POS++;
	update_input(display_flag);
    }
    if (in_completion == STATE_COMPLETE && c == ' ' && input_lastmsg) {
	new_free(&input_lastmsg);
	*new_nick = 0;
	in_completion = STATE_NORMAL;
    }
}

/* input_clear_to_eol: erases from the cursor to the end of the input buffer */
void input_clear_to_eol(char unused, char *not_used)
{
    cursor_to_input();
    malloc_strcpy(&cut_buffer, &(THIS_CHAR));
    THIS_CHAR = 0;
    term_clear_to_eol();
    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 unused, char *not_used)
{
    char *ptr = NULL;

    cursor_to_input();
    malloc_strcpy(&cut_buffer, &(MIN_CHAR));
    cut_buffer[THIS_POS - MIN_POS] = (char) 0;
    malloc_strcpy(&ptr, &(THIS_CHAR));
    MIN_CHAR = (char) 0;
    ADD_TO_INPUT(ptr);
    new_free(&ptr);
    THIS_POS = MIN_POS;
    term_move_cursor(MIN_POS, input_line);
    term_clear_to_eol();
    update_input(UPDATE_FROM_CURSOR);
}

/*
 * input_clear_line: clears entire input line
 */
void input_clear_line(char unused, char *not_used)
{
    cursor_to_input();
    malloc_strcpy(&cut_buffer, INPUT_BUFFER + MIN_POS);
    MIN_CHAR = (char) 0;
    THIS_POS = MIN_POS;
    term_move_cursor(current_screen->buffer_min_pos - count_ansi(current_screen->input_buffer, zone), input_line);
    term_clear_to_eol();
    update_input(NO_UPDATE);
}

/*
 * input_transpose_characters: swaps the positions of the two characters
 * before the cursor position 
 */
void input_transpose_characters(char unused, char *not_used)
{
    cursor_to_input();
    if (current_screen->buffer_pos > MIN_POS) {
	u_char c1[3] = { 0 };
	int pos, end_of_line = 0;

	if (THIS_CHAR)
	    pos = THIS_POS;
	else if (strlen(get_input()) > MIN_POS + 2) {
	    pos = THIS_CHAR - 1;
	    end_of_line = 1;
	} else
	    return;

	c1[0] = INPUT_BUFFER[pos];
	c1[1] = INPUT_BUFFER[pos] = INPUT_BUFFER[pos - 1];
	INPUT_BUFFER[pos - 1] = c1[0];
	term_cursor_left();
	if (end_of_line)
	    term_cursor_left();

	term_putchar(c1[0]);
	term_putchar(c1[1]);
	if (!end_of_line)
	    term_cursor_left();
	update_input(NO_UPDATE);
    }
}

void refresh_inputline(char unused, char *not_used)
{
    update_input(UPDATE_ALL);
}

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

    if (cut_buffer) {
	malloc_strcpy(&ptr, &(THIS_CHAR));
	/* Ooops... */
	THIS_CHAR = 0;
	ADD_TO_INPUT(cut_buffer);
	ADD_TO_INPUT(ptr);
	new_free(&ptr);
	update_input(UPDATE_FROM_CURSOR);
	THIS_POS += strlen(cut_buffer);
	if (THIS_POS > INPUT_BUFFER_SIZE)
	    THIS_POS = INPUT_BUFFER_SIZE;
	update_input(UPDATE_JUST_CURSOR);
    }
}

/* used with input_move_cursor */
#define RIGHT 1
#define LEFT 0

/* BIND functions: */
void forward_character(char dumb, char *dumber)
{
    input_move_cursor(RIGHT);
}

void backward_character(char dumb, char *dumber)
{
    input_move_cursor(LEFT);
}

void backward_history(char dumb, char *dumber)
{
    get_history(PREV);
}

void forward_history(char dumb, char *dumber)
{
    get_history(NEXT);
}

void toggle_insert_mode(char dumb, char *dumber)
{
    int tog = get_int_var(INSERT_MODE_VAR);

    tog ^= 1;
    set_int_var(INSERT_MODE_VAR, tog);
}

static NickTab *getnextnick(char *input_nick, char *oldnick, char *nick)
{
    struct channel *chan;
    struct nick_list *cnick = NULL;
    NickTab *tmp = tabkey_array;
    int server = from_server;
    static NickTab sucks = { NULL };

    if (tmp && (in_completion == STATE_NORMAL || in_completion == STATE_TABKEY)) {

	if (!oldnick && !nick && tmp) {
	    in_completion = STATE_TABKEY;
	    return tmp;
	}
	if (oldnick) {
	    for (; tmp; tmp = tmp->next) {
		if (!my_strnicmp(oldnick, tmp->nick, strlen(oldnick)))
		    break;
	    }
	    /* nick was not in the list. oops didn't come from here */
	    if (!tmp && in_completion == STATE_TABKEY)
		tmp = tabkey_array;
	    else if (tmp)
		tmp = tmp->next;
	}
	if (nick && in_completion != STATE_TABKEY) {
	    if (tmp && tmp->next) {
		for (; tmp; tmp = tmp->next)
		    if (!my_strnicmp(nick, tmp->nick, strlen(nick)))
			break;
	    }
	}
	if (tmp) {
	    in_completion = STATE_TABKEY;
	    return tmp;
	}
    }

    if ((chan = prepare_command(&server, NULL, 3))) {
	cnick = next_nicklist(chan, NULL);
	/* 
	 * we've never been here before so return first nick 
	 * user hasn't entered anything on the line.
	 */
	if (!oldnick && !nick && cnick) {
	    in_completion = STATE_CNICK;
	    sucks.nick = cnick->nick;
	    return &sucks;
	}
	/* 
	 * user has been here before so we attempt to find the correct
	 * first nick to start from.
	 */
	if (oldnick) {
	    /* find the old nick so we have a frame of reference */
	    for (; cnick; cnick = next_nicklist(chan, cnick)) {
		if (!my_strnicmp(cnick->nick, oldnick, strlen(oldnick))) {
		    cnick = next_nicklist(chan, cnick);
		    break;
		}
	    }
	}
	/* 
	 * if the user has put something on the line
	 * we attempt to pattern match here.
	 */
	if (input_nick) {
	    /* 
	     * if oldnick was the last one in the channel 
	     * cnick will be NULL;
	     */
	    if (!cnick && oldnick)
		cnick = next_nicklist(chan, NULL);
	    /* we have a new nick */
	    else if (cnick) {
		/* 
		 * if there's more than one nick, start 
		 * scanning.
		 */
		for (; cnick; cnick = next_nicklist(chan, cnick)) {
		    if (!my_strnicmp(cnick->nick, input_nick, strlen(input_nick)) || !strcasecmp(cnick->nick, input_nick))
			break;
		}
	    }
	}
    }
    if (!cnick)
	in_completion = STATE_NORMAL;
    else
	in_completion = STATE_CNICK;
    if (cnick)
	sucks.nick = cnick->nick;
    return sucks.nick ? &sucks : NULL;
}

void input_msgreply(char dumb, char *dumber)
{
    char *cmdchar;
    char *line, *cmd, *t;
    char *snick;
    NickTab *nick = NULL;
    int got_space = 0;

    if (!(cmdchar = get_string_var(CMDCHARS_VAR)))
	cmdchar = DEFAULT_CMDCHARS;

    t = line = m_strdup(get_input());
    if (t)
	got_space = strchr(t, ' ') ? 1 : 0;
    cmd = next_arg(line, &line);
    snick = next_arg(line, &line);
    if ((cmd && *cmd == *cmdchar && got_space) || !cmd) {

	if (cmd && *cmd == *cmdchar)
	    cmd++;
	if (in_completion == STATE_NORMAL && snick)
	    strncpy(new_nick, snick, sizeof(new_nick) - 1);

	if ((nick = getnextnick(new_nick, input_lastmsg, snick))) {
	    if (nick->nick && *(nick->nick)) {
		snick = nick->nick;
		malloc_strcpy(&input_lastmsg, nick->nick);
	    }
	}
	if (nick) {
	    char *tmp = NULL;

	    input_clear_line('\0', NULL);
	    if (get_fset_var(FORMAT_NICK_MSG_FSET))
		malloc_strcpy(&tmp,
			      stripansicodes(convert_output_format
					     (get_fset_var(FORMAT_NICK_MSG_FSET), "%s%s %s %s", cmdchar,
					      nick->type ? nick->type : cmd ? cmd : "msg", nick->nick, line ? line : empty_str)));
	    else
		malloc_sprintf(&tmp, "%s%s %s %s", cmdchar, nick->type ? nick->type : cmd ? cmd : "msg", nick->nick,
			       line ? line : empty_str);
	    set_input(tmp);
	    new_free(&tmp);
	} else
	    command_completion(0, NULL);
    } else
	command_completion(0, NULL);
    update_input(UPDATE_ALL);
    new_free(&t);
}

void add_autonick_input(char *nick, char *line)
{
    char *tmp1 = NULL;

    input_clear_line('\0', NULL);
    if ((do_hook(AR_REPLY_LIST, "%s", nick))) {
	if (get_fset_var(FORMAT_NICK_AUTO_FSET))
	    malloc_strcpy(&tmp1,
			  stripansicodes(convert_output_format
					 (get_fset_var(FORMAT_NICK_AUTO_FSET), "%s %s", nick, line ? line : empty_str)));
	else
	    malloc_sprintf(&tmp1, "%s: %s", nick, line);
	set_input(tmp1);
	new_free(&tmp1);
    }
    update_input(UPDATE_ALL);
}

void send_line(char dumb, char *dumber)
{
    int server;
    WaitPrompt *OldPrompt;

    server = from_server;
    from_server = get_window_server(0);
    unhold_a_window(curr_scr_win);
    if (current_screen->promptlist && current_screen->promptlist->type == WAIT_PROMPT_LINE) {
	OldPrompt = current_screen->promptlist;
	(*OldPrompt->func) (OldPrompt->data, get_input());
	set_input(empty_str);
	current_screen->promptlist = OldPrompt->next;
	new_free(&OldPrompt->data);
	new_free(&OldPrompt->prompt);
	new_free(&OldPrompt);
	change_input_prompt(-1);
    } else {
	char *line, *tmp = NULL;

	line = get_input();
	if (line && (*line != get_int_var(CMDCHARS_VAR)) && get_int_var(NICK_COMPLETION_VAR)) {
	    char auto_comp_char = ':';

	    if (!(auto_comp_char = (char) get_int_var(NICK_COMPLETION_CHAR_VAR)))
		auto_comp_char = ':';

	    /* possible nick completion */
	    if (strchr(line, auto_comp_char)) {
		char *p;
		struct channel *chan;
		struct nick_list *nick;
		char *channel;

		malloc_strcpy(&tmp, line);
		p = strchr(tmp, auto_comp_char);
		*p++ = 0;
		if (*tmp && *p && (channel = get_current_channel_by_refnum(0))) {
		    chan = lookup_channel(channel, from_server, 0);
		    for (nick = next_nicklist(chan, NULL); nick; nick = next_nicklist(chan, nick))
			if (!my_strnicmp(tmp, nick->nick, strlen(tmp)))
			    break;
		    if (nick) {
			if (get_fset_var(FORMAT_NICK_COMP_FSET))
			    malloc_strcpy(&tmp,
					  stripansicodes(convert_output_format
							 (get_fset_var(FORMAT_NICK_COMP_FSET), "%s %s", nick->nick, p)));
			else
			    malloc_sprintf(&tmp, "%s%c %s", nick->nick, auto_comp_char, p);
		    } else
			malloc_strcpy(&tmp, line);
		} else
		    malloc_strcpy(&tmp, line);
	    } else
		malloc_strcpy(&tmp, line);
	} else
	    malloc_strcpy(&tmp, line);
	if (do_hook(INPUT_LIST, "%s", tmp)) {
	    if (get_int_var(INPUT_ALIASES_VAR))
		parse_line(NULL, tmp, empty_str, 1, 0);
	    else
		parse_line(NULL, tmp, NULL, 1, 0);
	}
	update_input(UPDATE_ALL);
	new_free(&tmp);
    }
    new_free(&input_lastmsg);
    *new_nick = 0;
    in_completion = STATE_NORMAL;
    from_server = server;
}

void meta9_char(char dumb, char *dumber)
{
    current_screen->meta_hit[9] = 1;
}

void meta8_char(char dumb, char *dumber)
{
    current_screen->meta_hit[8] = 1;
}

void meta7_char(char dumb, char *dumber)
{
    current_screen->meta_hit[7] = 1;
}

void meta6_char(char dumb, char *dumber)
{
    current_screen->meta_hit[6] = 1;
}

void meta5_char(char dumb, char *dumber)
{
    current_screen->meta_hit[5] = 1;
}

void meta4_char(char dumb, char *dumber)
{
    current_screen->meta_hit[4] = 1 - current_screen->meta_hit[4];
}

void meta3_char(char dumb, char *dumber)
{
    current_screen->meta_hit[3] = 1;
}

void meta2_char(char dumb, char *dumber)
{
    current_screen->meta_hit[2] = 1;
}

void meta1_char(char dumb, char *dumber)
{
    current_screen->meta_hit[1] = 1;
}

void quote_char(char c, char *dumber)
{
    current_screen->quote_hit = 1;
}

/* These four functions are boomerang functions, which allow the highlight
 * characters to be bound by simply having these functions put in the
 * appropriate characters when you press any key to which you have bound
 * that highlight character. >;-)
 */
void insert_bold(char c, char *dumber)
{
    input_add_character(BOLD_TOG, dumber);
}

void insert_reverse(char c, char *dumber)
{
    input_add_character(REV_TOG, dumber);
}

void insert_underline(char c, char *dumber)
{
    input_add_character(UND_TOG, dumber);
}

void highlight_off(char c, char *dumber)
{
    input_add_character(ALL_OFF, dumber);
}

/* type_text: the BIND function TYPE_TEXT */
void type_text(char c, char *str)
{
    if (!str)
	return;
    for (; *str; str++)
	input_add_character(*str, empty_str);
}

/*
 * clear_screen: the CLEAR_SCREEN function for BIND.  Clears the screen and
 * starts it if it is held 
 */
void clear_screen(char c, char *str)
{
    hold_mode(NULL, OFF, 1);
    clear_window_by_refnum(0);
}

/* parse_text: the bindable function that executes its string */
void parse_text(char c, char *str)
{
    parse_line(NULL, str, empty_str, 0, 0);
}

/*
 * edit_char: handles each character for an input stream.  Not too difficult
 * to work out.
 */
void edit_char(u_char key)
{
    void (*func) (char, char *) = NULL;
    char *ptr = NULL;
    u_char extended_key;
    WaitPrompt *oldprompt;
    int meta_hit = 0, meta_not_hit;
    int i;

    /* were we waiting for a keypress? */
    if (current_screen->promptlist && current_screen->promptlist->type == WAIT_PROMPT_KEY) {
	char key_[2] = "\0";

	key_[0] = key;
	oldprompt = current_screen->promptlist;

	(*oldprompt->func) (oldprompt->data, key_);

	set_input(empty_str);
	current_screen->promptlist = oldprompt->next;
	new_free(&oldprompt->data);
	new_free(&oldprompt->prompt);
	new_free(&oldprompt);
	change_input_prompt(-1);
	return;
    }

    extended_key = key;

    /* Check to see if its an eight bit char and if we allow it */
    if (!get_int_var(EIGHT_BIT_CHARACTERS_VAR))
	key &= 0x7f;		/* mask out non-ascii crap */

    /* Check to see if this is a meta-key */
    for (i = 1; i <= 9; i++) {
	if (current_screen->meta_hit[i]) {
	    if (keys[i][key]) {
		func = key_names[keys[i][key]->key_index].func;
		ptr = keys[i][key]->stuff;
	    }
	    current_screen->meta_hit[i] = 0;
	    meta_hit = 1;
	    break;
	}
    }
    if (!meta_hit) {
	if (keys[0][key]) {
	    func = key_names[keys[0][key]->key_index].func;
	    ptr = keys[0][key]->stuff;
	}
    }

    /* is there a meta key that isnt still outstanding? */
    meta_not_hit = 1;
    for (i = 1; i <= 3; i++)
	meta_not_hit = meta_not_hit && !current_screen->meta_hit[i];
    for (i = 5; i <= 9; i++)
	meta_not_hit = meta_not_hit && !current_screen->meta_hit[i];

    if (meta_not_hit) {
	/* did we just hit the quote character? */
	if (current_screen->quote_hit) {
	    current_screen->quote_hit = 0;
	    input_add_character(extended_key, empty_str);
	}

	/* nope. none of these.  just a regular character */
	else if (func)
	    func(extended_key, ptr ? ptr : empty_str);
    } else
	term_beep();		/* two metas in a row gets a beep */
}

void my_scrollback(char dumb, char *dumber)
{
    scrollback_backwards(dumb, dumber);
    extended_handled = 1;
}

void my_scrollforward(char dumb, char *dumber)
{
    scrollback_forwards(dumb, dumber);
    extended_handled = 1;
}

void my_scrollend(char dumb, char *dumber)
{
    scrollback_end(dumb, dumber);
    extended_handled = 1;
}

void dcc_plist(char dumb, char *dumber)
{
    dcc_glist(NULL, NULL);
    extended_handled = 1;
}

int in_window_command;

static void handle_swap(int windownum)
{
    char *p = NULL;

    malloc_sprintf(&p, "SWAP %d", windownum);
    t_parse_command("WINDOW", p);
    set_channel_window(curr_scr_win, get_current_channel_by_refnum(curr_scr_win->refnum), curr_scr_win->server);
    new_free(&p);
    set_input_prompt(curr_scr_win, get_string_var(INPUT_PROMPT_VAR), 0);
    update_input(UPDATE_ALL);
    update_all_windows();
}

void window_swap1(char dumb, char *dumber)
{
    handle_swap(1);
    extended_handled = 1;
}

void window_swap2(char dumb, char *dumber)
{
    handle_swap(2);
    extended_handled = 1;
}

void window_swap3(char dumb, char *dumber)
{
    handle_swap(3);
    extended_handled = 1;
}

void window_swap4(char dumb, char *dumber)
{
    handle_swap(4);
    extended_handled = 1;
}

void window_swap5(char dumb, char *dumber)
{
    handle_swap(5);
    extended_handled = 1;
}

void window_swap6(char dumb, char *dumber)
{
    handle_swap(6);
    extended_handled = 1;
}

void window_swap7(char dumb, char *dumber)
{
    handle_swap(7);
    extended_handled = 1;
}
void window_swap8(char dumb, char *dumber)
{
    handle_swap(8);
    extended_handled = 1;
}
void window_swap9(char dumb, char *dumber)
{
    handle_swap(9);
    extended_handled = 1;
}
void window_swap10(char dumb, char *dumber)
{
    handle_swap(10);
    extended_handled = 1;
}

void change_to_split(char dumb, char *dumber)
{
    if (!last_split_server)
	return;
    t_parse_command("SERVER", last_split_server);
}

void join_last_invite(char dumb, char *dumber)
{
    if (invite_channel)
	send_to_server(SERVER(from_server), "JOIN %s", invite_channel);
    else
	bitchsay("You haven't been invited to a channel yet");
}

void wholeft(char dumb, char *dumber)
{
    show_wholeft(NULL);
}

void window_key_balance(char dumb, char *dumber)
{
    in_window_command = 1;
    message_from(NULL, LOG_CURRENT);
    recalculate_windows();
    update_all_windows();
    in_window_command = 0;
    message_from(NULL, LOG_CRAP);
}

void window_grow_one(char dumb, char *dumber)
{
    in_window_command = 1;
    message_from(NULL, LOG_CURRENT);
    resize_window(1, curr_scr_win, 1);
    update_all_windows();
    in_window_command = 0;
    message_from(NULL, LOG_CRAP);
}

void window_key_hide(char dumb, char *dumber)
{
    in_window_command = 1;
    message_from(NULL, LOG_CURRENT);
    hide_window(curr_scr_win);
    update_all_windows();
    in_window_command = 0;
    message_from(NULL, LOG_CRAP);
}

void window_key_kill(char dumb, char *dumber)
{
    in_window_command = 1;
    message_from(NULL, LOG_CURRENT);
    delete_window(curr_scr_win);
    update_all_windows();
    in_window_command = 0;
    message_from(NULL, LOG_CRAP);
}

void window_key_list(char dumb, char *dumber)
{
    in_window_command = 1;
    message_from(NULL, LOG_CURRENT);
    window_list(curr_scr_win, NULL);
    in_window_command = 0;
    message_from(NULL, LOG_CRAP);
}

void window_key_move(char dumb, char *dumber)
{
    in_window_command = 1;
    message_from(NULL, LOG_CURRENT);
    move_window(curr_scr_win, 1);
    update_all_windows();
    in_window_command = 0;
    message_from(NULL, LOG_CRAP);
}

void window_shrink_one(char dumb, char *dumber)
{
    in_window_command = 1;
    message_from(NULL, LOG_CURRENT);
    resize_window(1, curr_scr_win, -1);
    update_all_windows();
    in_window_command = 0;
    message_from(NULL, LOG_CRAP);
}

void ignore_last_nick(char dumb, char *dumber)
{
    NickTab *nick;
    char *tmp1;

    if ((nick = gettabkey(1, NULL))) {
	set_input(empty_str);
	tmp1 = m_sprintf("%sig %s", get_string_var(CMDCHARS_VAR), nick->nick);
	set_input(tmp1);
	new_free(&tmp1);
    }
    update_input(UPDATE_ALL);
}

static char *getchannick(char *oldnick, char *nick)
{
    struct channel *chan;
    char *channel, *tnick = NULL;
    struct nick_list *cnick;

    channel = get_current_channel_by_refnum(0);
    if (channel) {
	if (!(chan = lookup_channel(channel, from_server, 0)) || !(cnick = next_nicklist(chan, NULL))) {
	    in_completion = STATE_NORMAL;
	    return NULL;
	}
	/* 
	 * we've never been here before so return first nick 
	 * user hasn't entered anything on the line.
	 */
	if (!oldnick && !nick && cnick) {
	    in_completion = STATE_CNICK;
	    return cnick->nick;
	}
	/* 
	 * user has been here before so we attempt to find the correct
	 * first nick to start from.
	 */
	if (oldnick) {
	    /* find the old nick so we have a frame of reference */
	    for (cnick = next_nicklist(chan, NULL); cnick; cnick = next_nicklist(chan, cnick)) {
		if (!my_strnicmp(cnick->nick, oldnick, strlen(oldnick))) {
		    tnick = cnick->nick;
		    if ((cnick = next_nicklist(chan, cnick)))
			tnick = cnick->nick;
		    break;

		}

	    }
	}
	/* 
	 * if the user has put something on the line
	 * we attempt to pattern match here.
	 */
	if (nick && in_completion == STATE_NORMAL) {
	    /* 
	     * if oldnick was the last one in the channel 
	     * cnick will be NULL;
	     */
	    if (!cnick) {
		cnick = next_nicklist(chan, NULL);
		tnick = cnick->nick;
	    }
	    /* we have a new nick */
	    else if (next_nicklist(chan, cnick)) {
		/* 
		 * if there's more than one nick, start 
		 * scanning.
		 */
		for (; cnick; cnick = next_nicklist(chan, cnick)) {
		    if (!my_strnicmp(cnick->nick, nick, strlen(nick))) {
			tnick = cnick->nick;
			break;
		    }
		}
	    } else
		tnick = cnick->nick;
	} else if (in_completion == STATE_CNICK) {
	    /* 
	     * else we've been here before so
	     * attempt to continue through the nicks 
	     */
	    if (!cnick)
		cnick = next_nicklist(chan, NULL);
	    tnick = cnick->nick;
	}
    }
    if (tnick)
	in_completion = STATE_CNICK;

    return tnick;
}

void nick_completion(char dumb, char *dumber)
{
    char *q, *line;
    int i = -1;
    char *nick = NULL, *tmp;

    q = line = m_strdup(&current_screen->input_buffer[MIN_POS]);
    if (in_completion == STATE_NORMAL) {
	i = word_count(line);
	nick = extract_words(line, i - 1, i);
    }
    if (nick)
	line[strlen(line) - strlen(nick)] = 0;
    else
	*line = 0;
    if ((tmp = getchannick(input_lastmsg, nick && *nick ? nick : NULL))) {
	malloc_strcat(&q, tmp);
	set_input(q);
	update_input(UPDATE_ALL);
	malloc_strcpy(&input_lastmsg, tmp);
	in_completion = STATE_COMPLETE;
    }
    new_free(&q);
    new_free(&nick);
}

void addtabkey(const char *nick, const char *type)
{
    NickTab *tmp, *new;

    tmp = tabkey_array;

    if (!tmp || !(new = (NickTab *) remove_from_list((struct list **) &tmp, nick))) {
	new = new_malloc(sizeof(NickTab));
	malloc_strcpy(&new->nick, nick);
	if (type)
	    malloc_strcpy(&new->type, type);
    }
    /* 
     * most recent nick is at the top of the list 
     */
    new->next = tmp;
    tmp = new;
    tabkey_array = tmp;
}

NickTab *gettabkey(int direction, char *nick)
{
    NickTab *tmp, *new;

    new = tmp = tabkey_array;

    if (nick) {
	for (; tmp; tmp = tmp->next)
	    if (!my_strnicmp(nick, tmp->nick, strlen(nick)))
		return tmp;
	return NULL;
    }
    tmp = new;
    if (!tmp)
	return NULL;

    switch (direction) {
    case 1:
    default:
	{
	    /* 
	     * need at least two nicks in the list
	     */
	    if (new->next) {
		/* 
		 * reset top of array
		 */
		tabkey_array = new->next;

		/* 
		 * set the current nick next pointer to NULL
		 * and then reset top of list.
		 */

		new->next = NULL;
		tmp = tabkey_array;

		/* 
		 * find the last nick in the list
		 * so we can make the old top pointer 
		 * point to the item
		 */
		while (tmp)
		    if (tmp->next)
			tmp = tmp->next;
		    else
			break;
		/* set the pointer and then return. */
		tmp->next = new;
	    }
	    break;
	}
    case -1:
	{
	    if (new && new->next) {
		tmp = new;
		while (tmp)
		    if (tmp->next && tmp->next->next)
			tmp = tmp->next;
		    else
			break;
		/* 
		 * tmp now points at last two items in list 
		 * now just swap some pointers.
		 */
		new = tmp->next;
		tmp->next = NULL;
		new->next = tabkey_array;
		tabkey_array = new;
	    }
	    break;
	}
    }
    if (new && new->nick)
	return new;
    return NULL;
}
